✨ Allow updating remote timetables with permission
This commit is contained in:
@ -82,6 +82,34 @@ export async function getTimetable(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
// Edit timetable API endpoint (/api/timetable)
|
||||
// Updates a remote timetable with the requested data
|
||||
export async function putTimetable(req, res) {
|
||||
const timetableId = parseInt(req.query.id);
|
||||
const data = req.body.data;
|
||||
if (
|
||||
!(await hasPermission(req.locals.session, "timetable.update", timetableId))
|
||||
) {
|
||||
res.status(401).send({
|
||||
success: false,
|
||||
error: "missing_permission",
|
||||
message: "You don't have permission to update this timetable!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.timetable.update({
|
||||
where: {
|
||||
id: timetableId,
|
||||
},
|
||||
data: {
|
||||
data,
|
||||
title: req.body.title,
|
||||
},
|
||||
});
|
||||
res.status(201).send();
|
||||
}
|
||||
|
||||
// Helper function for converting a date string
|
||||
// (eg. "2022-06-02" or "1654128000000") to a
|
||||
// unix timestamp
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
getSubstitutions,
|
||||
getHistory,
|
||||
getClasses,
|
||||
putTimetable,
|
||||
getInfo,
|
||||
putKey,
|
||||
deleteKey,
|
||||
@ -63,6 +64,7 @@ app.get("/api/info", getInfo);
|
||||
app.put("/api/key", putKey);
|
||||
app.delete("/api/key", deleteKey);
|
||||
app.get("/api/timetable", getTimetable);
|
||||
app.put("/api/timetable", putTimetable);
|
||||
app.get("/api/substitutions", getSubstitutions);
|
||||
app.get("/api/history", getHistory);
|
||||
app.get("/api/classes", getClasses);
|
||||
|
@ -7,11 +7,12 @@ import {
|
||||
TrashIcon,
|
||||
AlertCircleIcon,
|
||||
CopyIcon,
|
||||
UploadCloudIcon,
|
||||
} from "lucide-vue-next";
|
||||
import { DownloadIcon } from "lucide-vue-next";
|
||||
|
||||
defineProps(["timetable", "editable", "selected"]);
|
||||
defineEmits(["click", "edit", "delete", "copy", "export"]);
|
||||
defineProps(["timetable", "editable", "remote", "selected"]);
|
||||
defineEmits(["click", "edit", "delete", "copy", "export", "upload"]);
|
||||
|
||||
const deleteConfirm = ref(false);
|
||||
|
||||
@ -38,7 +39,8 @@ watch(deleteConfirm, (value) => {
|
||||
|
||||
<div class="buttons">
|
||||
<DownloadIcon @click="$emit('export')" />
|
||||
<Edit2Icon v-if="editable" @click="$emit('edit')" />
|
||||
<Edit2Icon v-if="editable && !remote" @click="$emit('edit')" />
|
||||
<UploadCloudIcon v-if="editable && remote" @click="$emit('upload')" />
|
||||
<CopyIcon @click="$emit('copy')" />
|
||||
<TrashIcon
|
||||
v-if="editable && !deleteConfirm"
|
||||
|
14
src/permission.js
Normal file
14
src/permission.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { sessionInfo } from "@/store";
|
||||
|
||||
export function hasPermission(permission, forValue) {
|
||||
let hasPermission = false;
|
||||
for (const perm of sessionInfo.value.permissions) {
|
||||
if (perm == permission) hasPermission = true;
|
||||
else if (perm == permission + ":" + forValue) hasPermission = true;
|
||||
}
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
export function canEditTimetable(id) {
|
||||
return hasPermission("timetable.update", id);
|
||||
}
|
@ -1,9 +1,18 @@
|
||||
<script setup>
|
||||
import TimetableCard from "@/components/settings/timetable-card.vue";
|
||||
import { timetables, localTimetables, timetableId } from "@/store";
|
||||
import {
|
||||
timetable,
|
||||
timetables,
|
||||
localTimetables,
|
||||
timetableId,
|
||||
baseUrl,
|
||||
fetchTimetables,
|
||||
} from "@/store";
|
||||
import { canEditTimetable } from "@/permission";
|
||||
import { PlusIcon, PaperclipIcon } from "lucide-vue-next";
|
||||
import { toRaw, ref } from "vue";
|
||||
import download from "downloadjs";
|
||||
import { loading, loadingProgress } from "@/store";
|
||||
|
||||
function copyTimetable(timetable) {
|
||||
const newTimetable = structuredClone(toRaw(timetable));
|
||||
@ -47,6 +56,34 @@ function exportTimetable(timetable) {
|
||||
"application/json"
|
||||
);
|
||||
}
|
||||
|
||||
async function uploadTimetable(id) {
|
||||
loadingProgress.value = 0.1;
|
||||
loading.value = true;
|
||||
|
||||
const timetableData = timetable.value;
|
||||
const response = await fetch(
|
||||
baseUrl + "/timetable?id=" + encodeURIComponent(id),
|
||||
{
|
||||
method: "put",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(timetableData),
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status != 201) {
|
||||
alert("Failed uploading timetable!");
|
||||
} else {
|
||||
loadingProgress.value = 0.5;
|
||||
await fetchTimetables();
|
||||
timetableId.value = id;
|
||||
}
|
||||
|
||||
loadingProgress.value = 1;
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -59,6 +96,7 @@ function exportTimetable(timetable) {
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="true"
|
||||
:remote="false"
|
||||
@click="timetableId = timetable.id"
|
||||
@edit="$router.push('timetable/edit/' + timetable.id)"
|
||||
@copy="copyTimetable(timetable)"
|
||||
@ -92,10 +130,12 @@ function exportTimetable(timetable) {
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="false"
|
||||
:editable="canEditTimetable(timetable.id)"
|
||||
:remote="true"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
@export="exportTimetable(timetable)"
|
||||
@upload="uploadTimetable(timetable.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user