diff --git a/src/authenticate.ts b/server/authenticate.ts similarity index 91% rename from src/authenticate.ts rename to server/authenticate.ts index 0a4a5ec20..8c5273c7c 100644 --- a/src/authenticate.ts +++ b/server/authenticate.ts @@ -1,6 +1,6 @@ import jwt from './jwt.ts'; import type { Request } from 'express'; -import type { User } from './user.ts'; +import type { User } from '../src/user.ts'; export default ({ cookies, headers }: Request): User | undefined => { if (headers.authorization && headers.authorization.startsWith('Bearer ')) { diff --git a/server/cleanupAccounts.js b/server/cleanupAccounts.js index 816709ca5..43b950331 100644 --- a/server/cleanupAccounts.js +++ b/server/cleanupAccounts.js @@ -3,7 +3,7 @@ import './setup.ts'; import * as Sentry from '@sentry/node'; import dbConnection from './db.ts'; -import mailer from '../src/mailer.ts'; +import mailer from './mailer.ts'; const execute = process.env.EXECUTE === '1'; console.log(execute ? 'WILL EXECUTE!' : 'Dry run'); diff --git a/src/crypto.ts b/server/crypto.ts similarity index 83% rename from src/crypto.ts rename to server/crypto.ts index c6571d2da..cf5b968d5 100644 --- a/src/crypto.ts +++ b/server/crypto.ts @@ -1,8 +1,7 @@ import crypto from 'crypto'; import fs from 'fs'; import type { KeyObject, BinaryLike } from 'crypto'; - -const __dirname = new URL('.', import.meta.url).pathname; +import { rootDir } from './paths.ts'; class Crypto { privateKey: KeyObject; @@ -26,4 +25,4 @@ class Crypto { } } -export default new Crypto(`${__dirname}/../keys/private.pem`, `${__dirname}/../keys/public.pem`); +export default new Crypto(`${rootDir}/keys/private.pem`, `${rootDir}/keys/public.pem`); diff --git a/server/db.ts b/server/db.ts index b39e999f7..fff9e7531 100644 --- a/server/db.ts +++ b/server/db.ts @@ -1,7 +1,6 @@ import * as sqlite from 'sqlite'; import sqlite3 from 'sqlite3'; - -const __dirname = new URL('.', import.meta.url).pathname; +import { rootDir } from './paths.ts'; export type SQLQuery = sqlite.ISqlite.SqlType; export interface Database { @@ -12,6 +11,6 @@ export interface Database { } export default (): Promise => sqlite.open({ - filename: `${__dirname}/../db.sqlite`, + filename: `${rootDir}/db.sqlite`, driver: sqlite3.Database, }); diff --git a/server/express/admin.ts b/server/express/admin.ts index a00416c00..d530dc796 100644 --- a/server/express/admin.ts +++ b/server/express/admin.ts @@ -7,7 +7,7 @@ import type { LocaleDescription } from '../../locale/locales.ts'; import allLocales from '../../locale/locales.ts'; import fs from 'fs'; import { caches } from '../../src/cache.ts'; -import mailer from '../../src/mailer.ts'; +import mailer from '../mailer.ts'; import { profilesSnapshot } from './profile.ts'; import { archiveBan, liftBan } from '../ban.ts'; import markdownit from 'markdown-it'; @@ -16,6 +16,7 @@ import { encodeTime, decodeTime, ulid } from 'ulid'; import Suml from 'suml'; import buildLocaleList from '../../src/buildLocaleList.ts'; import auditLog from '../audit.ts'; +import { rootDir } from '../paths.ts'; import type { UserRow } from './user.ts'; import type { Database } from '../db.ts'; import type { Config } from '../../locale/config.ts'; @@ -620,7 +621,7 @@ router.get('/admin/moderation', handleErrorAsync(async (req, res) => { return res.status(401).json({ error: 'Unauthorised' }); } - const dir = `${__dirname}/../../moderation`; + const dir = `${rootDir}/moderation`; return res.json({ susRegexes: fs.readFileSync(`${dir}/sus.txt`).toString('utf-8') diff --git a/server/express/banner.ts b/server/express/banner.ts index 802eb3ac7..e210f4aa4 100644 --- a/server/express/banner.ts +++ b/server/express/banner.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import SQL from 'sql-template-strings'; import { createCanvas, loadImage } from 'canvas'; import type { CanvasRenderingContext2D, Image } from 'canvas'; +import { rootDir } from '../paths.ts'; import { loadSuml, loadSumlFromBase } from '../loader.ts'; import avatar from '../avatar.ts'; import { buildPronoun, parsePronounGroups, parsePronouns } from '../../src/buildPronoun.ts'; @@ -19,8 +20,8 @@ const baseTranslations = loadSumlFromBase('locale/_base/translations') as Transl const translator = new Translator(translations, baseTranslations, global.config); -const pronouns = parsePronouns(global.config, loadTsv(`${__dirname}/../../data/pronouns/pronouns.tsv`)); -const pronounGroups = parsePronounGroups(loadTsv(`${__dirname}/../../data/pronouns/pronounGroups.tsv`)); +const pronouns = parsePronouns(global.config, loadTsv(`${rootDir}/data/pronouns/pronouns.tsv`)); +const pronounGroups = parsePronounGroups(loadTsv(`${rootDir}/data/pronouns/pronounGroups.tsv`)); const pronounLibrary = new PronounLibrary(global.config, pronounGroups, pronouns); const drawCircle = (context: CanvasRenderingContext2D, image: Image, x: number, y: number, size: number): void => { diff --git a/server/express/blog.ts b/server/express/blog.ts index 0a81c8d2f..44fde8b2a 100644 --- a/server/express/blog.ts +++ b/server/express/blog.ts @@ -1,4 +1,5 @@ import { Router } from 'express'; +import { rootDir } from '../paths.ts'; import { handleErrorAsync } from '../../src/helpers.ts'; import fs from 'fs'; import { caches } from '../../src/cache.ts'; @@ -15,7 +16,7 @@ const router = Router(); router.get('/blog', handleErrorAsync(async (req, res) => { const posts = await caches.blog.fetch(async () => { - const dir = `${__dirname}/../../data/blog`; + const dir = `${rootDir}/data/blog`; const posts: Post[] = []; fs.readdirSync(dir).forEach((file) => { if (!file.endsWith('.md')) { diff --git a/server/express/home.ts b/server/express/home.ts index 0220f6e85..d22885328 100644 --- a/server/express/home.ts +++ b/server/express/home.ts @@ -1,4 +1,5 @@ import { Router } from 'express'; +import { rootDir } from '../paths.ts'; import { handleErrorAsync } from '../../src/helpers.ts'; import buildLocaleList from '../../src/buildLocaleList.ts'; import fs from 'fs'; @@ -14,7 +15,7 @@ router.get('/locales', handleErrorAsync(async (req, res) => { })); router.get('/version', handleErrorAsync(async (req, res) => { - const versionFile = `${__dirname}/../../cache/version`; + const versionFile = `${rootDir}/cache/version`; return res.json(fs.existsSync(versionFile) ? fs.readFileSync(versionFile).toString('utf-8') : null); })); diff --git a/server/express/images.ts b/server/express/images.ts index 03d7adca7..4c6ee5b7e 100644 --- a/server/express/images.ts +++ b/server/express/images.ts @@ -8,6 +8,7 @@ import sharp from 'sharp'; import fs from 'fs'; import SQL from 'sql-template-strings'; import path from 'path'; +import { rootDir } from '../paths.ts'; import auditLog from '../audit.ts'; import { awsConfig, awsParams } from '../aws.ts'; @@ -109,7 +110,7 @@ router.post('/images/upload', multer({ limits: { fileSize: 10 * 1024 * 1024 } }) router.get('/download/:filename*', handleErrorAsync(async (req, res) => { const filename = req.params.filename + req.params[0]; - const filepath = `${__dirname}/../../locale/${global.config.locale}/files/${filename}`; + const filepath = `${rootDir}/locale/${global.config.locale}/files/${filename}`; if (!fs.existsSync(filepath)) { return res.status(404).json({ error: 'Not found' }); diff --git a/server/express/profile.ts b/server/express/profile.ts index 96de1286d..6e2b76115 100644 --- a/server/express/profile.ts +++ b/server/express/profile.ts @@ -5,6 +5,7 @@ import md5 from 'js-md5'; import { ulid } from 'ulid'; import * as Sentry from '@sentry/node'; import type { ParsedQs } from 'qs'; +import { rootDir } from '../paths.ts'; import avatar from '../avatar.ts'; import { handleErrorAsync, now, isValidLink } from '../../src/helpers.ts'; import { caches } from '../../src/cache.ts'; @@ -17,7 +18,7 @@ import { normaliseUrl } from '../../src/links.ts'; import allLocales from '../../locale/locales.ts'; import type { LocaleDescription } from '../../locale/locales.ts'; import auditLog from '../audit.ts'; -import crypto from '../../src/crypto.ts'; +import crypto from '../crypto.ts'; import { awsConfig, awsParams } from '../aws.ts'; import { S3, NoSuchKey } from '@aws-sdk/client-s3'; import zlib from 'zlib'; @@ -35,8 +36,6 @@ import type { } from '../../src/profile.ts'; import type { Opinion } from '../../src/opinions.ts'; -const __dirname = new URL('.', import.meta.url).pathname; - interface LinkRow { url: string | null; expiresAt: number | null; @@ -369,7 +368,7 @@ export const profilesSnapshot = async ( return JSON.stringify(await fetchProfiles(db, username, true, opts), null, 4); }; -const susRegexes = fs.readFileSync(`${__dirname}/../../moderation/sus.txt`).toString('utf-8') +const susRegexes = fs.readFileSync(`${rootDir}/moderation/sus.txt`).toString('utf-8') .split('\n') .filter((x) => !!x); diff --git a/server/express/user.ts b/server/express/user.ts index edcf7d7a0..ee78bf1f1 100644 --- a/server/express/user.ts +++ b/server/express/user.ts @@ -4,8 +4,8 @@ import type { NextFunction, Request, Response } from 'express'; import SQL from 'sql-template-strings'; import { ulid } from 'ulid'; import { buildDict, makeId, now, handleErrorAsync, obfuscateEmail } from '../../src/helpers.ts'; -import jwt from '../../src/jwt.ts'; -import mailer from '../../src/mailer.ts'; +import jwt from '../jwt.ts'; +import mailer from '../mailer.ts'; import { loadSuml } from '../loader.ts'; import avatar from '../avatar.ts'; import type { SocialProfilePayload } from '../social.ts'; diff --git a/server/index.ts b/server/index.ts index 567b93bdc..97f6959ab 100644 --- a/server/index.ts +++ b/server/index.ts @@ -6,7 +6,7 @@ import { useBase } from 'h3'; import { defineExpressHandler } from 'h3-express'; import * as Sentry from '@sentry/node'; import type { StartSpanOptions } from '@sentry/types'; -import authenticate from '../src/authenticate.ts'; +import authenticate from './authenticate.ts'; import dbConnection from './db.ts'; import type { Database, SQLQuery } from './db.ts'; import grant from 'grant'; diff --git a/src/jwt.ts b/server/jwt.ts similarity index 90% rename from src/jwt.ts rename to server/jwt.ts index 355a9ef00..d51ad9eca 100644 --- a/src/jwt.ts +++ b/server/jwt.ts @@ -2,6 +2,7 @@ import jwt from 'jsonwebtoken'; import fs from 'fs'; import type { JwtPayload } from 'jsonwebtoken'; import * as Sentry from '@sentry/node'; +import { rootDir } from './paths.ts'; const __dirname = new URL('.', import.meta.url).pathname; @@ -36,4 +37,4 @@ class Jwt { } } -export default new Jwt(`${__dirname}/../keys/private.pem`, `${__dirname}/../keys/public.pem`); +export default new Jwt(`${rootDir}/keys/private.pem`, `${rootDir}/keys/public.pem`); diff --git a/server/loader.ts b/server/loader.ts index f45674841..008dd0999 100644 --- a/server/loader.ts +++ b/server/loader.ts @@ -2,8 +2,9 @@ import fs from 'fs'; import Suml from 'suml'; import { loadTsv as baseLoadTsv } from '../src/tsv.ts'; +import { rootDir } from './paths.ts'; -export const loadSumlFromBase = (name: string): unknown => new Suml().parse(fs.readFileSync(`./${name}.suml`, 'utf-8')); +export const loadSumlFromBase = (name: string): unknown => new Suml().parse(fs.readFileSync(`${rootDir}/${name}.suml`, 'utf-8')); export const loadSuml = (name: string): unknown => loadSumlFromBase(`data/${name}`); -export const loadTsv = (name: string): T[] => baseLoadTsv(`./data/${name}.tsv`); +export const loadTsv = (name: string): T[] => baseLoadTsv(`${rootDir}/data/${name}.tsv`); diff --git a/server/localeFont.ts b/server/localeFont.ts index 0c94c5b63..58cc18090 100644 --- a/server/localeFont.ts +++ b/server/localeFont.ts @@ -1,11 +1,10 @@ import { registerFont } from 'canvas'; import fs from 'fs'; - -const __dirname = new URL('.', import.meta.url).pathname; +import { rootDir } from './paths.ts'; const vars: Record = {}; -for (const [, name, value] of fs.readFileSync(`${__dirname}/../data/variables.scss`).toString('utf-8') +for (const [, name, value] of fs.readFileSync(`${rootDir}/data/variables.scss`).toString('utf-8') .matchAll(/^\$([^:]+): '([^']+)'(?:, '[^']+')*;$/gm)) { vars[name] = value; } diff --git a/src/mailer.ts b/server/mailer.ts similarity index 95% rename from src/mailer.ts rename to server/mailer.ts index 38e731bdc..41f70c04d 100644 --- a/src/mailer.ts +++ b/server/mailer.ts @@ -1,23 +1,19 @@ import nodemailer from 'nodemailer'; import fs from 'fs'; -import Suml from 'suml'; import * as Sentry from '@sentry/node'; import type StreamTransport from 'nodemailer/lib/stream-transport/index.d.ts'; import type { Readable } from 'stream'; +import { loadSuml, loadSumlFromBase } from './loader.ts'; +import { rootDir } from './paths.ts'; import type { Translations } from '../locale/translations.ts'; -const __dirname = new URL('.', import.meta.url).pathname; - const color = '#C71585'; -const logo = fs.readFileSync(`${__dirname}/../public/logo/logo-primary.svg`).toString('utf-8'); +const logo = fs.readFileSync(`${rootDir}/public/logo/logo-primary.svg`).toString('utf-8'); const logoEncoded = `data:image/svg+xml,${encodeURIComponent(logo)}`; -const loadSuml = (name: string): unknown => { - return new Suml().parse(fs.readFileSync(`${__dirname}/../${name}.suml`).toString()); -}; -const translations = loadSuml('data/translations') as Translations; -const fallbackTranslations = loadSuml('locale/_base/translations') as Translations; +const translations = loadSuml('translations') as Translations; +const fallbackTranslations = loadSumlFromBase('locale/_base/translations') as Translations; let transport: string | StreamTransport.Options = process.env.MAILER_TRANSPORT!; if (!transport && process.env.NODE_ENV === 'development') { diff --git a/server/miastamaszerujace.ts b/server/miastamaszerujace.ts index 9b3d912e9..b93842fc6 100644 --- a/server/miastamaszerujace.ts +++ b/server/miastamaszerujace.ts @@ -5,7 +5,7 @@ import { JSDOM } from 'jsdom'; import * as Sentry from '@sentry/node'; import { Day } from '../src/calendar/helpers.ts'; import fs from 'fs'; -import mailer from '../src/mailer.ts'; +import mailer from './mailer.ts'; import type { MiastamaszerujaceEvent } from '../src/calendar/helpers.ts'; import { createEvents } from 'ics'; diff --git a/server/notify.js b/server/notify.js index 3be6279e2..057688019 100644 --- a/server/notify.js +++ b/server/notify.js @@ -1,7 +1,7 @@ import './setup.ts'; import dbConnection from './db.ts'; -import mailer from '../src/mailer.ts'; +import mailer from './mailer.ts'; import { isGrantedForUser } from '../src/helpers.ts'; const shouldNotify = (frequency) => { diff --git a/server/paths.ts b/server/paths.ts new file mode 100644 index 000000000..6c374e8ad --- /dev/null +++ b/server/paths.ts @@ -0,0 +1,5 @@ +// Nitro is designed to be independent of the filesystem, but that requires migration in some express routes +// in dev mode, import.meta.url references `.nuxt/dev/index.mjs` and not the file path of this file +// in production mode, import.meta.url is not a directory, so by convention the working directory must be the repo root, +// however, `nuxi preview` automatically changes into .output +export const rootDir = process.cwd().replace(/\/\.output$/, ''); diff --git a/server/subscriptions.js b/server/subscriptions.js index b579ccd9d..1c1b61e47 100644 --- a/server/subscriptions.js +++ b/server/subscriptions.js @@ -3,7 +3,7 @@ import './setup.ts'; import dbConnection from './db.ts'; import SQL from 'sql-template-strings'; import { ulid, decodeTime } from 'ulid'; -import mailer from '../src/mailer.ts'; +import mailer from './mailer.ts'; const CAMPAIGNS = [ { diff --git a/src/cache.ts b/src/cache.ts index e3c6a1ff9..a3f93ba8a 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,13 +1,13 @@ import fs from 'fs'; -const __dirname = new URL('.', import.meta.url).pathname; +import { rootDir } from '../server/paths.ts'; export class CacheObject { path: string; maxAgeMinutes: number; constructor(dir: string, filename: string, maxAgeMinutes: number) { - const cacheDir = `${__dirname}/../cache/${dir}`; + const cacheDir = `${rootDir}/cache/${dir}`; if (filename.includes('..')) { throw 'Insecure'; } diff --git a/src/stats.ts b/src/stats.ts index f11ae92b4..37857adde 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -1,5 +1,5 @@ import { decodeTime, ulid } from 'ulid'; -import mailer from './mailer.ts'; +import mailer from '../server/mailer.ts'; import Plausible from 'plausible-api'; import fetch from 'node-fetch'; import { listMissingTranslations } from './missingTranslations.ts';