mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-03 19:17:07 -04:00
(refactor) migrate /api/names/** from express to h3
This commit is contained in:
parent
317758db01
commit
0299dbec0e
@ -6,7 +6,7 @@ import useConfig from '~/composables/useConfig.ts';
|
||||
import useDialogue from '~/composables/useDialogue.ts';
|
||||
import useHash from '~/composables/useHash.ts';
|
||||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||||
import { Name, type NameRaw } from '~/src/classes.ts';
|
||||
import { Name } from '~/src/classes.ts';
|
||||
import { buildDict } from '~/src/helpers.ts';
|
||||
|
||||
definePageMeta({
|
||||
@ -21,7 +21,7 @@ useSimpleHead({
|
||||
const { handleHash, setHash } = useHash();
|
||||
|
||||
const namesAsyncData = useAsyncData(async () => {
|
||||
const namesRaw = await $fetch<NameRaw[]>('/api/names');
|
||||
const namesRaw = await $fetch('/api/names');
|
||||
|
||||
return buildDict(function* () {
|
||||
const sorted = namesRaw.sort((a, b) => {
|
||||
|
19
server/api/names/approve/[id].post.ts
Normal file
19
server/api/names/approve/[id].post.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { auditLog } from '~/server/audit.ts';
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
import { approveNameEntry } from '~/server/names.ts';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { user, isGranted } = await useAuthentication(event);
|
||||
if (!isGranted('names')) {
|
||||
throw createError({
|
||||
status: 401,
|
||||
statusMessage: 'Unauthorised',
|
||||
});
|
||||
}
|
||||
|
||||
const id = getRouterParam(event, 'id')!;
|
||||
const db = useDatabase();
|
||||
await approveNameEntry(db, id, getLocale(event));
|
||||
|
||||
await auditLog({ user }, 'names/approved', { id });
|
||||
});
|
26
server/api/names/hide/[id].post.ts
Normal file
26
server/api/names/hide/[id].post.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import SQL from 'sql-template-strings';
|
||||
|
||||
import { auditLog } from '~/server/audit.ts';
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { user, isGranted } = await useAuthentication(event);
|
||||
if (!isGranted('names')) {
|
||||
throw createError({
|
||||
status: 401,
|
||||
statusMessage: 'Unauthorised',
|
||||
});
|
||||
}
|
||||
|
||||
const id = getRouterParam(event, 'id');
|
||||
const db = useDatabase();
|
||||
await db.get(SQL`
|
||||
UPDATE names
|
||||
SET approved = 0
|
||||
WHERE id = ${id}
|
||||
`);
|
||||
|
||||
await invalidateCache('names', getLocale(event));
|
||||
|
||||
await auditLog({ user }, 'names/hidden', { id });
|
||||
});
|
8
server/api/names/index.get.ts
Normal file
8
server/api/names/index.get.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
import { getNameEntries } from '~/server/names.ts';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { isGranted } = await useAuthentication(event);
|
||||
const db = useDatabase();
|
||||
return await getNameEntries(db, isGranted, getLocale(event));
|
||||
});
|
26
server/api/names/remove/[id].post.ts
Normal file
26
server/api/names/remove/[id].post.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import SQL from 'sql-template-strings';
|
||||
|
||||
import { auditLog } from '~/server/audit.ts';
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { user, isGranted } = await useAuthentication(event);
|
||||
if (!isGranted('names')) {
|
||||
throw createError({
|
||||
status: 401,
|
||||
statusMessage: 'Unauthorised',
|
||||
});
|
||||
}
|
||||
|
||||
const id = getRouterParam(event, 'id');
|
||||
const db = useDatabase();
|
||||
await db.get(SQL`
|
||||
UPDATE names
|
||||
SET deleted=1
|
||||
WHERE id = ${id}
|
||||
`);
|
||||
|
||||
await invalidateCache('names', getLocale(event));
|
||||
|
||||
await auditLog({ user }, 'names/removed', { id });
|
||||
});
|
44
server/api/names/submit.post.ts
Normal file
44
server/api/names/submit.post.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import SQL from 'sql-template-strings';
|
||||
import { ulid } from 'ulid';
|
||||
|
||||
import { auditLog } from '~/server/audit.ts';
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
import { approveNameEntry } from '~/server/names.ts';
|
||||
import { isAllowedToPost } from '~/server/user.ts';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { user, isGranted } = await useAuthentication(event);
|
||||
const db = useDatabase();
|
||||
|
||||
if (!user || !await isAllowedToPost(db, user)) {
|
||||
throw createError({
|
||||
status: 401,
|
||||
statusMessage: 'Unauthorised',
|
||||
});
|
||||
}
|
||||
|
||||
const locale = getLocale(event);
|
||||
const body = await readBody(event);
|
||||
|
||||
const id = ulid();
|
||||
await db.get(SQL`
|
||||
INSERT INTO names (id, name, locale, origin, meaning, usage, legally, pros, cons, notablePeople, links, namedays, namedaysComment, deleted, approved, base_id, author_id)
|
||||
VALUES (
|
||||
${id},
|
||||
${body.name}, ${locale},
|
||||
${body.origin || null}, ${body.meaning || null}, ${body.usage || null}, ${body.legally || null},
|
||||
${body.pros.length ? body.pros.join('|') : null}, ${body.cons.length ? body.cons.join('|') : null},
|
||||
${body.notablePeople.length ? body.notablePeople.join('|') : null}, ${body.links.length ? body.links.join('|') : null},
|
||||
${body.namedays.length ? body.namedays.join('|') : null}, ${body.namedaysComment || null},
|
||||
0, 0, ${body.base}, ${user.id}
|
||||
)
|
||||
`);
|
||||
await auditLog({ user }, 'names/submitted', body);
|
||||
|
||||
if (isGranted('names')) {
|
||||
await approveNameEntry(db, id, locale);
|
||||
await auditLog({ user }, 'names/approved', { id });
|
||||
}
|
||||
|
||||
setResponseStatus(event, 201, 'Created');
|
||||
});
|
@ -1,151 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
import type { Request } from 'express';
|
||||
import { getH3Event } from 'h3-express';
|
||||
import SQL from 'sql-template-strings';
|
||||
import { ulid } from 'ulid';
|
||||
|
||||
import { handleErrorAsync } from '../../src/helpers.ts';
|
||||
import { auditLog } from '../audit.ts';
|
||||
import type { Database } from '../db.ts';
|
||||
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
|
||||
interface NameRow {
|
||||
id: string;
|
||||
name: string;
|
||||
locale: string;
|
||||
origin: string | null;
|
||||
meaning: string | null;
|
||||
usage: string | null;
|
||||
legally: string | null;
|
||||
pros: string | null;
|
||||
cons: string | null;
|
||||
notablePeople: string | null;
|
||||
links: string | null;
|
||||
namedays: string | null;
|
||||
namedaysComment: string | null;
|
||||
deleted: number;
|
||||
approved: number;
|
||||
base_id: string | null;
|
||||
author_id: string | null;
|
||||
}
|
||||
|
||||
const approve = async (db: Database, id: string, locale: string) => {
|
||||
const { base_id } = (await db.get<Pick<NameRow, 'base_id'>>(SQL`SELECT base_id FROM names WHERE id=${id}`))!;
|
||||
if (base_id) {
|
||||
await db.get(SQL`
|
||||
UPDATE names
|
||||
SET deleted=1
|
||||
WHERE id = ${base_id}
|
||||
`);
|
||||
}
|
||||
await db.get(SQL`
|
||||
UPDATE names
|
||||
SET approved = 1, base_id = NULL
|
||||
WHERE id = ${id}
|
||||
`);
|
||||
await invalidateCache('names', locale);
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
|
||||
const getNames = defineCachedFunction(async (db: Database, isGranted: Request['isGranted'], locale: string) => {
|
||||
return await db.all(SQL`
|
||||
SELECT n.*, u.username AS author FROM names n
|
||||
LEFT JOIN users u ON n.author_id = u.id
|
||||
WHERE n.locale = ${locale}
|
||||
AND n.deleted = 0
|
||||
AND n.approved >= ${isGranted('names') ? 0 : 1}
|
||||
ORDER BY n.approved, n.name
|
||||
`);
|
||||
}, {
|
||||
name: 'names',
|
||||
getKey: (db, isGranted, locale) => locale,
|
||||
shouldBypassCache: (db, isGranted) => isGranted('names'),
|
||||
maxAge: 24 * 60 * 60,
|
||||
});
|
||||
|
||||
router.get('/names', handleErrorAsync(async (req, res) => {
|
||||
const locale = getLocale(getH3Event(req));
|
||||
return res.json(await getNames(req.db, req.isGranted, locale));
|
||||
}));
|
||||
|
||||
router.post('/names/submit', handleErrorAsync(async (req, res) => {
|
||||
if (!req.user || !await req.isUserAllowedToPost()) {
|
||||
return res.status(401).json({ error: 'Unauthorised' });
|
||||
}
|
||||
|
||||
const locale = getLocale(getH3Event(req));
|
||||
|
||||
const id = ulid();
|
||||
await req.db.get(SQL`
|
||||
INSERT INTO names (id, name, locale, origin, meaning, usage, legally, pros, cons, notablePeople, links, namedays, namedaysComment, deleted, approved, base_id, author_id)
|
||||
VALUES (
|
||||
${id},
|
||||
${req.body.name}, ${locale},
|
||||
${req.body.origin || null}, ${req.body.meaning || null}, ${req.body.usage || null}, ${req.body.legally || null},
|
||||
${req.body.pros.length ? req.body.pros.join('|') : null}, ${req.body.cons.length ? req.body.cons.join('|') : null},
|
||||
${req.body.notablePeople.length ? req.body.notablePeople.join('|') : null}, ${req.body.links.length ? req.body.links.join('|') : null},
|
||||
${req.body.namedays.length ? req.body.namedays.join('|') : null}, ${req.body.namedaysComment || null},
|
||||
0, 0, ${req.body.base}, ${req.user ? req.user.id : null}
|
||||
)
|
||||
`);
|
||||
await auditLog(req, 'names/submitted', { ...req.body });
|
||||
|
||||
if (req.isGranted('names')) {
|
||||
await approve(req.db, id, locale);
|
||||
await auditLog(req, 'names/approved', { id });
|
||||
}
|
||||
|
||||
return res.json('ok');
|
||||
}));
|
||||
|
||||
router.post('/names/hide/:id', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('names')) {
|
||||
return res.status(401).json({ error: 'Unauthorised' });
|
||||
}
|
||||
|
||||
await req.db.get(SQL`
|
||||
UPDATE names
|
||||
SET approved = 0
|
||||
WHERE id = ${req.params.id}
|
||||
`);
|
||||
|
||||
await invalidateCache('names', getLocale(getH3Event(req)));
|
||||
|
||||
await auditLog(req, 'names/hidden', { id: req.params.id });
|
||||
|
||||
return res.json('ok');
|
||||
}));
|
||||
|
||||
router.post('/names/approve/:id', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('names')) {
|
||||
return res.status(401).json({ error: 'Unauthorised' });
|
||||
}
|
||||
|
||||
await approve(req.db, req.params.id, getLocale(getH3Event(req)));
|
||||
|
||||
await auditLog(req, 'names/approved', { id: req.params.id });
|
||||
|
||||
return res.json('ok');
|
||||
}));
|
||||
|
||||
router.post('/names/remove/:id', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('names')) {
|
||||
return res.status(401).json({ error: 'Unauthorised' });
|
||||
}
|
||||
|
||||
await req.db.get(SQL`
|
||||
UPDATE names
|
||||
SET deleted=1
|
||||
WHERE id = ${req.params.id}
|
||||
`);
|
||||
|
||||
await invalidateCache('names', getLocale(getH3Event(req)));
|
||||
|
||||
await auditLog(req, 'names/removed', { id: req.params.id });
|
||||
|
||||
return res.json('ok');
|
||||
}));
|
||||
|
||||
export default router;
|
1
server/global.d.ts
vendored
1
server/global.d.ts
vendored
@ -13,7 +13,6 @@ declare global {
|
||||
isGranted: (area?: string, locale?: string) => boolean;
|
||||
locales: Record<string, LocaleDescription>;
|
||||
db: Database;
|
||||
isUserAllowedToPost: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
|
@ -22,7 +22,6 @@ import discord from './express/discord.ts';
|
||||
import grantOverridesRoute from './express/grantOverrides.ts';
|
||||
import imagesRoute from './express/images.ts';
|
||||
import mfaRoute from './express/mfa.ts';
|
||||
import namesRoute from './express/names.ts';
|
||||
import profileRoute from './express/profile.ts';
|
||||
import pronounceRoute from './express/pronounce.ts';
|
||||
import sentryRoute from './express/sentry.ts';
|
||||
@ -33,7 +32,6 @@ import { config } from './social.ts';
|
||||
|
||||
import { closeAuditLogConnection } from '~/server/audit.ts';
|
||||
import { getLocale } from '~/server/data.ts';
|
||||
import { isAllowedToPost } from '~/server/user.ts';
|
||||
|
||||
class StorageStore extends session.Store {
|
||||
get(sid: string, callback: (err: unknown, session?: (session.SessionData | null)) => void): void {
|
||||
@ -133,7 +131,6 @@ router.use(async function (req, res, next) {
|
||||
req.isGranted = authentication.isGranted;
|
||||
req.locales = buildLocaleList(locale, locale === '_');
|
||||
req.db = new LazyDatabase();
|
||||
req.isUserAllowedToPost = (): Promise<boolean> => isAllowedToPost(req.db, req.user);
|
||||
res.on('finish', async () => {
|
||||
await req.db.close();
|
||||
await closeAuditLogConnection();
|
||||
@ -159,7 +156,6 @@ router.use(adminRoute);
|
||||
router.use(mfaRoute);
|
||||
router.use(pronounceRoute);
|
||||
router.use(censusRoute);
|
||||
router.use(namesRoute);
|
||||
router.use(imagesRoute);
|
||||
router.use(calendarRoute);
|
||||
router.use(translationsRoute);
|
||||
|
63
server/names.ts
Normal file
63
server/names.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import SQL from 'sql-template-strings';
|
||||
|
||||
import type { Database } from '~/server/db.ts';
|
||||
import type { User } from '~/src/user.ts';
|
||||
|
||||
interface NameRow {
|
||||
id: string;
|
||||
name: string;
|
||||
locale: string;
|
||||
origin: string | null;
|
||||
meaning: string | null;
|
||||
usage: string | null;
|
||||
legally: string | null;
|
||||
pros: string | null;
|
||||
cons: string | null;
|
||||
notablePeople: string | null;
|
||||
links: string | null;
|
||||
namedays: string | null;
|
||||
namedaysComment: string | null;
|
||||
deleted: boolean;
|
||||
approved: boolean;
|
||||
base_id: string | null;
|
||||
author_id: string | null;
|
||||
}
|
||||
|
||||
type NameRowWithAuthor = NameRow & { author: User['username'] };
|
||||
|
||||
export const getNameEntries = defineCachedFunction(async (
|
||||
db: Database,
|
||||
isGranted: IsGrantedFn,
|
||||
locale: string,
|
||||
) => {
|
||||
return await db.all<NameRowWithAuthor>(SQL`
|
||||
SELECT n.*, u.username AS author FROM names n
|
||||
LEFT JOIN users u ON n.author_id = u.id
|
||||
WHERE n.locale = ${locale}
|
||||
AND n.deleted = 0
|
||||
AND n.approved >= ${isGranted('names') ? 0 : 1}
|
||||
ORDER BY n.approved, n.name
|
||||
`);
|
||||
}, {
|
||||
name: 'names',
|
||||
getKey: (db, isGranted, locale) => locale,
|
||||
shouldBypassCache: (db, isGranted) => isGranted('names'),
|
||||
maxAge: 24 * 60 * 60,
|
||||
});
|
||||
|
||||
export const approveNameEntry = async (db: Database, id: string, locale: string) => {
|
||||
const { base_id } = (await db.get<Pick<NameRow, 'base_id'>>(SQL`SELECT base_id FROM names WHERE id=${id}`))!;
|
||||
if (base_id) {
|
||||
await db.get(SQL`
|
||||
UPDATE names
|
||||
SET deleted=1
|
||||
WHERE id = ${base_id}
|
||||
`);
|
||||
}
|
||||
await db.get(SQL`
|
||||
UPDATE names
|
||||
SET approved = 1, base_id = NULL
|
||||
WHERE id = ${id}
|
||||
`);
|
||||
await invalidateCache('names', locale);
|
||||
};
|
@ -1317,16 +1317,16 @@ export class TermsEntry implements Entry {
|
||||
export interface NameRaw {
|
||||
id: string;
|
||||
name: string;
|
||||
origin: string;
|
||||
meaning: string;
|
||||
usage: string;
|
||||
legally: string;
|
||||
pros: string;
|
||||
cons: string;
|
||||
notablePeople: string;
|
||||
links: string;
|
||||
namedays: string;
|
||||
namedaysComment: string;
|
||||
origin: string | null;
|
||||
meaning: string | null;
|
||||
usage: string | null;
|
||||
legally: string | null;
|
||||
pros: string | null;
|
||||
cons: string | null;
|
||||
notablePeople: string | null;
|
||||
links: string | null;
|
||||
namedays: string | null;
|
||||
namedaysComment: string | null;
|
||||
approved: boolean;
|
||||
base_id: string | null;
|
||||
author: string | null;
|
||||
@ -1335,16 +1335,16 @@ export interface NameRaw {
|
||||
export class Name {
|
||||
id: string;
|
||||
name: string;
|
||||
origin: string;
|
||||
meaning: string;
|
||||
usage: string;
|
||||
legally: string;
|
||||
origin: string | null;
|
||||
meaning: string | null;
|
||||
usage: string | null;
|
||||
legally: string | null;
|
||||
pros: string[];
|
||||
cons: string[];
|
||||
notablePeople: string[];
|
||||
links: string[];
|
||||
namedays: string[];
|
||||
namedaysComment: string;
|
||||
namedaysComment: string | null;
|
||||
approved: boolean;
|
||||
base: string | null;
|
||||
author: string | null;
|
||||
|
Loading…
x
Reference in New Issue
Block a user