diff --git a/server/api/admin.js b/server/api/admin.js new file mode 100644 index 0000000..3b5fb98 --- /dev/null +++ b/server/api/admin.js @@ -0,0 +1,78 @@ +import Prisma from "@prisma/client"; +const prisma = new Prisma.PrismaClient(); + +export function registerAdmin(app) { + app.get("/api/admin/timetable", listTimetables); + app.post("/api/admin/timetable", createTimetable); + app.put("/api/admin/timetable", editTimetable); + app.delete("/api/admin/timetable", deleteTimetable); +} + +function sendMissingArguments(res) { + res.status(400).send({ + success: false, + error: "missing_arguments", + }); +} + +async function listTimetables(_, res) { + res.send( + await prisma.timetable.findMany({ + select: { + id: true, + title: true, + class: true, + source: true, + trusted: true, + }, + }) + ); +} + +async function createTimetable(req, res) { + let data = req.body; + if (!data.title || !data.data || !data.class) { + sendMissingArguments(res); + return; + } + const timetable = await prisma.timetable.create({ + data: req.body, + }); + res.status(201).send(timetable); +} + +async function editTimetable(req, res) { + let id = parseInt(req.query.id); + if (!id) { + sendMissingArguments(res); + return; + } + try { + const timetable = await prisma.timetable.update({ + where: { + id, + }, + data: req.body, + }); + res.status(201).send(timetable); + } catch (e) { + res.status(500).send(e); + } +} + +async function deleteTimetable(req, res) { + if (!req.query.id) { + sendMissingArguments(res); + return; + } + try { + await prisma.timetable.delete({ + where: { + id: parseInt(req.query.id), + }, + }); + res.status(200).send(); + } catch (e) { + res.status(500).send(e); + } +} diff --git a/server/api/permission.js b/server/api/permission.js index f62486f..f43796f 100644 --- a/server/api/permission.js +++ b/server/api/permission.js @@ -32,6 +32,18 @@ export async function hasPermission(sessionToken, permission, forValue) { return hasPermission; } +export async function checkAdmin(req, res, next) { + if (!(await hasPermission(req.locals.session, "admin"))) { + res.status(401).send({ + success: false, + error: "admin_only", + message: "You need to be admin to do this!", + }); + return; + } + next(); +} + export async function applyKey(sessionToken, key) { if (!key) return false; const foundKey = await prisma.key.findUnique({ diff --git a/server/index.js b/server/index.js index a606b57..ed08d1d 100644 --- a/server/index.js +++ b/server/index.js @@ -17,6 +17,8 @@ import auth from "./api/auth.js"; import { Parser } from "./parser/index.js"; import { BolleClient } from "./parser/bolle.js"; import { parseSubstitutionPlan } from "./parser/untis.js"; +import { registerAdmin } from "./api/admin.js"; +import { checkAdmin } from "./api/permission.js"; // Check that credentials are supplied if ( @@ -69,6 +71,11 @@ app.get("/api/substitutions", getSubstitutions); app.get("/api/history", getHistory); app.get("/api/classes", getClasses); app.post("/api/token", auth.token); + +// Register Admin endpoints +app.use("/api/admin", checkAdmin); +registerAdmin(app); + // Respond with 400 for non-existent endpoints app.get("/api/*", (_req, res) => { res.sendStatus(400); diff --git a/src/components/settings/expand-section.vue b/src/components/settings/expand-section.vue new file mode 100644 index 0000000..a6a9d8a --- /dev/null +++ b/src/components/settings/expand-section.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/components/settings/timetable-card.vue b/src/components/settings/timetable-card.vue index 9985509..7506088 100644 --- a/src/components/settings/timetable-card.vue +++ b/src/components/settings/timetable-card.vue @@ -11,7 +11,7 @@ import { } from "lucide-vue-next"; import { DownloadIcon } from "lucide-vue-next"; -defineProps(["timetable", "editable", "remote", "selected"]); +defineProps(["timetable", "editable", "remote", "selected", "admin"]); defineEmits(["click", "edit", "delete", "copy", "export", "upload"]); const deleteConfirm = ref(false); @@ -32,16 +32,17 @@ watch(deleteConfirm, (value) => {
{{ timetable.title }} {{ $t("settings.source") }}: {{ timetable.source }}{{ $t("settings.source") }}: {{ timetable.source }} + , Class: {{ timetable.class }}
- + - + { +router.beforeEach((to, from) => { if (from.meta.dataView) { lastDataRoute.value = from; } + if (to.name == "title.settings.admin" && !hasPermission("admin")) { + alert("Nope"); + router.replace("/"); + } return true; }); diff --git a/src/strings.js b/src/strings.js index 68c6696..07d9bfd 100644 --- a/src/strings.js +++ b/src/strings.js @@ -14,6 +14,7 @@ export const strings = { groups: "Timetable Groups", appearance: "Appearance", keys: "Manage Keys", + admin: "Admin Settings", about: "About", }, }, diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue index 12b0f79..9225e59 100644 --- a/src/views/SettingsView.vue +++ b/src/views/SettingsView.vue @@ -1,12 +1,14 @@ + + + +