feat: establish backend foundation

This commit is contained in:
2026-08-12 23:04:21 +03:00
parent f8dcfb71c1
commit ab21426478
24 changed files with 2313 additions and 382 deletions
+39
View File
@@ -0,0 +1,39 @@
import { validateEnv } from './env.validation';
describe('validateEnv', () => {
it('normalizes FRONTEND_ORIGIN to its canonical origin', () => {
expect(
validateEnv({
DATABASE_URL: 'file:./data.db',
FRONTEND_ORIGIN: 'http://localhost:5173/',
}),
).toEqual({
DATABASE_URL: 'file:./data.db',
FRONTEND_ORIGIN: 'http://localhost:5173',
PORT: 3000,
});
});
it('fails fast when required configuration is missing', () => {
expect(() =>
validateEnv({ FRONTEND_ORIGIN: 'http://localhost:5173' }),
).toThrow('DATABASE_URL must be a SQLite file URL');
});
it.each([
'https://frontend.example/app',
'https://frontend.example?token=secret',
'https://frontend.example#fragment',
'ftp://frontend.example',
])(
'rejects a frontend origin that is not a canonical HTTP origin: %s',
(origin) => {
expect(() =>
validateEnv({
DATABASE_URL: 'file:./data.db',
FRONTEND_ORIGIN: origin,
}),
).toThrow('FRONTEND_ORIGIN must be a valid HTTP origin');
},
);
});
+46
View File
@@ -0,0 +1,46 @@
export interface Environment {
DATABASE_URL: string;
FRONTEND_ORIGIN: string;
PORT: number;
}
export function validateEnv(config: Record<string, unknown>): Environment {
const databaseUrl = config['DATABASE_URL'];
const frontendOrigin = config['FRONTEND_ORIGIN'];
const port = config['PORT'] ?? 3000;
if (typeof databaseUrl !== 'string' || !databaseUrl.startsWith('file:')) {
throw new Error('DATABASE_URL must be a SQLite file URL');
}
if (typeof frontendOrigin !== 'string') {
throw new Error('FRONTEND_ORIGIN must be a valid HTTP origin');
}
let parsedOrigin: URL;
try {
parsedOrigin = new URL(frontendOrigin);
} catch {
throw new Error('FRONTEND_ORIGIN must be a valid HTTP origin');
}
if (
(parsedOrigin.protocol !== 'http:' && parsedOrigin.protocol !== 'https:') ||
parsedOrigin.pathname !== '/' ||
parsedOrigin.search ||
parsedOrigin.hash
) {
throw new Error('FRONTEND_ORIGIN must be a valid HTTP origin');
}
const parsedPort = Number(port);
if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
throw new Error('PORT must be a valid port number');
}
return {
DATABASE_URL: databaseUrl,
FRONTEND_ORIGIN: parsedOrigin.origin,
PORT: parsedPort,
};
}
+3
View File
@@ -0,0 +1,3 @@
export function shouldExposeSwagger(nodeEnv: string | undefined): boolean {
return nodeEnv === 'development';
}