AppForge: migrate off Supabase to external Postgres + Better Auth + local storage

- New Express + TypeScript backend (server/) with pg, Better Auth, local file storage
- De-Supabased Postgres schema (server/db) and TS reimplementations of DB functions
- Frontend data layer rewired to REST (rest-client + backend-client compat shim)
- Removed all Supabase references (code, config, deps, docs)
- New brand assets: gradient favicon/app icons + dark/white wordmark logos

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-06-28 04:50:29 -04:00
co-authored by Claude Opus 4.8
commit 1a8b8ee5a0
271 changed files with 63734 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
import { query, one } from '../db.js';
import { env } from '../env.js';
interface TriggerArgs {
buildId: string;
websiteUrl: string;
appName: string;
platform: string;
packageName: string;
config: Record<string, unknown>;
}
/**
* Trigger a cloud build.
*
* If CODEMAGIC_API_TOKEN + CODEMAGIC_APP_ID are configured, this calls the
* Codemagic API for real. Otherwise it runs a local SIMULATION so the build
* flow is demonstrable in development (progress + placeholder artifact).
*/
export async function triggerCloudBuild(args: TriggerArgs): Promise<{ cloudBuildId?: string; message: string }> {
if (env.CODEMAGIC_API_TOKEN && env.CODEMAGIC_APP_ID) {
const res = await fetch('https://api.codemagic.io/builds', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-auth-token': env.CODEMAGIC_API_TOKEN },
body: JSON.stringify({
appId: env.CODEMAGIC_APP_ID,
workflowId: args.platform === 'ios' ? 'ios-workflow' : 'android-workflow',
branch: 'main',
environment: {
variables: {
APP_NAME: args.appName,
WEBSITE_URL: args.websiteUrl,
PACKAGE_NAME: args.packageName,
},
},
}),
});
const json: any = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(json?.error || json?.message || 'Codemagic build trigger failed');
const cloudBuildId = json?.buildId || json?._id;
await query(`UPDATE public.app_builds SET status = 'building', progress = 5 WHERE id = $1`, [args.buildId]);
return { cloudBuildId, message: 'Cloud build started (Codemagic)' };
}
// ---- Simulation (no Codemagic configured) ----
await query(`UPDATE public.app_builds SET status = 'building', progress = 10 WHERE id = $1`, [args.buildId]);
simulateBuild(args.buildId).catch((e) => console.error('[cloud-build sim]', e.message));
return { message: 'Cloud build simulated (Codemagic not configured)' };
}
async function simulateBuild(buildId: string) {
const steps = [25, 45, 65, 85];
for (const p of steps) {
await delay(2000);
await query(`UPDATE public.app_builds SET progress = $2 WHERE id = $1 AND status = 'building'`, [buildId, p]);
}
await delay(2000);
const build = await one<any>(`SELECT package_name FROM public.app_builds WHERE id = $1`, [buildId]);
const fileName = `${(build?.package_name || 'app').replace(/\./g, '-')}.apk`;
await query(
`UPDATE public.app_builds
SET status = 'complete', progress = 100,
download_url = $2, file_size_bytes = $3
WHERE id = $1 AND status = 'building'`,
[buildId, `/storage/apk-builds/simulated/${fileName}`, 12 * 1024 * 1024]
);
}
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
/** Returns the current build row (used by the status endpoint). */
export async function getCloudBuildStatus(buildId: string) {
return one(`SELECT * FROM public.app_builds WHERE id = $1`, [buildId]);
}