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:
1 parent
800630ea4b
commit
3156bb790a
14 files changed
+123
-22
No files matched your search
@@ -67,3 +67,14 @@ test('Route: anderer Kunde bleibt ausgesperrt', async () => {
|
||||
assert.equal(r.allowed, false)
|
||||
assert.equal(r.reason, 'no_project')
|
||||
})
|
||||
test('Route: archiviertes Projekt ist fuer den Kunden gesperrt (Entscheidung 2026-10-04)', async () => {
|
||||
const doc = aw.all('websiteProjects').find((d) => d.$id === 'p1')
|
||||
doc.archived = true
|
||||
try {
|
||||
const r = await access(token.c1)
|
||||
assert.equal(r.allowed, false)
|
||||
assert.equal(r.reason, 'no_project')
|
||||
} finally {
|
||||
doc.archived = false
|
||||
}
|
||||
})
|
||||
@@ -76,6 +76,8 @@ test('Neues Projekt ohne Subdomain: kein subdomain-Konflikt mehr, Kunde bleibt d
|
||||
// Gitea-Beschreibung = Anzeigename (Abgleich/Webhook lesen den Namen von dort)
|
||||
const gen = gitea.calls.find((c) => c.method === 'POST' && c.path.endsWith('/generate'))
|
||||
assert.equal(gen.body.description, 'Zweites ohne Website')
|
||||
// Kundenquelltext nicht oeffentlich (Entscheidung 2026-10-04)
|
||||
assert.equal(gen.body.private, true)
|
||||
})
|
||||
|
||||
test('Neues Projekt: Webhook repository/created legt das Dokument zuerst an -> eines, mit Kunde und Ticket', async () => {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Entscheidungen vom 04.10.2026: Projekt-Aktionen fuer alle Mitarbeiter, archivierte Projekte im
|
||||
// Kundenportal ausgeblendet. (Private Repos: siehe projektAblauf.test.js, generate-Aufruf.)
|
||||
import { test, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
import { startFakeStripe } from './support/fakeStripe.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
|
||||
const aw = await startAppwriteMock()
|
||||
const st = await startFakeStripe()
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
const { startTestApp, rawGet, get, sessionCookie } = await import('./support/testApp.js')
|
||||
const { default: projectsRouter } = await import('../server/routes/projects.js')
|
||||
const { default: servicesRouter } = await import('../server/routes/admin/services.js')
|
||||
const { default: customersRouter } = await import('../server/routes/admin/customers.js')
|
||||
const app = await startTestApp({
|
||||
'/api/projects': projectsRouter,
|
||||
'/api/admin/services': servicesRouter,
|
||||
'/api/admin/customers': customersRouter,
|
||||
})
|
||||
after(() => Promise.all([aw.close(), st.close(), app.close()]))
|
||||
|
||||
aw.seed('websiteProjects', { $id: 'p_aktiv', customerId: 'c1', projectName: 'Aktiv', subdomain: 'aktiv', status: 'deployed' })
|
||||
aw.seed('websiteProjects', { $id: 'p_archiv', customerId: 'c1', projectName: 'Alt', subdomain: 'alt', status: 'archived', archived: true })
|
||||
const kunde = { cookie: sessionCookie({ customerId: 'c1', appwriteUserId: 'u1' }) }
|
||||
const bearer = (jwt) => ({ headers: { Authorization: `Bearer ${jwt}` } })
|
||||
|
||||
test('Kundenportal: archivierte Projekte stehen nicht in der Liste', async () => {
|
||||
const { projects } = await get('/api/projects', kunde)
|
||||
assert.deepEqual(projects.map((p) => p.id), ['p_aktiv'])
|
||||
})
|
||||
|
||||
test('Kundenportal: archiviertes Projekt auch per Direktaufruf nicht erreichbar', async () => {
|
||||
assert.equal((await rawGet('/api/projects/p_archiv/work', kunde)).status, 404)
|
||||
assert.equal((await rawGet('/api/projects/p_aktiv/work', kunde)).status, 200)
|
||||
})
|
||||
|
||||
test('Projekt-Routen: Mitarbeiter ohne Admin-Label kommen rein, Kunden-Konto nicht', async () => {
|
||||
assert.equal((await rawGet('/api/admin/services', bearer('jwt-staff'))).status, 200)
|
||||
assert.equal((await rawGet('/api/admin/services', bearer('jwt-admin'))).status, 200)
|
||||
const kundeKonto = await rawGet('/api/admin/services', bearer('jwt-kunde'))
|
||||
assert.equal(kundeKonto.status, 403)
|
||||
assert.equal(kundeKonto.body.error, 'Nur für Mitarbeiter')
|
||||
assert.equal((await rawGet('/api/admin/services')).status, 401)
|
||||
})
|
||||
|
||||
test('Kunden-Verwaltung bleibt nur fuer Admins', async () => {
|
||||
const r = await rawGet('/api/admin/customers', bearer('jwt-staff'))
|
||||
assert.equal(r.status, 403)
|
||||
assert.equal(r.body.error, 'Admin-Berechtigung erforderlich')
|
||||
})
|
||||
|
||||
test('Funktionen: Kunde sieht archivierte Projekte nicht, die Mitarbeiter-Ansicht schon', async () => {
|
||||
const { listCustomerFeatures } = await import('../server/services/siteFeatures.js')
|
||||
assert.deepEqual((await listCustomerFeatures('c1', { docs: [] })).map((p) => p.projectId), ['p_aktiv'])
|
||||
assert.deepEqual(
|
||||
(await listCustomerFeatures('c1', { docs: [], includeArchived: true })).map((p) => p.projectId).sort(),
|
||||
['p_aktiv', 'p_archiv']
|
||||
)
|
||||
})
|
||||
Reference in new issue
Block a user