From fd72540b6a73a20761683f55d539b5a8b609fb6e Mon Sep 17 00:00:00 2001 From: Leon Serfaty <80597822+silkoserfo@users.noreply.github.com> Date: Mon, 29 Jun 2026 02:40:49 -0400 Subject: [PATCH] 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 --- .dockerignore | 12 ++++++++++++ Dockerfile | 26 ++++++++++++++++++++++++++ docs/index.html | 8 ++++---- package.json | 4 ++-- server/src/app.ts | 17 ++++++++++++++++- server/src/index.ts | 10 ++++++++++ server/src/lib/schema.ts | 24 ++++++++++++++++++++++++ server/src/scripts/db-push.ts | 18 ++++++++++++++++++ src/components/Footer.tsx | 5 +---- src/hooks/useSystemSettings.ts | 4 ++-- src/pages/Settings.tsx | 2 +- 11 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 server/src/lib/schema.ts create mode 100644 server/src/scripts/db-push.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bf973cd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +dist +.git +.env +.env.local +.env.*.local +server/storage +*.log +.vscode +.idea +android +ios diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d93ad15 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# AppForge — single-service production image +# Builds the Vite frontend and runs the Express backend, which serves both the +# API (/api), uploaded files (/storage), and the built frontend (SPA). + +FROM node:20-bookworm-slim + +WORKDIR /app + +# System deps occasionally needed by build tooling +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install dependencies (full install — tsx/vite are needed for build & runtime) +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy source and build the frontend +COPY . . +RUN npm run build + +ENV NODE_ENV=production +ENV PORT=3000 +EXPOSE 3000 + +# The server applies the DB schema on boot (idempotent) and serves everything +CMD ["npm", "run", "start"] diff --git a/docs/index.html b/docs/index.html index 20a54e8..dec315c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ AppForge Documentation - Website to Native Mobile App Platform - + @@ -165,7 +165,7 @@ v2.0.4

AppForge Documentation

-

The complete self-hosted SaaS platform for converting websites into native iOS & Android apps.
Built by WRAPCODERS with React, TypeScript, Express, Postgres, and Capacitor.

+

The complete self-hosted SaaS platform for converting websites into native iOS & Android apps.
Built with React, TypeScript, Express, Postgres, and Capacitor.

Quick Start → API Reference @@ -2915,7 +2915,7 @@ npm run build

Still Need Help?

Check the documentation sections above or reach out to the development team.

@@ -2924,7 +2924,7 @@ npm run build diff --git a/package.json b/package.json index 7fcba2e..66f36d0 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,8 @@ "version": "2.0.2", "private": true, "description": "AppForge is a comprehensive platform for building and managing applications.", - "homepage": "https://appforge.wrapcoders.com", "license": "ISC", - "author": "WRAPCODERS", + "author": "AppForge", "type": "module", "main": "eslint.config.js", "directories": { @@ -16,6 +15,7 @@ "dev:web": "vite", "dev:server": "tsx watch server/src/index.ts", "db:push": "tsx server/src/scripts/db-push.ts", + "start": "tsx server/src/index.ts", "build": "vite build", "build:dev": "vite build --mode development", "lint": "eslint .", diff --git a/server/src/app.ts b/server/src/app.ts index 668731c..fb0e081 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -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; } diff --git a/server/src/index.ts b/server/src/index.ts index 430a235..5bad699 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -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}`); diff --git a/server/src/lib/schema.ts b/server/src/lib/schema.ts new file mode 100644 index 0000000..a0946cb --- /dev/null +++ b/server/src/lib/schema.ts @@ -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 { + 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}`); + } +} diff --git a/server/src/scripts/db-push.ts b/server/src/scripts/db-push.ts new file mode 100644 index 0000000..1cbc577 --- /dev/null +++ b/server/src/scripts/db-push.ts @@ -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(); diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 1d3e74a..5bb2147 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -67,13 +67,10 @@ const Footer = () => { {/* Bottom */} -
+

© {new Date().getFullYear()} {settings.app_name}. All rights reserved.

-

- Made with ❤️ by WRAPCODERS -

diff --git a/src/hooks/useSystemSettings.ts b/src/hooks/useSystemSettings.ts index 9a54233..dee15f7 100644 --- a/src/hooks/useSystemSettings.ts +++ b/src/hooks/useSystemSettings.ts @@ -40,8 +40,8 @@ interface SystemSettings { } const DEFAULTS: SystemSettings = { - app_name: "My App", - app_tagline: "Convert websites to native apps", + app_name: "AppForge", + app_tagline: "Convert any website into a native mobile app", maintenance_mode: false, demo_mode: false, default_signup_credits: 5, diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index e99cf5b..a43b445 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -259,7 +259,7 @@ const Settings = () => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = `wrapcoders-data-export-${new Date().toISOString().split('T')[0]}.json`; + a.download = `appforge-data-export-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a);