Lumi/plugins/okf/index.js
2026-06-18 22:16:35 +02:00

239 lines
7.4 KiB
JavaScript

const path = require("path");
const {
PERMISSION_LEVELS,
REVIEW_STATES,
STATUS_VALUES,
VISIBILITY_VALUES,
accessForUser,
createEntry,
ensureTables,
getEditableEntry,
getEntryBySlug,
grantPermission,
listEntries,
listPermissions,
listVersions,
revokePermission,
restoreVersion,
searchForAi,
setEntryWorkflow,
updateEntry
} = require("./backend/okf_store");
const { renderMarkdown } = require("./backend/markdown");
const PLUGIN_ID = "okf";
module.exports = {
id: PLUGIN_ID,
init({ web, db }) {
ensureTables(db);
if (!global.lumiFrameworks) {
global.lumiFrameworks = {};
}
global.lumiFrameworks.okf = {
search: ({ query, user, limit } = {}) => searchForAi(db, query || "", user, limit || 5),
accessForUser: (user) => accessForUser(db, user)
};
const router = web.createRouter();
router.use((req, res, next) => {
res.locals.okfAccess = accessForUser(db, req.session.user);
next();
});
router.get("/", requireLogin, (req, res) => {
const filters = {
q: req.query.q || "",
category: req.query.category || "",
tag: req.query.tag || ""
};
const entries = listEntries(db, filters, req.session.user);
res.render(view("index"), {
title: "Knowledge",
entries,
filters,
categories: categoriesFor(entries),
tags: tagsFor(entries)
});
});
router.get("/admin", requireOkfManagement(db), (req, res) => {
renderAdmin(req, res, db);
});
router.post("/admin/entries", requireOkfEdit(db), (req, res) => {
try {
const entry = createEntry(db, req.body, req.session.user);
req.session.flash = { type: "success", message: "OKF entry created." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin`);
}
});
router.post("/admin/entries/:slug", requireOkfEdit(db), (req, res) => {
try {
const entry = updateEntry(db, req.params.slug, req.body, req.session.user);
req.session.flash = { type: "success", message: "OKF entry updated." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(req.params.slug)}`);
}
});
router.post("/admin/entries/:slug/:action", requireOkfManagement(db), (req, res) => {
try {
const entry = setEntryWorkflow(db, req.params.slug, req.params.action, req.session.user, req.body.note || "");
req.session.flash = { type: "success", message: "OKF workflow updated." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(req.params.slug)}`);
}
});
router.post("/admin/entries/:slug/versions/:version/restore", requireOkfManagement(db), (req, res) => {
try {
const selected = getEditableEntry(db, req.params.slug);
if (!selected) throw new Error("OKF entry was not found.");
const entry = restoreVersion(
db,
selected.id,
req.params.version,
req.session.user,
req.body.note || ""
);
req.session.flash = { type: "success", message: "OKF version restored." };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(entry.slug)}`);
} catch (error) {
req.session.flash = { type: "error", message: error.message };
res.redirect(`/plugins/${PLUGIN_ID}/admin?edit=${encodeURIComponent(req.params.slug)}`);
}
});
router.post("/admin/permissions", requireAdmin, (req, res) => {
try {
grantPermission(db, req.body, req.session.user);
req.session.flash = { type: "success", message: "OKF permission granted." };
} catch (error) {
req.session.flash = { type: "error", message: error.message };
}
res.redirect(`/plugins/${PLUGIN_ID}/admin#okf-permissions`);
});
router.post("/admin/permissions/:id/revoke", requireAdmin, (req, res) => {
try {
revokePermission(db, req.params.id, req.session.user);
req.session.flash = { type: "success", message: "OKF permission revoked." };
} catch (error) {
req.session.flash = { type: "error", message: error.message };
}
res.redirect(`/plugins/${PLUGIN_ID}/admin#okf-permissions`);
});
router.get("/:slug", requireLogin, (req, res) => {
const entry = getEntryBySlug(db, req.params.slug, req.session.user);
if (!entry) {
return res.status(404).render("error", {
title: "Knowledge entry unavailable",
message: "That OKF entry was not found or is not visible to your account."
});
}
res.render(view("entry"), {
title: entry.title,
entry,
renderMarkdown
});
});
web.mount(`/plugins/${PLUGIN_ID}`, router, {
label: "Knowledge",
role: "public",
authRequired: true,
section: "community",
canAccess: (user) => Boolean(user)
});
web.addNavItem({
label: "OKF Management",
path: `/plugins/${PLUGIN_ID}/admin`,
role: "public",
authRequired: true,
section: "admin",
canAccess: (user) => accessForUser(db, user).canEdit
});
}
};
function renderAdmin(req, res, db) {
const filters = {
q: req.query.q || "",
status: req.query.status || "",
category: req.query.category || "",
tag: req.query.tag || ""
};
const entries = listEntries(db, filters, req.session.user, { management: true });
const selected = req.query.edit ? getEditableEntry(db, req.query.edit) : null;
const versions = selected ? listVersions(db, selected.id) : [];
res.render(view("admin"), {
title: "OKF Management",
entries,
filters,
selected,
versions,
permissions: listPermissions(db),
levels: PERMISSION_LEVELS,
statuses: STATUS_VALUES,
visibilityValues: VISIBILITY_VALUES,
reviewStates: REVIEW_STATES,
categories: categoriesFor(entries),
tags: tagsFor(entries),
renderMarkdown
});
}
function view(name) {
return path.join(__dirname, "views", `${name}.ejs`);
}
function requireLogin(req, res, next) {
if (req.session.user) return next();
return res.redirect("/login");
}
function requireAdmin(req, res, next) {
if (req.session.user?.isAdmin) return next();
return denied(res);
}
function requireOkfManagement(db) {
return (req, res, next) => {
if (accessForUser(db, req.session.user).canEdit) return next();
return denied(res);
};
}
function requireOkfEdit(db) {
return (req, res, next) => {
if (accessForUser(db, req.session.user).canEdit) return next();
return denied(res);
};
}
function denied(res) {
return res.status(403).render("error", {
title: "Access denied",
message: "You do not have access to that page."
});
}
function categoriesFor(entries) {
return Array.from(new Set(entries.map((entry) => entry.category).filter(Boolean))).sort();
}
function tagsFor(entries) {
return Array.from(new Set(entries.flatMap((entry) => entry.tags || []).filter(Boolean))).sort();
}