Production deploy setup + finish AppForge rebrand
- Backend now serves the built frontend (dist) + SPA fallback for single-service deploy - Schema applied idempotently on boot (ensureSchema) + working db:push script - Add Dockerfile + .dockerignore + start script - Footer credit removed; default app name -> AppForge; docs/package.json de-attributed Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1a8b8ee5a0
commit
fd72540b6a
+16
-1
@@ -1,7 +1,9 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { toNodeHandler } from 'better-auth/node';
|
||||
import { env } from './env.js';
|
||||
import { env, ROOT_DIR } from './env.js';
|
||||
import { auth } from './auth.js';
|
||||
import { withSession } from './middleware/auth.js';
|
||||
import { ensureBuckets } from './lib/storage.js';
|
||||
@@ -64,5 +66,18 @@ export function createApp() {
|
||||
// Fallback for unknown API routes
|
||||
app.use('/api', (_req, res) => res.status(404).json({ error: 'Not found' }));
|
||||
|
||||
// ---- Serve the built frontend (production single-service deploy) ----
|
||||
const distDir = path.join(ROOT_DIR, 'dist');
|
||||
if (fs.existsSync(distDir)) {
|
||||
app.use(express.static(distDir));
|
||||
// SPA fallback: serve index.html for any non-API, non-storage route
|
||||
app.get('*', (req, res, next) => {
|
||||
if (req.path.startsWith('/api') || req.path.startsWith(env.STORAGE_PUBLIC_PREFIX)) {
|
||||
return next();
|
||||
}
|
||||
res.sendFile(path.join(distDir, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import './env.js';
|
||||
import { env } from './env.js';
|
||||
import { createApp } from './app.js';
|
||||
import { pool } from './db.js';
|
||||
import { ensureSchema } from './lib/schema.js';
|
||||
|
||||
async function main() {
|
||||
// verify DB connectivity early
|
||||
@@ -12,6 +13,15 @@ async function main() {
|
||||
console.error('[server] FAILED to connect to Postgres:', (e as Error).message);
|
||||
}
|
||||
|
||||
// Apply schema on boot (idempotent) unless explicitly disabled
|
||||
if (process.env.RUN_DB_MIGRATIONS !== 'false') {
|
||||
try {
|
||||
await ensureSchema();
|
||||
} catch (e) {
|
||||
console.error('[server] schema apply failed:', (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
const app = createApp();
|
||||
app.listen(env.PORT, () => {
|
||||
console.log(`[server] AppForge API listening on http://localhost:${env.PORT}`);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { pool } from '../db.js';
|
||||
import { ROOT_DIR } from '../env.js';
|
||||
|
||||
/**
|
||||
* Applies the SQL schema files (idempotent — CREATE ... IF NOT EXISTS) to the
|
||||
* configured database. Safe to run on every boot; a no-op once the schema
|
||||
* exists. Set RUN_DB_MIGRATIONS=false to skip.
|
||||
*/
|
||||
export async function ensureSchema(): Promise<void> {
|
||||
const dir = path.join(ROOT_DIR, 'server', 'db');
|
||||
const files = ['auth-schema.sql', 'schema.sql'];
|
||||
for (const file of files) {
|
||||
const p = path.join(dir, file);
|
||||
if (!fs.existsSync(p)) {
|
||||
console.warn(`[schema] missing ${file}, skipping`);
|
||||
continue;
|
||||
}
|
||||
const sql = fs.readFileSync(p, 'utf8');
|
||||
await pool.query(sql);
|
||||
console.log(`[schema] applied ${file}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import '../env.js';
|
||||
import { ensureSchema } from '../lib/schema.js';
|
||||
import { pool } from '../db.js';
|
||||
|
||||
/** Standalone schema apply (npm run db:push). */
|
||||
async function main() {
|
||||
try {
|
||||
await ensureSchema();
|
||||
console.log('[db:push] schema is up to date');
|
||||
} catch (e) {
|
||||
console.error('[db:push] failed:', (e as Error).message);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user