Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import express, { Router } from 'express';
|
||||
import cors from 'cors';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { apiRouter } from './routes.js';
|
||||
import { sseRouter } from './sse.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const PORT = parseInt(process.env.PORT || '3001', 10);
|
||||
const APP_PASSWORD = process.env.APP_PASSWORD || 'UNIKAT_jury';
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Health check (no auth required)
|
||||
app.get('/api/health', (_req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Password protection middleware
|
||||
app.use((req, res, next) => {
|
||||
if (req.path === '/api/health') { next(); return; }
|
||||
|
||||
// Check query param
|
||||
const token = req.query.token as string | undefined;
|
||||
if (token === APP_PASSWORD) {
|
||||
res.cookie('jury_auth', APP_PASSWORD, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 });
|
||||
if (!req.path.startsWith('/api')) {
|
||||
res.redirect(req.path || '/');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cookie
|
||||
const cookieHeader = req.headers.cookie || '';
|
||||
const cookies = Object.fromEntries(
|
||||
cookieHeader.split(';').map(c => {
|
||||
const [key, ...val] = c.trim().split('=');
|
||||
return [key, val.join('=')];
|
||||
})
|
||||
);
|
||||
if (cookies.jury_auth === APP_PASSWORD) { next(); return; }
|
||||
|
||||
// Not authenticated
|
||||
if (req.path.startsWith('/api')) {
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Login page
|
||||
res.status(401).send(`<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UNIKAT Jury Voting</title>
|
||||
<style>
|
||||
body { background: #0a0a0a; color: #f0f0f0; font-family: 'Outfit', sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
|
||||
.login { text-align: center; max-width: 320px; padding: 40px; }
|
||||
h1 { font-size: 1.5rem; margin-bottom: 8px; }
|
||||
p { color: #999; margin-bottom: 24px; }
|
||||
input { width: 100%; padding: 14px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); background: rgba(25,25,25,0.6); color: #f0f0f0; font-size: 1rem; text-align: center; outline: none; }
|
||||
input:focus { border-color: #00FF00; }
|
||||
button { width: 100%; margin-top: 12px; padding: 14px; border-radius: 50px; border: none; background: linear-gradient(135deg, #00FF00, #00b300); color: #000; font-weight: 800; font-size: 1.1rem; cursor: pointer; }
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login">
|
||||
<h1>🗳️ Jury Voting</h1>
|
||||
<p>15. UNIKAT Ideenwettbewerb</p>
|
||||
<form onsubmit="login(event)">
|
||||
<input type="password" id="pw" placeholder="Passwort eingeben" autofocus>
|
||||
<button type="submit">Einloggen</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
function login(e) {
|
||||
e.preventDefault();
|
||||
const pw = document.getElementById('pw').value;
|
||||
window.location.href = '/?token=' + encodeURIComponent(pw);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`);
|
||||
});
|
||||
|
||||
// API routes
|
||||
app.use(apiRouter);
|
||||
app.use(sseRouter);
|
||||
|
||||
// Serve static frontend in production
|
||||
const distPath = path.resolve(__dirname, '..', 'dist');
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
app.use(express.static(distPath));
|
||||
app.use((_req, res) => {
|
||||
res.sendFile(path.join(distPath, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
|
||||
export default app;
|
||||
Reference in New Issue
Block a user