Preview: Bereitschaft aus echtem Zustand statt hasPreview (Standardwert false sperrte Previews)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-09-30 23:01:17 +00:00
1 parent 04b45e4207
commit c37dd49b05
4 files changed
+87 -14

No files matched your search

+1 -7
View File
@@ -2,6 +2,7 @@ import { Router } from 'express'
import { config } from '../../config.js'
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
import { listDocuments, Query } from '../../services/appwriteAdmin.js'
import { isPreviewProjectReady } from '../../services/previewReadiness.js'
import { createStaffPreviewToken } from '../../services/staffPreviewTokens.js'
const router = Router()
@@ -10,13 +11,6 @@ function isStaffAdmin(user) {
return (user?.labels || []).includes('admin')
}
function isPreviewProjectReady(project) {
if (!project) return false
if (project.hasPreview === false) return false
const status = (project.provisioningStatus || project.status || '').toLowerCase()
return ['ready', 'deployed'].includes(status)
}
function safePreviewUrl(url) {
try {
const parsed = new URL(String(url || ''))
+1 -7
View File
@@ -26,6 +26,7 @@ import {
previewSessionCookieOptions,
verifyPreviewSessionToken,
} from '../services/previewSession.js'
import { isPreviewProjectReady } from '../services/previewReadiness.js'
import { consumeStaffPreviewToken } from '../services/staffPreviewTokens.js'
import { resolveStaffAccount } from '../services/staffAccount.js'
import { MFA_COOKIE, MFA_TTL_MS, resendStaffMfa, startStaffMfa, verifyStaffMfa } from '../services/staffMfa.js'
@@ -120,13 +121,6 @@ async function resolveSessionCustomer(session) {
return customer
}
function isPreviewProjectReady(project) {
if (!project) return false
if (project.hasPreview === false) return false
const status = (project.provisioningStatus || project.status || '').toLowerCase()
return ['ready', 'deployed'].includes(status)
}
function isAdminSession(session) {
return session?.role === 'admin' || (session?.labels || []).includes('admin')
}
+16
View File
@@ -0,0 +1,16 @@
/**
* Ist die Vorschau eines Projekts freigabebereit? Wird von /api/auth/preview-access und
* dem Staff-Preview-Open genutzt.
*
* `hasPreview` wird bewusst NICHT mehr ausgewertet: Das Appwrite-Attribut wurde am 22.07.2026
* mit Standardwert false angelegt und wird seit Commit 617d463 nicht mehr gepflegt, daher
* waere jedes neuere Projekt faelschlich gesperrt. Massgeblich ist der echte Zustand.
*/
export function isPreviewProjectReady(project) {
if (!project) return false
if (!String(project.subdomain || '').trim()) return false
if (project.previewMode === 'off') return false
if (project.projectType === 'repo' || project.projectType === 'project') return false
const status = String(project.provisioningStatus || project.status || '').toLowerCase()
return ['ready', 'deployed'].includes(status)
}
+69
View File
@@ -0,0 +1,69 @@
// Preview-Bereitschaft: echter Zustand statt hasPreview (Standardwert false sperrte Previews)
import { test, before, after } from 'node:test'
import assert from 'node:assert/strict'
import express from 'express'
import cookieParser from 'cookie-parser'
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
import { isPreviewProjectReady } from '../server/services/previewReadiness.js'
const base = { subdomain: 'kunde-a', projectType: 'website', provisioningStatus: 'deployed', hasPreview: false }
test('hasPreview false + deployed/ready + Subdomain => bereit', () => {
assert.equal(isPreviewProjectReady(base), true)
assert.equal(isPreviewProjectReady({ ...base, provisioningStatus: undefined, status: 'Ready' }), true)
})
test('kein Projekt / keine Subdomain => nicht bereit', () => {
assert.equal(isPreviewProjectReady(null), false)
assert.equal(isPreviewProjectReady({ ...base, subdomain: '' }), false)
assert.equal(isPreviewProjectReady({ ...base, subdomain: ' ' }), false)
})
test('previewMode off => nicht bereit', () => {
assert.equal(isPreviewProjectReady({ ...base, previewMode: 'off' }), false)
assert.equal(isPreviewProjectReady({ ...base, previewMode: 'dev' }), true)
})
test('projectType repo/project => nicht bereit', () => {
assert.equal(isPreviewProjectReady({ ...base, projectType: 'repo' }), false)
assert.equal(isPreviewProjectReady({ ...base, projectType: 'project' }), false)
})
test('Status pending/leer => nicht bereit', () => {
assert.equal(isPreviewProjectReady({ ...base, provisioningStatus: 'pending' }), false)
assert.equal(isPreviewProjectReady({ ...base, provisioningStatus: '', status: '' }), false)
})
let aw, server, baseUrl, token
before(async () => {
aw = await startFakeAppwrite({
seed: { websiteProjects: [{ $id: 'p1', customerId: 'c1', ...base }] },
})
Object.assign(process.env, { APPWRITE_ENDPOINT: aw.endpoint, APPWRITE_API_KEY: 'test-key', PREVIEW_SESSION_SECRET: 'x'.repeat(40) })
const { createPreviewSessionToken } = await import('../server/services/previewSession.js')
token = {
c1: await createPreviewSessionToken({ userId: 'u1', customerId: 'c1', email: 'a@b.de' }),
c2: await createPreviewSessionToken({ userId: 'u2', customerId: 'c2', email: 'c@d.de' }),
}
const { default: auth } = await import('../server/routes/auth.js')
const app = express()
app.use(cookieParser())
app.use('/api/auth', auth)
server = await new Promise((r) => { const s = app.listen(0, '127.0.0.1', () => r(s)) })
baseUrl = `http://127.0.0.1:${server.address().port}`
})
after(async () => {
server.closeAllConnections()
await new Promise((r) => server.close(r))
await aw.close()
})
const access = async (t) => {
const res = await fetch(`${baseUrl}/api/auth/preview-access?subdomain=kunde-a`, { headers: { Cookie: `preview_session=${t}` } })
return res.json()
}
test('Route: eigener Kunde, hasPreview false, deployed => allowed', async () => {
assert.equal((await access(token.c1)).allowed, true)
})
test('Route: anderer Kunde bleibt ausgesperrt', async () => {
const r = await access(token.c2)
assert.equal(r.allowed, false)
assert.equal(r.reason, 'no_project')
})