A self hosted solution for privately rating and reviewing different sorts of media
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 165 lines 3.9 kB view raw
1import { Category, Entry } from '@prisma/client'; 2import { Meilisearch } from 'meilisearch'; 3import prisma from './db'; 4import { validateSessionToken } from './auth/validateSession'; 5import { getDefaultWhereForTranslations } from './api/routers/dashboard_'; 6 7export const meilisearchClient = new Meilisearch({ 8 host: process.env.MEILISEARCH_URL!, 9 apiKey: process.env.MEILISEARCH_MASTER_KEY, 10}); 11 12export type MeilisearchEntry = { 13 id: number; 14 category: Category; 15 releaseYear: string; 16 titles: string[]; 17 people: string[]; 18 genres: string[]; 19 companies: string[]; 20}; 21 22export const MeilisearchEntriesUid = 'entries'; 23 24export const deleteAllDocuments = async () => { 25 const index = meilisearchClient.index(MeilisearchEntriesUid); 26 await index.deleteAllDocuments(); 27}; 28 29export const createIndexes = async () => { 30 const task = await meilisearchClient.createIndex(MeilisearchEntriesUid); 31 await meilisearchClient.tasks.waitForTask(task.taskUid); 32 33 const index = meilisearchClient.index(MeilisearchEntriesUid); 34 index.updateSearchableAttributes([ 35 'titles', 36 'releaseYear', 37 'people', 38 'companies', 39 'genres', 40 ]); 41 index.updateFilterableAttributes(['category']); 42}; 43 44export const addMeilisearchEntries = async (entries: MeilisearchEntry[]) => { 45 const index = meilisearchClient.index<MeilisearchEntry>( 46 MeilisearchEntriesUid 47 ); 48 await index.addDocuments(entries); 49}; 50 51export const searchEntries = async ( 52 query: string, 53 limit: number, 54 categories: Category[] 55) => { 56 const authUser = await validateSessionToken(); 57 const index = meilisearchClient.index<MeilisearchEntry>( 58 MeilisearchEntriesUid 59 ); 60 61 const categoryFilter = categories 62 .map(cat => `category = "${cat}"`) 63 .join(' OR '); 64 65 const searchEntries = await index.search(query, { 66 filter: [categoryFilter], 67 limit, 68 showRankingScore: true, 69 }); 70 let prismaEntries = await prisma.entry.findMany({ 71 where: { 72 id: { 73 in: searchEntries.hits.map(e => e.id), 74 }, 75 }, 76 include: { 77 translations: getDefaultWhereForTranslations(authUser), 78 userEntries: authUser 79 ? { 80 where: { 81 userId: authUser.id, 82 }, 83 } 84 : undefined, 85 }, 86 }); 87 88 const prismaEntryMap = new Map(prismaEntries.map(entry => [entry.id, entry])); 89 90 const orderedEntries = searchEntries.hits.map(hit => ({ 91 ...prismaEntryMap.get(hit.id)!, 92 _rankingScore: hit._rankingScore ?? null, 93 })); 94 95 return orderedEntries; 96}; 97 98export const addMeilisearchEntryByEntryId = async (entryId: number) => { 99 const entry = await prisma.entry.findFirst({ 100 where: { 101 id: entryId, 102 }, 103 include: { 104 genres: { 105 include: { 106 genre: true, 107 }, 108 }, 109 productionCompanies: { 110 include: { 111 company: true, 112 }, 113 }, 114 translations: { 115 include: { 116 language: true, 117 }, 118 }, 119 alternativeTitles: { 120 include: { 121 language: true, 122 }, 123 }, 124 cast: { 125 include: { 126 person: true, 127 }, 128 }, 129 crew: { 130 include: { 131 person: true, 132 }, 133 }, 134 }, 135 }); 136 137 if (!entry) { 138 return; 139 } 140 141 const document: MeilisearchEntry = { 142 id: entry.id, 143 category: entry.category, 144 releaseYear: entry.releaseDate.getUTCFullYear().toString(), 145 titles: Array.from( 146 new Set([ 147 entry.originalTitle, 148 ...entry.translations.map(e => e.name), 149 ...entry.alternativeTitles.map(e => e.title), 150 ]) 151 ), 152 people: Array.from( 153 new Set([ 154 ...entry.cast.map(e => e.person.name), 155 ...entry.crew.map(e => e.person.name), 156 ]) 157 ), 158 genres: Array.from(new Set([...entry.genres.map(e => e.genre.name)])), 159 companies: Array.from( 160 new Set([...entry.productionCompanies.map(e => e.company.name)]) 161 ), 162 }; 163 164 await addMeilisearchEntries([document]); 165};