Projekte: alle Mitarbeiter, private Repos, Archiv fuer Kunden ausgeblendet

Entscheidungen vom 04.10.2026:
- Projekt-Routen (website-projects, preview, gitea, services, analyse)
  fuer jeden Mitarbeiter (Label mitarbeiter oder admin); Kunden-,
  Mitarbeiter- und Finanz-Verwaltung bleiben nur fuer Admins
- Neue Repos aus der Vorlage werden privat angelegt
- Archivierte Projekte sieht der Kunde nicht mehr: Projektliste,
  Projektdetails, Preview-Zugang, Funktionen (Mitarbeiter-Ansicht schon)
- Projektliste des Kunden mit Limit (Appwrite liefert sonst nur 25)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 15:02:02 +00:00
1 parent 800630ea4b
commit 3156bb790a
14 files changed
+123 -22

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
@@ -49,7 +49,7 @@ async function ticketExists(ticketId) {
// Die Funktionsdokumente des Kunden werden einmal gelesen und fuer alle Projekte und Funktionen verwendet
async function adminView(customerId) {
const docs = await listCustomerFeatureDocs(customerId)
const projects = await listCustomerFeatures(customerId, { docs })
const projects = await listCustomerFeatures(customerId, { docs, includeArchived: true })
for (const p of projects) {
for (const key of FEATURE_KEYS) {
const view = p.features[key]
+3 -2
View File
@@ -1,10 +1,11 @@
import { Router } from 'express'
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 {
+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
+3 -2
View File
@@ -6,11 +6,12 @@ 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, kind = '' } = req.body || {}
+2 -1
View File
@@ -292,7 +292,8 @@ router.get('/preview-access', async (req, res) => {
const projects = await listDocuments(config.collections.websiteProjects, queries)
const project = projects[0]
if (!project) {
// Archiviert: fuer Kunden wie kein Projekt (das Portal blendet es aus)
if (!project || (project.archived && !admin)) {
return res.json({ allowed: false, reason: 'no_project' })
}
if (!isPreviewProjectReady(project)) {
+5 -2
View File
@@ -14,7 +14,8 @@ const router = Router()
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) {
// Archivierte Projekte sieht der Kunde nicht mehr (wie in der Liste)
if (!project || !customerId || project.customerId !== customerId || project.archived) {
return res.status(404).json({ error: 'Projekt nicht gefunden' })
}
req.project = project
@@ -31,9 +32,11 @@ router.get('/', requireSession, async (req, res) => {
const projects = await listDocuments(config.collections.websiteProjects, [
Query.equal('customerId', customerId),
Query.orderDesc('$createdAt'),
Query.limit(500), // ohne Limit liefert Appwrite nur 25
])
const sanitized = projects.map((p) => ({
// Archivierte Projekte (Vorschau entfernt) blendet das Portal aus (Entscheidung 2026-10-04)
const sanitized = projects.filter((p) => !p.archived).map((p) => ({
id: p.$id,
projectName: p.projectName || '',
subdomain: p.subdomain || '',