diff --git a/README.md b/README.md index eb4e92d..8efbc8a 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,20 @@ This starts both the Vite dev server (frontend) and the API server (default port Copy `.env.sample` to `.env` and set `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` for the Svelte app and (optionally) the API server. +## Production + +```bash +npm run build +npm run start +``` + +This builds the frontend to `dist/` and starts the server. The same process serves both the API and the built SPA at `http://localhost:3001` (or your `PORT`). Without a `dist/` folder (e.g. API-only), the server still runs and serves only the API. + ## API server (REST API for mobile / other clients) The Express API uses the same Supabase backend. Optional env: `SUPABASE_URL`, `SUPABASE_ANON_KEY` (default to `VITE_*`), `PORT` (default `3001`). -- **Run**: `npm run dev:server` or `npm run start:api` +- **Run**: `npm run dev:server`, `npm run start`, or `npm run start:api`. When `dist/` exists, the server also serves the frontend. - **Auth**: `POST /api/auth/login`, `POST /api/auth/register`, `GET /api/auth/session`, `GET /api/auth/profile` (Bearer token for protected routes; profile includes `display_name`, `email`, `avatar_url`) - **Decks**: `GET /api/decks/mine`, `GET /api/decks/published`, `GET /api/decks/:id`, `POST /api/decks`, `PATCH /api/decks/:id`, `DELETE /api/decks/:id`, `POST /api/decks/:id/publish`, `POST /api/decks/:id/copy`, `GET /api/decks/:id/update-preview`, `POST /api/decks/:id/apply-update` diff --git a/package.json b/package.json index 60a75a3..dcede9c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test:run": "vitest run", "test:coverage": "vitest run --coverage", "dev:server": "node server/index.js", + "start": "node server/index.js", "start:api": "node server/index.js" }, "devDependencies": { diff --git a/server/index.js b/server/index.js index fa2f1d0..91c3f77 100644 --- a/server/index.js +++ b/server/index.js @@ -1,9 +1,16 @@ import 'dotenv/config'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; import express from 'express'; import cors from 'cors'; import { createClient } from '@supabase/supabase-js'; import * as decksApi from '../src/lib/api/decks-core.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, '..'); +const distPath = path.join(projectRoot, 'dist'); + const app = express(); app.use(cors({ origin: true, credentials: true })); app.use(express.json()); @@ -358,7 +365,21 @@ app.delete('/api/decks/:id', requireAuth, async (req, res) => { } }); +// Serve built frontend when dist/ exists (production) +if (fs.existsSync(distPath)) { + app.use(express.static(distPath)); + app.get('*', (req, res, next) => { + if (req.path.startsWith('/api')) return next(); + res.sendFile(path.join(distPath, 'index.html'), (err) => { + if (err) next(err); + }); + }); +} + const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`API server listening on http://localhost:${PORT}`); + if (fs.existsSync(distPath)) { + console.log(`Serving frontend from dist/ (open http://localhost:${PORT})`); + } });