feat: SPA build + serving im Dockerfile und server.ts
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled

This commit is contained in:
Pepe44DEV
2026-07-01 04:14:27 +02:00
parent 40af301188
commit 0d3f0c5ae4
2 changed files with 75 additions and 37 deletions

View File

@@ -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 PRISMA_IGNORE_ENV_LOAD=true
# Install dependencies (inkl. dev)
# Install backend dependencies
COPY package*.json ./
RUN npm ci --include=dev
# Ensure prisma CLI available globally (avoids path issues)
RUN npm install -g prisma@5.4.2
# Install frontend dependencies
COPY frontend/package*.json ./frontend/
RUN npm --prefix frontend ci
# Copy source
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)
RUN prisma generate --schema=src/database/schema.prisma
# Build backend
RUN npm run build
# Optional: show versions in build log
RUN node -v && npm -v && npx prisma -v
CMD ["npm", "run", "dev"]
CMD ["npm", "start"]

View File

@@ -2,6 +2,7 @@ import express from 'express';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import path from 'path';
import fs from 'fs';
import authRouter from './routes/auth';
import dashboardRouter from './routes/dashboard';
import apiRouter from './routes/api';
@@ -11,7 +12,6 @@ export function createWebServer() {
const app = express();
const basePath = env.webBasePath || '/ucp';
const dashboardPath = `${basePath}/dashboard`;
const apiPath = `${basePath}/api`;
app.use(express.json({ limit: '5mb' }));
app.use(cookieParser());
app.use(
@@ -24,12 +24,10 @@ export function createWebServer() {
const mount = (suffix: string) => (basePath ? `${basePath}${suffix}` : suffix);
app.use(mount('/auth'), authRouter);
app.use(dashboardPath, dashboardRouter);
app.use(mount('/api'), apiRouter);
// fallback mounts if proxy strips base path
if (basePath) {
app.use('/api', apiRouter);
app.use('/dashboard', dashboardRouter);
}
// 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}`));
}
// Landing pages
app.get('/', (_req, res) => res.redirect(dashboardPath));
app.get(basePath || '/', (_req, res) => {
// Serve React SPA static assets
const frontendDist = path.join(process.cwd(), 'frontend', 'dist');
// 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(`
<!doctype html>
<html lang="de">
@@ -67,6 +87,14 @@ export function createWebServer() {
</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')));
return app;