Files
Webklar-Kundenbereich/server/routes/projects.js
T
ClaudeandClaude Opus 5.5 4593065f79 Kundenbereich: neues Design mit Seitenleiste (Design "Kundenbereich Neu")
- Kopfzeile mit Firmenname, Kundennr., Initialen-Avatar und Abmelden
- Seitenleiste: Navigation Projekte/Fortschritt/Rechnungen/Chat mit Zaehlern
  (offene Rechnungen, ungelesene Nachrichten), "Dein Ansprechpartner"
  (Mitarbeiter des juengsten zugewiesenen Tickets, sonst WEBklar-Team) und
  Wartungs-Abo-Karte (Angebot bzw. Status, Stunden, naechste Abbuchung,
  Abo verwalten, Uebernahmegutschrift, Widerruf, Mindestlaufzeit, Kuendigen)
- Projektkarte: Vorschau mit "Website oeffnen", Status, Vorschau-Adresse,
  Live-Domain, letzte Aenderung, Schnellzugriffe, Projektdetails
- Abo-Bestellung und Stundenkonto/Verlauf sowie Uebernahmegutschrift als
  eigene Ansichten; Direktlinks #abo/#gutschrift (auch ueber den Login)
- Pflichtlinks (Kuendigungsbutton, Widerruf, Bedingungen, Datenschutz,
  Impressum) als Fusszeile; Systemschriften statt externer Fonts
- Mobil: Navigation im 2x2-Raster, kein horizontales Scrollen
- API: /api/contact, updatedAt in /api/projects; Mail-Links auf Ansichten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 21:03:14 +00:00

116 lines
4.2 KiB
JavaScript

import { Router } from 'express'
import { config } from '../config.js'
import { getDocument, listDocuments, Query } from '../services/appwriteAdmin.js'
import { getSessionCustomerId, requireSession } from '../middleware/session.js'
import { getProjectGitInsights, getProjectFileOverview } from '../services/projectInsights.js'
const router = Router()
/**
* Laedt das Projekt und stellt sicher, dass es dem eingeloggten Kunden gehoert.
* Fremde/unbekannte IDs antworten einheitlich mit 404 (kein Enumeration-Leak).
*/
async function loadOwnProject(req, res, next) {
const customerId = getSessionCustomerId(req)
const project = await getDocument(config.collections.websiteProjects, req.params.id).catch(() => null)
if (!project || !customerId || project.customerId !== customerId) {
return res.status(404).json({ error: 'Projekt nicht gefunden' })
}
req.project = project
return next()
}
router.get('/', requireSession, async (req, res) => {
const customerId = getSessionCustomerId(req)
if (!customerId) {
return res.status(401).json({ error: 'Nicht angemeldet' })
}
try {
const projects = await listDocuments(config.collections.websiteProjects, [
Query.equal('customerId', customerId),
Query.orderDesc('$createdAt'),
])
const sanitized = projects.map((p) => ({
id: p.$id,
projectName: p.projectName || '',
subdomain: p.subdomain || '',
previewUrl: p.previewUrl || '',
liveDomain: p.liveDomain || '',
status: p.status || '',
provisioningStatus: p.provisioningStatus || '',
templateName: p.templateName || '',
giteaRepoUrl: p.giteaRepoUrl || '',
repoFullName: p.repoFullName || '',
updatedAt: p.updatedAt || p.$updatedAt || '',
}))
return res.json({ projects: sanitized })
} catch (err) {
return res.status(500).json({ error: err.message || 'Projekte konnten nicht geladen werden' })
}
})
/** Letzte Git-Pushes + Projektgroesse. */
router.get('/:id/git', requireSession, loadOwnProject, async (req, res) => {
try {
const { repo, commits } = await getProjectGitInsights(req.project)
return res.json({ repo, commits })
} catch (err) {
return res.status(500).json({ error: err.message || 'Git-Daten konnten nicht geladen werden' })
}
})
/** Datei-Uebersicht (Top-Level, Groessensummen). */
router.get('/:id/files', requireSession, loadOwnProject, async (req, res) => {
try {
const summary = await getProjectFileOverview(req.project)
if (!summary) return res.json({ totalFiles: 0, totalSizeBytes: 0, truncated: false, topLevel: [] })
return res.json(summary)
} catch (err) {
return res.status(500).json({ error: err.message || 'Datei-Uebersicht konnte nicht geladen werden' })
}
})
/** Ticket-Arbeiten am Projekt (inkl. Git-Push-Worksheets, "Zeit offen" markiert). */
router.get('/:id/work', requireSession, loadOwnProject, async (req, res) => {
try {
const project = req.project
if (!project.ticketId) {
return res.json({ ticket: null, worksheets: [] })
}
const ticket = await getDocument(config.collections.workorders, project.ticketId).catch(() => null)
const worksheets = await listDocuments(config.collections.worksheets, [
Query.equal('workorderId', project.ticketId),
Query.orderDesc('$createdAt'),
Query.limit(50),
]).catch(() => [])
const sanitized = worksheets
// GIT-Eintraege einschliessen, sonstige reine Kommentare ausblenden
.filter((ws) => ws.serviceType === 'GIT' || !ws.isComment)
.map((ws) => ({
wsid: ws.wsid || '',
date: ws.endDate || ws.startDate || '',
time: ws.endTime || ws.startTime || '',
employeeName: ws.employeeName || ws.employeeShort || '',
serviceType: ws.serviceType || '',
details: ws.details || '',
totalTime: Number(ws.totalTime || 0),
isGit: ws.serviceType === 'GIT',
pending: ws.serviceType === 'GIT' && !ws.startTime,
}))
return res.json({
ticket: ticket ? { woid: ticket.woid || '', topic: ticket.topic || '', status: ticket.status || '' } : null,
worksheets: sanitized,
})
} catch (err) {
return res.status(500).json({ error: err.message || 'Arbeiten konnten nicht geladen werden' })
}
})
export default router