-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BC-7466 - Switch SHD administration of schools (students) to new dele…
…tion routines (#278)
- Loading branch information
1 parent
50a6f6f
commit 37cc76b
Showing
15 changed files
with
668 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,32 @@ | ||
const request = require('request'); | ||
const rp = require('request-promise'); | ||
const request = require("request"); | ||
const rp = require("request-promise"); | ||
|
||
const api = (req, { useCallback = false, json = true, version = 'v1' } = {}) => { | ||
const headers = {}; | ||
if(req && req.cookies && req.cookies.jwt) { | ||
headers['Authorization'] = (req.cookies.jwt.startsWith('Bearer ') ? '' : 'Bearer ') + req.cookies.jwt; | ||
} | ||
if(process.env.API_KEY) { | ||
headers['x-api-key'] = process.env.API_KEY; | ||
} | ||
const api = ( | ||
req, | ||
{ useCallback = false, json = true, version = "v1", adminApi = false } = {} | ||
) => { | ||
let baseUrl = process.env.BACKEND_URL || "http://localhost:3030/api/"; | ||
|
||
const baseUrl = process.env.BACKEND_URL || 'http://localhost:3030/api/'; | ||
const handler = useCallback ? request : rp; | ||
return handler.defaults({ | ||
baseUrl: new URL(version, baseUrl).href, | ||
json, | ||
headers | ||
}); | ||
const headers = {}; | ||
|
||
if (adminApi) { | ||
baseUrl = process.env.ADMIN_API_URL || "http://localhost:4030/admin/api/"; | ||
headers["x-api-key"] = process.env.ADMIN_API_KEY || "thisisasupersecureapikeythatisabsolutelysave"; | ||
} else if (req && req.cookies && req.cookies.jwt) { | ||
headers["Authorization"] = | ||
(req.cookies.jwt.startsWith("Bearer ") ? "" : "Bearer ") + | ||
req.cookies.jwt; | ||
} | ||
|
||
const handler = useCallback ? request : rp; | ||
|
||
const apiRequest = { | ||
baseUrl: new URL(version, baseUrl).href, | ||
json, | ||
headers, | ||
}; | ||
|
||
return handler.defaults(apiRequest); | ||
}; | ||
|
||
module.exports = { api }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
const { api } = require("../../api"); | ||
const moment = require("moment"); | ||
|
||
moment.locale("de"); | ||
|
||
const getFormattedDate = (date) => { | ||
const formattedDate = moment(date).format("DD.MM.YYYY, HH:mm"); | ||
return formattedDate; | ||
}; | ||
|
||
const germanRoleNames = { | ||
student: "Schüler", | ||
teacher: "Lehrer", | ||
administrator: "Admin", | ||
expert: "Experte", | ||
superhero: "Superhero", | ||
invalid: "Ungültig", | ||
}; | ||
|
||
const mapUserIds = (batch) => { | ||
const invalidUsersCount = batch.invalidUsers.length; | ||
const invalidUsers = { | ||
roleName: "invalid", | ||
userCount: invalidUsersCount, | ||
}; | ||
|
||
const usersByRole = batch.usersByRole.concat( | ||
batch.skippedUsersByRole, | ||
invalidUsers | ||
); | ||
|
||
return usersByRole | ||
.sort((a, b) => b.userCount - a.userCount) | ||
.map((role) => { | ||
return { | ||
roleName: germanRoleNames[role.roleName], | ||
userCount: role.userCount, | ||
}; | ||
}); | ||
}; | ||
|
||
const mapBatches = (batches) => { | ||
return batches.map((batch) => { | ||
const formattedDate = getFormattedDate(batch.createdAt); | ||
const batchTitle = `${batch.name} - ${formattedDate} Uhr`; | ||
|
||
const isValidBatch = batch.usersByRole.length > 0; | ||
const status = isValidBatch ? batch.status : "invalid"; | ||
const canDeleteBatch = status === "created" || status === "invalid"; | ||
const canStartDeletion = canDeleteBatch && isValidBatch; | ||
|
||
const ids = mapUserIds(batch); | ||
const overallCount = ids.reduce((acc, role) => { | ||
return acc + role.userCount; | ||
}, 0); | ||
|
||
return { | ||
id: batch.id, | ||
status, | ||
usersByRole: ids, | ||
createdAt: formattedDate, | ||
batchTitle, | ||
overallCount, | ||
canDeleteBatch, | ||
canStartDeletion, | ||
}; | ||
}); | ||
}; | ||
|
||
const getDeletionBatches = async (req, res, next) => { | ||
try { | ||
const response = await api(req, { adminApi: true }).get( | ||
`/deletion-batches` | ||
); | ||
|
||
const formattedBatches = mapBatches(response.data); | ||
|
||
res.render("batch-deletion/batch-deletion", { | ||
title: "Sammellöschung von Schülern", | ||
user: res.locals.currentUser, | ||
themeTitle: process.env.SC_NAV_TITLE || "Schul-Cloud", | ||
batches: formattedBatches, | ||
}); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; | ||
|
||
const getDeletionBatchDetails = async (req, res, next) => { | ||
try { | ||
const { id } = req.params; | ||
|
||
const response = await api(req, { adminApi: true }).get( | ||
`/deletion-batches/${id}` | ||
); | ||
|
||
res.status(200).json(response); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; | ||
|
||
const deleteBatch = async (req, res, next) => { | ||
try { | ||
const { id } = req.params; | ||
await api(req, { adminApi: true }).delete(`/deletion-batches/${id}`); | ||
|
||
res.sendStatus(200); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; | ||
|
||
const sendDeletionRequest = async (req, res, next) => { | ||
try { | ||
const { id } = req.params; | ||
await api(req, { adminApi: true }).post(`/deletion-batches/${id}/execute`); | ||
|
||
res.sendStatus(200); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; | ||
|
||
const sendFile = async (req, res, next) => { | ||
const { fileContent, batchTitle } = req.body; | ||
|
||
if (!fileContent || !batchTitle) { | ||
return res | ||
.status(400) | ||
.send({ message: "No file content or batch title provided" }); | ||
} | ||
|
||
const targetRefIds = fileContent.split("\n").map((item) => item.trim()); | ||
try { | ||
const response = await api(req, { adminApi: true }).post( | ||
"/deletion-batches/", | ||
{ | ||
json: { | ||
name: batchTitle, | ||
targetRefDomain: "user", | ||
targetRefIds, | ||
}, | ||
} | ||
); | ||
|
||
res.status(200).send({ message: "File sent successfully" }); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; | ||
|
||
module.exports = { | ||
getDeletionBatches, | ||
getDeletionBatchDetails, | ||
deleteBatch, | ||
sendDeletionRequest, | ||
sendFile, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* One Controller per layout view | ||
*/ | ||
|
||
const express = require("express"); | ||
const router = express.Router(); | ||
const authHelper = require("../../helpers/authentication"); | ||
const apiRequests = require("./api-requests"); | ||
|
||
router.use(authHelper.authChecker); | ||
|
||
router.get("/", apiRequests.getDeletionBatches); | ||
|
||
router.post("/", apiRequests.sendFile); | ||
|
||
router.get("/:id", apiRequests.getDeletionBatchDetails); | ||
|
||
router.delete("/:id", apiRequests.deleteBatch); | ||
|
||
router.post("/:id/execute", apiRequests.sendDeletionRequest); | ||
|
||
module.exports = router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
$(document).ready(() => { | ||
const setHTMLForIds = (ids, idType) => { | ||
const section = document.querySelector(`#${idType}-ids-section`); | ||
|
||
if (ids.length === 0) { | ||
section.innerHTML = `<span class='no-ids-text'>Nothing ${idType}</span>`; | ||
return; | ||
} | ||
const idsString = ids.join("\n"); | ||
const textAreaString = `<textarea id="${idType}-ids" class="id-list" rows="3" readonly>${idsString}</textarea>`; | ||
|
||
section.innerHTML = textAreaString; | ||
}; | ||
|
||
function copyToClipboard(event) { | ||
const id = this.getAttribute("data-text-id"); | ||
const text = document.getElementById(id).innerHTML; | ||
|
||
navigator.clipboard | ||
.writeText(text) | ||
.then(() => { | ||
alert("IDs copied to clipboard!"); | ||
}) | ||
.catch((err) => { | ||
console.error("Failed to copy text: ", err); | ||
}); | ||
} | ||
|
||
function fetchDeletionBatchDetails(batchId) { | ||
fetch(`/batch-deletion/${batchId}`) | ||
.then((res) => { | ||
if (!res.ok) { | ||
throw new Error(`${res.status} - ${res.statusText}`); | ||
} | ||
return res.json(); | ||
}) | ||
.then((data) => { | ||
setHTMLForIds(data.pendingDeletions, "pending"); | ||
setHTMLForIds(data.successfulDeletions, "deleted"); | ||
setHTMLForIds(data.failedDeletions, "failed"); | ||
setHTMLForIds(data.invalidIds, "invalid"); | ||
|
||
const mappedSkippedIds = data.skippedDeletions | ||
.map((listItem) => { | ||
return ["\n" + listItem.roleName, ...listItem.ids]; | ||
}) | ||
.flat(); | ||
setHTMLForIds(mappedSkippedIds.flat(), "skipped"); | ||
|
||
document.querySelectorAll(".copy-btn").forEach((button) => { | ||
button.addEventListener("click", copyToClipboard); | ||
}); | ||
}) | ||
.catch((error) => { | ||
console.error("error", error); | ||
}); | ||
} | ||
|
||
function deleteBatch(batchId) { | ||
fetch(`/batch-deletion/${batchId}`, { method: "DELETE" }) | ||
.then((res) => { | ||
if (res.ok) { | ||
location.reload(); | ||
} else { | ||
console.error("Error:", res.statusText); | ||
} | ||
}) | ||
.catch((error) => { | ||
console.error("error", error); | ||
}); | ||
} | ||
|
||
function sendDeletionRequest(batchId) { | ||
fetch(`/batch-deletion/${batchId}/execute`, { method: "POST" }) | ||
.then((res) => { | ||
if (res.ok) { | ||
location.reload(); | ||
} else { | ||
console.error("Error:", res.statusText); | ||
} | ||
}) | ||
.catch((error) => { | ||
console.error("error", error); | ||
}); | ||
} | ||
|
||
document.querySelectorAll(".details-toggle").forEach((button) => { | ||
button.addEventListener("click", function () { | ||
const title = this.getAttribute("data-title"); | ||
const batchId = this.getAttribute("data-batch-id"); | ||
|
||
document.querySelector(".modal-title").innerText = title; | ||
|
||
fetchDeletionBatchDetails(batchId); | ||
}); | ||
}); | ||
|
||
document.querySelectorAll(".delete-batch-btn").forEach((button) => { | ||
button.addEventListener("click", function () { | ||
const batchId = this.getAttribute("data-batch-id"); | ||
|
||
deleteBatch(batchId); | ||
}); | ||
}); | ||
|
||
document.querySelectorAll(".start-deletion-btn").forEach((button) => { | ||
button.addEventListener("click", function () { | ||
const batchId = this.getAttribute("data-batch-id"); | ||
this.setAttribute("disabled", true); | ||
|
||
sendDeletionRequest(batchId); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.