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

@@ -0,0 +1,84 @@
import { randomUUID } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { Pool } from 'pg';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { PostgresProfileSettingsRepository } from '../src/domains/profile-settings/repository.js';
const databaseUrl = process.env.DATABASE_URL;
const localDatabase = databaseUrl ? ['127.0.0.1', 'localhost', '::1'].includes(new URL(databaseUrl).hostname) : false;
if (databaseUrl && !localDatabase) throw new Error('Profile settings integration tests only permit a localhost DATABASE_URL');
describe.skipIf(!databaseUrl || !localDatabase)('PostgresProfileSettingsRepository', () => {
const adminPool = new Pool({ connectionString: databaseUrl });
const schema = `profile_settings_test_${randomUUID().replaceAll('-', '')}`;
const userId = '11111111-1111-4111-8111-111111111111';
let pool: Pool;
let repository: PostgresProfileSettingsRepository;
beforeAll(async () => {
await adminPool.query(`CREATE SCHEMA "${schema}"`);
const isolatedUrl = new URL(databaseUrl!);
isolatedUrl.searchParams.set('options', `-c search_path=${schema}`);
pool = new Pool({ connectionString: isolatedUrl.toString() });
repository = new PostgresProfileSettingsRepository(isolatedUrl.toString());
for (const migrationName of [
'0000_identity_and_feature_interests.sql',
'0001_couples.sql',
'0002_packs_catalog_progress.sql',
'0003_answers_matches.sql',
'0004_chat.sql',
'0005_profile_settings.sql',
'0006_media_storage.sql',
]) {
const migration = await readFile(new URL(`../drizzle/${migrationName}`, import.meta.url), 'utf8');
for (const statement of migration.split('--> statement-breakpoint')) {
if (statement.trim()) await pool.query(statement);
}
}
await pool.query('insert into profiles (id, name) values ($1, $2)', [userId, 'Alice']);
});
afterAll(async () => {
await repository.close();
await pool.end();
await adminPool.query(`DROP SCHEMA "${schema}" CASCADE`);
await adminPool.end();
});
it('persists profile, activity, public key, push token, and onboarding state', async () => {
await repository.updateProfile(userId, {
name: 'Alicia', gender: 'female', usage_reason: 'deeper_connection', hide_nsfw: true,
max_intensity: 2, show_explicit_content: false, onboarding_completed: true,
onboarding_version: 1, public_key_jwk: { kty: 'RSA' }, push_token: 'ExponentPushToken[test]',
});
await repository.updateLastActive(userId);
const row = (await pool.query('select * from profiles where id = $1', [userId])).rows[0];
expect(row).toMatchObject({
name: 'Alicia', gender: 'female', usage_reason: 'deeper_connection', hide_nsfw: true,
max_intensity: 2, show_explicit_content: false, onboarding_completed: true,
onboarding_version: 1, public_key_jwk: { kty: 'RSA' }, push_token: 'ExponentPushToken[test]',
});
expect(row.last_active_at).toBeInstanceOf(Date);
});
it('creates default preferences, updates one key, and leaves the others enabled', async () => {
const initial = await repository.getNotificationPreferences(userId);
expect(initial.messages_enabled).toBe(true);
const updated = await repository.updateNotificationPreference(userId, 'messages_enabled', false);
expect(updated.messages_enabled).toBe(false);
expect(updated.matches_enabled).toBe(true);
});
it('stores text feedback owned by the authenticated user', async () => {
const submitted = await repository.submitFeedback(userId, {
type: 'general', title: 'Hello', description: 'Useful feedback', device_info: { os: 'ios' },
});
const row = (await pool.query('select * from feedback where id = $1', [submitted.id])).rows[0];
expect(row).toMatchObject({ user_id: userId, type: 'general', title: 'Hello' });
const mediaColumn = await pool.query(
`select column_name from information_schema.columns
where table_schema = current_schema() and table_name = 'feedback' and column_name = 'screenshot_url'`,
);
expect(mediaColumn.rowCount).toBe(0);
});
});