project-standalo-sonic-cloud/lib/search.ts

287 lines
6.3 KiB
TypeScript

import { prisma } from './prisma'
export async function indexEntity(
entityType: string,
entityId: string,
title: string,
content?: string,
metadata?: Record<string, unknown>
): Promise<any> {
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<string[]> {
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<void> {
await prisma.searchIndex.delete({
where: {
entityType_entityId: {
entityType,
entityId,
},
},
})
}
export async function reindexAll(): Promise<void> {
// 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,
}
)
}
}