Merge branch 'feat/endpoints-in-backend-om-eigen-leerpaden-en-leerobjecten-toe-te-voegen-aan-de-databank-#248' of https://github.com/SELab-2/Dwengo-1 into feat/endpoints-in-backend-om-eigen-leerpaden-en-leerobjecten-toe-te-voegen-aan-de-databank-#248
This commit is contained in:
commit
226c9786dd
32 changed files with 357 additions and 303 deletions
|
@ -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<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,12 @@ export async function getLearningPaths(req: Request, res: Response): Promise<voi
|
|||
hruidList = themes.flatMap((theme) => 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<void> {
|
||||
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.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
|||
return this.find(
|
||||
{
|
||||
admins: {
|
||||
username: adminUsername
|
||||
}
|
||||
username: adminUsername,
|
||||
},
|
||||
},
|
||||
{ populate: ['admins'] } // Make sure to load admin relations
|
||||
);
|
||||
|
@ -50,5 +50,4 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
|||
}
|
||||
return learningObject;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,7 @@ import { LearningPathTransition } from '../../entities/content/learning-path-tra
|
|||
|
||||
export class LearningPathRepository extends DwengoEntityRepository<LearningPath> {
|
||||
public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> {
|
||||
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<LearningPath>
|
|||
return this.findAll({
|
||||
where: {
|
||||
admins: {
|
||||
username: adminUsername
|
||||
}
|
||||
username: adminUsername,
|
||||
},
|
||||
},
|
||||
populate: ['nodes', 'nodes.transitions', 'admins']
|
||||
populate: ['nodes', 'nodes.transitions', 'admins'],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ export class Attachment {
|
|||
@ManyToOne({
|
||||
entity: () => LearningObject,
|
||||
primary: true,
|
||||
deleteRule: 'cascade'
|
||||
deleteRule: 'cascade',
|
||||
})
|
||||
learningObject!: LearningObject;
|
||||
|
||||
|
|
|
@ -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<Attachment> = new Collection<Attachment>(this);
|
||||
|
||||
|
|
|
@ -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<boolean>
|
||||
): RequestHandler {
|
||||
return async (
|
||||
req: AuthenticatedRequest,
|
||||
_res: express.Response,
|
||||
next: express.NextFunction
|
||||
): Promise<void> => {
|
||||
export function authorize(accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean>): RequestHandler {
|
||||
return async (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): Promise<void> => {
|
||||
if (!req.auth) {
|
||||
throw new UnauthorizedException();
|
||||
} else if (!(await accessCondition(req.auth, req))) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -141,7 +141,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
*/
|
||||
async getLearningObjectsAdministratedBy(_adminUsername: string): Promise<FilteredLearningObject[]> {
|
||||
return []; // The dwengo database does not contain any learning objects administrated by users.
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default dwengoApiLearningObjectProvider;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Learni
|
|||
let zip: unzipper.CentralDirectory;
|
||||
try {
|
||||
zip = await unzipper.Open.file(filePath);
|
||||
} catch(_: unknown) {
|
||||
throw new BadRequestException("invalidZip");
|
||||
} catch (_: unknown) {
|
||||
throw new BadRequestException('invalidZip');
|
||||
}
|
||||
|
||||
|
||||
let metadata: LearningObjectMetadata | undefined = undefined;
|
||||
const attachments: {name: string, content: Buffer}[] = [];
|
||||
const attachments: { name: string; content: Buffer }[] = [];
|
||||
let content: Buffer | undefined = undefined;
|
||||
|
||||
if (zip.files.length === 0) {
|
||||
throw new BadRequestException("emptyZip")
|
||||
throw new BadRequestException('emptyZip');
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
zip.files.map(async file => {
|
||||
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<Learni
|
|||
} else {
|
||||
attachments.push({
|
||||
name: file.path,
|
||||
content: await processFile(file)
|
||||
content: await processFile(file),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -48,27 +47,24 @@ export async function processLearningObjectZip(filePath: string): Promise<Learni
|
|||
);
|
||||
|
||||
if (!metadata) {
|
||||
throw new BadRequestException("missingMetadata");
|
||||
throw new BadRequestException('missingMetadata');
|
||||
}
|
||||
if (!content) {
|
||||
throw new BadRequestException("missingIndex");
|
||||
throw new BadRequestException('missingIndex');
|
||||
}
|
||||
|
||||
|
||||
const learningObject = createLearningObject(metadata, content, attachments);
|
||||
|
||||
return learningObject;
|
||||
}
|
||||
|
||||
function createLearningObject(
|
||||
metadata: LearningObjectMetadata, content: Buffer, attachments: { name: string; content: Buffer; }[]
|
||||
): LearningObject {
|
||||
function createLearningObject(metadata: LearningObjectMetadata, content: Buffer, attachments: { name: string; content: Buffer }[]): LearningObject {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const attachmentRepo = getAttachmentRepository();
|
||||
|
||||
const returnValue = {
|
||||
callbackUrl: metadata.return_value?.callback_url ?? "",
|
||||
callbackSchema: metadata.return_value?.callback_schema ? JSON.stringify(metadata.return_value.callback_schema) : ""
|
||||
callbackUrl: metadata.return_value?.callback_url ?? '',
|
||||
callbackSchema: metadata.return_value?.callback_schema ? JSON.stringify(metadata.return_value.callback_schema) : '',
|
||||
};
|
||||
|
||||
const learningObject = learningObjectRepo.create({
|
||||
|
@ -76,26 +72,30 @@ function createLearningObject(
|
|||
available: metadata.available ?? true,
|
||||
content: content,
|
||||
contentType: metadata.content_type as DwengoContentType,
|
||||
copyright: metadata.copyright ?? "",
|
||||
description: metadata.description ?? "",
|
||||
copyright: metadata.copyright ?? '',
|
||||
description: metadata.description ?? '',
|
||||
educationalGoals: metadata.educational_goals ?? [],
|
||||
hruid: metadata.hruid,
|
||||
keywords: metadata.keywords,
|
||||
language: metadata.language,
|
||||
license: metadata.license ?? "",
|
||||
license: metadata.license ?? '',
|
||||
returnValue,
|
||||
skosConcepts: metadata.skos_concepts ?? [],
|
||||
teacherExclusive: metadata.teacher_exclusive,
|
||||
title: metadata.title,
|
||||
version: metadata.version
|
||||
version: metadata.version,
|
||||
});
|
||||
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);
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -110,9 +110,7 @@ const learningPathService = {
|
|||
* Fetch the learning paths administrated by the teacher with the given username.
|
||||
*/
|
||||
async getLearningPathsAdministratedBy(adminUsername: string): Promise<LearningPath[]> {
|
||||
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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue