mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-03 19:17:07 -04:00
175 lines
5.9 KiB
TypeScript
175 lines
5.9 KiB
TypeScript
import * as Sentry from '@sentry/node';
|
|
import type { StartSpanOptions } from '@sentry/types';
|
|
import express from 'express';
|
|
import type { Request, Response, NextFunction } from 'express';
|
|
import session from 'express-session';
|
|
import grant from 'grant';
|
|
import { useBase } from 'h3';
|
|
import { defineExpressHandler, getH3Event } from 'h3-express';
|
|
|
|
import buildLocaleList from '../src/buildLocaleList.ts';
|
|
import { longtimeCookieSetting } from '../src/cookieSettings.ts';
|
|
import formatError from '../src/error.ts';
|
|
|
|
import './dotenv.ts';
|
|
|
|
import dbConnection from './db.ts';
|
|
import type { Database, SQLQuery } from './db.ts';
|
|
import adminRoute from './express/admin.ts';
|
|
import calendarRoute from './express/calendar.ts';
|
|
import censusRoute from './express/census.ts';
|
|
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 profileRoute from './express/profile.ts';
|
|
import pronounceRoute from './express/pronounce.ts';
|
|
import sentryRoute from './express/sentry.ts';
|
|
import subscriptionRoute from './express/subscription.ts';
|
|
import translationsRoute from './express/translations.ts';
|
|
import userRoute from './express/user.ts';
|
|
import { config } from './social.ts';
|
|
|
|
import { closeAuditLogConnection } from '~/server/audit.ts';
|
|
import { getLocale } from '~/server/data.ts';
|
|
|
|
class StorageStore extends session.Store {
|
|
get(sid: string, callback: (err: unknown, session?: (session.SessionData | null)) => void): void {
|
|
useStorage('session').getItem<session.SessionData>(sid)
|
|
.then((session) => callback(null, session))
|
|
.catch((error) => callback(error));
|
|
}
|
|
|
|
set(sid: string, session: session.SessionData, callback?: (err?: unknown) => void): void {
|
|
// unwrap session to make it a primitive object (otherwise unstorage will reject serializing the object)
|
|
useStorage('session').setItem(sid, { ...session }, { ttl: 24 * 60 * 60 })
|
|
.then(() => callback?.())
|
|
.catch((error) => callback?.(error));
|
|
}
|
|
|
|
destroy(sid: string, callback?: (err?: unknown) => void): void {
|
|
useStorage('session').removeItem(sid)
|
|
.then(() => callback?.())
|
|
.catch((error) => callback?.(error));
|
|
}
|
|
}
|
|
|
|
const router = express.Router();
|
|
|
|
router.use(session({
|
|
secret: process.env.SECRET!,
|
|
cookie: { ...longtimeCookieSetting, sameSite: undefined }, // somehow, sameSite=lax breaks sign-in with apple 🙄
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
store: new StorageStore(),
|
|
}));
|
|
|
|
export class LazyDatabase implements Database {
|
|
db: Database | null;
|
|
|
|
constructor() {
|
|
this.db = null;
|
|
}
|
|
|
|
async init(): Promise<void> {
|
|
if (this.db === null) {
|
|
this.db = await dbConnection();
|
|
// https://kerkour.com/sqlite-for-servers
|
|
await this.db.get('PRAGMA journal_mode = WAL;');
|
|
await this.db.get('PRAGMA busy_timeout = 5000;');
|
|
await this.db.get('PRAGMA synchronous = NORMAL;');
|
|
await this.db.get('PRAGMA cache_size = 1000000000;');
|
|
await this.db.get('PRAGMA foreign_keys = true;');
|
|
await this.db.get('PRAGMA temp_store = memory;');
|
|
}
|
|
}
|
|
|
|
buildSpanOptions(sql: SQLQuery): StartSpanOptions {
|
|
return {
|
|
name: typeof sql === 'string' ? sql : sql.sql,
|
|
op: 'db',
|
|
attributes: {
|
|
'db.system': 'sqlite',
|
|
},
|
|
};
|
|
}
|
|
|
|
async get<T = unknown>(sql: SQLQuery, ...args: unknown[]): Promise<T | undefined> {
|
|
await this.init();
|
|
return Sentry.startSpan(this.buildSpanOptions(sql), () => this.db!.get(sql, ...args));
|
|
}
|
|
|
|
async each<T = unknown>(sql: SQLQuery, callback: (err: unknown, row: T) => void): Promise<number> {
|
|
await this.init();
|
|
return Sentry.startSpan(this.buildSpanOptions(sql), () => this.db!.each(sql, callback));
|
|
}
|
|
|
|
async all<T = unknown>(sql: SQLQuery, ...args: unknown[]): Promise<T[]> {
|
|
await this.init();
|
|
return Sentry.startSpan(this.buildSpanOptions(sql), () => this.db!.all(sql, ...args));
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
if (this.db !== null) {
|
|
try {
|
|
await this.db.close();
|
|
this.db = null;
|
|
} catch (error) {
|
|
Sentry.captureException(error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
router.use(async function (req, res, next) {
|
|
try {
|
|
const locale = getLocale(getH3Event(req));
|
|
|
|
const authentication = await useAuthentication(getH3Event(req));
|
|
req.rawUser = authentication.rawUser;
|
|
req.user = authentication.user;
|
|
req.isGranted = authentication.isGranted;
|
|
req.locales = buildLocaleList(locale, locale === '_');
|
|
req.db = new LazyDatabase();
|
|
res.on('finish', async () => {
|
|
await req.db.close();
|
|
await closeAuditLogConnection();
|
|
});
|
|
res.set('Access-Control-Allow-Origin', '*');
|
|
res.set('Access-Control-Allow-Headers', 'authorization,content-type');
|
|
// necessary for grant and not added by h3-express
|
|
res.locals ??= {};
|
|
next();
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
router.use(grantOverridesRoute);
|
|
router.use(grant.express()(config));
|
|
|
|
router.use(sentryRoute);
|
|
|
|
router.use(userRoute);
|
|
router.use(profileRoute);
|
|
router.use(adminRoute);
|
|
router.use(mfaRoute);
|
|
router.use(pronounceRoute);
|
|
router.use(censusRoute);
|
|
router.use(imagesRoute);
|
|
router.use(calendarRoute);
|
|
router.use(translationsRoute);
|
|
router.use(subscriptionRoute);
|
|
router.use(discord);
|
|
|
|
router.use((err: Error, req: Request, res: Response, _next: NextFunction) => {
|
|
console.error(formatError(err, req));
|
|
res.status(500).send('Unexpected server error');
|
|
req.db.close();
|
|
closeAuditLogConnection();
|
|
});
|
|
|
|
router.use((_req, res) => res.status(404).send('Not found'));
|
|
|
|
export default useBase('/api', defineExpressHandler(router));
|