Portal: Admin-Panel, Rechnungsanalyse und alte Admin-Schnittstellen entfernt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-02 03:50:14 +00:00
1 parent 3a4fa581c0
commit 7c00633db0
43 files changed
+229 -4001

No files matched your search

+2 -2
View File
@@ -5,8 +5,8 @@ import { withLocks } from '../../services/keyedLock.js'
import { adminActivate, assertOwnProject, disable, getFeatureDoc, listCustomerFeatures } from '../../services/siteFeatures.js'
/**
* Funktionen (News-Pinnwand) je Kundenprojekt im Admin-Panel.
* Eingehaengt in routes/portalAdmin.js unter /api/portal-admin/funktionen, dort hinter requireAdminSession.
* Funktionen (News-Pinnwand) je Kundenprojekt im Ticketsystem (Kunden-Reiter "Funktionen").
* Eingehaengt in routes/team/index.js unter /api/team/funktionen, dort hinter requireStaffJwt.
*/
export const funktionenAdminRouter = Router()
-97
View File
@@ -1,97 +0,0 @@
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()
function isStaffAdmin(user) {
return (user?.labels || []).includes('admin')
}
function safePreviewUrl(url) {
try {
const parsed = new URL(String(url || ''))
if (
parsed.hostname.endsWith('.project.webklar.com') ||
parsed.hostname === 'project.webklar.com'
) {
return parsed.toString()
}
} catch {
/* ignore */
}
return null
}
function previewUrlForSubdomain(subdomain) {
const safe = String(subdomain || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]/g, '')
if (!safe) return null
return `https://${safe}.${config.preview.baseHost}`
}
/**
* Einmal-Link fuer die Preview eines Projekts (eingeloest von GET /api/auth/staff-preview). Genutzt von dieser
* Admin-Route und vom Team-Eingang (/api/team/preview-link, alle Mitarbeiter). Liefert { status, body }.
*/
export async function buildStaffPreviewLink({ userId, email, name, subdomain: rawSubdomain, previewUrl: rawPreviewUrl }) {
const subdomain = String(rawSubdomain || '')
.trim()
.toLowerCase()
const previewUrl = safePreviewUrl(rawPreviewUrl) || previewUrlForSubdomain(subdomain)
if (!subdomain || !previewUrl) {
return { status: 400, body: { error: 'subdomain oder gueltige previewUrl erforderlich' } }
}
try {
const projects = await listDocuments(config.collections.websiteProjects, [
Query.equal('subdomain', subdomain),
Query.limit(1),
])
const project = projects[0]
if (!project) {
return { status: 404, body: { error: 'Projekt nicht gefunden' } }
}
if (!isPreviewProjectReady(project)) {
return { status: 409, body: { error: 'Projekt-Vorschau ist noch nicht bereit' } }
}
const token = createStaffPreviewToken({
userId,
email: email || '',
name: name || email || 'Admin',
previewUrl,
customerId: project.customerId || '',
})
const portalBase = `https://${config.preview.baseHost}`
return { status: 200, body: { openUrl: `${portalBase}/api/auth/staff-preview?token=${encodeURIComponent(token)}` } }
} catch (err) {
return { status: err.status || 500, body: { error: err.message || 'Preview-Login fehlgeschlagen' } }
}
}
router.use(requireAppwriteStaff)
router.post('/', async (req, res) => {
const user = req.appwriteUser
if (!isStaffAdmin(user)) {
return res.status(403).json({ error: 'Admin-Berechtigung erforderlich' })
}
const { status, body } = await buildStaffPreviewLink({
userId: user.$id,
email: user.email,
name: user.name,
subdomain: req.body?.subdomain,
previewUrl: req.body?.previewUrl,
})
return res.status(status).json(body)
})
export default router
-37
View File
@@ -1,37 +0,0 @@
import { Router } from 'express'
import { config } from '../../config.js'
import { appwriteAccountFromJwt } from '../../middleware/staffJwt.js'
import { createStaffPreviewToken } from '../../services/staffPreviewTokens.js'
import { resolveStaffAccount } from '../../services/staffAccount.js'
const router = Router()
/**
* Wechsel Ticketsystem -> Projekt-Admin ohne zweiten Login. Wie requireAppwriteStaff wird das JWT
* ueber /account geprueft (eine Sitzung ohne zweiten Faktor bekommt dort 401), aber ohne Label-Pflicht:
* jeder Mitarbeiter darf rueber, wie beim normalen Portal-Login.
*/
router.post('/', async (req, res) => {
try {
const user = await appwriteAccountFromJwt(req)
if (!user?.$id) return res.status(401).json({ error: 'Bitte im Ticketsystem neu anmelden.' })
const staff = await resolveStaffAccount(user.$id, user.email || '', user)
if (!staff) return res.status(403).json({ error: 'Kein Mitarbeiterkonto.' })
const token = createStaffPreviewToken({
purpose: 'switch',
userId: user.$id,
email: staff.email,
name: staff.name,
isAdmin: staff.isAdmin,
})
return res.json({
openUrl: `https://${config.preview.baseHost}/api/auth/staff-switch?token=${encodeURIComponent(token)}`,
})
} catch (err) {
return res.status(500).json({ error: err.message || 'Wechsel fehlgeschlagen' })
}
})
export default router