Kunden-Dashboard: - Tab-Navigation: Projekte | Was wurde gemacht | Rechnungen | Chat - Projekt-Details pro Projekt: letzte Git-Commits (Titel+Beschreibung), Projektgroesse, Datei-Uebersicht (Top-Level aggregiert), Ticket-Arbeiten - Rechnungen: gestylte Liste mit Status-Pillen, Ansehen/Zahlen-Link, PDF-Download ueber Server-Proxy (IDOR-geschuetzt) - Chat mit dem Webklar-Team (Polling, Ungelesen-Badge, viewAs blockiert) Admin-Dashboard: - Chat-Tab: Konversationsliste + Thread + Antwortfeld, Ungelesen-Badge Backend: - giteaAdmin: getRepoInfo, listRepoCommits, getRepoTreeSummary - projectInsights: 5-min-Cache, Invalidierung per Gitea-Webhook - /api/projects/:id/git|files|work mit Ownership-Check (404) - /api/chat/* (Kunde) + /api/portal-admin/chats/* (Admin), portalMessages-Collection - mailer: E-Mail-Benachrichtigung an Admins bei Kundennachricht (15-min-Throttle) - gitWorksheet: dd.mm.yyyy, voller Commit-Body, startTime leer (Zeit-Nachtrag), Auto-Webpage-Ticket bei Push auf Projekt ohne Ticket (ensureProjectTicket) - customerActivity: Git-Push-Eintraege fuer Kunden sichtbar Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
115 lines
4.2 KiB
JavaScript
115 lines
4.2 KiB
JavaScript
import express from 'express'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { config, assertServerConfig, WOMS_DATABASE_ID } from './config.js'
|
|
import { verifyDatabaseAccess } from './services/appwriteAdmin.js'
|
|
import { syncPreviewTls } from './services/previewDeploy.js'
|
|
import { sessionMiddleware } from './middleware/session.js'
|
|
import authRoutes from './routes/auth.js'
|
|
import projectsRoutes from './routes/projects.js'
|
|
import featuresRoutes from './routes/features.js'
|
|
import customerDataRoutes from './routes/customerData.js'
|
|
import portalAdminRoutes from './routes/portalAdmin.js'
|
|
import chatRoutes from './routes/chat.js'
|
|
import giteaWebhookRoutes from './routes/webhook/gitea.js'
|
|
import adminGiteaRoutes from './routes/admin/gitea.js'
|
|
import adminWebsiteProjectsRoutes from './routes/admin/websiteProjects.js'
|
|
import adminCustomersRoutes from './routes/admin/customers.js'
|
|
import adminEmployeesRoutes from './routes/admin/employees.js'
|
|
import adminInvoiceClientsRoutes from './routes/admin/invoiceClients.js'
|
|
import adminPreviewOpenRoutes from './routes/admin/previewOpen.js'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const publicDir = path.join(__dirname, '..', 'public')
|
|
|
|
assertServerConfig()
|
|
|
|
const app = express()
|
|
app.set('trust proxy', 1)
|
|
app.use(sessionMiddleware())
|
|
// rawBody wird fuer die HMAC-Verifikation des Gitea-Webhooks benoetigt
|
|
app.use(express.json({ limit: '2mb', verify: (req, _res, buf) => { req.rawBody = buf } }))
|
|
|
|
app.use((req, res, next) => {
|
|
const origin = req.get('Origin')
|
|
if (origin && config.cors.allowedOrigins.includes(origin)) {
|
|
res.setHeader('Access-Control-Allow-Origin', origin)
|
|
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type')
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS')
|
|
if (req.method === 'OPTIONS') {
|
|
return res.sendStatus(204)
|
|
}
|
|
}
|
|
return next()
|
|
})
|
|
|
|
app.use('/api/auth', authRoutes)
|
|
app.use('/api/projects', projectsRoutes)
|
|
app.use('/api/features', featuresRoutes)
|
|
app.use('/api', customerDataRoutes)
|
|
app.use('/api/chat', chatRoutes)
|
|
app.use('/api/portal-admin', portalAdminRoutes)
|
|
app.use('/api/admin/gitea', adminGiteaRoutes)
|
|
app.use('/api/admin/website-projects', adminWebsiteProjectsRoutes)
|
|
app.use('/api/admin/customers', adminCustomersRoutes)
|
|
app.use('/api/admin/employees', adminEmployeesRoutes)
|
|
app.use('/api/admin/invoice-clients', adminInvoiceClientsRoutes)
|
|
app.use('/api/admin/preview-open', adminPreviewOpenRoutes)
|
|
app.use('/webhook', giteaWebhookRoutes)
|
|
|
|
app.get('/api/health', async (_req, res) => {
|
|
const dbAccess = await verifyDatabaseAccess()
|
|
res.json({
|
|
ok: dbAccess.ok,
|
|
service: 'webklar-kundenbereich',
|
|
databaseId: WOMS_DATABASE_ID,
|
|
endpoint: config.appwrite.endpoint,
|
|
appwriteDbAccess: dbAccess,
|
|
})
|
|
})
|
|
|
|
app.use(express.static(publicDir))
|
|
|
|
app.get('/dashboard.html', (req, res, next) => {
|
|
const raw = req.signedCookies?.[config.cookieName]
|
|
if (!raw) {
|
|
return res.redirect('/login.html')
|
|
}
|
|
next()
|
|
})
|
|
|
|
app.get('/', (_req, res) => {
|
|
res.redirect('/login.html')
|
|
})
|
|
|
|
app.use((err, _req, res, _next) => {
|
|
console.error('[server] Unbehandelter Fehler:', err)
|
|
if (!res.headersSent) {
|
|
res.status(500).json({ error: err.message || 'Interner Serverfehler' })
|
|
}
|
|
})
|
|
|
|
const server = app.listen(config.port, '0.0.0.0', () => {
|
|
console.log(`Webklar Kundenbereich läuft auf Port ${config.port}`)
|
|
syncPreviewTls()
|
|
.then((r) => console.log('[startup] Preview-TLS-Sync:', JSON.stringify(r)))
|
|
.catch((err) => console.error('[startup] Preview-TLS-Sync fehlgeschlagen:', err.message))
|
|
verifyDatabaseAccess().then((result) => {
|
|
if (result.ok) return
|
|
console.error(
|
|
'[startup] APPWRITE_API_KEY: Kein Lesezugriff auf woms-database. In Appwrite Console neuen API-Key mit Scopes databases.read + databases.write anlegen und in .env eintragen.'
|
|
)
|
|
if (result.reason) console.error(`[startup] Appwrite: ${result.reason}`)
|
|
})
|
|
})
|
|
|
|
server.on('error', (err) => {
|
|
if (err.code === 'EADDRINUSE') {
|
|
console.error(
|
|
`[server] Port ${config.port} ist bereits belegt. Windows: netstat -ano | findstr :${config.port} → taskkill /PID <PID> /F. Oder in .env einen anderen PORT setzen (z. B. PORT=3002).`
|
|
)
|
|
process.exit(1)
|
|
}
|
|
throw err
|
|
})
|