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);