chore: aktuellen Live-Stand sichern (Admin-API, Services, Auth-Erweiterungen)
Sichert den bisher uncommitteten Produktionsstand des Kundenbereich-Servers, damit kuenftige Deploys (git reset --hard) nichts mehr verwerfen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
53
server/routes/admin/customers.js
Normal file
53
server/routes/admin/customers.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Router } from 'express'
|
||||
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
|
||||
import {
|
||||
createCustomerWithPortalAccess,
|
||||
deleteCustomerWithPortalAccess,
|
||||
listCustomersForAdmin,
|
||||
updateCustomerWithPortalAccess,
|
||||
} from '../../services/customerAdmin.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(requireAppwriteStaff)
|
||||
|
||||
router.get('/', async (_req, res) => {
|
||||
try {
|
||||
const customers = await listCustomersForAdmin()
|
||||
return res.json({ customers })
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Kunden konnten nicht geladen werden' })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const result = await createCustomerWithPortalAccess(req.body || {})
|
||||
return res.status(201).json(result)
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Kunde konnte nicht angelegt werden' })
|
||||
}
|
||||
})
|
||||
|
||||
router.patch('/:customerId', async (req, res) => {
|
||||
try {
|
||||
const result = await updateCustomerWithPortalAccess(req.params.customerId, req.body || {})
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Kunde konnte nicht aktualisiert werden' })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:customerId', async (req, res) => {
|
||||
try {
|
||||
return res.json(await deleteCustomerWithPortalAccess(req.params.customerId))
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Kunde konnte nicht gelöscht werden' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
51
server/routes/admin/employees.js
Normal file
51
server/routes/admin/employees.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Router } from 'express'
|
||||
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
|
||||
import {
|
||||
createEmployeeWithLogin,
|
||||
deleteEmployeeWithLogin,
|
||||
listEmployeesForAdmin,
|
||||
updateEmployeeWithLogin,
|
||||
} from '../../services/employeeAdmin.js'
|
||||
|
||||
const router = Router()
|
||||
router.use(requireAppwriteStaff)
|
||||
|
||||
router.get('/', async (_req, res) => {
|
||||
try {
|
||||
return res.json({ employees: await listEmployeesForAdmin() })
|
||||
} catch (error) {
|
||||
return res.status(error.status || 500).json({ error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
return res.status(201).json(await createEmployeeWithLogin(req.body || {}))
|
||||
} catch (error) {
|
||||
return res.status(error.status || 500).json({ error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.patch('/:employeeId', async (req, res) => {
|
||||
try {
|
||||
return res.json(await updateEmployeeWithLogin(
|
||||
req.params.employeeId,
|
||||
req.body || {},
|
||||
req.appwriteUser.$id
|
||||
))
|
||||
} catch (error) {
|
||||
return res.status(error.status || 500).json({ error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:employeeId', async (req, res) => {
|
||||
try {
|
||||
return res.json(
|
||||
await deleteEmployeeWithLogin(req.params.employeeId, req.appwriteUser.$id)
|
||||
)
|
||||
} catch (error) {
|
||||
return res.status(error.status || 500).json({ error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
37
server/routes/admin/gitea.js
Normal file
37
server/routes/admin/gitea.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Router } from 'express'
|
||||
import { syncReposToAppwrite, fetchRepoReadme } from '../../services/giteaAdmin.js'
|
||||
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(requireAppwriteStaff)
|
||||
|
||||
router.post('/sync-repos', async (_req, res) => {
|
||||
try {
|
||||
const result = await syncReposToAppwrite()
|
||||
return res.json({ ok: true, ...result })
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Gitea-Sync fehlgeschlagen' })
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/repo-readme', async (req, res) => {
|
||||
const repoFullName = String(req.query.repoFullName || '').trim()
|
||||
if (!repoFullName) {
|
||||
return res.status(400).json({ error: 'repoFullName ist erforderlich' })
|
||||
}
|
||||
|
||||
try {
|
||||
const readme = await fetchRepoReadme(repoFullName)
|
||||
if (!readme) {
|
||||
return res.json({ ok: true, found: false, repoFullName })
|
||||
}
|
||||
return res.json({ ok: true, found: true, repoFullName, ...readme })
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'README konnte nicht geladen werden' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
19
server/routes/admin/invoiceClients.js
Normal file
19
server/routes/admin/invoiceClients.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Router } from 'express'
|
||||
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
|
||||
import { listInvoiceNinjaClients } from '../../services/invoiceNinja.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(requireAppwriteStaff)
|
||||
|
||||
router.get('/', async (_req, res) => {
|
||||
try {
|
||||
const clients = await listInvoiceNinjaClients()
|
||||
return res.json({ clients })
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Kunden konnten nicht geladen werden' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
91
server/routes/admin/previewOpen.js
Normal file
91
server/routes/admin/previewOpen.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Router } from 'express'
|
||||
import { config } from '../../config.js'
|
||||
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
|
||||
import { listDocuments, Query } from '../../services/appwriteAdmin.js'
|
||||
import { createStaffPreviewToken } from '../../services/staffPreviewTokens.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
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 || ''))
|
||||
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}`
|
||||
}
|
||||
|
||||
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 subdomain = String(req.body?.subdomain || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const previewUrl = safePreviewUrl(req.body?.previewUrl) || previewUrlForSubdomain(subdomain)
|
||||
|
||||
if (!subdomain || !previewUrl) {
|
||||
return res.status(400).json({ 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 res.status(404).json({ error: 'Projekt nicht gefunden' })
|
||||
}
|
||||
if (!isPreviewProjectReady(project)) {
|
||||
return res.status(409).json({ error: 'Projekt-Vorschau ist noch nicht bereit' })
|
||||
}
|
||||
|
||||
const token = createStaffPreviewToken({
|
||||
userId: user.$id,
|
||||
email: user.email || '',
|
||||
name: user.name || user.email || 'Admin',
|
||||
previewUrl,
|
||||
customerId: project.customerId || '',
|
||||
})
|
||||
|
||||
const portalBase = `https://${config.preview.baseHost}`
|
||||
return res.json({
|
||||
openUrl: `${portalBase}/api/auth/staff-preview?token=${encodeURIComponent(token)}`,
|
||||
})
|
||||
} catch (err) {
|
||||
return res.status(err.status || 500).json({ error: err.message || 'Preview-Login fehlgeschlagen' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
32
server/routes/admin/websiteProjects.js
Normal file
32
server/routes/admin/websiteProjects.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Router } from 'express'
|
||||
import { createProjectFromTemplate } from '../../services/giteaAdmin.js'
|
||||
import { requireAppwriteStaff } from '../../middleware/appwriteStaff.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(requireAppwriteStaff)
|
||||
|
||||
router.post('/create-from-template', async (req, res) => {
|
||||
const { repoName, displayName, subdomain, customerId = '', ticketId = '', owner } = req.body || {}
|
||||
|
||||
if (!repoName || !subdomain) {
|
||||
return res.status(400).json({ error: 'repoName und subdomain sind erforderlich' })
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await createProjectFromTemplate({
|
||||
repoName,
|
||||
displayName,
|
||||
subdomain,
|
||||
customerId,
|
||||
ticketId,
|
||||
owner,
|
||||
})
|
||||
return res.status(201).json({ ok: true, ...result })
|
||||
} catch (err) {
|
||||
const status = err.status || 500
|
||||
return res.status(status).json({ error: err.message || 'Projekt konnte nicht angelegt werden' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user