diff --git a/backend/src/controllers/learning-objects.ts b/backend/src/controllers/learning-objects.ts index 967ce355..21cf9515 100644 --- a/backend/src/controllers/learning-objects.ts +++ b/backend/src/controllers/learning-objects.ts @@ -7,8 +7,8 @@ import { BadRequestException } from '../exceptions/bad-request-exception.js'; import { NotFoundException } from '../exceptions/not-found-exception.js'; import { envVars, getEnvVar } from '../util/envVars.js'; import { FilteredLearningObject, LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; -import {UploadedFile} from "express-fileupload"; -import {AuthenticatedRequest} from "../middleware/auth/authenticated-request"; +import { UploadedFile } from 'express-fileupload'; +import { AuthenticatedRequest } from '../middleware/auth/authenticated-request'; function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifierDTO { if (!req.params.hruid) { @@ -32,12 +32,13 @@ function getLearningPathIdentifierFromRequest(req: Request): LearningPathIdentif } export async function getAllLearningObjects(req: Request, res: Response): Promise { - if (req.query.admin) { // If the admin query parameter is present, the user wants to have all learning objects with this admin. - const learningObjects = - await learningObjectService.getLearningObjectsAdministratedBy(req.query.admin as string); + if (req.query.admin) { + // If the admin query parameter is present, the user wants to have all learning objects with this admin. + const learningObjects = await learningObjectService.getLearningObjectsAdministratedBy(req.query.admin as string); res.json(learningObjects); - } else { // Else he/she wants all learning objects on the path specified by the request parameters. + } else { + // Else he/she wants all learning objects on the path specified by the request parameters. const learningPathId = getLearningPathIdentifierFromRequest(req); const full = req.query.full; @@ -86,10 +87,9 @@ export async function handlePostLearningObject(req: AuthenticatedRequest, res: R if (!req.files || !req.files.learningObject) { throw new BadRequestException('No file uploaded'); } - const learningObject = await learningObjectService.storeLearningObject( - (req.files.learningObject as UploadedFile).tempFilePath, - [req.auth!.username] - ); + const learningObject = await learningObjectService.storeLearningObject((req.files.learningObject as UploadedFile).tempFilePath, [ + req.auth!.username, + ]); res.json(learningObject); } @@ -97,17 +97,17 @@ export async function handleDeleteLearningObject(req: AuthenticatedRequest, res: const learningObjectId = getLearningObjectIdentifierFromRequest(req); if (!learningObjectId.version) { - throw new BadRequestException("When deleting a learning object, a version must be specified."); + throw new BadRequestException('When deleting a learning object, a version must be specified.'); } const deletedLearningObject = await learningObjectService.deleteLearningObject({ hruid: learningObjectId.hruid, version: learningObjectId.version, - language: learningObjectId.language + language: learningObjectId.language, }); if (deletedLearningObject) { res.json(deletedLearningObject); } else { - throw new NotFoundException("Learning object not found"); + throw new NotFoundException('Learning object not found'); } } diff --git a/backend/src/controllers/learning-paths.ts b/backend/src/controllers/learning-paths.ts index 5c1fb8e1..d7c45428 100644 --- a/backend/src/controllers/learning-paths.ts +++ b/backend/src/controllers/learning-paths.ts @@ -60,7 +60,12 @@ export async function getLearningPaths(req: Request, res: Response): Promise theme.hruids); } - const learningPaths = await learningPathService.fetchLearningPaths(hruidList, language as Language, `HRUIDs: ${hruidList.join(', ')}`, forGroup); + const learningPaths = await learningPathService.fetchLearningPaths( + hruidList, + language as Language, + `HRUIDs: ${hruidList.join(', ')}`, + forGroup + ); res.json(learningPaths.data); } } @@ -71,12 +76,12 @@ function postOrPutLearningPath(isPut: boolean): (req: AuthenticatedRequest, res: const teacher = await getTeacher(req.auth!.username); if (isPut) { if (req.params.hruid !== path.hruid || req.params.language !== path.language) { - throw new BadRequestException("id_not_matching_query_params"); + throw new BadRequestException('id_not_matching_query_params'); } - await learningPathService.deleteLearningPath({hruid: path.hruid, language: path.language as Language}); + await learningPathService.deleteLearningPath({ hruid: path.hruid, language: path.language as Language }); } res.json(await learningPathService.createNewLearningPath(path, [teacher])); - } + }; } export const postLearningPath = postOrPutLearningPath(false); @@ -85,12 +90,12 @@ export const putLearningPath = postOrPutLearningPath(true); export async function deleteLearningPath(req: AuthenticatedRequest, res: Response): Promise { const id: LearningPathIdentifier = { hruid: req.params.hruid, - language: req.params.language as Language + language: req.params.language as Language, }; const deletedPath = await learningPathService.deleteLearningPath(id); if (deletedPath) { res.json(deletedPath); } else { - throw new NotFoundException("The learning path could not be found."); + throw new NotFoundException('The learning path could not be found.'); } } diff --git a/backend/src/data/content/learning-object-repository.ts b/backend/src/data/content/learning-object-repository.ts index a862bfc2..e793b991 100644 --- a/backend/src/data/content/learning-object-repository.ts +++ b/backend/src/data/content/learning-object-repository.ts @@ -36,8 +36,8 @@ export class LearningObjectRepository extends DwengoEntityRepository { public async findByHruidAndLanguage(hruid: string, language: Language): Promise { - return this.findOne( - { hruid: hruid, language: language }, - { populate: ['nodes', 'nodes.transitions', 'admins'] } - ); + return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions', 'admins'] }); } /** @@ -37,10 +34,10 @@ export class LearningPathRepository extends DwengoEntityRepository return this.findAll({ where: { admins: { - username: adminUsername - } + username: adminUsername, + }, }, - populate: ['nodes', 'nodes.transitions', 'admins'] + populate: ['nodes', 'nodes.transitions', 'admins'], }); } diff --git a/backend/src/entities/content/attachment.entity.ts b/backend/src/entities/content/attachment.entity.ts index e85e3107..99901495 100644 --- a/backend/src/entities/content/attachment.entity.ts +++ b/backend/src/entities/content/attachment.entity.ts @@ -9,7 +9,7 @@ export class Attachment { @ManyToOne({ entity: () => LearningObject, primary: true, - deleteRule: 'cascade' + deleteRule: 'cascade', }) learningObject!: LearningObject; diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts index 335bd2b2..1d129b9c 100644 --- a/backend/src/entities/content/learning-object.entity.ts +++ b/backend/src/entities/content/learning-object.entity.ts @@ -1,14 +1,4 @@ -import { - ArrayType, - Collection, - Embedded, - Entity, - Enum, - ManyToMany, - OneToMany, - PrimaryKey, - Property -} from '@mikro-orm/core'; +import { ArrayType, Collection, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; import { Attachment } from './attachment.entity.js'; import { Teacher } from '../users/teacher.entity.js'; import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; @@ -92,7 +82,7 @@ export class LearningObject { @OneToMany({ entity: () => Attachment, - mappedBy: 'learningObject' + mappedBy: 'learningObject', }) attachments: Collection = new Collection(this); diff --git a/backend/src/middleware/auth/auth.ts b/backend/src/middleware/auth/auth.ts index 2e1c3765..43cce940 100644 --- a/backend/src/middleware/auth/auth.ts +++ b/backend/src/middleware/auth/auth.ts @@ -116,14 +116,8 @@ export const authenticateUser = [verifyJwtToken, addAuthenticationInfo]; * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates * to true. */ -export function authorize( - accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise -): RequestHandler { - return async ( - req: AuthenticatedRequest, - _res: express.Response, - next: express.NextFunction - ): Promise => { +export function authorize(accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise): RequestHandler { + return async (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): Promise => { if (!req.auth) { throw new UnauthorizedException(); } else if (!(await accessCondition(req.auth, req))) { diff --git a/backend/src/middleware/auth/checks/learning-object-auth-checks.ts b/backend/src/middleware/auth/checks/learning-object-auth-checks.ts index 7ef91947..2aad56b7 100644 --- a/backend/src/middleware/auth/checks/learning-object-auth-checks.ts +++ b/backend/src/middleware/auth/checks/learning-object-auth-checks.ts @@ -1,8 +1,8 @@ -import { Language } from "@dwengo-1/common/util/language"; -import learningObjectService from "../../../services/learning-objects/learning-object-service"; -import { authorize } from "../auth"; -import { AuthenticatedRequest } from "../authenticated-request"; -import { AuthenticationInfo } from "../authentication-info"; +import { Language } from '@dwengo-1/common/util/language'; +import learningObjectService from '../../../services/learning-objects/learning-object-service'; +import { authorize } from '../auth'; +import { AuthenticatedRequest } from '../authenticated-request'; +import { AuthenticationInfo } from '../authentication-info'; export const onlyAdminsForLearningObject = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { const { hruid } = req.params; @@ -10,7 +10,7 @@ export const onlyAdminsForLearningObject = authorize(async (auth: Authentication const admins = await learningObjectService.getAdmins({ hruid, language: language as Language, - version: parseInt(version as string) + version: parseInt(version as string), }); return admins.includes(auth.username); }); diff --git a/backend/src/middleware/auth/checks/learning-path-auth-checks.ts b/backend/src/middleware/auth/checks/learning-path-auth-checks.ts index 95a94d44..6d4ec438 100644 --- a/backend/src/middleware/auth/checks/learning-path-auth-checks.ts +++ b/backend/src/middleware/auth/checks/learning-path-auth-checks.ts @@ -1,13 +1,13 @@ -import { Language } from "@dwengo-1/common/util/language"; -import learningPathService from "../../../services/learning-paths/learning-path-service"; -import { authorize } from "../auth"; -import { AuthenticatedRequest } from "../authenticated-request"; -import { AuthenticationInfo } from "../authentication-info"; +import { Language } from '@dwengo-1/common/util/language'; +import learningPathService from '../../../services/learning-paths/learning-path-service'; +import { authorize } from '../auth'; +import { AuthenticatedRequest } from '../authenticated-request'; +import { AuthenticationInfo } from '../authentication-info'; export const onlyAdminsForLearningPath = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { const adminsForLearningPath = await learningPathService.getAdmins({ hruid: req.params.hruid, - language: req.params.language as Language + language: req.params.language as Language, }); return adminsForLearningPath && adminsForLearningPath.includes(auth.username); }); diff --git a/backend/src/routes/learning-objects.ts b/backend/src/routes/learning-objects.ts index 46339ce5..a2dd9d30 100644 --- a/backend/src/routes/learning-objects.ts +++ b/backend/src/routes/learning-objects.ts @@ -5,12 +5,12 @@ import { getLearningObject, getLearningObjectHTML, handleDeleteLearningObject, - handlePostLearningObject + handlePostLearningObject, } from '../controllers/learning-objects.js'; import submissionRoutes from './submissions.js'; import questionRoutes from './questions.js'; -import fileUpload from "express-fileupload"; +import fileUpload from 'express-fileupload'; import { teachersOnly } from '../middleware/auth/auth.js'; import { onlyAdminsForLearningObject } from '../middleware/auth/checks/learning-object-auth-checks.js'; @@ -28,7 +28,7 @@ const router = express.Router(); // Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie router.get('/', getAllLearningObjects); -router.post('/', teachersOnly, fileUpload({useTempFiles: true}), handlePostLearningObject) +router.post('/', teachersOnly, fileUpload({ useTempFiles: true }), handlePostLearningObject); // Parameter: hruid of learning object // Query: language @@ -40,7 +40,7 @@ router.get('/:hruid', getLearningObject); // Query: language // Route to delete a learning object based on its hruid. // Example: http://localhost:3000/learningObject/un_ai7?language=nl&version=1 -router.delete('/:hruid', onlyAdminsForLearningObject, handleDeleteLearningObject) +router.delete('/:hruid', onlyAdminsForLearningObject, handleDeleteLearningObject); router.use('/:hruid/submissions', submissionRoutes); diff --git a/backend/src/routes/learning-paths.ts b/backend/src/routes/learning-paths.ts index 525ec34f..fdc894d0 100644 --- a/backend/src/routes/learning-paths.ts +++ b/backend/src/routes/learning-paths.ts @@ -25,7 +25,7 @@ const router = express.Router(); // Example: http://localhost:3000/learningPath?theme=kiks router.get('/', getLearningPaths); -router.post('/', teachersOnly, postLearningPath) +router.post('/', teachersOnly, postLearningPath); router.put('/:hruid/:language', onlyAdminsForLearningPath, putLearningPath); router.delete('/:hruid/:language', onlyAdminsForLearningPath, deleteLearningPath); diff --git a/backend/src/services/learning-objects/database-learning-object-provider.ts b/backend/src/services/learning-objects/database-learning-object-provider.ts index 9d16d820..38bb08c5 100644 --- a/backend/src/services/learning-objects/database-learning-object-provider.ts +++ b/backend/src/services/learning-objects/database-learning-object-provider.ts @@ -116,10 +116,8 @@ const databaseLearningObjectProvider: LearningObjectProvider = { async getLearningObjectsAdministratedBy(adminUsername: string): Promise { const learningObjectRepo = getLearningObjectRepository(); const learningObjects = await learningObjectRepo.findAllByAdmin(adminUsername); - return learningObjects - .map(it => convertLearningObject(it)) - .filter(it => it != null); - } + return learningObjects.map((it) => convertLearningObject(it)).filter((it) => it != null); + }, }; export default databaseLearningObjectProvider; diff --git a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts index 804d2d20..5ceca40c 100644 --- a/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts +++ b/backend/src/services/learning-objects/dwengo-api-learning-object-provider.ts @@ -141,7 +141,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = { */ async getLearningObjectsAdministratedBy(_adminUsername: string): Promise { return []; // The dwengo database does not contain any learning objects administrated by users. - } + }, }; export default dwengoApiLearningObjectProvider; diff --git a/backend/src/services/learning-objects/learning-object-service.ts b/backend/src/services/learning-objects/learning-object-service.ts index 7f547bab..9e94c49d 100644 --- a/backend/src/services/learning-objects/learning-object-service.ts +++ b/backend/src/services/learning-objects/learning-object-service.ts @@ -2,14 +2,10 @@ import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provid import { LearningObjectProvider } from './learning-object-provider.js'; import { envVars, getEnvVar } from '../../util/envVars.js'; import databaseLearningObjectProvider from './database-learning-object-provider.js'; -import { - FilteredLearningObject, - LearningObjectIdentifierDTO, - LearningPathIdentifier -} from '@dwengo-1/common/interfaces/learning-content'; -import {getLearningObjectRepository, getTeacherRepository} from "../../data/repositories"; -import {processLearningObjectZip} from "./learning-object-zip-processing-service"; -import {LearningObject} from "../../entities/content/learning-object.entity"; +import { FilteredLearningObject, LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; +import { getLearningObjectRepository, getTeacherRepository } from '../../data/repositories'; +import { processLearningObjectZip } from './learning-object-zip-processing-service'; +import { LearningObject } from '../../entities/content/learning-object.entity'; import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; import { NotFoundException } from '../../exceptions/not-found-exception.js'; @@ -74,17 +70,15 @@ const learningObjectService = { // Lookup the admin teachers based on their usernames and add them to the admins of the learning object. const teacherRepo = getTeacherRepository(); - const adminTeachers = await Promise.all( - admins.map(async it => teacherRepo.findByUsername(it)) - ); - adminTeachers.forEach(it => { + const adminTeachers = await Promise.all(admins.map(async (it) => teacherRepo.findByUsername(it))); + adminTeachers.forEach((it) => { if (it !== null) { learningObject.admins.add(it); } }); try { - await learningObjectRepository.save(learningObject, {preventOverwrite: true}); + await learningObjectRepository.save(learningObject, { preventOverwrite: true }); } catch (e: unknown) { learningObjectRepository.getEntityManager().clear(); throw e; @@ -109,10 +103,10 @@ const learningObjectService = { const learningObjectRepo = getLearningObjectRepository(); const learningObject = await learningObjectRepo.findByIdentifier(id); if (!learningObject) { - throw new NotFoundException("The specified learning object does not exist."); + throw new NotFoundException('The specified learning object does not exist.'); } - return learningObject.admins.map(admin => admin.username); - } + return learningObject.admins.map((admin) => admin.username); + }, }; export default learningObjectService; diff --git a/backend/src/services/learning-objects/learning-object-zip-processing-service.ts b/backend/src/services/learning-objects/learning-object-zip-processing-service.ts index 409c6c76..bef79e1c 100644 --- a/backend/src/services/learning-objects/learning-object-zip-processing-service.ts +++ b/backend/src/services/learning-objects/learning-object-zip-processing-service.ts @@ -1,9 +1,9 @@ import unzipper from 'unzipper'; import mime from 'mime-types'; -import {LearningObject} from "../../entities/content/learning-object.entity"; -import {getAttachmentRepository, getLearningObjectRepository} from "../../data/repositories"; -import {BadRequestException} from "../../exceptions/bad-request-exception"; -import {LearningObjectMetadata} from "@dwengo-1/common/interfaces/learning-content"; +import { LearningObject } from '../../entities/content/learning-object.entity'; +import { getAttachmentRepository, getLearningObjectRepository } from '../../data/repositories'; +import { BadRequestException } from '../../exceptions/bad-request-exception'; +import { LearningObjectMetadata } from '@dwengo-1/common/interfaces/learning-content'; import { DwengoContentType } from './processing/content-type'; const METADATA_PATH_REGEX = /.*[/^]metadata\.json$/; @@ -17,22 +17,21 @@ export async function processLearningObjectZip(filePath: string): Promise { - if (file.type !== "Directory") { + zip.files.map(async (file) => { + if (file.type !== 'Directory') { if (METADATA_PATH_REGEX.test(file.path)) { metadata = await processMetadataJson(file); } else if (CONTENT_PATH_REGEX.test(file.path)) { @@ -40,7 +39,7 @@ export async function processLearningObjectZip(filePath: string): Promise + attachmentRepo.create({ + name: it.name, + content: it.content, + mimeType: mime.lookup(it.name) || 'text/plain', + learningObject, + }) + ); + attachmentEntities.forEach((it) => { + learningObject.attachments.add(it); }); - const attachmentEntities = attachments.map(it => attachmentRepo.create({ - name: it.name, - content: it.content, - mimeType: mime.lookup(it.name) || "text/plain", - learningObject - })); - attachmentEntities.forEach(it => { learningObject.attachments.add(it); }); return learningObject; } diff --git a/backend/src/services/learning-paths/database-learning-path-provider.ts b/backend/src/services/learning-paths/database-learning-path-provider.ts index 1bb3be95..0b4f4d37 100644 --- a/backend/src/services/learning-paths/database-learning-path-provider.ts +++ b/backend/src/services/learning-paths/database-learning-path-provider.ts @@ -112,12 +112,13 @@ async function convertNode( ) .map((trans, i) => { try { - return convertTransition(trans, i, nodesToLearningObjects) + return convertTransition(trans, i, nodesToLearningObjects); } catch (_: unknown) { logger.error(`Transition could not be resolved: ${JSON.stringify(trans)}`); return undefined; // Do not crash on invalid transitions, just ignore them so the rest of the learning path keeps working. } - }).filter(it => it !== undefined); + }) + .filter((it) => it !== undefined); return { _id: learningObject.uuid, language: learningObject.language, diff --git a/backend/src/services/learning-paths/learning-path-service.ts b/backend/src/services/learning-paths/learning-path-service.ts index 1c83b3dc..5645f95a 100644 --- a/backend/src/services/learning-paths/learning-path-service.ts +++ b/backend/src/services/learning-paths/learning-path-service.ts @@ -110,9 +110,7 @@ const learningPathService = { * Fetch the learning paths administrated by the teacher with the given username. */ async getLearningPathsAdministratedBy(adminUsername: string): Promise { - const providerResponses = await Promise.all( - allProviders.map(async (provider) => provider.getLearningPathsAdministratedBy(adminUsername)) - ); + const providerResponses = await Promise.all(allProviders.map(async (provider) => provider.getLearningPathsAdministratedBy(adminUsername))); return providerResponses.flat(); }, @@ -157,7 +155,7 @@ const learningPathService = { if (deletedPath) { return deletedPath; } - throw new NotFoundException("No learning path with the given identifier found."); + throw new NotFoundException('No learning path with the given identifier found.'); }, /** @@ -168,10 +166,10 @@ const learningPathService = { const repo = getLearningPathRepository(); const path = await repo.findByHruidAndLanguage(id.hruid, id.language); if (!path) { - throw new NotFoundException("No learning path with the given identifier found."); + throw new NotFoundException('No learning path with the given identifier found.'); } - return path.admins.map(admin => admin.username); - } + return path.admins.map((admin) => admin.username); + }, }; export default learningPathService; diff --git a/frontend/src/components/BrowseThemes.vue b/frontend/src/components/BrowseThemes.vue index cfebe419..83610b87 100644 --- a/frontend/src/components/BrowseThemes.vue +++ b/frontend/src/components/BrowseThemes.vue @@ -5,7 +5,7 @@ import { AGE_TO_THEMES, THEMESITEMS } from "@/utils/constants.ts"; import { useThemeQuery } from "@/queries/themes.ts"; import type { Theme } from "@/data-objects/theme.ts"; -import authService from "@/services/auth/auth-service"; + import authService from "@/services/auth/auth-service"; const props = defineProps({ selectedTheme: { type: String, required: true }, diff --git a/frontend/src/components/ButtonWithConfirmation.vue b/frontend/src/components/ButtonWithConfirmation.vue index 96370acd..93dec470 100644 --- a/frontend/src/components/ButtonWithConfirmation.vue +++ b/frontend/src/components/ButtonWithConfirmation.vue @@ -1,17 +1,17 @@ - + diff --git a/frontend/src/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue b/frontend/src/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue index 2429742d..c7932249 100644 --- a/frontend/src/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue +++ b/frontend/src/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue @@ -1,36 +1,38 @@ diff --git a/frontend/src/views/own-learning-content/learning-paths/LearningPathPreviewCard.vue b/frontend/src/views/own-learning-content/learning-paths/LearningPathPreviewCard.vue index 50553b4a..f61164ae 100644 --- a/frontend/src/views/own-learning-content/learning-paths/LearningPathPreviewCard.vue +++ b/frontend/src/views/own-learning-content/learning-paths/LearningPathPreviewCard.vue @@ -1,30 +1,34 @@ - + diff --git a/frontend/src/views/own-learning-content/learning-paths/OwnLearningPathsView.vue b/frontend/src/views/own-learning-content/learning-paths/OwnLearningPathsView.vue index 85aeae63..ab855857 100644 --- a/frontend/src/views/own-learning-content/learning-paths/OwnLearningPathsView.vue +++ b/frontend/src/views/own-learning-content/learning-paths/OwnLearningPathsView.vue @@ -1,27 +1,30 @@