287 lines
6.3 KiB
TypeScript
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,
|
|
}
|
|
)
|
|
}
|
|
} |