Portal: Admin-Panel, Rechnungsanalyse und alte Admin-Schnittstellen entfernt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3a4fa581c0
commit
7c00633db0
43 files changed
+229
-4001
No files matched your search
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user