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 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"]

View File

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