Files
Webklar-Kundenbereich/server/routes/admin/websiteProjects.js
T
ClaudeandClaude Opus 5.5 e43cdb667b Projekt-Ablauf im Ticketsystem: Anlegen, Archivieren, Import repariert
- Neues Projekt ohne Subdomain: subdomain null statt '' (Unique-Index
  subdomain_unique, jedes weitere scheiterte mit "Document with the
  requested ID already exists"); ebenso Typ "Internes Projekt"
- Anlegen unter derselben Repo-Sperre wie der Webhook repository/created;
  upsertWebsiteProjectByRepo macht aus einem 409 ein Update
- Gitea-Beschreibung = Anzeigename; Art (kind) beim Anlegen aus dem Ticket
- Reaktivieren setzt status zurueck (vorher blieb 'archived' stehen)
- Import: archivierte Repos als archiviert, Archiv-Stand aus Gitea wird
  gespiegelt, ein fehlerhaftes Repo bricht den Lauf nicht mehr ab
- Abgleich ueberschreibt Projektnamen nicht mehr
- CORS erlaubt PUT (Preview-Variablen speichern)
- Vorlage zieht beim Zuweisen nie in die Org um
- Erinnerungs-Lauf ueberspringt archivierte Projekte
- kaputte Umlaute (U+FFFD) in drei Fehlermeldungen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:22:53 +00:00

126 lines
4.2 KiB
JavaScript

import { Router } from 'express'
import {
createProjectFromTemplate,
updateWebsiteProject,
archiveRepo,
} from '../../services/giteaAdmin.js'
import { assignProject, reconcileProject } from '../../services/projectAssignment.js'
import { completeAssignment } from '../../services/projectKindAssign.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
const router = Router()
router.use(requireAppwriteStaff)
router.post('/create-from-template', async (req, res) => {
const { repoName, displayName, subdomain, customerId = '', ticketId = '', owner, kind = '' } = req.body || {}
if (!repoName && !displayName) {
return res.status(400).json({ error: 'repoName oder displayName ist erforderlich' })
}
try {
const result = await createProjectFromTemplate({
repoName,
displayName,
subdomain,
customerId,
ticketId,
owner,
kind,
staffName: req.appwriteUser?.name || req.appwriteUser?.email || '',
})
return res.status(201).json({ ok: true, ...result })
} catch (err) {
const status = err.status || 500
return res.status(status).json({ error: err.message || 'Projekt konnte nicht angelegt werden' })
}
})
// Projekt bearbeiten: Name / Subdomain / Typ -> synchronisiert Git + Preview + Appwrite
router.patch('/:id', async (req, res) => {
const { projectName, subdomain, projectType } = req.body || {}
try {
const result = await updateWebsiteProject(req.params.id, { projectName, subdomain, projectType })
return res.json({ ok: true, ...result })
} catch (err) {
const status = err.status || 500
return res.status(status).json({ error: err.message || 'Projekt konnte nicht aktualisiert werden' })
}
})
// Repository archivieren
router.post('/:id/archive', async (req, res) => {
try {
const result = await archiveRepo(req.params.id, true)
return res.json({ ok: true, ...result })
} catch (err) {
const status = err.status || 500
return res.status(status).json({ error: err.message || 'Projekt konnte nicht archiviert werden' })
}
})
// Repository reaktivieren
router.post('/:id/unarchive', async (req, res) => {
try {
const result = await archiveRepo(req.params.id, false)
return res.json({ ok: true, ...result })
} catch (err) {
const status = err.status || 500
return res.status(status).json({ error: err.message || 'Projekt konnte nicht reaktiviert werden' })
}
})
// Projekt einem Kunden/Ticket zuweisen -> stoesst Umzug nach GITEA_ORG + Nachtragen an
router.post('/:id/assign', async (req, res) => {
const { customerId, ticketId } = req.body || {}
if (!customerId && !ticketId) {
return res.status(400).json({ error: 'customerId oder ticketId erforderlich' })
}
try {
const result = await assignProject(req.params.id, {
customerId: String(customerId || ''),
ticketId: String(ticketId || ''),
})
return res.json({ ok: true, ...result })
} catch (err) {
if (err.status === 404) {
return res.status(404).json({ error: 'Projekt nicht gefunden' })
}
const status = err.status || 500
return res.status(status).json({ error: err.message })
}
})
// Zuordnen-Flow (WOMS): Art speichern, ggf. Ticket anlegen, Kunde/Ticket zuweisen, Preview
router.post('/:id/zuordnen', async (req, res) => {
const staffName = req.appwriteUser?.name || req.appwriteUser?.email || ''
try {
const result = await completeAssignment(req.params.id, req.body || {}, { staffName })
return res.json(result)
} catch (err) {
if (err.status === 409) {
return res.status(409).json({ error: 'bereits zugeordnet', kindSetBy: err.kindSetBy || '', kindSetAt: err.kindSetAt || '' })
}
const status = err.status || 500
return res.status(status).json({ error: err.message })
}
})
// Umzug + Pushes eines bereits zugewiesenen Projekts erneut nachtragen
router.post('/:id/pushes', async (req, res) => {
try {
const result = await reconcileProject(req.params.id)
return res.json({ ok: true, ...result })
} catch (err) {
if (err.status === 404) {
return res.status(404).json({ error: 'Projekt nicht gefunden' })
}
const status = err.status || 500
return res.status(status).json({ error: err.message })
}
})
export default router