import { prisma } from './prisma' export async function indexEntity( entityType: string, entityId: string, title: string, content?: string, metadata?: Record ): Promise { const searchIndex = await prisma.searchIndex.upsert({ where: { entityType_entityId: { entityType, entityId, }, }, update: { title, content, metadata: metadata ? JSON.stringify(metadata) : undefined, updatedAt: new Date(), }, create: { entityType, entityId, title, content, metadata: metadata ? JSON.stringify(metadata) : undefined, }, }) return { ...searchIndex, metadata: searchIndex.metadata ? JSON.parse(searchIndex.metadata) : undefined, } } export async function searchEntities( query: string, entityType?: string, limit: number = 20, offset: number = 0 ): Promise<{ results: any[]; total: number }> { const whereClause: any = { OR: [ { title: { contains: query, mode: 'insensitive', }, }, { content: { contains: query, mode: 'insensitive', }, }, ], } if (entityType) { whereClause.entityType = entityType } const [results, total] = await Promise.all([ prisma.searchIndex.findMany({ where: whereClause, take: limit, skip: offset, orderBy: [ { updatedAt: 'desc' }, ], }), prisma.searchIndex.count({ where: whereClause, }), ]) // Fetch full entity data based on type const enrichedResults = await Promise.all( results.map(async (result) => { const baseResult = { ...result, metadata: result.metadata ? JSON.parse(result.metadata) : undefined, } switch (result.entityType) { case 'song': const song = await prisma.song.findUnique({ where: { id: result.entityId }, include: { artist: { select: { id: true, name: true, slug: true, }, }, album: { select: { id: true, title: true, slug: true, coverUrl: true, }, }, }, }) return { ...baseResult, entity: song } case 'album': const album = await prisma.album.findUnique({ where: { id: result.entityId }, include: { artist: { select: { id: true, name: true, slug: true, }, }, _count: { select: { songs: true }, }, }, }) return { ...baseResult, entity: album } case 'artist': const artist = await prisma.artist.findUnique({ where: { id: result.entityId }, include: { _count: { select: { songs: true, albums: true }, }, }, }) return { ...baseResult, entity: artist } case 'playlist': const playlist = await prisma.playlist.findUnique({ where: { id: result.entityId }, include: { user: { select: { id: true, username: true, displayName: true, }, }, _count: { select: { songs: true }, }, }, }) return { ...baseResult, entity: playlist } default: return baseResult } }) ) return { results: enrichedResults, total, } } export async function getSearchSuggestions( query: string, limit: number = 10 ): Promise { const suggestions = await prisma.searchIndex.findMany({ where: { title: { contains: query, }, }, select: { title: true, }, take: limit, orderBy: { updatedAt: 'desc', }, }) // Extract unique suggestions const uniqueTitles = [...new Set(suggestions.map(s => s.title))] return uniqueTitles.slice(0, limit) } export async function removeFromIndex(entityType: string, entityId: string): Promise { await prisma.searchIndex.delete({ where: { entityType_entityId: { entityType, entityId, }, }, }) } export async function reindexAll(): Promise { // Clear existing index await prisma.searchIndex.deleteMany({}) // Reindex all songs const songs = await prisma.song.findMany({ where: { isPublic: true }, include: { artist: true, album: true, genres: { include: { genre: true }, }, }, }) for (const song of songs) { await indexEntity( 'song', song.id, song.title, `${song.title} ${song.artist.name} ${song.album?.title || ''} ${song.genres.map(g => g.genre.name).join(' ')}`, { artist: song.artist.name, album: song.album?.title, genres: song.genres.map(g => g.genre.name), duration: song.duration, } ) } // Reindex all albums const albums = await prisma.album.findMany({ include: { artist: true }, }) for (const album of albums) { await indexEntity( 'album', album.id, album.title, `${album.title} ${album.artist.name}`, { artist: album.artist.name, releaseDate: album.releaseDate, } ) } // Reindex all artists const artists = await prisma.artist.findMany() for (const artist of artists) { await indexEntity( 'artist', artist.id, artist.name, artist.bio || '', { verified: artist.verified, } ) } // Reindex public playlists const playlists = await prisma.playlist.findMany({ where: { isPublic: true }, include: { user: true }, }) for (const playlist of playlists) { await indexEntity( 'playlist', playlist.id, playlist.title, `${playlist.title} ${playlist.description || ''} ${playlist.user.displayName || playlist.user.username}`, { author: playlist.user.displayName || playlist.user.username, description: playlist.description, } ) } }