From 01549a085ac5855c22fcee88bd59a37f7a7643a4 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Tue, 31 Mar 2026 21:37:36 +0200 Subject: [PATCH] feat: add settings GET/POST controller --- lib/controllers/settings.js | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 lib/controllers/settings.js diff --git a/lib/controllers/settings.js b/lib/controllers/settings.js new file mode 100644 index 0000000..3efcddd --- /dev/null +++ b/lib/controllers/settings.js @@ -0,0 +1,92 @@ +/** + * Settings controller — admin page for ActivityPub plugin configuration. + * + * GET: loads settings from ap_settings, renders form with defaults + * POST: validates, saves settings, redirects with success message + */ +import { getSettings, saveSettings, DEFAULTS } from "../settings.js"; +import { getToken, validateToken } from "../csrf.js"; + +export function settingsGetController(mountPath) { + return async (request, response, next) => { + try { + const { application } = request.app.locals; + const settings = await getSettings(application.collections); + + response.render("activitypub-settings", { + title: response.locals.__("activitypub.settings.title"), + settings, + defaults: DEFAULTS, + mountPath, + saved: request.query.saved === "true", + csrfToken: getToken(request.session), + }); + } catch (error) { + next(error); + } + }; +} + +export function settingsPostController(mountPath) { + return async (request, response, next) => { + try { + if (!validateToken(request)) { + return response.status(403).render("error", { + title: "Error", + content: "Invalid CSRF token", + }); + } + + const { application } = request.app.locals; + const body = request.body; + + const settings = { + // Instance & Client API + instanceLanguages: (body.instanceLanguages || "en") + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + maxCharacters: + parseInt(body.maxCharacters, 10) || DEFAULTS.maxCharacters, + maxMediaAttachments: + parseInt(body.maxMediaAttachments, 10) || DEFAULTS.maxMediaAttachments, + defaultVisibility: body.defaultVisibility || DEFAULTS.defaultVisibility, + defaultLanguage: (body.defaultLanguage || DEFAULTS.defaultLanguage).trim(), + + // Federation & Delivery + timelineRetention: parseInt(body.timelineRetention, 10) || 0, + notificationRetentionDays: + parseInt(body.notificationRetentionDays, 10) || 0, + activityRetentionDays: + parseInt(body.activityRetentionDays, 10) || 0, + replyChainDepth: + parseInt(body.replyChainDepth, 10) || DEFAULTS.replyChainDepth, + broadcastBatchSize: + parseInt(body.broadcastBatchSize, 10) || DEFAULTS.broadcastBatchSize, + broadcastBatchDelay: + parseInt(body.broadcastBatchDelay, 10) || DEFAULTS.broadcastBatchDelay, + parallelWorkers: + parseInt(body.parallelWorkers, 10) || DEFAULTS.parallelWorkers, + logLevel: body.logLevel || DEFAULTS.logLevel, + + // Migration + refollowBatchSize: + parseInt(body.refollowBatchSize, 10) || DEFAULTS.refollowBatchSize, + refollowDelay: + parseInt(body.refollowDelay, 10) || DEFAULTS.refollowDelay, + refollowBatchDelay: + parseInt(body.refollowBatchDelay, 10) || DEFAULTS.refollowBatchDelay, + + // Security + refreshTokenTtlDays: + parseInt(body.refreshTokenTtlDays, 10) || DEFAULTS.refreshTokenTtlDays, + }; + + await saveSettings(application.collections, settings); + + response.redirect(`${mountPath}/admin/settings?saved=true`); + } catch (error) { + next(error); + } + }; +}