feat(backend): add standalone data plane

Replace the Supabase application data plane with the Node/PostgreSQL API, migrate mobile, admin, web, and MCP callers, add controlled cutover tooling, and define hardened Dokploy infrastructure.
This commit is contained in:
jjbrunton committed 2026-08-28 14:42:11 +01:00
1 parent 01bb8225ae
commit 006761d3b0
294 files changed
+16259 -6138

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
import { z } from 'zod';
const baseSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().int().min(1).max(65535).default(3003),
DATABASE_URL: z.string().url(),
SUPABASE_AUTH_URL: z.string().url().optional(),
SUPABASE_AUTH_ISSUER: z.string().url().optional(),
SUPABASE_AUTH_JWKS_URL: z.string().url().optional(),
SUPABASE_AUTH_AUDIENCE: z.string().min(1).default('authenticated'),
AUTH_TEST_JWKS: z.string().optional(),
MEDIA_ROOT: z.string().min(1).default('/data/media'),
MEDIA_SIGNING_SECRET: z.string().min(32).optional(),
MEDIA_PUBLIC_BASE_URL: z.string().url().optional(),
SUPABASE_AUTH_SERVICE_ROLE_KEY: z.string().min(1).optional(),
REVENUECAT_API_KEY: z.string().min(1).optional(),
REVENUECAT_ENTITLEMENT_ID: z.string().min(1).default('Sauci Pro'),
REVENUECAT_WEBHOOK_SECRET: z.string().min(1).optional(),
ADMIN_API_SERVICE_TOKEN: z.string().min(32).optional(),
ADMIN_API_SERVICE_USER_ID: z.string().uuid().optional(),
ADMIN_PRIVATE_KEY_JWK: z.string().optional(),
});
export interface AppConfig {
nodeEnv: 'development' | 'test' | 'production';
port: number;
databaseUrl: string;
authIssuer: string;
authJwksUrl?: string;
authAudience: string;
authTestJwks?: string;
mediaRoot: string;
mediaSigningSecret: string;
mediaPublicBaseUrl: string;
supabaseAuthServiceRoleKey?: string;
revenueCatApiKey?: string;
revenueCatEntitlementId: string;
revenueCatWebhookSecret?: string;
adminApiServiceToken?: string;
adminApiServiceUserId?: string;
adminPrivateKeyJwk?: string;
}
export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
const parsed = baseSchema.parse(env);
if (Boolean(parsed.ADMIN_API_SERVICE_TOKEN) !== Boolean(parsed.ADMIN_API_SERVICE_USER_ID)) {
throw new Error('ADMIN_API_SERVICE_TOKEN and ADMIN_API_SERVICE_USER_ID must be configured together');
}
if (parsed.ADMIN_PRIVATE_KEY_JWK) {
try { JSON.parse(parsed.ADMIN_PRIVATE_KEY_JWK); } catch { throw new Error('ADMIN_PRIVATE_KEY_JWK must be valid JSON'); }
}
if (parsed.AUTH_TEST_JWKS && parsed.NODE_ENV !== 'test') {
throw new Error('AUTH_TEST_JWKS is forbidden outside NODE_ENV=test');
}
const authUrl = parsed.SUPABASE_AUTH_URL?.replace(/\/$/, '');
const authIssuer = parsed.SUPABASE_AUTH_ISSUER ?? (authUrl ? `${authUrl}/auth/v1` : undefined);
const authJwksUrl = parsed.SUPABASE_AUTH_JWKS_URL ??
(authUrl ? `${authUrl}/auth/v1/.well-known/jwks.json` : undefined);
if (!authIssuer || (!authJwksUrl && !parsed.AUTH_TEST_JWKS)) {
throw new Error('Supabase Auth issuer and JWKS configuration are required');
}
if (parsed.NODE_ENV === 'production' &&
(new URL(authIssuer).protocol !== 'https:' ||
(authJwksUrl && new URL(authJwksUrl).protocol !== 'https:'))) {
throw new Error('Supabase Auth issuer and JWKS URLs must use HTTPS in production');
}
if (parsed.NODE_ENV === 'production' && !parsed.MEDIA_SIGNING_SECRET) {
throw new Error('MEDIA_SIGNING_SECRET is required in production');
}
const mediaPublicBaseUrl = parsed.MEDIA_PUBLIC_BASE_URL ?? `http://127.0.0.1:${parsed.PORT}`;
if (parsed.NODE_ENV === 'production' && new URL(mediaPublicBaseUrl).protocol !== 'https:') {
throw new Error('MEDIA_PUBLIC_BASE_URL must use HTTPS in production');
}
return {
nodeEnv: parsed.NODE_ENV,
port: parsed.PORT,
databaseUrl: parsed.DATABASE_URL,
authIssuer,
authJwksUrl,
authAudience: parsed.SUPABASE_AUTH_AUDIENCE,
authTestJwks: parsed.AUTH_TEST_JWKS,
mediaRoot: parsed.MEDIA_ROOT,
mediaSigningSecret: parsed.MEDIA_SIGNING_SECRET ?? 'local-media-signing-secret-change-me-123456',
mediaPublicBaseUrl: mediaPublicBaseUrl.replace(/\/$/, ''),
supabaseAuthServiceRoleKey: parsed.SUPABASE_AUTH_SERVICE_ROLE_KEY,
revenueCatApiKey: parsed.REVENUECAT_API_KEY,
revenueCatEntitlementId: parsed.REVENUECAT_ENTITLEMENT_ID,
revenueCatWebhookSecret: parsed.REVENUECAT_WEBHOOK_SECRET,
adminApiServiceToken: parsed.ADMIN_API_SERVICE_TOKEN,
adminApiServiceUserId: parsed.ADMIN_API_SERVICE_USER_ID,
adminPrivateKeyJwk: parsed.ADMIN_PRIVATE_KEY_JWK,
};
}