/** * Search utilities for OpenPrompt * Supports full-text search across prompts */ export interface SearchFilters { query?: string category?: string tags?: string[] model?: string minStars?: number minRuns?: number dateRange?: 'day' | 'week' | 'month' | 'all' sortBy?: 'trending' | 'recent' | 'stars' | 'runs' } export interface SearchResult { id: string slug: string title: string description: string | null category: string | null tags: string[] totalRuns: number starsCount: number remixesCount: number createdAt: Date creator: { name: string | null username: string | null image: string | null } | null score?: number // relevance score } /** * Build Prisma where clause from search filters */ export function buildSearchWhere(filters: SearchFilters) { const where: Record = { visibility: 'public', } // Text search (title + description) if (filters.query) { where.OR = [ { title: { contains: filters.query, mode: 'insensitive' } }, { description: { contains: filters.query, mode: 'insensitive' } }, { tags: { hasSome: [filters.query.toLowerCase()] } }, ] } // Category filter if (filters.category) { where.category = filters.category } // Tags filter if (filters.tags && filters.tags.length > 0) { where.tags = { hasSome: filters.tags } } // Model filter if (filters.model) { where.modelAllowed = { has: filters.model } } // Stars filter if (filters.minStars) { where.starsCount = { gte: filters.minStars } } // Runs filter if (filters.minRuns) { where.totalRuns = { gte: filters.minRuns } } // Date range filter if (filters.dateRange && filters.dateRange !== 'all') { const now = new Date() const cutoff = new Date() switch (filters.dateRange) { case 'day': cutoff.setDate(now.getDate() - 1) break case 'week': cutoff.setDate(now.getDate() - 7) break case 'month': cutoff.setMonth(now.getMonth() - 1) break } where.createdAt = { gte: cutoff } } return where } /** * Build Prisma orderBy from sort option */ export function buildSearchOrderBy(sortBy: SearchFilters['sortBy'] = 'trending') { switch (sortBy) { case 'recent': return [{ createdAt: 'desc' as const }] case 'stars': return [{ starsCount: 'desc' as const }, { createdAt: 'desc' as const }] case 'runs': return [{ totalRuns: 'desc' as const }, { createdAt: 'desc' as const }] case 'trending': default: // Trending uses a calculated score (handled separately) return [{ totalRuns: 'desc' as const }, { starsCount: 'desc' as const }] } } /** * Extract unique categories from prompts */ export function getCategories(): string[] { return [ 'Content', 'Development', 'Marketing', 'Business', 'Education', 'Creative', 'Research', ] } /** * Extract unique tags from prompts */ export async function getPopularTags(limit: number = 20): Promise { try { // Query database for actual popular tags const { prisma } = await import('@/lib/prisma') const prompts = await prisma.prompt.findMany({ where: { visibility: 'public', tags: { isEmpty: false } }, select: { tags: true }, orderBy: { totalRuns: 'desc' }, take: 200, }) // Count tag frequency const tagCounts = new Map() for (const prompt of prompts) { for (const tag of prompt.tags) { tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1) } } // Sort by frequency and return top N return [...tagCounts.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, limit) .map(([tag]) => tag) } catch { // Fallback to hardcoded list if DB query fails return [ 'blog', 'writing', 'code', 'email', 'social-media', 'seo', 'ai', 'productivity', 'marketing', 'business', ].slice(0, limit) } }