feat: SPA build + serving im Dockerfile und server.ts
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled
This commit is contained in:
18
dockerfile
18
dockerfile
@@ -12,20 +12,30 @@ ENV PATH="/usr/src/app/node_modules/.bin:${PATH}"
|
|||||||
ENV DATABASE_URL=postgresql://user:pass@localhost:5432/papo?schema=public
|
ENV DATABASE_URL=postgresql://user:pass@localhost:5432/papo?schema=public
|
||||||
ENV PRISMA_IGNORE_ENV_LOAD=true
|
ENV PRISMA_IGNORE_ENV_LOAD=true
|
||||||
|
|
||||||
# Install dependencies (inkl. dev)
|
# Install backend dependencies
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci --include=dev
|
RUN npm ci --include=dev
|
||||||
|
|
||||||
# Ensure prisma CLI available globally (avoids path issues)
|
# Install frontend dependencies
|
||||||
RUN npm install -g prisma@5.4.2
|
COPY frontend/package*.json ./frontend/
|
||||||
|
RUN npm --prefix frontend ci
|
||||||
|
|
||||||
# Copy source
|
# Copy source
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
RUN npm run build:web
|
||||||
|
|
||||||
|
# Ensure prisma CLI available globally (avoids path issues)
|
||||||
|
RUN npm install -g prisma@5.4.2
|
||||||
|
|
||||||
# Generate Prisma client (explicit schema path)
|
# Generate Prisma client (explicit schema path)
|
||||||
RUN prisma generate --schema=src/database/schema.prisma
|
RUN prisma generate --schema=src/database/schema.prisma
|
||||||
|
|
||||||
|
# Build backend
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
# Optional: show versions in build log
|
# Optional: show versions in build log
|
||||||
RUN node -v && npm -v && npx prisma -v
|
RUN node -v && npm -v && npx prisma -v
|
||||||
|
|
||||||
CMD ["npm", "run", "dev"]
|
CMD ["npm", "start"]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import express from 'express';
|
|||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
import authRouter from './routes/auth';
|
import authRouter from './routes/auth';
|
||||||
import dashboardRouter from './routes/dashboard';
|
import dashboardRouter from './routes/dashboard';
|
||||||
import apiRouter from './routes/api';
|
import apiRouter from './routes/api';
|
||||||
@@ -11,7 +12,6 @@ export function createWebServer() {
|
|||||||
const app = express();
|
const app = express();
|
||||||
const basePath = env.webBasePath || '/ucp';
|
const basePath = env.webBasePath || '/ucp';
|
||||||
const dashboardPath = `${basePath}/dashboard`;
|
const dashboardPath = `${basePath}/dashboard`;
|
||||||
const apiPath = `${basePath}/api`;
|
|
||||||
app.use(express.json({ limit: '5mb' }));
|
app.use(express.json({ limit: '5mb' }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(
|
app.use(
|
||||||
@@ -24,12 +24,10 @@ export function createWebServer() {
|
|||||||
|
|
||||||
const mount = (suffix: string) => (basePath ? `${basePath}${suffix}` : suffix);
|
const mount = (suffix: string) => (basePath ? `${basePath}${suffix}` : suffix);
|
||||||
app.use(mount('/auth'), authRouter);
|
app.use(mount('/auth'), authRouter);
|
||||||
app.use(dashboardPath, dashboardRouter);
|
|
||||||
app.use(mount('/api'), apiRouter);
|
app.use(mount('/api'), apiRouter);
|
||||||
// fallback mounts if proxy strips base path
|
// fallback mounts if proxy strips base path
|
||||||
if (basePath) {
|
if (basePath) {
|
||||||
app.use('/api', apiRouter);
|
app.use('/api', apiRouter);
|
||||||
app.use('/dashboard', dashboardRouter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect bare auth calls to the prefixed path when a base path is set
|
// Redirect bare auth calls to the prefixed path when a base path is set
|
||||||
@@ -37,9 +35,31 @@ export function createWebServer() {
|
|||||||
app.use('/auth', (_req, res) => res.redirect(`${basePath}${_req.originalUrl}`));
|
app.use('/auth', (_req, res) => res.redirect(`${basePath}${_req.originalUrl}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Landing pages
|
// Serve React SPA static assets
|
||||||
app.get('/', (_req, res) => res.redirect(dashboardPath));
|
const frontendDist = path.join(process.cwd(), 'frontend', 'dist');
|
||||||
app.get(basePath || '/', (_req, res) => {
|
|
||||||
|
// If SPA exists, it takes precedence for GET dashboard routes
|
||||||
|
if (fs.existsSync(path.join(frontendDist, 'index.html'))) {
|
||||||
|
const spaHtml = fs.readFileSync(path.join(frontendDist, 'index.html'), 'utf-8');
|
||||||
|
const configScript = `window.__PAPO__ = ${JSON.stringify({
|
||||||
|
baseRoot: basePath,
|
||||||
|
baseApi: mount('/api'),
|
||||||
|
baseAuth: mount('/auth'),
|
||||||
|
baseDashboard: dashboardPath
|
||||||
|
})}`;
|
||||||
|
|
||||||
|
app.use(basePath || '/', express.static(frontendDist));
|
||||||
|
|
||||||
|
app.get(`${dashboardPath}(/*)?`, (_req, res) => {
|
||||||
|
res.type('html').send(spaHtml.replace('__PAPO_CONFIG__', configScript));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get(mount('/'), (_req, res) => {
|
||||||
|
res.redirect(dashboardPath);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Legacy landing page when SPA is not built
|
||||||
|
app.get(mount('/'), (_req, res) => {
|
||||||
res.send(`
|
res.send(`
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
@@ -67,6 +87,14 @@ export function createWebServer() {
|
|||||||
</html>
|
</html>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old dashboard router – only handles POST/non-GET routes when SPA is active,
|
||||||
|
// or all routes when SPA is inactive
|
||||||
|
app.use(dashboardPath, dashboardRouter);
|
||||||
|
if (basePath) {
|
||||||
|
app.use('/dashboard', dashboardRouter);
|
||||||
|
}
|
||||||
|
|
||||||
app.use(mount('/static'), express.static(path.join(process.cwd(), 'static')));
|
app.use(mount('/static'), express.static(path.join(process.cwd(), 'static')));
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
Reference in New Issue
Block a user