mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-09 07:26:01 -04:00
150 lines
5.2 KiB
TypeScript
150 lines
5.2 KiB
TypeScript
/* 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<NounRaw, 'sourcesData'> => {
|
|
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<NounRaw, 'sourcesData'>, 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<NounRaw, 'sourcesData'> => {
|
|
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<NounRaw, 'sourcesData'>[],
|
|
) => {
|
|
const keys = new Set(nouns.flatMap((nounsRaw) => nounsRaw.sources ?? [])
|
|
.map((sourceKey) => `'${clearKey(sourceKey.split('#')[0])}'`));
|
|
|
|
const sources = await db.all<SourceRow & Pick<UserRow, 'username'>>(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<string, SourceRow> = {};
|
|
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<string, SourceRow>, 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<NounRaw[]> => {
|
|
const nouns = (await db.all<NounRowWithAuthor>(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<Pick<NounRow, 'base_id'>>(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);
|
|
};
|