/* eslint-disable camelcase */ import SQL from 'sql-template-strings'; import { loadWords, Noun } from '#shared/classes.ts'; import type { NounRaw } from '#shared/classes.ts'; import { clearKey, PermissionAreas } from '#shared/helpers.ts'; import { addWordsFromClassInstance } from '#shared/nouns.ts'; import type { NounsData } from '#shared/nouns.ts'; import type { User } from '#shared/user.ts'; import type { Config } from '~~/locale/config.ts'; import type { Database } from '~~/server/db.ts'; import type { UserRow } from '~~/server/express/user.ts'; import type { SourceRow } from '~~/server/sources.ts'; import type { IsGrantedFn } from '~~/server/utils/useAuthentication.ts'; export interface NounRow { id: string; key: string; words: string; classInstance: string | null; approved: number; base_id: string | null; locale: string; author_id: string | null; deleted: number; sources: string | null; categories: string | null; } type NounRowWithAuthor = NounRow & { author: User['username'] }; export const parseNounRow = (nounRow: NounRow): Omit => { return { id: nounRow.id, key: nounRow.key, words: JSON.parse(nounRow.words), classInstance: nounRow.classInstance ? JSON.parse(nounRow.classInstance) : null, categories: nounRow.categories?.split('|') ?? [], sources: nounRow.sources ? nounRow.sources.split(',') : [], }; }; export const buildNoun = (nounRaw: Omit, config: Config, nounsData: NounsData): Noun => { const nounRawWithLoadedWords = loadWords(nounRaw, config, { singular: {}, plural: {} }); if (!nounRawWithLoadedWords.classInstance) { return new Noun(config, nounRawWithLoadedWords); } const words = addWordsFromClassInstance(nounRawWithLoadedWords.words, nounRawWithLoadedWords.classInstance, nounsData); return new Noun(config, { ...nounRaw, words }); }; const parseNounRowWithAuthor = (nounRow: NounRowWithAuthor, isGranted: IsGrantedFn): Omit => { const noun = parseNounRow(nounRow); if (isGranted(PermissionAreas.Nouns)) { noun.approved = !!nounRow.approved; noun.base = nounRow.base_id; noun.author = nounRow.author; } return noun; }; export const addVersions = async ( db: Database, isGranted: IsGrantedFn, locale: string, nouns: Omit[], ) => { const keys = new Set(nouns.flatMap((nounsRaw) => nounsRaw.sources ?? []) .map((sourceKey) => `'${clearKey(sourceKey.split('#')[0])}'`)); const sources = await db.all>(SQL` SELECT s.*, u.username AS submitter FROM sources s LEFT JOIN users u ON s.submitter_id = u.id WHERE s.locale == ${locale} AND s.deleted = 0 AND s.approved >= ${isGranted(PermissionAreas.Sources) ? 0 : 1} AND s.key IN (`.append([...keys].join(',')).append(SQL`) `)); const sourcesMap: Record = {}; sources.forEach((s) => sourcesMap[s.key!] = s); return nouns.map((nounRaw) => ({ ...nounRaw, sourcesData: (nounRaw.sources ?? []) .map((sourceKey) => selectFragment(sourcesMap, sourceKey)) .filter((source) => source !== undefined), })); }; const selectFragment = (sourcesMap: Record, keyAndFragment: string) => { const [key, fragment] = keyAndFragment.split('#'); if (sourcesMap[key] === undefined) { return undefined; } if (fragment === undefined) { return sourcesMap[key]; } const source = { ...sourcesMap[key] }; const fragments = source.fragments ? source.fragments.replace(/\\@/g, '###').split('@') .map((x) => x.replace(/###/g, '@')) : []; source.fragments = fragments[parseInt(fragment) - 1]; return source; }; export const getNounEntries = defineCachedFunction(async ( db: Database, isGranted: IsGrantedFn, locale: string, ): Promise => { const nouns = (await db.all(SQL` SELECT n.*, u.username AS author FROM nouns n LEFT JOIN users u ON n.author_id = u.id WHERE n.locale = ${locale} AND n.deleted = 0 AND n.approved >= ${isGranted(PermissionAreas.Nouns) ? 0 : 1} ORDER BY n.approved, n.key `)).map((nounRow) => parseNounRowWithAuthor(nounRow, isGranted)); return await addVersions(db, isGranted, locale, nouns); }, { name: 'nouns', getKey: (db, isGranted, locale) => locale, shouldBypassCache: (db, isGranted) => isGranted(PermissionAreas.Nouns), maxAge: 24 * 60 * 60, }); export const approveNounEntry = async (db: Database, id: string, locale: string) => { const { base_id } = (await db.get>(SQL`SELECT base_id FROM nouns WHERE id=${id}`))!; if (base_id) { await db.get(SQL` UPDATE nouns SET deleted=1 WHERE id = ${base_id} `); } await db.get(SQL` UPDATE nouns SET approved = 1, base_id = NULL WHERE id = ${id} `); await invalidateCacheKind('nouns', locale); };