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 @@
+
+
+
+