Merge origin/main in feature/terminplaner-tp2 (Analyse-Cockpit, Subdomain-Sperre, Projekte)

Konflikte: funktionen.js folgt der Aufteilung aus main (fn-util.js, analyse.js); der Link
'Zu Verträge' der Analyse-Seite kommt jetzt auch in analyse.js aus vertraege-link.js.
Checkout und Team-Ansicht übernehmen assertOwnProject(..., { forNewOrder: true }) und
includeArchived aus main. Test-Pins der absoluten Importe nachgezogen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 07:11:58 +00:00
commit af63b69ec1
56 files changed
+4583 -717

No files matched your search

+3 -2
View File
@@ -2,12 +2,13 @@
import { Router } from 'express'
import { config } from '../../config.js'
import { getDocument } from '../../services/appwriteAdmin.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { requireAppwriteTeam } from '../../middleware/appwriteStaff.js'
import { activateAnalyse, analyseEligibility, controllerView, deactivateAnalyse, ELIGIBILITY_MESSAGES, validateTextform } from '../../services/analyse.js'
import { getFeatureDoc } from '../../services/siteFeatures.js'
const router = Router()
router.use(requireAppwriteStaff)
// Projekt-Aktionen: jeder Mitarbeiter (nicht nur Admins)
router.use(requireAppwriteTeam)
function sendError(res, err) {
const status = [400, 403, 404, 409, 503].includes(err.status) && !err.type ? err.status : 500
+1 -1
View File
@@ -121,7 +121,7 @@ async function unterlagenAlsAnhang() {
// je Anfrage einmal gelesen (ein gemeinsamer aboMemo fuer alle Projekte des Kunden), portalFeatures nicht erneut.
async function adminView(customerId) {
const docs = await listCustomerFeatureDocs(customerId)
const projects = await listCustomerFeatures(customerId, { docs })
const projects = await listCustomerFeatures(customerId, { docs, includeArchived: true })
const abo = aboMemo()
for (const p of projects) {
for (const key of FEATURE_KEYS) {
+11 -2
View File
@@ -1,10 +1,13 @@
import { Router } from 'express'
import { config } from '../../config.js'
import { listDocuments, Query } from '../../services/appwriteAdmin.js'
import { syncReposToAppwrite, fetchRepoReadme, importNewRepos } from '../../services/giteaAdmin.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { requireAppwriteTeam } from '../../middleware/appwriteStaff.js'
const router = Router()
router.use(requireAppwriteStaff)
// Projekt-Aktionen: jeder Mitarbeiter (nicht nur Admins)
router.use(requireAppwriteTeam)
router.post('/sync-repos', async (_req, res) => {
try {
@@ -35,6 +38,12 @@ router.get('/repo-readme', async (req, res) => {
}
try {
// Nur Repos, die ein Projekt sind - der System-Schluessel sieht sonst jedes (auch private) Repo
const known = await listDocuments(config.collections.websiteProjects, [
Query.equal('repoFullName', repoFullName),
Query.limit(1),
])
if (!known[0]) return res.status(404).json({ error: 'Projekt nicht gefunden' })
const readme = await fetchRepoReadme(repoFullName)
if (!readme) {
return res.json({ ok: true, found: false, repoFullName })
+3 -2
View File
@@ -2,7 +2,7 @@
import { Router } from 'express'
import { config } from '../../config.js'
import { getDocument, updateDocument } from '../../services/appwriteAdmin.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { requireAppwriteTeam } from '../../middleware/appwriteStaff.js'
import { envKey, encryptEnv, decryptEnv, envNames, validateEnvChange } from '../../services/previewEnv.js'
import { listRunLogs, readRunLog, isRunFile } from '../../services/previewLogs.js'
import * as runnerClient from '../../services/previewRunnerClient.js'
@@ -11,7 +11,8 @@ import { withLocks } from '../../services/keyedLock.js'
import { repoHasDevScript } from '../../services/previewDevScript.js'
const router = Router()
router.use(requireAppwriteStaff)
// Projekt-Aktionen: jeder Mitarbeiter (nicht nur Admins)
router.use(requireAppwriteTeam)
const BUSY = 'Server ausgelastet, versuche es gleich noch mal'
const MODES = ['auto', 'build', 'dev', 'off']
+3 -2
View File
@@ -2,11 +2,12 @@ import { Router } from 'express'
import { config } from '../../config.js'
import { listDocuments, Query } from '../../services/appwriteAdmin.js'
import { servicesCatalog } from '../../services/projectKinds.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { requireAppwriteTeam } from '../../middleware/appwriteStaff.js'
const router = Router()
router.use(requireAppwriteStaff)
// Projekt-Aktionen: jeder Mitarbeiter (nicht nur Admins)
router.use(requireAppwriteTeam)
const CHUNK = 100
+10 -3
View File
@@ -6,14 +6,15 @@ import {
} from '../../services/giteaAdmin.js'
import { assignProject, reconcileProject } from '../../services/projectAssignment.js'
import { completeAssignment } from '../../services/projectKindAssign.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { requireAppwriteTeam } from '../../middleware/appwriteStaff.js'
const router = Router()
router.use(requireAppwriteStaff)
// Projekt-Aktionen: jeder Mitarbeiter (nicht nur Admins)
router.use(requireAppwriteTeam)
router.post('/create-from-template', async (req, res) => {
const { repoName, displayName, subdomain, customerId = '', ticketId = '', owner } = req.body || {}
const { repoName, displayName, subdomain, customerId = '', ticketId = '', owner, kind = '' } = req.body || {}
if (!repoName && !displayName) {
return res.status(400).json({ error: 'repoName oder displayName ist erforderlich' })
@@ -27,6 +28,8 @@ router.post('/create-from-template', async (req, res) => {
customerId,
ticketId,
owner,
kind,
staffName: req.appwriteUser?.name || req.appwriteUser?.email || '',
})
return res.status(201).json({ ok: true, ...result })
} catch (err) {
@@ -38,6 +41,10 @@ router.post('/create-from-template', async (req, res) => {
// Projekt bearbeiten: Name / Subdomain / Typ -> synchronisiert Git + Preview + Appwrite
router.patch('/:id', async (req, res) => {
const { projectName, subdomain, projectType } = req.body || {}
// Vorlagen bleiben Admin-Sache (im Ticketsystem nur fuer Admins sichtbar)
if (projectType === 'template' && !req.appwriteUser?.labels?.includes('admin')) {
return res.status(403).json({ error: 'Nur Admins können Vorlagen anlegen.' })
}
try {
const result = await updateWebsiteProject(req.params.id, { projectName, subdomain, projectType })
return res.json({ ok: true, ...result })