Admin: News je Projekt aktivieren und abschalten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a421308019
commit
6dac6aa073
6 files changed
+361
-9
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { Router } from 'express'
|
||||
import { config } from '../../config.js'
|
||||
import { getDocument } from '../../services/appwriteAdmin.js'
|
||||
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.
|
||||
*/
|
||||
export const funktionenAdminRouter = Router()
|
||||
|
||||
const NOTE_MAX = 300
|
||||
const adminName = (req) => req.session.adminName || req.session.name || 'Admin'
|
||||
|
||||
function sendError(res, err) {
|
||||
const status = err.status >= 400 && err.status < 600 ? err.status : 500
|
||||
if (status >= 500) console.error('[funktionen-admin]', err.message)
|
||||
return res.status(status).json({ error: err.message || 'Interner Serverfehler' })
|
||||
}
|
||||
|
||||
/** Neuester Eintrag zuerst; aeltere Eintraege fallen vorn heraus, sobald 300 Zeichen voll sind. */
|
||||
export function composeAdminNote(previous, { by, action, note }, now = new Date()) {
|
||||
const entry = `${now.toISOString()} ${by}: ${action}${note ? ` – ${note}` : ''}`.slice(0, NOTE_MAX)
|
||||
const older = String(previous || '')
|
||||
return older ? `${entry} | ${older}`.slice(0, NOTE_MAX) : entry
|
||||
}
|
||||
|
||||
async function ticketExists(ticketId) {
|
||||
if (!ticketId) return false
|
||||
try {
|
||||
await getDocument(config.collections.workorders, ticketId)
|
||||
return true
|
||||
} catch (err) {
|
||||
if (err.status === 404) return false
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function adminView(customerId) {
|
||||
const projects = await listCustomerFeatures(customerId)
|
||||
for (const p of projects) {
|
||||
const news = p.features.news
|
||||
const doc = await getFeatureDoc(customerId, p.projectId, 'news')
|
||||
news.adminNote = doc?.adminNote || ''
|
||||
news.ticketMissing = news.state === 'setup' && !(doc?.ticketId && (await ticketExists(doc.ticketId)))
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
funktionenAdminRouter.get('/:customerId', async (req, res) => {
|
||||
try {
|
||||
res.json({ projects: await adminView(req.params.customerId) })
|
||||
} catch (err) {
|
||||
sendError(res, err)
|
||||
}
|
||||
})
|
||||
|
||||
funktionenAdminRouter.post('/:customerId/news', async (req, res) => {
|
||||
try {
|
||||
const { customerId } = req.params
|
||||
const { projectId, action } = req.body || {}
|
||||
const note = typeof req.body?.note === 'string' ? req.body.note.trim().slice(0, 120) : ''
|
||||
if (action !== 'activate' && action !== 'disable') {
|
||||
return res.status(400).json({ error: 'Unbekannte Aktion' })
|
||||
}
|
||||
if (typeof projectId !== 'string' || !projectId) {
|
||||
return res.status(400).json({ error: 'Projekt fehlt' })
|
||||
}
|
||||
await assertOwnProject(customerId, projectId)
|
||||
const by = adminName(req)
|
||||
await withLocks([`feature-admin:${projectId}`], async () => {
|
||||
const doc = await getFeatureDoc(customerId, projectId, 'news')
|
||||
const adminNote = composeAdminNote(doc?.adminNote, { by, action, note })
|
||||
if (action === 'activate') {
|
||||
await adminActivate(customerId, projectId, 'news', { by, adminNote })
|
||||
} else {
|
||||
if (!doc) throw Object.assign(new Error('Für dieses Projekt gibt es noch keine News-Pinnwand'), { status: 409 })
|
||||
await disable(doc.$id, { by, adminNote })
|
||||
}
|
||||
})
|
||||
res.json({ projects: (await adminView(customerId)).filter((p) => p.projectId === projectId) })
|
||||
} catch (err) {
|
||||
sendError(res, err)
|
||||
}
|
||||
})
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
storeChatUpload,
|
||||
uploadRateLimited,
|
||||
} from '../services/chatAttachments.js'
|
||||
import { funktionenAdminRouter } from './admin/funktionenAdmin.js'
|
||||
import { offersAdminRouter } from './offers.js'
|
||||
import { storageTasksAdminRouter } from './storageTasks.js'
|
||||
import { takeoverAdminRouter } from './takeoverCredits.js'
|
||||
@@ -40,6 +41,8 @@ router.use('/takeover-credits', takeoverAdminRouter)
|
||||
router.use('/offers', offersAdminRouter)
|
||||
// Zusatzspeicher-Aufgaben fuers Team (Tab "E-Mail-Speicher")
|
||||
router.use('/storage-tasks', storageTasksAdminRouter)
|
||||
// Funktionen (News-Pinnwand) je Kundenprojekt aktivieren/abschalten (Kundenliste, Zeile "Funktionen")
|
||||
router.use('/funktionen', funktionenAdminRouter)
|
||||
|
||||
function norm(value) {
|
||||
return String(value || '').toLowerCase()
|
||||
|
||||
@@ -223,8 +223,8 @@ function byNote(by, verb) {
|
||||
return by ? `${verb} von ${by}`.slice(0, 300) : undefined
|
||||
}
|
||||
|
||||
export async function activate(docId, { by } = {}) {
|
||||
const note = byNote(by, 'Freigeschaltet')
|
||||
export async function activate(docId, { by, adminNote } = {}) {
|
||||
const note = adminNote ?? byNote(by, 'Freigeschaltet')
|
||||
return updateDocument(config.collections.portalFeatures, docId, {
|
||||
...stateFields('active'),
|
||||
activatedAt: new Date().toISOString(),
|
||||
@@ -232,8 +232,8 @@ export async function activate(docId, { by } = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function disable(docId, { by } = {}) {
|
||||
const note = byNote(by, 'Abgeschaltet')
|
||||
export async function disable(docId, { by, adminNote } = {}) {
|
||||
const note = adminNote ?? byNote(by, 'Abgeschaltet')
|
||||
return updateDocument(config.collections.portalFeatures, docId, {
|
||||
...stateFields('disabled'),
|
||||
disabledAt: new Date().toISOString(),
|
||||
@@ -242,11 +242,11 @@ export async function disable(docId, { by } = {}) {
|
||||
}
|
||||
|
||||
/** Freischaltung ohne Zahlung und ohne Ticket (unlockedByPurchase bleibt false). */
|
||||
export async function adminActivate(customerId, projectId, key, { by } = {}) {
|
||||
export async function adminActivate(customerId, projectId, key, { by, adminNote } = {}) {
|
||||
assertKey(key)
|
||||
await assertOwnProject(customerId, projectId)
|
||||
const doc = await ensureFeatureDoc(customerId, projectId, key)
|
||||
return activate(doc.$id, { by })
|
||||
return activate(doc.$id, { by, adminNote })
|
||||
}
|
||||
|
||||
/** Alle 'setup'-Funktionen (optional nur eines Kunden), deren Ticket 'Closed' ist, werden 'active'. */
|
||||
|
||||
Reference in new issue
Block a user