feat(backend): opvragen van leerobjecten van een leerkracht
This commit is contained in:
parent
78353d6b65
commit
6600441b08
11 changed files with 152 additions and 38 deletions
|
@ -8,6 +8,7 @@ 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";
|
||||
|
||||
function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifierDTO {
|
||||
if (!req.params.hruid) {
|
||||
|
@ -31,17 +32,24 @@ function getLearningPathIdentifierFromRequest(req: Request): LearningPathIdentif
|
|||
}
|
||||
|
||||
export async function getAllLearningObjects(req: Request, res: Response): Promise<void> {
|
||||
const learningPathId = getLearningPathIdentifierFromRequest(req);
|
||||
const full = req.query.full;
|
||||
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);
|
||||
|
||||
let learningObjects: FilteredLearningObject[] | string[];
|
||||
if (full) {
|
||||
learningObjects = await learningObjectService.getLearningObjectsFromPath(learningPathId);
|
||||
} else {
|
||||
learningObjects = await learningObjectService.getLearningObjectIdsFromPath(learningPathId);
|
||||
res.json(learningObjects);
|
||||
} 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;
|
||||
|
||||
let learningObjects: FilteredLearningObject[] | string[];
|
||||
if (full) {
|
||||
learningObjects = await learningObjectService.getLearningObjectsFromPath(learningPathId);
|
||||
} else {
|
||||
learningObjects = await learningObjectService.getLearningObjectIdsFromPath(learningPathId);
|
||||
}
|
||||
|
||||
res.json({ learningObjects: learningObjects });
|
||||
}
|
||||
|
||||
res.json({ learningObjects: learningObjects });
|
||||
}
|
||||
|
||||
export async function getLearningObject(req: Request, res: Response): Promise<void> {
|
||||
|
@ -74,9 +82,13 @@ export async function getAttachment(req: Request, res: Response): Promise<void>
|
|||
res.setHeader('Content-Type', attachment.mimeType).send(attachment.content);
|
||||
}
|
||||
|
||||
export async function handlePostLearningObject(req: Request, res: Response): Promise<void> {
|
||||
if (!req.files || !req.files[0]) {
|
||||
export async function handlePostLearningObject(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
if (!req.files || !req.files.learningObject) {
|
||||
throw new BadRequestException('No file uploaded');
|
||||
}
|
||||
await learningObjectService.storeLearningObject((req.files[0] as UploadedFile).tempFilePath);
|
||||
const learningObject = await learningObjectService.storeLearningObject(
|
||||
(req.files.learningObject as UploadedFile).tempFilePath,
|
||||
[req.auth!.username]
|
||||
);
|
||||
res.json(learningObject);
|
||||
}
|
||||
|
|
|
@ -33,9 +33,9 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
|||
);
|
||||
}
|
||||
|
||||
public async findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> {
|
||||
public async findAllByAdmin(adminUsername: string): Promise<LearningObject[]> {
|
||||
return this.find(
|
||||
{ admins: teacher },
|
||||
{ admins: { $contains: adminUsername } },
|
||||
{ populate: ['admins'] } // Make sure to load admin relations
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import { ArrayType, 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';
|
||||
|
@ -28,7 +38,7 @@ export class LearningObject {
|
|||
@ManyToMany({
|
||||
entity: () => Teacher,
|
||||
})
|
||||
admins!: Teacher[];
|
||||
admins: Collection<Teacher> = new Collection<Teacher>(this);
|
||||
|
||||
@Property({ type: 'string' })
|
||||
title!: string;
|
||||
|
@ -84,7 +94,7 @@ export class LearningObject {
|
|||
entity: () => Attachment,
|
||||
mappedBy: 'learningObject',
|
||||
})
|
||||
attachments: Attachment[] = [];
|
||||
attachments: Collection<Attachment> = new Collection<Attachment>(this);
|
||||
|
||||
@Property({ type: 'blob' })
|
||||
content!: Buffer;
|
||||
|
|
|
@ -109,6 +109,17 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
);
|
||||
return learningObjects.filter((it) => it !== null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all learning objects containing the given username as an admin.
|
||||
*/
|
||||
async getLearningObjectsAdministratedBy(adminUsername: string): Promise<FilteredLearningObject[]> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const learningObjects = await learningObjectRepo.findAllByAdmin(adminUsername);
|
||||
return learningObjects
|
||||
.map(it => convertLearningObject(it))
|
||||
.filter(it => it != null);
|
||||
}
|
||||
};
|
||||
|
||||
export default databaseLearningObjectProvider;
|
||||
|
|
|
@ -135,6 +135,13 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
|
||||
return html;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain all learning objects who have the user with the given username as an admin.
|
||||
*/
|
||||
async getLearningObjectsAdministratedBy(_adminUsername: string): Promise<FilteredLearningObject[]> {
|
||||
return []; // The dwengo database does not contain any learning objects administrated by users.
|
||||
}
|
||||
};
|
||||
|
||||
export default dwengoApiLearningObjectProvider;
|
||||
|
|
|
@ -20,4 +20,9 @@ export interface LearningObjectProvider {
|
|||
* Obtain a HTML-rendering of the learning object with the given identifier (as a string).
|
||||
*/
|
||||
getLearningObjectHTML(id: LearningObjectIdentifierDTO): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Obtain all learning object who have the user with the given username as an admin.
|
||||
*/
|
||||
getLearningObjectsAdministratedBy(username: string): Promise<FilteredLearningObject[]>;
|
||||
}
|
||||
|
|
|
@ -7,9 +7,10 @@ import {
|
|||
LearningObjectIdentifierDTO,
|
||||
LearningPathIdentifier
|
||||
} from '@dwengo-1/common/interfaces/learning-content';
|
||||
import {getLearningObjectRepository} from "../../data/repositories";
|
||||
import {getLearningObjectRepository, getTeacherRepository} from "../../data/repositories";
|
||||
import {processLearningObjectZip} from "./learning-object-zip-processing-service";
|
||||
import {BadRequestException} from "../../exceptions/bad-request-exception";
|
||||
import {LearningObject} from "../../entities/content/learning-object.entity";
|
||||
|
||||
function getProvider(id: LearningObjectIdentifierDTO): LearningObjectProvider {
|
||||
if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) {
|
||||
|
@ -50,19 +51,40 @@ const learningObjectService = {
|
|||
return getProvider(id).getLearningObjectHTML(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain all learning objects administrated by the user with the given username.
|
||||
*/
|
||||
async getLearningObjectsAdministratedBy(adminUsername: string): Promise<FilteredLearningObject[]> {
|
||||
return databaseLearningObjectProvider.getLearningObjectsAdministratedBy(adminUsername);
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the learning object in the given zip file in the database.
|
||||
* @param learningObjectPath The path where the uploaded learning object resides.
|
||||
* @param admins The usernames of the users which should be administrators of the learning object.
|
||||
*/
|
||||
async storeLearningObject(learningObjectPath: string): Promise<void> {
|
||||
async storeLearningObject(learningObjectPath: string, admins: string[]): Promise<LearningObject> {
|
||||
const learningObjectRepository = getLearningObjectRepository();
|
||||
const learningObject = await processLearningObjectZip(learningObjectPath);
|
||||
|
||||
console.log(learningObject);
|
||||
if (!learningObject.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) {
|
||||
throw new BadRequestException("Learning object name must start with the user content prefix!");
|
||||
learningObject.hruid = getEnvVar(envVars.UserContentPrefix) + learningObject.hruid;
|
||||
}
|
||||
|
||||
// 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(it => teacherRepo.findByUsername(it))
|
||||
);
|
||||
adminTeachers.forEach(it => {
|
||||
if (it != null) {
|
||||
learningObject.admins.add(it);
|
||||
}
|
||||
});
|
||||
|
||||
await learningObjectRepository.save(learningObject, {preventOverwrite: true});
|
||||
return learningObject;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ import {getAttachmentRepository, getLearningObjectRepository} from "../../data/r
|
|||
import {BadRequestException} from "../../exceptions/bad-request-exception";
|
||||
import {LearningObjectMetadata} from "@dwengo-1/common/dist/interfaces/learning-content";
|
||||
|
||||
const METADATA_PATH_REGEX = /.*[/^]metadata\.json$/;
|
||||
const CONTENT_PATH_REGEX = /.*[/^]content\.[a-zA-Z]*$/;
|
||||
|
||||
/**
|
||||
* Process an uploaded zip file and construct a LearningObject from its contents.
|
||||
* @param filePath Path of the zip file to process.
|
||||
|
@ -15,40 +18,62 @@ export async function processLearningObjectZip(filePath: string): Promise<Learni
|
|||
|
||||
const zip = await unzipper.Open.file(filePath);
|
||||
|
||||
let metadata: LearningObjectMetadata | null = null;
|
||||
let metadata: LearningObjectMetadata | undefined = undefined;
|
||||
const attachments: {name: string, content: Buffer}[] = [];
|
||||
let content: Buffer | null = null;
|
||||
let content: Buffer | undefined = undefined;
|
||||
|
||||
if (zip.files.length == 0) {
|
||||
throw new BadRequestException("empty_zip")
|
||||
}
|
||||
|
||||
for (const file of zip.files) {
|
||||
if (file.type === "Directory") {
|
||||
throw new BadRequestException("The learning object zip file should not contain directories.");
|
||||
} else if (file.path === "metadata.json") {
|
||||
metadata = await processMetadataJson(file);
|
||||
} else if (file.path.startsWith("index.")) {
|
||||
content = await processFile(file);
|
||||
} else {
|
||||
attachments.push({
|
||||
name: file.path,
|
||||
content: await processFile(file)
|
||||
});
|
||||
if (file.type !== "Directory") {
|
||||
if (METADATA_PATH_REGEX.test(file.path)) {
|
||||
metadata = await processMetadataJson(file);
|
||||
} else if (CONTENT_PATH_REGEX.test(file.path)) {
|
||||
content = await processFile(file);
|
||||
} else {
|
||||
attachments.push({
|
||||
name: file.path,
|
||||
content: await processFile(file)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadata) {
|
||||
throw new BadRequestException("Missing metadata.json file");
|
||||
throw new BadRequestException("missing_metadata");
|
||||
}
|
||||
if (!content) {
|
||||
throw new BadRequestException("Missing index file");
|
||||
throw new BadRequestException("missing_index");
|
||||
}
|
||||
|
||||
const learningObject = learningObjectRepo.create(metadata);
|
||||
|
||||
const learningObject = learningObjectRepo.create({
|
||||
admins: [],
|
||||
available: metadata.available ?? true,
|
||||
content: content,
|
||||
contentType: metadata.content_type,
|
||||
copyright: metadata.copyright,
|
||||
description: metadata.description,
|
||||
educationalGoals: metadata.educational_goals,
|
||||
hruid: metadata.hruid,
|
||||
keywords: metadata.keywords,
|
||||
language: metadata.language,
|
||||
license: "",
|
||||
returnValue: metadata.return_value,
|
||||
skosConcepts: metadata.skos_concepts,
|
||||
teacherExclusive: metadata.teacher_exclusive,
|
||||
title: metadata.title,
|
||||
version: metadata.version
|
||||
});
|
||||
const attachmentEntities = attachments.map(it => attachmentRepo.create({
|
||||
name: it.name,
|
||||
content: it.content,
|
||||
mimeType: mime.lookup(it.name) || "text/plain",
|
||||
learningObject
|
||||
}))
|
||||
learningObject.attachments.push(...attachmentEntities);
|
||||
attachmentEntities.forEach(it => learningObject.attachments.add(it));
|
||||
|
||||
return learningObject;
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom
|
|||
|
||||
// Find all learning objects that this teacher manages
|
||||
const learningObjectRepository: LearningObjectRepository = getLearningObjectRepository();
|
||||
const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher);
|
||||
const learningObjects: LearningObject[] = await learningObjectRepository.findAllByAdmin(teacher);
|
||||
|
||||
if (!learningObjects || learningObjects.length === 0) {
|
||||
return [];
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue