style: fix linting issues met Prettier
This commit is contained in:
parent
aa1a85e64e
commit
2a2881ec30
84 changed files with 846 additions and 1013 deletions
|
@ -1,4 +1,4 @@
|
|||
import {EnvVars, getEnvVar} from "./util/envvars";
|
||||
import { EnvVars, getEnvVar } from './util/envvars';
|
||||
|
||||
// API
|
||||
export const DWENGO_API_BASE = getEnvVar(EnvVars.LearningContentRepoApiBaseUrl);
|
||||
|
|
|
@ -1,38 +1,35 @@
|
|||
import { Request, Response } from 'express';
|
||||
import { FALLBACK_LANG } from '../config.js';
|
||||
import {FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier} from '../interfaces/learning-content';
|
||||
import learningObjectService from "../services/learning-objects/learning-object-service";
|
||||
import {EnvVars, getEnvVar} from "../util/envvars";
|
||||
import {Language} from "../entities/content/language";
|
||||
import {BadRequestException} from "../exceptions";
|
||||
import attachmentService from "../services/learning-objects/attachment-service";
|
||||
import {NotFoundError} from "@mikro-orm/core";
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../interfaces/learning-content';
|
||||
import learningObjectService from '../services/learning-objects/learning-object-service';
|
||||
import { EnvVars, getEnvVar } from '../util/envvars';
|
||||
import { Language } from '../entities/content/language';
|
||||
import { BadRequestException } from '../exceptions';
|
||||
import attachmentService from '../services/learning-objects/attachment-service';
|
||||
import { NotFoundError } from '@mikro-orm/core';
|
||||
|
||||
function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier {
|
||||
if (!req.params.hruid) {
|
||||
throw new BadRequestException("HRUID is required.");
|
||||
throw new BadRequestException('HRUID is required.');
|
||||
}
|
||||
return {
|
||||
hruid: req.params.hruid as string,
|
||||
language: (req.query.language || getEnvVar(EnvVars.FallbackLanguage)) as Language,
|
||||
version: parseInt(req.query.version as string)
|
||||
version: parseInt(req.query.version as string),
|
||||
};
|
||||
}
|
||||
|
||||
function getLearningPathIdentifierFromRequest(req: Request): LearningPathIdentifier {
|
||||
if (!req.query.hruid) {
|
||||
throw new BadRequestException("HRUID is required.");
|
||||
throw new BadRequestException('HRUID is required.');
|
||||
}
|
||||
return {
|
||||
hruid: req.params.hruid as string,
|
||||
language: (req.query.language as Language) || FALLBACK_LANG
|
||||
}
|
||||
language: (req.query.language as Language) || FALLBACK_LANG,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllLearningObjects(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
export async function getAllLearningObjects(req: Request, res: Response): Promise<void> {
|
||||
const learningPathId = getLearningPathIdentifierFromRequest(req);
|
||||
const full = req.query.full;
|
||||
|
||||
|
@ -46,10 +43,7 @@ export async function getAllLearningObjects(
|
|||
res.json(learningObjects);
|
||||
}
|
||||
|
||||
export async function getLearningObject(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
export async function getLearningObject(req: Request, res: Response): Promise<void> {
|
||||
const learningObjectId = getLearningObjectIdentifierFromRequest(req);
|
||||
|
||||
const learningObject = await learningObjectService.getLearningObjectById(learningObjectId);
|
||||
|
@ -71,5 +65,5 @@ export async function getAttachment(req: Request, res: Response): Promise<void>
|
|||
if (!attachment) {
|
||||
throw new NotFoundError(`Attachment ${name} not found`);
|
||||
}
|
||||
res.setHeader("Content-Type", attachment.mimeType).send(attachment.content)
|
||||
res.setHeader('Content-Type', attachment.mimeType).send(attachment.content);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import { Request, Response } from 'express';
|
||||
import { themes } from '../data/themes.js';
|
||||
import { FALLBACK_LANG } from '../config.js';
|
||||
import learningPathService from "../services/learning-paths/learning-path-service";
|
||||
import {NotFoundException} from "../exceptions";
|
||||
import learningPathService from '../services/learning-paths/learning-path-service';
|
||||
import { NotFoundException } from '../exceptions';
|
||||
|
||||
/**
|
||||
* Fetch learning paths based on query parameters.
|
||||
*/
|
||||
export async function getLearningPaths(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
export async function getLearningPaths(req: Request, res: Response): Promise<void> {
|
||||
const hruids = req.query.hruid;
|
||||
const themeKey = req.query.theme as string;
|
||||
const searchQuery = req.query.search as string;
|
||||
|
@ -19,9 +16,7 @@ export async function getLearningPaths(
|
|||
let hruidList;
|
||||
|
||||
if (hruids) {
|
||||
hruidList = Array.isArray(hruids)
|
||||
? hruids.map(String)
|
||||
: [String(hruids)];
|
||||
hruidList = Array.isArray(hruids) ? hruids.map(String) : [String(hruids)];
|
||||
} else if (themeKey) {
|
||||
const theme = themes.find((t) => t.title === themeKey);
|
||||
if (theme) {
|
||||
|
@ -30,20 +25,13 @@ export async function getLearningPaths(
|
|||
throw new NotFoundException(`Theme "${themeKey}" not found.`);
|
||||
}
|
||||
} else if (searchQuery) {
|
||||
const searchResults = await learningPathService.searchLearningPaths(
|
||||
searchQuery,
|
||||
language
|
||||
);
|
||||
const searchResults = await learningPathService.searchLearningPaths(searchQuery, language);
|
||||
res.json(searchResults);
|
||||
return;
|
||||
} else {
|
||||
hruidList = themes.flatMap((theme) => theme.hruids);
|
||||
}
|
||||
|
||||
const learningPaths = await learningPathService.fetchLearningPaths(
|
||||
hruidList,
|
||||
language,
|
||||
`HRUIDs: ${hruidList.join(', ')}`
|
||||
);
|
||||
const learningPaths = await learningPathService.fetchLearningPaths(hruidList, language, `HRUIDs: ${hruidList.join(', ')}`);
|
||||
res.json(learningPaths.data);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { Request, Response } from 'express';
|
||||
import { themes } from '../data/themes.js';
|
||||
import { FALLBACK_LANG } from '../config.js';
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
||||
import { Attachment } from '../../entities/content/attachment.entity.js';
|
||||
import {Language} from "../../entities/content/language";
|
||||
import {LearningObjectIdentifier} from "../../entities/content/learning-object-identifier";
|
||||
import { Language } from '../../entities/content/language';
|
||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier';
|
||||
|
||||
export class AttachmentRepository extends DwengoEntityRepository<Attachment> {
|
||||
public findByLearningObjectIdAndName(
|
||||
learningObjectId: LearningObjectIdentifier,
|
||||
name: string
|
||||
): Promise<Attachment | null> {
|
||||
public findByLearningObjectIdAndName(learningObjectId: LearningObjectIdentifier, name: string): Promise<Attachment | null> {
|
||||
return this.findOne({
|
||||
learningObject: {
|
||||
hruid: learningObjectId.hruid,
|
||||
|
@ -18,24 +15,23 @@ export class AttachmentRepository extends DwengoEntityRepository<Attachment> {
|
|||
});
|
||||
}
|
||||
|
||||
public findByMostRecentVersionOfLearningObjectAndName(
|
||||
hruid: string,
|
||||
language: Language,
|
||||
attachmentName: string
|
||||
): Promise<Attachment | null> {
|
||||
return this.findOne({
|
||||
learningObject: {
|
||||
hruid: hruid,
|
||||
language: language
|
||||
},
|
||||
name: attachmentName
|
||||
}, {
|
||||
orderBy: {
|
||||
public findByMostRecentVersionOfLearningObjectAndName(hruid: string, language: Language, attachmentName: string): Promise<Attachment | null> {
|
||||
return this.findOne(
|
||||
{
|
||||
learningObject: {
|
||||
version: 'DESC'
|
||||
}
|
||||
hruid: hruid,
|
||||
language: language,
|
||||
},
|
||||
name: attachmentName,
|
||||
},
|
||||
{
|
||||
orderBy: {
|
||||
learningObject: {
|
||||
version: 'DESC',
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
// This repository is read-only for now since creating own learning object is an extension feature.
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
||||
import { LearningObject } from '../../entities/content/learning-object.entity.js';
|
||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
||||
import {Language} from "../../entities/content/language";
|
||||
import { Language } from '../../entities/content/language';
|
||||
|
||||
export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> {
|
||||
public findByIdentifier(
|
||||
identifier: LearningObjectIdentifier
|
||||
): Promise<LearningObject | null> {
|
||||
public findByIdentifier(identifier: LearningObjectIdentifier): Promise<LearningObject | null> {
|
||||
return this.findOne(
|
||||
{
|
||||
hruid: identifier.hruid,
|
||||
|
@ -14,7 +12,7 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
|||
version: identifier.version,
|
||||
},
|
||||
{
|
||||
populate: ["keywords"]
|
||||
populate: ['keywords'],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -23,13 +21,13 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
|||
return this.findOne(
|
||||
{
|
||||
hruid: hruid,
|
||||
language: language
|
||||
language: language,
|
||||
},
|
||||
{
|
||||
populate: ["keywords"],
|
||||
populate: ['keywords'],
|
||||
orderBy: {
|
||||
version: "DESC"
|
||||
}
|
||||
version: 'DESC',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
import {DwengoEntityRepository} from '../dwengo-entity-repository.js';
|
||||
import {LearningPath} from '../../entities/content/learning-path.entity.js';
|
||||
import {Language} from '../../entities/content/language.js';
|
||||
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
||||
import { LearningPath } from '../../entities/content/learning-path.entity.js';
|
||||
import { Language } from '../../entities/content/language.js';
|
||||
|
||||
export class LearningPathRepository extends DwengoEntityRepository<LearningPath> {
|
||||
public findByHruidAndLanguage(
|
||||
hruid: string,
|
||||
language: Language
|
||||
): Promise<LearningPath | null> {
|
||||
return this.findOne(
|
||||
{ hruid: hruid, language: language },
|
||||
{ populate: ["nodes", "nodes.transitions"] }
|
||||
);
|
||||
public findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> {
|
||||
return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions'] });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,12 +18,9 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath>
|
|||
return this.findAll({
|
||||
where: {
|
||||
language: language,
|
||||
$or: [
|
||||
{ title: { $like: `%${query}%`} },
|
||||
{ description: { $like: `%${query}%`} }
|
||||
]
|
||||
$or: [{ title: { $like: `%${query}%` } }, { description: { $like: `%${query}%` } }],
|
||||
},
|
||||
populate: ["nodes", "nodes.transitions"]
|
||||
populate: ['nodes', 'nodes.transitions'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ import { LearningPath } from '../entities/content/learning-path.entity.js';
|
|||
import { LearningPathRepository } from './content/learning-path-repository.js';
|
||||
import { AttachmentRepository } from './content/attachment-repository.js';
|
||||
import { Attachment } from '../entities/content/attachment.entity.js';
|
||||
import {LearningPathNode} from "../entities/content/learning-path-node.entity";
|
||||
import {LearningPathTransition} from "../entities/content/learning-path-transition.entity";
|
||||
import { LearningPathNode } from '../entities/content/learning-path-node.entity';
|
||||
import { LearningPathTransition } from '../entities/content/learning-path-transition.entity';
|
||||
|
||||
let entityManager: EntityManager | undefined;
|
||||
|
||||
|
@ -73,17 +73,8 @@ export const getQuestionRepository = repositoryGetter<Question, QuestionReposito
|
|||
export const getAnswerRepository = repositoryGetter<Answer, AnswerRepository>(Answer);
|
||||
|
||||
/* Learning content */
|
||||
export const getLearningObjectRepository = repositoryGetter<
|
||||
LearningObject,
|
||||
LearningObjectRepository
|
||||
>(LearningObject);
|
||||
export const getLearningPathRepository = repositoryGetter<
|
||||
LearningPath,
|
||||
LearningPathRepository
|
||||
>(LearningPath);
|
||||
export const getLearningObjectRepository = repositoryGetter<LearningObject, LearningObjectRepository>(LearningObject);
|
||||
export const getLearningPathRepository = repositoryGetter<LearningPath, LearningPathRepository>(LearningPath);
|
||||
export const getLearningPathNodeRepository = repositoryGetter(LearningPathNode);
|
||||
export const getLearningPathTransitionRepository = repositoryGetter(LearningPathTransition);
|
||||
export const getAttachmentRepository = repositoryGetter<
|
||||
Attachment,
|
||||
AttachmentRepository
|
||||
>(Attachment);
|
||||
export const getAttachmentRepository = repositoryGetter<Attachment, AttachmentRepository>(Attachment);
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro
|
|||
import { Class } from '../classes/class.entity.js';
|
||||
import { Group } from './group.entity.js';
|
||||
import { Language } from '../content/language.js';
|
||||
import {AssignmentRepository} from "../../data/assignments/assignment-repository";
|
||||
import { AssignmentRepository } from '../../data/assignments/assignment-repository';
|
||||
|
||||
@Entity({repository: () => AssignmentRepository})
|
||||
@Entity({ repository: () => AssignmentRepository })
|
||||
export class Assignment {
|
||||
@ManyToOne({ entity: () => Class, primary: true })
|
||||
within!: Class;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Entity, ManyToMany, ManyToOne, PrimaryKey } from '@mikro-orm/core';
|
||||
import { Assignment } from './assignment.entity.js';
|
||||
import { Student } from '../users/student.entity.js';
|
||||
import {GroupRepository} from "../../data/assignments/group-repository";
|
||||
import { GroupRepository } from '../../data/assignments/group-repository';
|
||||
|
||||
@Entity({repository: () => GroupRepository})
|
||||
@Entity({ repository: () => GroupRepository })
|
||||
export class Group {
|
||||
@ManyToOne({
|
||||
entity: () => Assignment,
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Student } from '../users/student.entity.js';
|
|||
import { Group } from './group.entity.js';
|
||||
import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { Language } from '../content/language.js';
|
||||
import {SubmissionRepository} from "../../data/assignments/submission-repository";
|
||||
import { SubmissionRepository } from '../../data/assignments/submission-repository';
|
||||
|
||||
@Entity({repository: () => SubmissionRepository})
|
||||
@Entity({ repository: () => SubmissionRepository })
|
||||
export class Submission {
|
||||
@PrimaryKey({ type: 'string' })
|
||||
learningObjectHruid!: string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Entity, Enum, ManyToOne } from '@mikro-orm/core';
|
||||
import { Student } from '../users/student.entity.js';
|
||||
import { Class } from './class.entity.js';
|
||||
import {ClassJoinRequestRepository} from "../../data/classes/class-join-request-repository";
|
||||
import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository';
|
||||
|
||||
@Entity({repository: () => ClassJoinRequestRepository})
|
||||
@Entity({ repository: () => ClassJoinRequestRepository })
|
||||
export class ClassJoinRequest {
|
||||
@ManyToOne({
|
||||
entity: () => Student,
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Collection, Entity, ManyToMany, PrimaryKey, Property } from '@mikro-orm
|
|||
import { v4 } from 'uuid';
|
||||
import { Teacher } from '../users/teacher.entity.js';
|
||||
import { Student } from '../users/student.entity.js';
|
||||
import {ClassRepository} from "../../data/classes/class-repository";
|
||||
import { ClassRepository } from '../../data/classes/class-repository';
|
||||
|
||||
@Entity({repository: () => ClassRepository})
|
||||
@Entity({ repository: () => ClassRepository })
|
||||
export class Class {
|
||||
@PrimaryKey()
|
||||
classId = v4();
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Entity, ManyToOne } from '@mikro-orm/core';
|
||||
import { Teacher } from '../users/teacher.entity.js';
|
||||
import { Class } from './class.entity.js';
|
||||
import {TeacherInvitationRepository} from "../../data/classes/teacher-invitation-repository";
|
||||
import { TeacherInvitationRepository } from '../../data/classes/teacher-invitation-repository';
|
||||
|
||||
/**
|
||||
* Invitation of a teacher into a class (in order to teach it).
|
||||
*/
|
||||
@Entity({repository: () => TeacherInvitationRepository})
|
||||
@Entity({ repository: () => TeacherInvitationRepository })
|
||||
export class TeacherInvitation {
|
||||
@ManyToOne({
|
||||
entity: () => Teacher,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { LearningObject } from './learning-object.entity.js';
|
||||
import {AttachmentRepository} from "../../data/content/attachment-repository";
|
||||
import { AttachmentRepository } from '../../data/content/attachment-repository';
|
||||
|
||||
@Entity({repository: () => AttachmentRepository})
|
||||
@Entity({ repository: () => AttachmentRepository })
|
||||
export class Attachment {
|
||||
@ManyToOne({
|
||||
entity: () => LearningObject,
|
||||
|
|
|
@ -182,5 +182,5 @@ export enum Language {
|
|||
Yiddish = 'yi',
|
||||
Yoruba = 'yo',
|
||||
Zhuang = 'za',
|
||||
Zulu = 'zu'
|
||||
Zulu = 'zu',
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ import { Embeddable, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey,
|
|||
import { Language } from './language.js';
|
||||
import { Attachment } from './attachment.entity.js';
|
||||
import { Teacher } from '../users/teacher.entity.js';
|
||||
import {DwengoContentType} from "../../services/learning-objects/processing/content-type";
|
||||
import {v4} from "uuid";
|
||||
import {LearningObjectRepository} from "../../data/content/learning-object-repository";
|
||||
import { DwengoContentType } from '../../services/learning-objects/processing/content-type';
|
||||
import { v4 } from 'uuid';
|
||||
import { LearningObjectRepository } from '../../data/content/learning-object-repository';
|
||||
|
||||
@Entity({repository: () => LearningObjectRepository})
|
||||
@Entity({ repository: () => LearningObjectRepository })
|
||||
export class LearningObject {
|
||||
@PrimaryKey({ type: 'string' })
|
||||
hruid!: string;
|
||||
|
@ -20,7 +20,7 @@ export class LearningObject {
|
|||
@PrimaryKey({ type: 'number' })
|
||||
version: number = 1;
|
||||
|
||||
@Property({type: 'uuid', unique: true})
|
||||
@Property({ type: 'uuid', unique: true })
|
||||
uuid = v4();
|
||||
|
||||
@ManyToMany({
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import {Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property} from "@mikro-orm/core";
|
||||
import {Language} from "./language";
|
||||
import {LearningPath} from "./learning-path.entity";
|
||||
import {LearningPathTransition} from "./learning-path-transition.entity";
|
||||
import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { Language } from './language';
|
||||
import { LearningPath } from './learning-path.entity';
|
||||
import { LearningPathTransition } from './learning-path-transition.entity';
|
||||
|
||||
@Entity()
|
||||
export class LearningPathNode {
|
||||
|
||||
@ManyToOne({ entity: () => LearningPath, primary: true })
|
||||
learningPath!: LearningPath;
|
||||
|
||||
@PrimaryKey({ type: "integer", autoincrement: true })
|
||||
@PrimaryKey({ type: 'integer', autoincrement: true })
|
||||
nodeNumber!: number;
|
||||
|
||||
@Property({ type: 'string' })
|
||||
|
@ -27,7 +26,7 @@ export class LearningPathNode {
|
|||
@Property({ type: 'bool' })
|
||||
startNode!: boolean;
|
||||
|
||||
@OneToMany({ entity: () => LearningPathTransition, mappedBy: "node" })
|
||||
@OneToMany({ entity: () => LearningPathTransition, mappedBy: 'node' })
|
||||
transitions: LearningPathTransition[] = [];
|
||||
|
||||
@Property({ length: 3 })
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {Entity, ManyToOne, PrimaryKey, Property} from "@mikro-orm/core";
|
||||
import {LearningPathNode} from "./learning-path-node.entity";
|
||||
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { LearningPathNode } from './learning-path-node.entity';
|
||||
|
||||
@Entity()
|
||||
export class LearningPathTransition {
|
||||
@ManyToOne({entity: () => LearningPathNode, primary: true })
|
||||
@ManyToOne({ entity: () => LearningPathNode, primary: true })
|
||||
node!: LearningPathNode;
|
||||
|
||||
@PrimaryKey({ type: 'numeric' })
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
import {
|
||||
Entity,
|
||||
Enum,
|
||||
ManyToMany, OneToMany,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from '@mikro-orm/core';
|
||||
import { Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { Language } from './language.js';
|
||||
import { Teacher } from '../users/teacher.entity.js';
|
||||
import {LearningPathRepository} from "../../data/content/learning-path-repository";
|
||||
import {LearningPathNode} from "./learning-path-node.entity";
|
||||
import { LearningPathRepository } from '../../data/content/learning-path-repository';
|
||||
import { LearningPathNode } from './learning-path-node.entity';
|
||||
|
||||
@Entity({repository: () => LearningPathRepository})
|
||||
@Entity({ repository: () => LearningPathRepository })
|
||||
export class LearningPath {
|
||||
@PrimaryKey({ type: 'string' })
|
||||
hruid!: string;
|
||||
|
@ -30,6 +24,6 @@ export class LearningPath {
|
|||
@Property({ type: 'blob', nullable: true })
|
||||
image: Buffer | null = null;
|
||||
|
||||
@OneToMany({ entity: () => LearningPathNode, mappedBy: "learningPath" })
|
||||
@OneToMany({ entity: () => LearningPathNode, mappedBy: 'learningPath' })
|
||||
nodes: LearningPathNode[] = [];
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { Question } from './question.entity.js';
|
||||
import { Teacher } from '../users/teacher.entity.js';
|
||||
import {AnswerRepository} from "../../data/questions/answer-repository";
|
||||
import { AnswerRepository } from '../../data/questions/answer-repository';
|
||||
|
||||
@Entity({repository: () => AnswerRepository})
|
||||
@Entity({ repository: () => AnswerRepository })
|
||||
export class Answer {
|
||||
@ManyToOne({
|
||||
entity: () => Teacher,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { Language } from '../content/language.js';
|
||||
import { Student } from '../users/student.entity.js';
|
||||
import {QuestionRepository} from "../../data/questions/question-repository";
|
||||
import { QuestionRepository } from '../../data/questions/question-repository';
|
||||
|
||||
@Entity({repository: () => QuestionRepository})
|
||||
@Entity({ repository: () => QuestionRepository })
|
||||
export class Question {
|
||||
@PrimaryKey({ type: 'string' })
|
||||
learningObjectHruid!: string;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Collection, Entity, ManyToMany } from '@mikro-orm/core';
|
||||
import { User } from './user.entity.js';
|
||||
import { Class } from '../classes/class.entity.js';
|
||||
import {TeacherRepository} from "../../data/users/teacher-repository";
|
||||
import { TeacherRepository } from '../../data/users/teacher-repository';
|
||||
|
||||
@Entity({repository: () => TeacherRepository})
|
||||
@Entity({ repository: () => TeacherRepository })
|
||||
export class Teacher extends User {
|
||||
@ManyToMany(() => Class)
|
||||
classes!: Collection<Class>;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Language} from "../entities/content/language";
|
||||
import { Language } from '../entities/content/language';
|
||||
|
||||
export interface Transition {
|
||||
default: boolean;
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import express from 'express';
|
||||
import {
|
||||
getAllLearningObjects, getAttachment,
|
||||
getLearningObject, getLearningObjectHTML,
|
||||
} from '../controllers/learning-objects.js';
|
||||
import { getAllLearningObjects, getAttachment, getLearningObject, getLearningObjectHTML } from '../controllers/learning-objects.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
import {getAttachmentRepository} from "../../data/repositories";
|
||||
import {Attachment} from "../../entities/content/attachment.entity";
|
||||
import {LearningObjectIdentifier} from "../../interfaces/learning-content";
|
||||
import { getAttachmentRepository } from '../../data/repositories';
|
||||
import { Attachment } from '../../entities/content/attachment.entity';
|
||||
import { LearningObjectIdentifier } from '../../interfaces/learning-content';
|
||||
|
||||
const attachmentService = {
|
||||
getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> {
|
||||
const attachmentRepo = getAttachmentRepository();
|
||||
|
||||
if (learningObjectId.version) {
|
||||
return attachmentRepo.findByLearningObjectIdAndName({
|
||||
hruid: learningObjectId.hruid,
|
||||
language: learningObjectId.language,
|
||||
version: learningObjectId.version,
|
||||
}, attachmentName);
|
||||
}
|
||||
return attachmentRepo.findByMostRecentVersionOfLearningObjectAndName(learningObjectId.hruid, learningObjectId.language, attachmentName);
|
||||
|
||||
}
|
||||
}
|
||||
return attachmentRepo.findByLearningObjectIdAndName(
|
||||
{
|
||||
hruid: learningObjectId.hruid,
|
||||
language: learningObjectId.language,
|
||||
version: learningObjectId.version,
|
||||
},
|
||||
attachmentName
|
||||
);
|
||||
}
|
||||
return attachmentRepo.findByMostRecentVersionOfLearningObjectAndName(learningObjectId.hruid, learningObjectId.language, attachmentName);
|
||||
},
|
||||
};
|
||||
|
||||
export default attachmentService;
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
import {LearningObjectProvider} from "./learning-object-provider";
|
||||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectIdentifier,
|
||||
LearningPathIdentifier
|
||||
} from "../../interfaces/learning-content";
|
||||
import {getLearningObjectRepository, getLearningPathRepository} from "../../data/repositories";
|
||||
import {Language} from "../../entities/content/language";
|
||||
import {LearningObject} from "../../entities/content/learning-object.entity";
|
||||
import {getUrlStringForLearningObject} from "../../util/links";
|
||||
import processingService from "./processing/processing-service";
|
||||
import {NotFoundError} from "@mikro-orm/core";
|
||||
|
||||
import { LearningObjectProvider } from './learning-object-provider';
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content';
|
||||
import { getLearningObjectRepository, getLearningPathRepository } from '../../data/repositories';
|
||||
import { Language } from '../../entities/content/language';
|
||||
import { LearningObject } from '../../entities/content/learning-object.entity';
|
||||
import { getUrlStringForLearningObject } from '../../util/links';
|
||||
import processingService from './processing/processing-service';
|
||||
import { NotFoundError } from '@mikro-orm/core';
|
||||
|
||||
function convertLearningObject(learningObject: LearningObject | null): FilteredLearningObject | null {
|
||||
if (!learningObject) {
|
||||
|
@ -34,20 +29,18 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL
|
|||
educationalGoals: learningObject.educationalGoals,
|
||||
returnValue: {
|
||||
callback_url: learningObject.returnValue.callbackUrl,
|
||||
callback_schema: JSON.parse(learningObject.returnValue.callbackSchema)
|
||||
callback_schema: JSON.parse(learningObject.returnValue.callbackSchema),
|
||||
},
|
||||
skosConcepts: learningObject.skosConcepts,
|
||||
targetAges: learningObject.targetAges || [],
|
||||
teacherExclusive: learningObject.teacherExclusive
|
||||
}
|
||||
teacherExclusive: learningObject.teacherExclusive,
|
||||
};
|
||||
}
|
||||
|
||||
function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise<LearningObject | null> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
|
||||
return learningObjectRepo.findLatestByHruidAndLanguage(
|
||||
id.hruid, id.language as Language
|
||||
);
|
||||
return learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,16 +61,11 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
|
||||
const learningObject = await learningObjectRepo.findLatestByHruidAndLanguage(
|
||||
id.hruid, id.language as Language
|
||||
);
|
||||
const learningObject = await learningObjectRepo.findLatestByHruidAndLanguage(id.hruid, id.language as Language);
|
||||
if (!learningObject) {
|
||||
return null;
|
||||
}
|
||||
return await processingService.render(
|
||||
learningObject,
|
||||
(id) => findLearningObjectEntityById(id)
|
||||
);
|
||||
return await processingService.render(learningObject, (id) => findLearningObjectEntityById(id));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -88,9 +76,9 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
|
||||
const learningPath = await learningPathRepo.findByHruidAndLanguage(id.hruid, id.language);
|
||||
if (!learningPath) {
|
||||
throw new NotFoundError("The learning path with the given ID could not be found.");
|
||||
throw new NotFoundError('The learning path with the given ID could not be found.');
|
||||
}
|
||||
return learningPath.nodes.map(it => it.learningObjectHruid); // TODO: Determine this based on the submissions of the user.
|
||||
return learningPath.nodes.map((it) => it.learningObjectHruid); // TODO: Determine this based on the submissions of the user.
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -101,23 +89,23 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
|
||||
const learningPath = await learningPathRepo.findByHruidAndLanguage(id.hruid, id.language);
|
||||
if (!learningPath) {
|
||||
throw new NotFoundError("The learning path with the given ID could not be found.");
|
||||
throw new NotFoundError('The learning path with the given ID could not be found.');
|
||||
}
|
||||
const learningObjects = await Promise.all(
|
||||
learningPath.nodes.map(it => {
|
||||
learningPath.nodes.map((it) => {
|
||||
const learningObject = this.getLearningObjectById({
|
||||
hruid: it.learningObjectHruid,
|
||||
language: it.language,
|
||||
version: it.version
|
||||
})
|
||||
version: it.version,
|
||||
});
|
||||
if (learningObject === null) {
|
||||
console.log(`WARN: Learning object corresponding with node ${it} not found!`);
|
||||
}
|
||||
return learningObject;
|
||||
})
|
||||
);
|
||||
return learningObjects.filter(it => it !== null); // TODO: Determine this based on the submissions of the user.
|
||||
}
|
||||
}
|
||||
return learningObjects.filter((it) => it !== null); // TODO: Determine this based on the submissions of the user.
|
||||
},
|
||||
};
|
||||
|
||||
export default databaseLearningObjectProvider;
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import { DWENGO_API_BASE } from '../../config.js';
|
||||
import { fetchWithLogging } from '../../util/apiHelper.js';
|
||||
import {
|
||||
FilteredLearningObject, LearningObjectIdentifier,
|
||||
FilteredLearningObject,
|
||||
LearningObjectIdentifier,
|
||||
LearningObjectMetadata,
|
||||
LearningObjectNode, LearningPathIdentifier,
|
||||
LearningObjectNode,
|
||||
LearningPathIdentifier,
|
||||
LearningPathResponse,
|
||||
} from '../../interfaces/learning-content.js';
|
||||
import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js';
|
||||
import {LearningObjectProvider} from "./learning-object-provider";
|
||||
import { LearningObjectProvider } from './learning-object-provider';
|
||||
|
||||
/**
|
||||
* Helper function to convert the learning object metadata retrieved from the API to a FilteredLearningObject which
|
||||
* our API should return.
|
||||
* @param data
|
||||
*/
|
||||
function filterData(
|
||||
data: LearningObjectMetadata
|
||||
): FilteredLearningObject {
|
||||
function filterData(data: LearningObjectMetadata): FilteredLearningObject {
|
||||
return {
|
||||
key: data.hruid, // Hruid learningObject (not path)
|
||||
_id: data._id,
|
||||
|
@ -43,25 +43,16 @@ function filterData(
|
|||
/**
|
||||
* Generic helper function to fetch all learning objects from a given path (full data or just HRUIDs)
|
||||
*/
|
||||
async function fetchLearningObjects(
|
||||
learningPathId: LearningPathIdentifier,
|
||||
full: boolean
|
||||
): Promise<FilteredLearningObject[] | string[]> {
|
||||
async function fetchLearningObjects(learningPathId: LearningPathIdentifier, full: boolean): Promise<FilteredLearningObject[] | string[]> {
|
||||
try {
|
||||
const learningPathResponse: LearningPathResponse =
|
||||
await dwengoApiLearningPathProvider.fetchLearningPaths(
|
||||
[learningPathId.hruid],
|
||||
learningPathId.language,
|
||||
`Learning path for HRUID "${learningPathId.hruid}"`
|
||||
);
|
||||
const learningPathResponse: LearningPathResponse = await dwengoApiLearningPathProvider.fetchLearningPaths(
|
||||
[learningPathId.hruid],
|
||||
learningPathId.language,
|
||||
`Learning path for HRUID "${learningPathId.hruid}"`
|
||||
);
|
||||
|
||||
if (
|
||||
!learningPathResponse.success ||
|
||||
!learningPathResponse.data?.length
|
||||
) {
|
||||
console.error(
|
||||
`⚠️ WARNING: Learning path "${learningPathId.hruid}" exists but contains no learning objects.`
|
||||
);
|
||||
if (!learningPathResponse.success || !learningPathResponse.data?.length) {
|
||||
console.error(`⚠️ WARNING: Learning path "${learningPathId.hruid}" exists but contains no learning objects.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -72,10 +63,12 @@ async function fetchLearningObjects(
|
|||
}
|
||||
|
||||
return await Promise.all(
|
||||
nodes.map(async (node) => dwengoApiLearningObjectProvider.getLearningObjectById({
|
||||
nodes.map(async (node) =>
|
||||
dwengoApiLearningObjectProvider.getLearningObjectById({
|
||||
hruid: node.learningobject_hruid,
|
||||
language: learningPathId.language
|
||||
}))
|
||||
language: learningPathId.language,
|
||||
})
|
||||
)
|
||||
).then((objects) => objects.filter((obj): obj is FilteredLearningObject => obj !== null));
|
||||
} catch (error) {
|
||||
console.error('❌ Error fetching learning objects:', error);
|
||||
|
@ -87,19 +80,17 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
/**
|
||||
* Fetches a single learning object by its HRUID
|
||||
*/
|
||||
async getLearningObjectById(
|
||||
id: LearningObjectIdentifier
|
||||
): Promise<FilteredLearningObject | null> {
|
||||
async getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> {
|
||||
const metadataUrl = `${DWENGO_API_BASE}/learningObject/getMetadata`;
|
||||
const metadata = await fetchWithLogging<LearningObjectMetadata>(
|
||||
metadataUrl,
|
||||
`Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`,
|
||||
{
|
||||
params: id
|
||||
params: id,
|
||||
}
|
||||
);
|
||||
|
||||
if (!metadata || typeof metadata !== "object") {
|
||||
if (!metadata || typeof metadata !== 'object') {
|
||||
console.error(`⚠️ WARNING: Learning object "${id.hruid}" not found.`);
|
||||
return null;
|
||||
}
|
||||
|
@ -111,10 +102,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
* Fetch full learning object data (metadata)
|
||||
*/
|
||||
async getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> {
|
||||
return (await fetchLearningObjects(
|
||||
id,
|
||||
true,
|
||||
)) as FilteredLearningObject[];
|
||||
return (await fetchLearningObjects(id, true)) as FilteredLearningObject[];
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -130,13 +118,9 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
*/
|
||||
async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw`;
|
||||
const html = await fetchWithLogging<string>(
|
||||
htmlUrl,
|
||||
`Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`,
|
||||
{
|
||||
params: id
|
||||
}
|
||||
);
|
||||
const html = await fetchWithLogging<string>(htmlUrl, `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, {
|
||||
params: id,
|
||||
});
|
||||
|
||||
if (!html) {
|
||||
console.error(`⚠️ WARNING: Learning object "${id.hruid}" not found.`);
|
||||
|
@ -144,7 +128,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default dwengoApiLearningObjectProvider;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectIdentifier,
|
||||
LearningPathIdentifier
|
||||
} from "../../interfaces/learning-content";
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content';
|
||||
|
||||
export interface LearningObjectProvider {
|
||||
/**
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectIdentifier,
|
||||
LearningPathIdentifier
|
||||
} from "../../interfaces/learning-content";
|
||||
import dwengoApiLearningObjectProvider from "./dwengo-api-learning-object-provider";
|
||||
import {LearningObjectProvider} from "./learning-object-provider";
|
||||
import {EnvVars, getEnvVar} from "../../util/envvars";
|
||||
import databaseLearningObjectProvider from "./database-learning-object-provider";
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content';
|
||||
import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provider';
|
||||
import { LearningObjectProvider } from './learning-object-provider';
|
||||
import { EnvVars, getEnvVar } from '../../util/envvars';
|
||||
import databaseLearningObjectProvider from './database-learning-object-provider';
|
||||
|
||||
function getProvider(id: LearningObjectIdentifier): LearningObjectProvider {
|
||||
if (id.hruid.startsWith(getEnvVar(EnvVars.UserContentPrefix))) {
|
||||
return databaseLearningObjectProvider;
|
||||
}
|
||||
return dwengoApiLearningObjectProvider;
|
||||
|
||||
}
|
||||
return dwengoApiLearningObjectProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +41,7 @@ const learningObjectService = {
|
|||
*/
|
||||
getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
return getProvider(id).getLearningObjectHTML(id);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default learningObjectService;
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import {type} from "node:os";
|
||||
import {DwengoContentType} from "../content-type";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import { type } from 'node:os';
|
||||
import { DwengoContentType } from '../content-type';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
|
||||
class AudioProcessor extends StringProcessor {
|
||||
|
||||
constructor() {
|
||||
super(DwengoContentType.AUDIO_MPEG);
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
*/
|
||||
|
||||
enum DwengoContentType {
|
||||
TEXT_PLAIN = "text/plain",
|
||||
TEXT_MARKDOWN = "text/markdown",
|
||||
IMAGE_BLOCK = "image/image-block",
|
||||
IMAGE_INLINE = "image/image",
|
||||
AUDIO_MPEG = "audio/mpeg",
|
||||
APPLICATION_PDF = "application/pdf",
|
||||
EXTERN = "extern",
|
||||
BLOCKLY = "blockly",
|
||||
GIFT = "text/gift",
|
||||
CT_SCHEMA = "text/ct-schema"
|
||||
TEXT_PLAIN = 'text/plain',
|
||||
TEXT_MARKDOWN = 'text/markdown',
|
||||
IMAGE_BLOCK = 'image/image-block',
|
||||
IMAGE_INLINE = 'image/image',
|
||||
AUDIO_MPEG = 'audio/mpeg',
|
||||
APPLICATION_PDF = 'application/pdf',
|
||||
EXTERN = 'extern',
|
||||
BLOCKLY = 'blockly',
|
||||
GIFT = 'text/gift',
|
||||
CT_SCHEMA = 'text/ct-schema',
|
||||
}
|
||||
|
||||
export { DwengoContentType }
|
||||
export { DwengoContentType };
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import {ProcessingError} from "../processing-error";
|
||||
import {isValidHttpUrl} from "../../../../util/links";
|
||||
import {DwengoContentType} from "../content-type";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import { ProcessingError } from '../processing-error';
|
||||
import { isValidHttpUrl } from '../../../../util/links';
|
||||
import { DwengoContentType } from '../content-type';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
|
||||
class ExternProcessor extends StringProcessor {
|
||||
constructor() {
|
||||
|
@ -17,23 +17,23 @@ class ExternProcessor extends StringProcessor {
|
|||
|
||||
override renderFn(externURL: string) {
|
||||
if (!isValidHttpUrl(externURL)) {
|
||||
throw new ProcessingError("The url is not valid: " + externURL);
|
||||
throw new ProcessingError('The url is not valid: ' + externURL);
|
||||
}
|
||||
|
||||
// If a seperate youtube-processor would be added, this code would need to move to that processor
|
||||
// Converts youtube urls to youtube-embed urls
|
||||
const match = /(.*youtube.com\/)watch\?v=(.*)/.exec(externURL)
|
||||
const match = /(.*youtube.com\/)watch\?v=(.*)/.exec(externURL);
|
||||
if (match) {
|
||||
externURL = match[1] + "embed/" + match[2];
|
||||
externURL = match[1] + 'embed/' + match[2];
|
||||
}
|
||||
|
||||
return DOMPurify.sanitize(`
|
||||
return DOMPurify.sanitize(
|
||||
`
|
||||
<div class="iframe-container">
|
||||
<iframe src="${externURL}" allowfullscreen></iframe>
|
||||
</div>`,
|
||||
{ ADD_TAGS: ["iframe"], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling']}
|
||||
{ ADD_TAGS: ['iframe'], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'] }
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,21 +3,20 @@
|
|||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import {GIFTQuestion, parse} from "gift-pegjs"
|
||||
import {DwengoContentType} from "../content-type";
|
||||
import {GIFTQuestionRenderer} from "./question-renderers/gift-question-renderer";
|
||||
import {MultipleChoiceQuestionRenderer} from "./question-renderers/multiple-choice-question-renderer";
|
||||
import {CategoryQuestionRenderer} from "./question-renderers/category-question-renderer";
|
||||
import {DescriptionQuestionRenderer} from "./question-renderers/description-question-renderer";
|
||||
import {EssayQuestionRenderer} from "./question-renderers/essay-question-renderer";
|
||||
import {MatchingQuestionRenderer} from "./question-renderers/matching-question-renderer";
|
||||
import {NumericalQuestionRenderer} from "./question-renderers/numerical-question-renderer";
|
||||
import {ShortQuestionRenderer} from "./question-renderers/short-question-renderer";
|
||||
import {TrueFalseQuestionRenderer} from "./question-renderers/true-false-question-renderer";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import { GIFTQuestion, parse } from 'gift-pegjs';
|
||||
import { DwengoContentType } from '../content-type';
|
||||
import { GIFTQuestionRenderer } from './question-renderers/gift-question-renderer';
|
||||
import { MultipleChoiceQuestionRenderer } from './question-renderers/multiple-choice-question-renderer';
|
||||
import { CategoryQuestionRenderer } from './question-renderers/category-question-renderer';
|
||||
import { DescriptionQuestionRenderer } from './question-renderers/description-question-renderer';
|
||||
import { EssayQuestionRenderer } from './question-renderers/essay-question-renderer';
|
||||
import { MatchingQuestionRenderer } from './question-renderers/matching-question-renderer';
|
||||
import { NumericalQuestionRenderer } from './question-renderers/numerical-question-renderer';
|
||||
import { ShortQuestionRenderer } from './question-renderers/short-question-renderer';
|
||||
import { TrueFalseQuestionRenderer } from './question-renderers/true-false-question-renderer';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
|
||||
class GiftProcessor extends StringProcessor {
|
||||
|
||||
private renderers: RendererMap = {
|
||||
Category: new CategoryQuestionRenderer(),
|
||||
Description: new DescriptionQuestionRenderer(),
|
||||
|
@ -26,8 +25,8 @@ class GiftProcessor extends StringProcessor {
|
|||
Numerical: new NumericalQuestionRenderer(),
|
||||
Short: new ShortQuestionRenderer(),
|
||||
TF: new TrueFalseQuestionRenderer(),
|
||||
MC: new MultipleChoiceQuestionRenderer()
|
||||
}
|
||||
MC: new MultipleChoiceQuestionRenderer(),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super(DwengoContentType.GIFT);
|
||||
|
@ -40,11 +39,11 @@ class GiftProcessor extends StringProcessor {
|
|||
let i = 1;
|
||||
for (const question of quizQuestions) {
|
||||
html += ` <div class='gift-question' id='gift-q${i}'>\n`;
|
||||
html += " " + this.renderQuestion(question, i).replaceAll(/\n(.+)/g, "\n $1"); // Replace for indentation.
|
||||
html += ' ' + this.renderQuestion(question, i).replaceAll(/\n(.+)/g, '\n $1'); // Replace for indentation.
|
||||
html += ` </div>\n`;
|
||||
i++;
|
||||
}
|
||||
html += "</div>\n"
|
||||
html += '</div>\n';
|
||||
|
||||
return DOMPurify.sanitize(html);
|
||||
}
|
||||
|
@ -56,7 +55,7 @@ class GiftProcessor extends StringProcessor {
|
|||
}
|
||||
|
||||
type RendererMap = {
|
||||
[K in GIFTQuestion["type"]]: GIFTQuestionRenderer<Extract<GIFTQuestion, { type: K }>>
|
||||
[K in GIFTQuestion['type']]: GIFTQuestionRenderer<Extract<GIFTQuestion, { type: K }>>;
|
||||
};
|
||||
|
||||
export default GiftProcessor;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {Category} from "gift-pegjs";
|
||||
import {ProcessingError} from "../../processing-error";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { Category } from 'gift-pegjs';
|
||||
import { ProcessingError } from '../../processing-error';
|
||||
|
||||
export class CategoryQuestionRenderer extends GIFTQuestionRenderer<Category> {
|
||||
render(question: Category, questionNumber: number): string {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {Description} from "gift-pegjs";
|
||||
import {ProcessingError} from "../../processing-error";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { Description } from 'gift-pegjs';
|
||||
import { ProcessingError } from '../../processing-error';
|
||||
|
||||
export class DescriptionQuestionRenderer extends GIFTQuestionRenderer<Description> {
|
||||
render(question: Description, questionNumber: number): string {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {Essay} from "gift-pegjs";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { Essay } from 'gift-pegjs';
|
||||
|
||||
export class EssayQuestionRenderer extends GIFTQuestionRenderer<Essay> {
|
||||
render(question: Essay, questionNumber: number): string {
|
||||
let renderedHtml = "";
|
||||
let renderedHtml = '';
|
||||
if (question.title) {
|
||||
renderedHtml += `<h2 class='gift-title' id='gift-q${questionNumber}-title'>${question.title}</h2>\n`;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {GIFTQuestion} from "gift-pegjs";
|
||||
import { GIFTQuestion } from 'gift-pegjs';
|
||||
|
||||
/**
|
||||
* Subclasses of this class are renderers which can render a specific type of GIFT questions to HTML.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {Matching} from "gift-pegjs";
|
||||
import {ProcessingError} from "../../processing-error";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { Matching } from 'gift-pegjs';
|
||||
import { ProcessingError } from '../../processing-error';
|
||||
|
||||
export class MatchingQuestionRenderer extends GIFTQuestionRenderer<Matching> {
|
||||
render(question: Matching, questionNumber: number): string {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {MultipleChoice} from "gift-pegjs";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { MultipleChoice } from 'gift-pegjs';
|
||||
|
||||
export class MultipleChoiceQuestionRenderer extends GIFTQuestionRenderer<MultipleChoice> {
|
||||
render(question: MultipleChoice, questionNumber: number): string {
|
||||
let renderedHtml = "";
|
||||
let renderedHtml = '';
|
||||
if (question.title) {
|
||||
renderedHtml += `<h2 class='gift-title' id='gift-q${questionNumber}-title'>${question.title}</h2>\n`;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {Numerical} from "gift-pegjs";
|
||||
import {ProcessingError} from "../../processing-error";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { Numerical } from 'gift-pegjs';
|
||||
import { ProcessingError } from '../../processing-error';
|
||||
|
||||
export class NumericalQuestionRenderer extends GIFTQuestionRenderer<Numerical> {
|
||||
render(question: Numerical, questionNumber: number): string {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {ShortAnswer} from "gift-pegjs";
|
||||
import {ProcessingError} from "../../processing-error";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { ShortAnswer } from 'gift-pegjs';
|
||||
import { ProcessingError } from '../../processing-error';
|
||||
|
||||
export class ShortQuestionRenderer extends GIFTQuestionRenderer<ShortAnswer> {
|
||||
render(question: ShortAnswer, questionNumber: number): string {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GIFTQuestionRenderer} from "./gift-question-renderer";
|
||||
import {TrueFalse} from "gift-pegjs";
|
||||
import {ProcessingError} from "../../processing-error";
|
||||
import { GIFTQuestionRenderer } from './gift-question-renderer';
|
||||
import { TrueFalse } from 'gift-pegjs';
|
||||
import { ProcessingError } from '../../processing-error';
|
||||
|
||||
export class TrueFalseQuestionRenderer extends GIFTQuestionRenderer<TrueFalse> {
|
||||
render(question: TrueFalse, questionNumber: number): string {
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
* Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/image/block_image_processor.js
|
||||
*/
|
||||
|
||||
import InlineImageProcessor from "./inline-image-processor.js"
|
||||
import InlineImageProcessor from './inline-image-processor.js';
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
|
||||
class BlockImageProcessor extends InlineImageProcessor {
|
||||
constructor(){
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override renderFn(imageUrl: string){
|
||||
override renderFn(imageUrl: string) {
|
||||
const inlineHtml = super.render(imageUrl);
|
||||
return DOMPurify.sanitize(`<div>${inlineHtml}</div>`);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import {DwengoContentType} from "../content-type.js";
|
||||
import {ProcessingError} from "../processing-error.js";
|
||||
import {isValidHttpUrl} from "../../../../util/links";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import { DwengoContentType } from '../content-type.js';
|
||||
import { ProcessingError } from '../processing-error.js';
|
||||
import { isValidHttpUrl } from '../../../../util/links';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
|
||||
class InlineImageProcessor extends StringProcessor {
|
||||
constructor(contentType: DwengoContentType = DwengoContentType.IMAGE_INLINE) {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
/**
|
||||
* Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/markdown/learing_object_markdown_renderer.js [sic!]
|
||||
*/
|
||||
import PdfProcessor from "../pdf/pdf-processor.js";
|
||||
import AudioProcessor from "../audio/audio-processor.js";
|
||||
import ExternProcessor from "../extern/extern-processor.js";
|
||||
import InlineImageProcessor from "../image/inline-image-processor.js";
|
||||
import * as marked from "marked";
|
||||
import {getUrlStringForLearningObjectHTML, isValidHttpUrl} from "../../../../util/links";
|
||||
import {ProcessingError} from "../processing-error";
|
||||
import {LearningObjectIdentifier} from "../../../../interfaces/learning-content";
|
||||
import {Language} from "../../../../entities/content/language";
|
||||
import PdfProcessor from '../pdf/pdf-processor.js';
|
||||
import AudioProcessor from '../audio/audio-processor.js';
|
||||
import ExternProcessor from '../extern/extern-processor.js';
|
||||
import InlineImageProcessor from '../image/inline-image-processor.js';
|
||||
import * as marked from 'marked';
|
||||
import { getUrlStringForLearningObjectHTML, isValidHttpUrl } from '../../../../util/links';
|
||||
import { ProcessingError } from '../processing-error';
|
||||
import { LearningObjectIdentifier } from '../../../../interfaces/learning-content';
|
||||
import { Language } from '../../../../entities/content/language';
|
||||
|
||||
import Image = marked.Tokens.Image;
|
||||
import Heading = marked.Tokens.Heading;
|
||||
|
@ -27,11 +27,11 @@ const prefixes = {
|
|||
};
|
||||
|
||||
function extractLearningObjectIdFromHref(href: string): LearningObjectIdentifier {
|
||||
const [hruid, language, version] = href.split(/\/(.+)/, 2)[1].split("/");
|
||||
const [hruid, language, version] = href.split(/\/(.+)/, 2)[1].split('/');
|
||||
return {
|
||||
hruid,
|
||||
language: language as Language,
|
||||
version: parseInt(version)
|
||||
version: parseInt(version),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -41,39 +41,40 @@ function extractLearningObjectIdFromHref(href: string): LearningObjectIdentifier
|
|||
* - links to other learning objects,
|
||||
* - embeddings of other learning objects.
|
||||
*/
|
||||
const dwengoMarkedRenderer: RendererObject = {
|
||||
const dwengoMarkedRenderer: RendererObject = {
|
||||
heading(heading: Heading): string {
|
||||
const text = heading.text;
|
||||
const level = heading.depth;
|
||||
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
|
||||
|
||||
return `<h${level}>\n` +
|
||||
` <a name="${escapedText}" class="anchor" href="#${escapedText}">\n` +
|
||||
` <span class="header-link"></span>\n` +
|
||||
` </a>\n` +
|
||||
` ${text}\n` +
|
||||
`</h${level}>\n`
|
||||
return (
|
||||
`<h${level}>\n` +
|
||||
` <a name="${escapedText}" class="anchor" href="#${escapedText}">\n` +
|
||||
` <span class="header-link"></span>\n` +
|
||||
` </a>\n` +
|
||||
` ${text}\n` +
|
||||
`</h${level}>\n`
|
||||
);
|
||||
},
|
||||
|
||||
// When the syntax for a link is used => [text](href "title")
|
||||
// Render a custom link when the prefix for a learning object is used.
|
||||
link(link: Link): string {
|
||||
const href = link.href;
|
||||
const title = link.title || "";
|
||||
const title = link.title || '';
|
||||
const text = marked.parseInline(link.text); // There could for example be an image in the link.
|
||||
|
||||
if (href.startsWith(prefixes.learningObject)) {
|
||||
// Link to learning-object
|
||||
const learningObjectId = extractLearningObjectIdFromHref(href);
|
||||
return `<a href="${getUrlStringForLearningObjectHTML(learningObjectId)}" target="_blank" title="${title}">${text}</a>`;
|
||||
}
|
||||
// Any other link
|
||||
if (!isValidHttpUrl(href)) {
|
||||
throw new ProcessingError("Link is not a valid HTTP URL!");
|
||||
}
|
||||
//<a href="https://kiks.ilabt.imec.be/hub/tmplogin?id=0101" title="Notebooks Werking"><img src="Knop.png" alt="" title="Knop"></a>
|
||||
return `<a href="${href}" target="_blank" title="${title}">${text}</a>`;
|
||||
|
||||
}
|
||||
// Any other link
|
||||
if (!isValidHttpUrl(href)) {
|
||||
throw new ProcessingError('Link is not a valid HTTP URL!');
|
||||
}
|
||||
//<a href="https://kiks.ilabt.imec.be/hub/tmplogin?id=0101" title="Notebooks Werking"><img src="Knop.png" alt="" title="Knop"></a>
|
||||
return `<a href="${href}" target="_blank" title="${title}">${text}</a>`;
|
||||
},
|
||||
|
||||
// When the syntax for an image is used => 
|
||||
|
@ -98,12 +99,11 @@ function extractLearningObjectIdFromHref(href: string): LearningObjectIdentifier
|
|||
// Embedded youtube video or notebook (or other extern content)
|
||||
const proc = new ExternProcessor();
|
||||
return proc.render(href.split(/\/(.+)/, 2)[1]);
|
||||
}
|
||||
// Embedded image
|
||||
const proc = new InlineImageProcessor();
|
||||
return proc.render(href)
|
||||
|
||||
}
|
||||
// Embedded image
|
||||
const proc = new InlineImageProcessor();
|
||||
return proc.render(href);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default dwengoMarkedRenderer;
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
* Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/markdown/markdown_processor.js
|
||||
*/
|
||||
|
||||
import {marked} from 'marked';
|
||||
import { marked } from 'marked';
|
||||
import InlineImageProcessor from '../image/inline-image-processor.js';
|
||||
import {DwengoContentType} from "../content-type";
|
||||
import dwengoMarkedRenderer from "./dwengo-marked-renderer";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import {ProcessingError} from "../processing-error";
|
||||
import { DwengoContentType } from '../content-type';
|
||||
import dwengoMarkedRenderer from './dwengo-marked-renderer';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
import { ProcessingError } from '../processing-error';
|
||||
|
||||
class MarkdownProcessor extends StringProcessor {
|
||||
constructor() {
|
||||
|
@ -15,10 +15,10 @@ class MarkdownProcessor extends StringProcessor {
|
|||
}
|
||||
|
||||
override renderFn(mdText: string) {
|
||||
let html = "";
|
||||
let html = '';
|
||||
try {
|
||||
marked.use({renderer: dwengoMarkedRenderer});
|
||||
html = marked(mdText, {async: false});
|
||||
marked.use({ renderer: dwengoMarkedRenderer });
|
||||
html = marked(mdText, { async: false });
|
||||
html = this.replaceLinks(html); // Replace html image links path
|
||||
} catch (e: any) {
|
||||
throw new ProcessingError(e.message);
|
||||
|
@ -28,14 +28,10 @@ class MarkdownProcessor extends StringProcessor {
|
|||
|
||||
replaceLinks(html: string) {
|
||||
const proc = new InlineImageProcessor();
|
||||
html = html.replace(/<img.*?src="(.*?)".*?(alt="(.*?)")?.*?(title="(.*?)")?.*?>/g, (
|
||||
match: string,
|
||||
src: string,
|
||||
alt: string,
|
||||
altText: string,
|
||||
title: string,
|
||||
titleText: string
|
||||
) => proc.render(src));
|
||||
html = html.replace(
|
||||
/<img.*?src="(.*?)".*?(alt="(.*?)")?.*?(title="(.*?)")?.*?>/g,
|
||||
(match: string, src: string, alt: string, altText: string, title: string, titleText: string) => proc.render(src)
|
||||
);
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import {DwengoContentType} from "../content-type.js";
|
||||
import {isValidHttpUrl} from "../../../../util/links.js";
|
||||
import {ProcessingError} from "../processing-error.js";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import { DwengoContentType } from '../content-type.js';
|
||||
import { isValidHttpUrl } from '../../../../util/links.js';
|
||||
import { ProcessingError } from '../processing-error.js';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
|
||||
class PdfProcessor extends StringProcessor {
|
||||
constructor() {
|
||||
|
@ -20,9 +20,11 @@ class PdfProcessor extends StringProcessor {
|
|||
throw new ProcessingError(`PDF URL is invalid: ${pdfUrl}`);
|
||||
}
|
||||
|
||||
return DOMPurify.sanitize(`
|
||||
return DOMPurify.sanitize(
|
||||
`
|
||||
<embed src="${pdfUrl}" type="application/pdf" width="100%" height="800px"/>
|
||||
`, { ADD_TAGS: ["embed"] }
|
||||
`,
|
||||
{ ADD_TAGS: ['embed'] }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
* Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/processing_proxy.js
|
||||
*/
|
||||
|
||||
import BlockImageProcessor from "./image/block-image-processor.js";
|
||||
import InlineImageProcessor from "./image/inline-image-processor.js";
|
||||
import { MarkdownProcessor } from "./markdown/markdown-processor.js";
|
||||
import TextProcessor from "./text/text-processor.js";
|
||||
import AudioProcessor from "./audio/audio-processor.js";
|
||||
import PdfProcessor from "./pdf/pdf-processor.js";
|
||||
import ExternProcessor from "./extern/extern-processor.js";
|
||||
import GiftProcessor from "./gift/gift-processor.js";
|
||||
import {LearningObject} from "../../../entities/content/learning-object.entity";
|
||||
import Processor from "./processor";
|
||||
import {DwengoContentType} from "./content-type";
|
||||
import {LearningObjectIdentifier} from "../../../interfaces/learning-content";
|
||||
import {Language} from "../../../entities/content/language";
|
||||
import {replaceAsync} from "../../../util/async";
|
||||
import BlockImageProcessor from './image/block-image-processor.js';
|
||||
import InlineImageProcessor from './image/inline-image-processor.js';
|
||||
import { MarkdownProcessor } from './markdown/markdown-processor.js';
|
||||
import TextProcessor from './text/text-processor.js';
|
||||
import AudioProcessor from './audio/audio-processor.js';
|
||||
import PdfProcessor from './pdf/pdf-processor.js';
|
||||
import ExternProcessor from './extern/extern-processor.js';
|
||||
import GiftProcessor from './gift/gift-processor.js';
|
||||
import { LearningObject } from '../../../entities/content/learning-object.entity';
|
||||
import Processor from './processor';
|
||||
import { DwengoContentType } from './content-type';
|
||||
import { LearningObjectIdentifier } from '../../../interfaces/learning-content';
|
||||
import { Language } from '../../../entities/content/language';
|
||||
import { replaceAsync } from '../../../util/async';
|
||||
|
||||
const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = /<learning-object hruid="([^"]+)" language="([^"]+)" version="([^"]+)"\/>/g;
|
||||
const LEARNING_OBJECT_DOES_NOT_EXIST = "<div class='non-existing-learning-object' />";
|
||||
|
@ -32,12 +32,10 @@ class ProcessingService {
|
|||
new AudioProcessor(),
|
||||
new PdfProcessor(),
|
||||
new ExternProcessor(),
|
||||
new GiftProcessor()
|
||||
new GiftProcessor(),
|
||||
];
|
||||
|
||||
this.processors = new Map(
|
||||
processors.map(processor => [processor.contentType, processor])
|
||||
)
|
||||
this.processors = new Map(processors.map((processor) => [processor.contentType, processor]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,7 +63,7 @@ class ProcessingService {
|
|||
const learningObject = await fetchEmbeddedLearningObjects({
|
||||
hruid,
|
||||
language: language as Language,
|
||||
version: parseInt(version)
|
||||
version: parseInt(version),
|
||||
});
|
||||
|
||||
// If it does not exist, replace it by a placeholder.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {LearningObject} from "../../../entities/content/learning-object.entity";
|
||||
import {ProcessingError} from "./processing-error";
|
||||
import {DwengoContentType} from "./content-type";
|
||||
import { LearningObject } from '../../../entities/content/learning-object.entity';
|
||||
import { ProcessingError } from './processing-error';
|
||||
import { DwengoContentType } from './content-type';
|
||||
|
||||
/**
|
||||
* Abstract base class for all processors.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Processor from "./processor";
|
||||
import {LearningObject} from "../../../entities/content/learning-object.entity";
|
||||
import Processor from './processor';
|
||||
import { LearningObject } from '../../../entities/content/learning-object.entity';
|
||||
|
||||
export abstract class StringProcessor extends Processor<string> {
|
||||
/**
|
||||
|
@ -14,6 +14,6 @@ export abstract class StringProcessor extends Processor<string> {
|
|||
* @protected
|
||||
*/
|
||||
protected renderLearningObjectFn(toRender: LearningObject): string {
|
||||
return this.render(toRender.content.toString("ascii"));
|
||||
return this.render(toRender.content.toString('ascii'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
*/
|
||||
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import {DwengoContentType} from "../content-type.js";
|
||||
import {StringProcessor} from "../string-processor";
|
||||
import { DwengoContentType } from '../content-type.js';
|
||||
import { StringProcessor } from '../string-processor';
|
||||
|
||||
class TextProcessor extends StringProcessor {
|
||||
constructor() {
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
import {LearningPathProvider} from "./learning-path-provider";
|
||||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectNode,
|
||||
LearningPath,
|
||||
LearningPathResponse,
|
||||
Transition
|
||||
} from "../../interfaces/learning-content";
|
||||
import {
|
||||
LearningPath as LearningPathEntity
|
||||
} from "../../entities/content/learning-path.entity"
|
||||
import {getLearningPathRepository} from "../../data/repositories";
|
||||
import {Language} from "../../entities/content/language";
|
||||
import learningObjectService from "../learning-objects/learning-object-service";
|
||||
import { LearningPathNode } from "../../entities/content/learning-path-node.entity";
|
||||
import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity";
|
||||
import { LearningPathProvider } from './learning-path-provider';
|
||||
import { FilteredLearningObject, LearningObjectNode, LearningPath, LearningPathResponse, Transition } from '../../interfaces/learning-content';
|
||||
import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity';
|
||||
import { getLearningPathRepository } from '../../data/repositories';
|
||||
import { Language } from '../../entities/content/language';
|
||||
import learningObjectService from '../learning-objects/learning-object-service';
|
||||
import { LearningPathNode } from '../../entities/content/learning-path-node.entity';
|
||||
import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity';
|
||||
|
||||
/**
|
||||
* Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its
|
||||
|
@ -25,19 +17,19 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Ma
|
|||
// Its corresponding learning object.
|
||||
const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>(
|
||||
await Promise.all(
|
||||
nodes.map(node =>
|
||||
learningObjectService.getLearningObjectById({
|
||||
hruid: node.learningObjectHruid,
|
||||
version: node.version,
|
||||
language: node.language
|
||||
}).then(learningObject =>
|
||||
<[LearningPathNode, FilteredLearningObject | null]>[node, learningObject]
|
||||
)
|
||||
nodes.map((node) =>
|
||||
learningObjectService
|
||||
.getLearningObjectById({
|
||||
hruid: node.learningObjectHruid,
|
||||
version: node.version,
|
||||
language: node.language,
|
||||
})
|
||||
.then((learningObject) => <[LearningPathNode, FilteredLearningObject | null]>[node, learningObject])
|
||||
)
|
||||
)
|
||||
);
|
||||
if (nullableNodesToLearningObjects.values().some(it => it === null)) {
|
||||
throw new Error("At least one of the learning objects on this path could not be found.")
|
||||
if (nullableNodesToLearningObjects.values().some((it) => it === null)) {
|
||||
throw new Error('At least one of the learning objects on this path could not be found.');
|
||||
}
|
||||
return nullableNodesToLearningObjects as Map<LearningPathNode, FilteredLearningObject>;
|
||||
}
|
||||
|
@ -46,16 +38,19 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Ma
|
|||
* Convert the given learning path entity to an object which conforms to the learning path content.
|
||||
*/
|
||||
async function convertLearningPath(learningPath: LearningPathEntity, order: number): Promise<LearningPath> {
|
||||
const nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> =
|
||||
await getLearningObjectsForNodes(learningPath.nodes);
|
||||
const nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> = await getLearningObjectsForNodes(learningPath.nodes);
|
||||
|
||||
const targetAges =
|
||||
nodesToLearningObjects.values().flatMap(it => it.targetAges || []).toArray();
|
||||
const targetAges = nodesToLearningObjects
|
||||
.values()
|
||||
.flatMap((it) => it.targetAges || [])
|
||||
.toArray();
|
||||
|
||||
const keywords =
|
||||
nodesToLearningObjects.values().flatMap(it => it.keywords || []).toArray();
|
||||
const keywords = nodesToLearningObjects
|
||||
.values()
|
||||
.flatMap((it) => it.keywords || [])
|
||||
.toArray();
|
||||
|
||||
const image = learningPath.image ? learningPath.image.toString("base64") : undefined;
|
||||
const image = learningPath.image ? learningPath.image.toString('base64') : undefined;
|
||||
|
||||
return {
|
||||
_id: `${learningPath.hruid}/${learningPath.language}`, // For backwards compatibility with the original Dwengo API.
|
||||
|
@ -71,8 +66,8 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb
|
|||
keywords: keywords.join(' '),
|
||||
target_ages: targetAges,
|
||||
max_age: Math.max(...targetAges),
|
||||
min_age: Math.min(...targetAges)
|
||||
}
|
||||
min_age: Math.min(...targetAges),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,24 +75,23 @@ async function convertLearningPath(learningPath: LearningPathEntity, order: numb
|
|||
* learning objects into a list of learning path nodes as they should be represented in the API.
|
||||
* @param nodesToLearningObjects
|
||||
*/
|
||||
function convertNodes(
|
||||
nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>
|
||||
): LearningObjectNode[] {
|
||||
return nodesToLearningObjects.entries().map((entry) => {
|
||||
const [node, learningObject] = entry;
|
||||
return {
|
||||
_id: learningObject.uuid,
|
||||
language: learningObject.language,
|
||||
start_node: node.startNode,
|
||||
created_at: node.createdAt.toISOString(),
|
||||
updatedAt: node.updatedAt.toISOString(),
|
||||
learningobject_hruid: node.learningObjectHruid,
|
||||
version: learningObject.version,
|
||||
transitions: node.transitions.map((trans, i) =>
|
||||
convertTransition(trans, i, nodesToLearningObjects)
|
||||
)
|
||||
}
|
||||
}).toArray();
|
||||
function convertNodes(nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>): LearningObjectNode[] {
|
||||
return nodesToLearningObjects
|
||||
.entries()
|
||||
.map((entry) => {
|
||||
const [node, learningObject] = entry;
|
||||
return {
|
||||
_id: learningObject.uuid,
|
||||
language: learningObject.language,
|
||||
start_node: node.startNode,
|
||||
created_at: node.createdAt.toISOString(),
|
||||
updatedAt: node.updatedAt.toISOString(),
|
||||
learningobject_hruid: node.learningObjectHruid,
|
||||
version: learningObject.version,
|
||||
transitions: node.transitions.map((trans, i) => convertTransition(trans, i, nodesToLearningObjects)),
|
||||
};
|
||||
})
|
||||
.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,17 +109,17 @@ function convertTransition(
|
|||
): Transition {
|
||||
const nextNode = nodesToLearningObjects.get(transition.next);
|
||||
if (!nextNode) {
|
||||
throw new Error(`Learning object ${transition.next.learningObjectHruid}/${transition.next.language}/${transition.next.version} not found!`)
|
||||
throw new Error(`Learning object ${transition.next.learningObjectHruid}/${transition.next.language}/${transition.next.version} not found!`);
|
||||
} else {
|
||||
return {
|
||||
_id: "" + index, // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path.
|
||||
_id: '' + index, // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path.
|
||||
default: false, // We don't work with default transitions but retain this for backwards compatibility.
|
||||
next: {
|
||||
_id: nextNode._id + index, // Construct a unique ID for the transition for backwards compatibility.
|
||||
hruid: transition.next.learningObjectHruid,
|
||||
language: nextNode.language,
|
||||
version: nextNode.version
|
||||
}
|
||||
version: nextNode.version,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -140,19 +134,15 @@ const databaseLearningPathProvider: LearningPathProvider = {
|
|||
async fetchLearningPaths(hruids: string[], language: Language, source: string): Promise<LearningPathResponse> {
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
const learningPaths = await Promise.all(
|
||||
hruids.map(hruid => learningPathRepo.findByHruidAndLanguage(hruid, language))
|
||||
);
|
||||
const learningPaths = await Promise.all(hruids.map((hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)));
|
||||
const filteredLearningPaths = await Promise.all(
|
||||
learningPaths
|
||||
.filter(learningPath => learningPath !== null)
|
||||
.map((learningPath, index) => convertLearningPath(learningPath, index))
|
||||
learningPaths.filter((learningPath) => learningPath !== null).map((learningPath, index) => convertLearningPath(learningPath, index))
|
||||
);
|
||||
|
||||
return {
|
||||
success: filteredLearningPaths.length > 0,
|
||||
data: await Promise.all(filteredLearningPaths),
|
||||
source
|
||||
source,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -163,12 +153,8 @@ const databaseLearningPathProvider: LearningPathProvider = {
|
|||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language);
|
||||
return await Promise.all(
|
||||
searchResults.map((result, index) =>
|
||||
convertLearningPath(result, index)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return await Promise.all(searchResults.map((result, index) => convertLearningPath(result, index)));
|
||||
},
|
||||
};
|
||||
|
||||
export default databaseLearningPathProvider;
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
import { fetchWithLogging } from '../../util/apiHelper.js';
|
||||
import { DWENGO_API_BASE } from '../../config.js';
|
||||
import {
|
||||
LearningPath,
|
||||
LearningPathResponse,
|
||||
} from '../../interfaces/learning-content.js';
|
||||
import {LearningPathProvider} from "./learning-path-provider";
|
||||
import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js';
|
||||
import { LearningPathProvider } from './learning-path-provider';
|
||||
|
||||
const dwengoApiLearningPathProvider: LearningPathProvider = {
|
||||
async fetchLearningPaths(
|
||||
hruids: string[],
|
||||
language: string,
|
||||
source: string
|
||||
): Promise<LearningPathResponse> {
|
||||
async fetchLearningPaths(hruids: string[], language: string, source: string): Promise<LearningPathResponse> {
|
||||
if (hruids.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
|
@ -24,11 +17,7 @@ const dwengoApiLearningPathProvider: LearningPathProvider = {
|
|||
const apiUrl = `${DWENGO_API_BASE}/learningPath/getPathsFromIdList`;
|
||||
const params = { pathIdList: JSON.stringify({ hruids }), language };
|
||||
|
||||
const learningPaths = await fetchWithLogging<LearningPath[]>(
|
||||
apiUrl,
|
||||
`Learning paths for ${source}`,
|
||||
{ params }
|
||||
);
|
||||
const learningPaths = await fetchWithLogging<LearningPath[]>(apiUrl, `Learning paths for ${source}`, { params });
|
||||
|
||||
if (!learningPaths || learningPaths.length === 0) {
|
||||
console.error(`⚠️ WARNING: No learning paths found for ${source}.`);
|
||||
|
@ -46,20 +35,13 @@ const dwengoApiLearningPathProvider: LearningPathProvider = {
|
|||
data: learningPaths,
|
||||
};
|
||||
},
|
||||
async searchLearningPaths(
|
||||
query: string,
|
||||
language: string
|
||||
): Promise<LearningPath[]> {
|
||||
async searchLearningPaths(query: string, language: string): Promise<LearningPath[]> {
|
||||
const apiUrl = `${DWENGO_API_BASE}/learningPath/search`;
|
||||
const params = { all: query, language };
|
||||
|
||||
const searchResults = await fetchWithLogging<LearningPath[]>(
|
||||
apiUrl,
|
||||
`Search learning paths with query "${query}"`,
|
||||
{ params }
|
||||
);
|
||||
const searchResults = await fetchWithLogging<LearningPath[]>(apiUrl, `Search learning paths with query "${query}"`, { params });
|
||||
return searchResults ?? [];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default dwengoApiLearningPathProvider;
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import {Student} from "../../entities/users/student.entity";
|
||||
import {getSubmissionRepository} from "../../data/repositories";
|
||||
import {Group} from "../../entities/assignments/group.entity";
|
||||
import {Submission} from "../../entities/assignments/submission.entity";
|
||||
import {LearningObjectIdentifier} from "../../entities/content/learning-object-identifier";
|
||||
import {LearningPathNode} from "../../entities/content/learning-path-node.entity";
|
||||
import {LearningPathTransition} from "../../entities/content/learning-path-transition.entity";
|
||||
import {JSONPath} from 'jsonpath-plus';
|
||||
import { Student } from '../../entities/users/student.entity';
|
||||
import { getSubmissionRepository } from '../../data/repositories';
|
||||
import { Group } from '../../entities/assignments/group.entity';
|
||||
import { Submission } from '../../entities/assignments/submission.entity';
|
||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier';
|
||||
import { LearningPathNode } from '../../entities/content/learning-path-node.entity';
|
||||
import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
|
||||
/**
|
||||
* Returns the last submission for the learning object associated with the given node and for the student or group
|
||||
*/
|
||||
async function getLastRelevantSubmission(node: LearningPathNode, pathFor: {student?: Student, group?: Group}): Promise<Submission | null> {
|
||||
async function getLastRelevantSubmission(node: LearningPathNode, pathFor: { student?: Student; group?: Group }): Promise<Submission | null> {
|
||||
const submissionRepo = getSubmissionRepository();
|
||||
const learningObjectId: LearningObjectIdentifier = {
|
||||
hruid: node.learningObjectHruid,
|
||||
language: node.language,
|
||||
version: node.version
|
||||
version: node.version,
|
||||
};
|
||||
let lastSubmission: Submission | null;
|
||||
if (pathFor.group) {
|
||||
|
@ -23,47 +23,46 @@ async function getLastRelevantSubmission(node: LearningPathNode, pathFor: {stude
|
|||
} else if (pathFor.student) {
|
||||
lastSubmission = await submissionRepo.findMostRecentSubmissionForStudent(learningObjectId, pathFor.student);
|
||||
} else {
|
||||
throw new Error("The path must either be created for a certain group or for a certain student!");
|
||||
throw new Error('The path must either be created for a certain group or for a certain student!');
|
||||
}
|
||||
return lastSubmission;
|
||||
}
|
||||
|
||||
function transitionPossible(transition: LearningPathTransition, submitted: object | null): boolean {
|
||||
if (transition.condition === "true" || !transition.condition) {
|
||||
if (transition.condition === 'true' || !transition.condition) {
|
||||
return true; // If the transition is unconditional, we can go on.
|
||||
}
|
||||
if (submitted === null) {
|
||||
return false; // If the transition is not unconditional and there was no submission, the transition is not possible.
|
||||
}
|
||||
return JSONPath({path: transition.condition, json: submitted}).length === 0;
|
||||
return JSONPath({ path: transition.condition, json: submitted }).length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to create individual trajectories from learning paths based on the submissions of the student or group.
|
||||
*/
|
||||
const learningPathPersonalizingService = {
|
||||
async calculatePersonalizedTrajectory(nodes: LearningPathNode[], pathFor: {student?: Student, group?: Group}): Promise<LearningPathNode[]> {
|
||||
async calculatePersonalizedTrajectory(nodes: LearningPathNode[], pathFor: { student?: Student; group?: Group }): Promise<LearningPathNode[]> {
|
||||
const trajectory: LearningPathNode[] = [];
|
||||
|
||||
// Always start with the start node.
|
||||
let currentNode = nodes.filter(it => it.startNode)[0];
|
||||
let currentNode = nodes.filter((it) => it.startNode)[0];
|
||||
trajectory.push(currentNode);
|
||||
|
||||
while (true) {
|
||||
// At every node, calculate all the possible next transitions.
|
||||
const lastSubmission = await getLastRelevantSubmission(currentNode, pathFor);
|
||||
const submitted = lastSubmission === null ? null : JSON.parse(lastSubmission.content);
|
||||
const possibleTransitions = currentNode.transitions
|
||||
.filter(it => transitionPossible(it, submitted));
|
||||
const possibleTransitions = currentNode.transitions.filter((it) => transitionPossible(it, submitted));
|
||||
|
||||
if (possibleTransitions.length === 0) { // If there are none, the trajectory has ended.
|
||||
if (possibleTransitions.length === 0) {
|
||||
// If there are none, the trajectory has ended.
|
||||
return trajectory;
|
||||
} // Otherwise, take the first possible transition.
|
||||
currentNode = possibleTransitions[0].node;
|
||||
trajectory.push(currentNode);
|
||||
|
||||
} // Otherwise, take the first possible transition.
|
||||
currentNode = possibleTransitions[0].node;
|
||||
trajectory.push(currentNode);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default learningPathPersonalizingService;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {LearningPath, LearningPathResponse} from "../../interfaces/learning-content";
|
||||
import {Language} from "../../entities/content/language";
|
||||
import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content';
|
||||
import { Language } from '../../entities/content/language';
|
||||
|
||||
/**
|
||||
* Generic interface for a service which provides access to learning paths from a data source.
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import {
|
||||
LearningPath,
|
||||
LearningPathResponse
|
||||
} from "../../interfaces/learning-content";
|
||||
import dwengoApiLearningPathProvider from "./dwengo-api-learning-path-provider";
|
||||
import databaseLearningPathProvider from "./database-learning-path-provider";
|
||||
import {EnvVars, getEnvVar} from "../../util/envvars";
|
||||
import {Language} from "../../entities/content/language";
|
||||
import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content';
|
||||
import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider';
|
||||
import databaseLearningPathProvider from './database-learning-path-provider';
|
||||
import { EnvVars, getEnvVar } from '../../util/envvars';
|
||||
import { Language } from '../../entities/content/language';
|
||||
|
||||
const userContentPrefix = getEnvVar(EnvVars.UserContentPrefix);
|
||||
const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]
|
||||
const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider];
|
||||
|
||||
/**
|
||||
* Service providing access to data about learning paths from the appropriate data source (database or Dwengo-api)
|
||||
|
@ -18,18 +15,16 @@ const learningPathService = {
|
|||
* Fetch the learning paths with the given hruids from the data source.
|
||||
*/
|
||||
async fetchLearningPaths(hruids: string[], language: Language, source: string): Promise<LearningPathResponse> {
|
||||
const userContentHruids = hruids.filter(hruid => hruid.startsWith(userContentPrefix));
|
||||
const nonUserContentHruids = hruids.filter(hruid => !hruid.startsWith(userContentPrefix));
|
||||
const userContentHruids = hruids.filter((hruid) => hruid.startsWith(userContentPrefix));
|
||||
const nonUserContentHruids = hruids.filter((hruid) => !hruid.startsWith(userContentPrefix));
|
||||
|
||||
const userContentLearningPaths =
|
||||
await databaseLearningPathProvider.fetchLearningPaths(userContentHruids, language, source);
|
||||
const nonUserContentLearningPaths
|
||||
= await dwengoApiLearningPathProvider.fetchLearningPaths(nonUserContentHruids, language, source);
|
||||
const userContentLearningPaths = await databaseLearningPathProvider.fetchLearningPaths(userContentHruids, language, source);
|
||||
const nonUserContentLearningPaths = await dwengoApiLearningPathProvider.fetchLearningPaths(nonUserContentHruids, language, source);
|
||||
|
||||
return {
|
||||
data: (userContentLearningPaths.data || []).concat(nonUserContentLearningPaths.data || []),
|
||||
source: source,
|
||||
success: userContentLearningPaths.success || nonUserContentLearningPaths.success
|
||||
success: userContentLearningPaths.success || nonUserContentLearningPaths.success,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -37,13 +32,9 @@ const learningPathService = {
|
|||
* Search learning paths in the data source using the given search string.
|
||||
*/
|
||||
async searchLearningPaths(query: string, language: Language): Promise<LearningPath[]> {
|
||||
const providerResponses = await Promise.all(
|
||||
allProviders.map(
|
||||
provider => provider.searchLearningPaths(query, language)
|
||||
)
|
||||
);
|
||||
const providerResponses = await Promise.all(allProviders.map((provider) => provider.searchLearningPaths(query, language)));
|
||||
return providerResponses.flat();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default learningPathService;
|
||||
|
|
|
@ -17,9 +17,9 @@ export async function fetchWithLogging<T>(
|
|||
url: string,
|
||||
description: string,
|
||||
options?: {
|
||||
params?: Record<string, any>,
|
||||
query?: Record<string, any>,
|
||||
responseType?: "json" | "text",
|
||||
params?: Record<string, any>;
|
||||
query?: Record<string, any>;
|
||||
responseType?: 'json' | 'text';
|
||||
}
|
||||
): Promise<T | null> {
|
||||
try {
|
||||
|
|
|
@ -16,7 +16,7 @@ export async function replaceAsync(str: string, regex: RegExp, replacementFn: (m
|
|||
});
|
||||
|
||||
// Wait for the replacements to get loaded. Reverse them so when popping them, we work in a FIFO manner.
|
||||
const replacements: string[] = (await Promise.all(promises));
|
||||
const replacements: string[] = await Promise.all(promises);
|
||||
|
||||
// Second run through matches: Replace them by their previously computed replacements.
|
||||
return str.replace(regex, () => replacements.pop()!);
|
||||
|
|
|
@ -15,9 +15,9 @@ export const EnvVars: { [key: string]: EnvVar } = {
|
|||
DbUsername: { key: DB_PREFIX + 'USERNAME', required: true },
|
||||
DbPassword: { key: DB_PREFIX + 'PASSWORD', required: true },
|
||||
DbUpdate: { key: DB_PREFIX + 'UPDATE', defaultValue: false },
|
||||
LearningContentRepoApiBaseUrl: { key: PREFIX + "LEARNING_CONTENT_REPO_API_BASE_URL", defaultValue: "https://dwengo.org/backend/api"},
|
||||
FallbackLanguage: { key: PREFIX + "FALLBACK_LANGUAGE", defaultValue: "nl" },
|
||||
UserContentPrefix: { key: DB_PREFIX + 'USER_CONTENT_PREFIX', defaultValue: "u_" },
|
||||
LearningContentRepoApiBaseUrl: { key: PREFIX + 'LEARNING_CONTENT_REPO_API_BASE_URL', defaultValue: 'https://dwengo.org/backend/api' },
|
||||
FallbackLanguage: { key: PREFIX + 'FALLBACK_LANGUAGE', defaultValue: 'nl' },
|
||||
UserContentPrefix: { key: DB_PREFIX + 'USER_CONTENT_PREFIX', defaultValue: 'u_' },
|
||||
IdpStudentUrl: { key: STUDENT_IDP_PREFIX + 'URL', required: true },
|
||||
IdpStudentClientId: { key: STUDENT_IDP_PREFIX + 'CLIENT_ID', required: true },
|
||||
IdpStudentJwksEndpoint: { key: STUDENT_IDP_PREFIX + 'JWKS_ENDPOINT', required: true },
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {LearningObjectIdentifier} from "../interfaces/learning-content";
|
||||
import { LearningObjectIdentifier } from '../interfaces/learning-content';
|
||||
|
||||
export function isValidHttpUrl(url: string): boolean {
|
||||
try {
|
||||
const parsedUrl = new URL(url, "http://test.be");
|
||||
return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
|
||||
const parsedUrl = new URL(url, 'http://test.be');
|
||||
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {getAttachmentRepository, getLearningObjectRepository} from "../../../src/data/repositories";
|
||||
import {AttachmentRepository} from "../../../src/data/content/attachment-repository";
|
||||
import {LearningObjectRepository} from "../../../src/data/content/learning-object-repository";
|
||||
import example from "../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example";
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import {Attachment} from "../../../src/entities/content/attachment.entity";
|
||||
import {LearningObjectIdentifier} from "../../../src/entities/content/learning-object-identifier";
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { getAttachmentRepository, getLearningObjectRepository } from '../../../src/data/repositories';
|
||||
import { AttachmentRepository } from '../../../src/data/content/attachment-repository';
|
||||
import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository';
|
||||
import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import { Attachment } from '../../../src/entities/content/attachment.entity';
|
||||
import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier';
|
||||
|
||||
const NEWER_TEST_SUFFIX = "nEweR";
|
||||
const NEWER_TEST_SUFFIX = 'nEweR';
|
||||
|
||||
function createTestLearningObjects(learningObjectRepo: LearningObjectRepository): {older: LearningObject, newer: LearningObject} {
|
||||
function createTestLearningObjects(learningObjectRepo: LearningObjectRepository): { older: LearningObject; newer: LearningObject } {
|
||||
const olderExample = example.createLearningObject();
|
||||
learningObjectRepo.save(olderExample);
|
||||
|
||||
const newerExample = example.createLearningObject();
|
||||
newerExample.title = "Newer example";
|
||||
newerExample.title = 'Newer example';
|
||||
newerExample.version = 100;
|
||||
|
||||
return {
|
||||
|
@ -24,9 +24,9 @@ function createTestLearningObjects(learningObjectRepo: LearningObjectRepository)
|
|||
};
|
||||
}
|
||||
|
||||
describe("AttachmentRepository", () => {
|
||||
describe('AttachmentRepository', () => {
|
||||
let attachmentRepo: AttachmentRepository;
|
||||
let exampleLearningObjects: {older: LearningObject, newer: LearningObject};
|
||||
let exampleLearningObjects: { older: LearningObject; newer: LearningObject };
|
||||
let attachmentsOlderLearningObject: Attachment[];
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -35,10 +35,8 @@ describe("AttachmentRepository", () => {
|
|||
exampleLearningObjects = createTestLearningObjects(getLearningObjectRepository());
|
||||
});
|
||||
|
||||
it("can add attachments to learning objects without throwing an error", () => {
|
||||
attachmentsOlderLearningObject = Object
|
||||
.values(example.createAttachment)
|
||||
.map(fn => fn(exampleLearningObjects.older));
|
||||
it('can add attachments to learning objects without throwing an error', () => {
|
||||
attachmentsOlderLearningObject = Object.values(example.createAttachment).map((fn) => fn(exampleLearningObjects.older));
|
||||
|
||||
for (const attachment of attachmentsOlderLearningObject) {
|
||||
attachmentRepo.save(attachment);
|
||||
|
@ -46,7 +44,7 @@ describe("AttachmentRepository", () => {
|
|||
});
|
||||
|
||||
let attachmentOnlyNewer: Attachment;
|
||||
it("allows us to add attachments with the same name to a different learning object without throwing an error", () => {
|
||||
it('allows us to add attachments with the same name to a different learning object without throwing an error', () => {
|
||||
attachmentOnlyNewer = Object.values(example.createAttachment)[0](exampleLearningObjects.newer);
|
||||
attachmentOnlyNewer.content.write(NEWER_TEST_SUFFIX);
|
||||
|
||||
|
@ -54,29 +52,23 @@ describe("AttachmentRepository", () => {
|
|||
});
|
||||
|
||||
let olderLearningObjectId: LearningObjectIdentifier;
|
||||
it("returns the correct attachment when queried by learningObjectId and attachment name", async () => {
|
||||
it('returns the correct attachment when queried by learningObjectId and attachment name', async () => {
|
||||
olderLearningObjectId = {
|
||||
hruid: exampleLearningObjects.older.hruid,
|
||||
language: exampleLearningObjects.older.language,
|
||||
version: exampleLearningObjects.older.version
|
||||
version: exampleLearningObjects.older.version,
|
||||
};
|
||||
|
||||
const result = await attachmentRepo.findByLearningObjectIdAndName(
|
||||
olderLearningObjectId,
|
||||
attachmentsOlderLearningObject[0].name
|
||||
);
|
||||
const result = await attachmentRepo.findByLearningObjectIdAndName(olderLearningObjectId, attachmentsOlderLearningObject[0].name);
|
||||
expect(result).toBe(attachmentsOlderLearningObject[0]);
|
||||
});
|
||||
|
||||
it("returns null when queried by learningObjectId and non-existing attachment name", async () => {
|
||||
const result = await attachmentRepo.findByLearningObjectIdAndName(
|
||||
olderLearningObjectId,
|
||||
"non-existing name"
|
||||
);
|
||||
it('returns null when queried by learningObjectId and non-existing attachment name', async () => {
|
||||
const result = await attachmentRepo.findByLearningObjectIdAndName(olderLearningObjectId, 'non-existing name');
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
it("returns the newer version of the attachment when only queried by hruid, language and attachment name (but not version)", async () => {
|
||||
it('returns the newer version of the attachment when only queried by hruid, language and attachment name (but not version)', async () => {
|
||||
const result = await attachmentRepo.findByMostRecentVersionOfLearningObjectAndName(
|
||||
exampleLearningObjects.older.hruid,
|
||||
exampleLearningObjects.older.language,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {beforeAll, describe, it, expect} from "vitest";
|
||||
import {LearningObjectRepository} from "../../../src/data/content/learning-object-repository";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {getLearningObjectRepository} from "../../../src/data/repositories";
|
||||
import example from "../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js"
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import {expectToBeCorrectEntity} from "../../test-utils/expectations";
|
||||
import { beforeAll, describe, it, expect } from 'vitest';
|
||||
import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { getLearningObjectRepository } from '../../../src/data/repositories';
|
||||
import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example.js';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import { expectToBeCorrectEntity } from '../../test-utils/expectations';
|
||||
|
||||
describe("LearningObjectRepository", () => {
|
||||
describe('LearningObjectRepository', () => {
|
||||
let learningObjectRepository: LearningObjectRepository;
|
||||
|
||||
let exampleLearningObject: LearningObject;
|
||||
|
@ -16,55 +16,57 @@ describe("LearningObjectRepository", () => {
|
|||
learningObjectRepository = getLearningObjectRepository();
|
||||
});
|
||||
|
||||
it("should be able to add a learning object to it without an error", async () => {
|
||||
it('should be able to add a learning object to it without an error', async () => {
|
||||
exampleLearningObject = example.createLearningObject();
|
||||
await learningObjectRepository.insert(exampleLearningObject);
|
||||
});
|
||||
|
||||
it("should return the learning object when queried by id", async () => {
|
||||
it('should return the learning object when queried by id', async () => {
|
||||
const result = await learningObjectRepository.findByIdentifier({
|
||||
hruid: exampleLearningObject.hruid,
|
||||
language: exampleLearningObject.language,
|
||||
version: exampleLearningObject.version
|
||||
version: exampleLearningObject.version,
|
||||
});
|
||||
expect(result).toBeInstanceOf(LearningObject);
|
||||
expectToBeCorrectEntity({
|
||||
name: "actual",
|
||||
entity: result!
|
||||
}, {
|
||||
name: "expected",
|
||||
entity: exampleLearningObject
|
||||
});
|
||||
expectToBeCorrectEntity(
|
||||
{
|
||||
name: 'actual',
|
||||
entity: result!,
|
||||
},
|
||||
{
|
||||
name: 'expected',
|
||||
entity: exampleLearningObject,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null when non-existing version is queried", async () => {
|
||||
it('should return null when non-existing version is queried', async () => {
|
||||
const result = await learningObjectRepository.findByIdentifier({
|
||||
hruid: exampleLearningObject.hruid,
|
||||
language: exampleLearningObject.language,
|
||||
version: 100
|
||||
version: 100,
|
||||
});
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
let newerExample: LearningObject;
|
||||
|
||||
it("should allow a learning object with the same id except a different version to be added", async () => {
|
||||
it('should allow a learning object with the same id except a different version to be added', async () => {
|
||||
newerExample = example.createLearningObject();
|
||||
newerExample.version = 10;
|
||||
newerExample.title += " (nieuw)";
|
||||
newerExample.title += ' (nieuw)';
|
||||
await learningObjectRepository.save(newerExample);
|
||||
});
|
||||
|
||||
it("should return the newest version of the learning object when queried by only hruid and language", async () => {
|
||||
it('should return the newest version of the learning object when queried by only hruid and language', async () => {
|
||||
const result = await learningObjectRepository.findLatestByHruidAndLanguage(newerExample.hruid, newerExample.language);
|
||||
expect(result).toBeInstanceOf(LearningObject);
|
||||
expect(result?.version).toBe(10);
|
||||
expect(result?.title).toContain("(nieuw)");
|
||||
expect(result?.title).toContain('(nieuw)');
|
||||
});
|
||||
|
||||
it("should return null when queried by non-existing hruid or language", async () => {
|
||||
const result = await learningObjectRepository.findLatestByHruidAndLanguage("something_that_does_not_exist", exampleLearningObject.language);
|
||||
it('should return null when queried by non-existing hruid or language', async () => {
|
||||
const result = await learningObjectRepository.findLatestByHruidAndLanguage('something_that_does_not_exist', exampleLearningObject.language);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {getLearningPathRepository} from "../../../src/data/repositories";
|
||||
import {LearningPathRepository} from "../../../src/data/content/learning-path-repository";
|
||||
import example from "../../test-assets/learning-paths/pn-werking-example";
|
||||
import {LearningPath} from "../../../src/entities/content/learning-path.entity";
|
||||
import {expectToBeCorrectEntity} from "../../test-utils/expectations";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { getLearningPathRepository } from '../../../src/data/repositories';
|
||||
import { LearningPathRepository } from '../../../src/data/content/learning-path-repository';
|
||||
import example from '../../test-assets/learning-paths/pn-werking-example';
|
||||
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
|
||||
import { expectToBeCorrectEntity } from '../../test-utils/expectations';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
|
||||
function expectToHaveFoundPrecisely(expected: LearningPath, result: LearningPath[]): void {
|
||||
expect(result).toHaveProperty("length");
|
||||
expect(result).toHaveProperty('length');
|
||||
expect(result.length).toBe(1);
|
||||
expectToBeCorrectEntity({ entity: result[0]! }, { entity: expected });
|
||||
}
|
||||
|
||||
function expectToHaveFoundNothing(result: LearningPath[]): void {
|
||||
expect(result).toHaveProperty("length");
|
||||
expect(result).toHaveProperty('length');
|
||||
expect(result.length).toBe(0);
|
||||
}
|
||||
|
||||
describe("LearningPathRepository", () => {
|
||||
describe('LearningPathRepository', () => {
|
||||
let learningPathRepo: LearningPathRepository;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -28,51 +28,39 @@ describe("LearningPathRepository", () => {
|
|||
|
||||
let examplePath: LearningPath;
|
||||
|
||||
it("should be able to add a learning path without throwing an error", async () => {
|
||||
it('should be able to add a learning path without throwing an error', async () => {
|
||||
examplePath = example.createLearningPath();
|
||||
await learningPathRepo.insert(examplePath);
|
||||
});
|
||||
|
||||
it("should return the added path when it is queried by hruid and language", async () => {
|
||||
it('should return the added path when it is queried by hruid and language', async () => {
|
||||
const result = await learningPathRepo.findByHruidAndLanguage(examplePath.hruid, examplePath.language);
|
||||
expect(result).toBeInstanceOf(LearningPath);
|
||||
expectToBeCorrectEntity({ entity: result! }, { entity: examplePath });
|
||||
});
|
||||
|
||||
it("should return null to a query on a non-existing hruid or language", async () => {
|
||||
const result = await learningPathRepo.findByHruidAndLanguage("not_existing_hruid", examplePath.language);
|
||||
it('should return null to a query on a non-existing hruid or language', async () => {
|
||||
const result = await learningPathRepo.findByHruidAndLanguage('not_existing_hruid', examplePath.language);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
it("should return the learning path when we search for a search term occurring in its title", async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(
|
||||
examplePath.title.slice(4, 9),
|
||||
examplePath.language
|
||||
);
|
||||
it('should return the learning path when we search for a search term occurring in its title', async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.title.slice(4, 9), examplePath.language);
|
||||
expectToHaveFoundPrecisely(examplePath, result);
|
||||
});
|
||||
|
||||
it("should return the learning path when we search for a search term occurring in its description", async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(
|
||||
examplePath.description.slice(8, 15),
|
||||
examplePath.language
|
||||
);
|
||||
it('should return the learning path when we search for a search term occurring in its description', async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.description.slice(8, 15), examplePath.language);
|
||||
expectToHaveFoundPrecisely(examplePath, result);
|
||||
});
|
||||
|
||||
it("should return null when we search for something not occurring in its title or description", async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(
|
||||
"something not occurring in the path",
|
||||
examplePath.language
|
||||
);
|
||||
it('should return null when we search for something not occurring in its title or description', async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage('something not occurring in the path', examplePath.language);
|
||||
expectToHaveFoundNothing(result);
|
||||
});
|
||||
|
||||
it("should return null when we search for something occurring in its title, but another language", async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(
|
||||
examplePath.description.slice(1, 3),
|
||||
Language.Kalaallisut
|
||||
);
|
||||
it('should return null when we search for something occurring in its title, but another language', async () => {
|
||||
const result = await learningPathRepo.findByQueryStringAndLanguage(examplePath.description.slice(1, 3), Language.Kalaallisut);
|
||||
expectToHaveFoundNothing(result);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {getLearningObjectRepository} from "../../../src/data/repositories";
|
||||
import example from "../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example";
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import databaseLearningObjectProvider from "../../../src/services/learning-objects/database-learning-object-provider";
|
||||
import {
|
||||
createExampleLearningObjectWithAttachments
|
||||
} from "../../test-assets/learning-objects/create-example-learning-object-with-attachments";
|
||||
import {expectToBeCorrectFilteredLearningObject} from "../../test-utils/expectations";
|
||||
import {FilteredLearningObject} from "../../../src/interfaces/learning-content";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { getLearningObjectRepository } from '../../../src/data/repositories';
|
||||
import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider';
|
||||
import { createExampleLearningObjectWithAttachments } from '../../test-assets/learning-objects/create-example-learning-object-with-attachments';
|
||||
import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations';
|
||||
import { FilteredLearningObject } from '../../../src/interfaces/learning-content';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
|
||||
async function initExampleData(): Promise<LearningObject> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
|
@ -18,39 +16,39 @@ async function initExampleData(): Promise<LearningObject> {
|
|||
return exampleLearningObject;
|
||||
}
|
||||
|
||||
describe("DatabaseLearningObjectProvider", () => {
|
||||
describe('DatabaseLearningObjectProvider', () => {
|
||||
let exampleLearningObject: LearningObject;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestApp();
|
||||
exampleLearningObject = await initExampleData();
|
||||
});
|
||||
describe("getLearningObjectById", () => {
|
||||
it("should return the learning object when it is queried by its id", async () => {
|
||||
describe('getLearningObjectById', () => {
|
||||
it('should return the learning object when it is queried by its id', async () => {
|
||||
const result: FilteredLearningObject | null = await databaseLearningObjectProvider.getLearningObjectById(exampleLearningObject);
|
||||
expect(result).toBeTruthy();
|
||||
expectToBeCorrectFilteredLearningObject(result!, exampleLearningObject);
|
||||
});
|
||||
|
||||
it("should return the learning object when it is queried by only hruid and language (but not version)", async () => {
|
||||
it('should return the learning object when it is queried by only hruid and language (but not version)', async () => {
|
||||
const result: FilteredLearningObject | null = await databaseLearningObjectProvider.getLearningObjectById({
|
||||
hruid: exampleLearningObject.hruid,
|
||||
language: exampleLearningObject.language
|
||||
language: exampleLearningObject.language,
|
||||
});
|
||||
expect(result).toBeTruthy();
|
||||
expectToBeCorrectFilteredLearningObject(result!, exampleLearningObject);
|
||||
});
|
||||
|
||||
it("should return null when queried with an id that does not exist", async () => {
|
||||
it('should return null when queried with an id that does not exist', async () => {
|
||||
const result: FilteredLearningObject | null = await databaseLearningObjectProvider.getLearningObjectById({
|
||||
hruid: "non_existing_hruid",
|
||||
language: Language.Dutch
|
||||
hruid: 'non_existing_hruid',
|
||||
language: Language.Dutch,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
describe("getLearningObjectHTML", () => {
|
||||
it("should return the correct rendering of the learning object", async () => {
|
||||
describe('getLearningObjectHTML', () => {
|
||||
it('should return the correct rendering of the learning object', async () => {
|
||||
const result = await databaseLearningObjectProvider.getLearningObjectHTML(exampleLearningObject);
|
||||
expect(result).toEqual(example.getHTMLRendering());
|
||||
});
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import {getLearningObjectRepository} from "../../../src/data/repositories";
|
||||
import learningObjectExample from "../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example";
|
||||
import learningObjectService from "../../../src/services/learning-objects/learning-object-service";
|
||||
import {LearningObjectIdentifier} from "../../../src/interfaces/learning-content";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import {EnvVars, getEnvVar} from "../../../src/util/envvars";
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import { getLearningObjectRepository } from '../../../src/data/repositories';
|
||||
import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example';
|
||||
import learningObjectService from '../../../src/services/learning-objects/learning-object-service';
|
||||
import { LearningObjectIdentifier } from '../../../src/interfaces/learning-content';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
import { EnvVars, getEnvVar } from '../../../src/util/envvars';
|
||||
|
||||
const TEST_LEARNING_OBJECT_TITLE = "Test title";
|
||||
const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = "Werken met notebooks";
|
||||
const TEST_LEARNING_OBJECT_TITLE = 'Test title';
|
||||
const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks';
|
||||
const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifier = {
|
||||
hruid: "pn_werkingnotebooks",
|
||||
hruid: 'pn_werkingnotebooks',
|
||||
language: Language.Dutch,
|
||||
version: 3
|
||||
version: 3,
|
||||
};
|
||||
|
||||
async function initExampleData(): Promise<LearningObject> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const learningObject = learningObjectExample.createLearningObject();
|
||||
learningObject.title = TEST_LEARNING_OBJECT_TITLE
|
||||
learningObject.title = TEST_LEARNING_OBJECT_TITLE;
|
||||
await learningObjectRepo.save(learningObject);
|
||||
return learningObject;
|
||||
}
|
||||
|
||||
describe("LearningObjectService", () => {
|
||||
describe('LearningObjectService', () => {
|
||||
let exampleLearningObject: LearningObject;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -32,47 +32,50 @@ describe("LearningObjectService", () => {
|
|||
exampleLearningObject = await initExampleData();
|
||||
});
|
||||
|
||||
describe("getLearningObjectById", () => {
|
||||
it("returns the learning object from the Dwengo API if it does not have the user content prefix", async () => {
|
||||
describe('getLearningObjectById', () => {
|
||||
it('returns the learning object from the Dwengo API if it does not have the user content prefix', async () => {
|
||||
const result = await learningObjectService.getLearningObjectById(DWENGO_TEST_LEARNING_OBJECT_ID);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.title).toBe(EXPECTED_DWENGO_LEARNING_OBJECT_TITLE);
|
||||
});
|
||||
it("returns the learning object from the database if it does have the user content prefix", async () => {
|
||||
it('returns the learning object from the database if it does have the user content prefix', async () => {
|
||||
const result = await learningObjectService.getLearningObjectById(exampleLearningObject);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.title).toBe(exampleLearningObject.title);
|
||||
});
|
||||
it("returns null if the hruid does not have the user content prefix and does not exist in the Dwengo repo", async () => {
|
||||
it('returns null if the hruid does not have the user content prefix and does not exist in the Dwengo repo', async () => {
|
||||
const result = await learningObjectService.getLearningObjectById({
|
||||
hruid: "non-existing",
|
||||
language: Language.Dutch
|
||||
hruid: 'non-existing',
|
||||
language: Language.Dutch,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getLearningObjectHTML", () => {
|
||||
it("returns the expected HTML when queried with the identifier of a learning object saved in the database", async () => {
|
||||
describe('getLearningObjectHTML', () => {
|
||||
it('returns the expected HTML when queried with the identifier of a learning object saved in the database', async () => {
|
||||
const result = await learningObjectService.getLearningObjectHTML(exampleLearningObject);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result).toEqual(learningObjectExample.getHTMLRendering());
|
||||
});
|
||||
it("returns the same HTML as the Dwengo API when queried with the identifier of a learning object that does " +
|
||||
"not start with the user content prefix", async () => {
|
||||
const result = await learningObjectService.getLearningObjectHTML(DWENGO_TEST_LEARNING_OBJECT_ID);
|
||||
expect(result).not.toBeNull();
|
||||
it(
|
||||
'returns the same HTML as the Dwengo API when queried with the identifier of a learning object that does ' +
|
||||
'not start with the user content prefix',
|
||||
async () => {
|
||||
const result = await learningObjectService.getLearningObjectHTML(DWENGO_TEST_LEARNING_OBJECT_ID);
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
const htmlFromDwengoApi = await fetch(
|
||||
getEnvVar(EnvVars.LearningContentRepoApiBaseUrl)
|
||||
+ `/learningObject/getRaw?hruid=${DWENGO_TEST_LEARNING_OBJECT_ID.hruid}&language=${DWENGO_TEST_LEARNING_OBJECT_ID.language}&version=${DWENGO_TEST_LEARNING_OBJECT_ID.version}`
|
||||
).then(it => it.text());
|
||||
expect(result).toEqual(htmlFromDwengoApi);
|
||||
});
|
||||
it("returns null when queried with a non-existing identifier", async () => {
|
||||
const htmlFromDwengoApi = await fetch(
|
||||
getEnvVar(EnvVars.LearningContentRepoApiBaseUrl) +
|
||||
`/learningObject/getRaw?hruid=${DWENGO_TEST_LEARNING_OBJECT_ID.hruid}&language=${DWENGO_TEST_LEARNING_OBJECT_ID.language}&version=${DWENGO_TEST_LEARNING_OBJECT_ID.version}`
|
||||
).then((it) => it.text());
|
||||
expect(result).toEqual(htmlFromDwengoApi);
|
||||
}
|
||||
);
|
||||
it('returns null when queried with a non-existing identifier', async () => {
|
||||
const result = await learningObjectService.getLearningObjectHTML({
|
||||
hruid: "non_existing_hruid",
|
||||
language: Language.Dutch
|
||||
hruid: 'non_existing_hruid',
|
||||
language: Language.Dutch,
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import {describe, expect, it} from "vitest";
|
||||
import mdExample from "../../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example";
|
||||
import multipleChoiceExample from "../../../test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example";
|
||||
import essayExample from "../../../test-assets/learning-objects/test-essay/test-essay-example";
|
||||
import processingService from "../../../../src/services/learning-objects/processing/processing-service";
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import mdExample from '../../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example';
|
||||
import multipleChoiceExample from '../../../test-assets/learning-objects/test-multiple-choice/test-multiple-choice-example';
|
||||
import essayExample from '../../../test-assets/learning-objects/test-essay/test-essay-example';
|
||||
import processingService from '../../../../src/services/learning-objects/processing/processing-service';
|
||||
|
||||
describe("ProcessingService", () => {
|
||||
it("renders a markdown learning object correctly", async () => {
|
||||
describe('ProcessingService', () => {
|
||||
it('renders a markdown learning object correctly', async () => {
|
||||
const markdownLearningObject = mdExample.createLearningObject();
|
||||
const result = await processingService.render(markdownLearningObject);
|
||||
expect(result).toEqual(mdExample.getHTMLRendering());
|
||||
});
|
||||
|
||||
it("renders a multiple choice question correctly", async () => {
|
||||
it('renders a multiple choice question correctly', async () => {
|
||||
const multipleChoiceLearningObject = multipleChoiceExample.createLearningObject();
|
||||
const result = await processingService.render(multipleChoiceLearningObject);
|
||||
expect(result).toEqual(multipleChoiceExample.getHTMLRendering());
|
||||
});
|
||||
|
||||
it("renders an essay question correctly", async () => {
|
||||
it('renders an essay question correctly', async () => {
|
||||
const essayLearningObject = essayExample.createLearningObject();
|
||||
const result = await processingService.render(essayLearningObject);
|
||||
expect(result).toEqual(essayExample.getHTMLRendering());
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {LearningPath} from "../../../src/entities/content/learning-path.entity";
|
||||
import {getLearningObjectRepository, getLearningPathRepository} from "../../../src/data/repositories";
|
||||
import learningObjectExample from "../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example";
|
||||
import learningPathExample from "../../test-assets/learning-paths/pn-werking-example"
|
||||
import databaseLearningPathProvider from "../../../src/services/learning-paths/database-learning-path-provider";
|
||||
import {expectToBeCorrectLearningPath} from "../../test-utils/expectations";
|
||||
import {LearningObjectRepository} from "../../../src/data/content/learning-object-repository";
|
||||
import learningObjectService from "../../../src/services/learning-objects/learning-object-service";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
|
||||
import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories';
|
||||
import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example';
|
||||
import learningPathExample from '../../test-assets/learning-paths/pn-werking-example';
|
||||
import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider';
|
||||
import { expectToBeCorrectLearningPath } from '../../test-utils/expectations';
|
||||
import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository';
|
||||
import learningObjectService from '../../../src/services/learning-objects/learning-object-service';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
|
||||
async function initExampleData(): Promise<{ learningObject: LearningObject, learningPath: LearningPath }> {
|
||||
async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
const learningObject = learningObjectExample.createLearningObject();
|
||||
|
@ -21,9 +21,9 @@ async function initExampleData(): Promise<{ learningObject: LearningObject, lear
|
|||
return { learningObject, learningPath };
|
||||
}
|
||||
|
||||
describe("DatabaseLearningPathProvider", () => {
|
||||
describe('DatabaseLearningPathProvider', () => {
|
||||
let learningObjectRepo: LearningObjectRepository;
|
||||
let example: {learningObject: LearningObject, learningPath: LearningPath};
|
||||
let example: { learningObject: LearningObject; learningPath: LearningPath };
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupTestApp();
|
||||
|
@ -31,40 +31,43 @@ describe("DatabaseLearningPathProvider", () => {
|
|||
learningObjectRepo = getLearningObjectRepository();
|
||||
});
|
||||
|
||||
describe("fetchLearningPaths", () => {
|
||||
it("returns the learning path correctly", async () => {
|
||||
describe('fetchLearningPaths', () => {
|
||||
it('returns the learning path correctly', async () => {
|
||||
const result = await databaseLearningPathProvider.fetchLearningPaths(
|
||||
[example.learningPath.hruid],
|
||||
example.learningPath.language,
|
||||
"the source"
|
||||
'the source'
|
||||
);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.length).toBe(1);
|
||||
|
||||
const learningObjectsOnPath = (await Promise.all(
|
||||
example.learningPath.nodes.map(node =>
|
||||
learningObjectService.getLearningObjectById({
|
||||
hruid: node.learningObjectHruid,
|
||||
version: node.version,
|
||||
language: node.language
|
||||
}))
|
||||
)).filter(it => it !== null);
|
||||
const learningObjectsOnPath = (
|
||||
await Promise.all(
|
||||
example.learningPath.nodes.map((node) =>
|
||||
learningObjectService.getLearningObjectById({
|
||||
hruid: node.learningObjectHruid,
|
||||
version: node.version,
|
||||
language: node.language,
|
||||
})
|
||||
)
|
||||
)
|
||||
).filter((it) => it !== null);
|
||||
|
||||
expectToBeCorrectLearningPath(result.data![0], example.learningPath, learningObjectsOnPath)
|
||||
expectToBeCorrectLearningPath(result.data![0], example.learningPath, learningObjectsOnPath);
|
||||
});
|
||||
it("returns a non-successful response if a non-existing learning path is queried", async () => {
|
||||
it('returns a non-successful response if a non-existing learning path is queried', async () => {
|
||||
const result = await databaseLearningPathProvider.fetchLearningPaths(
|
||||
[example.learningPath.hruid],
|
||||
Language.Abkhazian, // Wrong language
|
||||
"the source"
|
||||
'the source'
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("searchLearningPaths", () => {
|
||||
it("returns the correct learning path when queried with a substring of its title", async () => {
|
||||
describe('searchLearningPaths', () => {
|
||||
it('returns the correct learning path when queried with a substring of its title', async () => {
|
||||
const result = await databaseLearningPathProvider.searchLearningPaths(
|
||||
example.learningPath.title.substring(2, 6),
|
||||
example.learningPath.language
|
||||
|
@ -73,7 +76,7 @@ describe("DatabaseLearningPathProvider", () => {
|
|||
expect(result[0].title).toBe(example.learningPath.title);
|
||||
expect(result[0].description).toBe(example.learningPath.description);
|
||||
});
|
||||
it("returns the correct learning path when queried with a substring of the description", async () => {
|
||||
it('returns the correct learning path when queried with a substring of the description', async () => {
|
||||
const result = await databaseLearningPathProvider.searchLearningPaths(
|
||||
example.learningPath.description.substring(5, 12),
|
||||
example.learningPath.language
|
||||
|
@ -82,9 +85,9 @@ describe("DatabaseLearningPathProvider", () => {
|
|||
expect(result[0].title).toBe(example.learningPath.title);
|
||||
expect(result[0].description).toBe(example.learningPath.description);
|
||||
});
|
||||
it("returns an empty result when queried with a text which is not a substring of the title or the description of a learning path", async () => {
|
||||
it('returns an empty result when queried with a text which is not a substring of the title or the description of a learning path', async () => {
|
||||
const result = await databaseLearningPathProvider.searchLearningPaths(
|
||||
"substring which does not occur in the title or the description of a learning object",
|
||||
'substring which does not occur in the title or the description of a learning object',
|
||||
example.learningPath.language
|
||||
);
|
||||
expect(result.length).toBe(0);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {setupTestApp} from "../../setup-tests";
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import {LearningPath} from "../../../src/entities/content/learning-path.entity";
|
||||
import {getLearningObjectRepository, getLearningPathRepository} from "../../../src/data/repositories";
|
||||
import learningObjectExample from "../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example";
|
||||
import learningPathExample from "../../test-assets/learning-paths/pn-werking-example";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import learningPathService from "../../../src/services/learning-paths/learning-path-service";
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { setupTestApp } from '../../setup-tests';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
|
||||
import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories';
|
||||
import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example';
|
||||
import learningPathExample from '../../test-assets/learning-paths/pn-werking-example';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
import learningPathService from '../../../src/services/learning-paths/learning-path-service';
|
||||
|
||||
async function initExampleData(): Promise<{ learningObject: LearningObject, learningPath: LearningPath }> {
|
||||
async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> {
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
const learningObject = learningObjectExample.createLearningObject();
|
||||
|
@ -18,88 +18,63 @@ async function initExampleData(): Promise<{ learningObject: LearningObject, lear
|
|||
return { learningObject, learningPath };
|
||||
}
|
||||
|
||||
const TEST_DWENGO_LEARNING_PATH_HRUID = "pn_werking";
|
||||
const TEST_DWENGO_LEARNING_PATH_TITLE = "Werken met notebooks";
|
||||
const TEST_DWENGO_EXCLUSIVE_LEARNING_PATH_SEARCH_QUERY = "Microscopie";
|
||||
const TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES = "su$m8f9usf89ud<p9<U8SDP8UP9";
|
||||
const TEST_DWENGO_LEARNING_PATH_HRUID = 'pn_werking';
|
||||
const TEST_DWENGO_LEARNING_PATH_TITLE = 'Werken met notebooks';
|
||||
const TEST_DWENGO_EXCLUSIVE_LEARNING_PATH_SEARCH_QUERY = 'Microscopie';
|
||||
const TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES = 'su$m8f9usf89ud<p9<U8SDP8UP9';
|
||||
|
||||
describe("LearningPathService", () => {
|
||||
let example: { learningObject: LearningObject, learningPath: LearningPath };
|
||||
describe('LearningPathService', () => {
|
||||
let example: { learningObject: LearningObject; learningPath: LearningPath };
|
||||
beforeAll(async () => {
|
||||
await setupTestApp();
|
||||
example = await initExampleData();
|
||||
});
|
||||
describe("fetchLearningPaths", () => {
|
||||
it("should return learning paths both from the database and from the Dwengo API", async () => {
|
||||
describe('fetchLearningPaths', () => {
|
||||
it('should return learning paths both from the database and from the Dwengo API', async () => {
|
||||
const result = await learningPathService.fetchLearningPaths(
|
||||
[example.learningPath.hruid, TEST_DWENGO_LEARNING_PATH_HRUID],
|
||||
example.learningPath.language,
|
||||
"the source"
|
||||
'the source'
|
||||
);
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result.data?.filter(it => it.hruid == TEST_DWENGO_LEARNING_PATH_HRUID).length).not.toBe(0);
|
||||
expect(result.data?.filter(it => it.hruid == example.learningPath.hruid).length).not.toBe(0);
|
||||
expect(result.data?.filter(it => it.hruid == TEST_DWENGO_LEARNING_PATH_HRUID)[0].title)
|
||||
.toEqual(TEST_DWENGO_LEARNING_PATH_TITLE);
|
||||
expect(result.data?.filter(it => it.hruid == example.learningPath.hruid)[0].title)
|
||||
.toEqual(example.learningPath.title);
|
||||
expect(result.data?.filter((it) => it.hruid == TEST_DWENGO_LEARNING_PATH_HRUID).length).not.toBe(0);
|
||||
expect(result.data?.filter((it) => it.hruid == example.learningPath.hruid).length).not.toBe(0);
|
||||
expect(result.data?.filter((it) => it.hruid == TEST_DWENGO_LEARNING_PATH_HRUID)[0].title).toEqual(TEST_DWENGO_LEARNING_PATH_TITLE);
|
||||
expect(result.data?.filter((it) => it.hruid == example.learningPath.hruid)[0].title).toEqual(example.learningPath.title);
|
||||
});
|
||||
it("should include both the learning objects from the Dwengo API and learning objects from the database in its response", async () => {
|
||||
const result = await learningPathService.fetchLearningPaths(
|
||||
[example.learningPath.hruid],
|
||||
example.learningPath.language,
|
||||
"the source"
|
||||
);
|
||||
it('should include both the learning objects from the Dwengo API and learning objects from the database in its response', async () => {
|
||||
const result = await learningPathService.fetchLearningPaths([example.learningPath.hruid], example.learningPath.language, 'the source');
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result.data?.length).toBe(1);
|
||||
|
||||
// Should include all the nodes, even those pointing to foreign learning objects.
|
||||
expect(
|
||||
[...result.data![0].nodes.map(it => it.learningobject_hruid)].sort()
|
||||
).toEqual(
|
||||
example.learningPath.nodes.map(it => it.learningObjectHruid).sort()
|
||||
expect([...result.data![0].nodes.map((it) => it.learningobject_hruid)].sort()).toEqual(
|
||||
example.learningPath.nodes.map((it) => it.learningObjectHruid).sort()
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("searchLearningPath", () => {
|
||||
it("should include both the learning paths from the Dwengo API and those from the database in its response", async () => {
|
||||
describe('searchLearningPath', () => {
|
||||
it('should include both the learning paths from the Dwengo API and those from the database in its response', async () => {
|
||||
// This matches the learning object in the database, but definitely also some learning objects in the Dwengo API.
|
||||
const result = await learningPathService.searchLearningPaths(
|
||||
example.learningPath.title.substring(2, 3),
|
||||
example.learningPath.language
|
||||
);
|
||||
const result = await learningPathService.searchLearningPaths(example.learningPath.title.substring(2, 3), example.learningPath.language);
|
||||
|
||||
// Should find the one from the database
|
||||
expect(
|
||||
result.filter(it =>
|
||||
it.hruid === example.learningPath.hruid && it.title === example.learningPath.title
|
||||
).length
|
||||
).toBe(1);
|
||||
expect(result.filter((it) => it.hruid === example.learningPath.hruid && it.title === example.learningPath.title).length).toBe(1);
|
||||
|
||||
// But should not only find that one.
|
||||
expect(result.length).not.toBeLessThan(2);
|
||||
});
|
||||
it("should still return results from the Dwengo API even though there are no matches in the database", async () => {
|
||||
const result = await learningPathService.searchLearningPaths(
|
||||
TEST_DWENGO_EXCLUSIVE_LEARNING_PATH_SEARCH_QUERY,
|
||||
Language.Dutch
|
||||
);
|
||||
it('should still return results from the Dwengo API even though there are no matches in the database', async () => {
|
||||
const result = await learningPathService.searchLearningPaths(TEST_DWENGO_EXCLUSIVE_LEARNING_PATH_SEARCH_QUERY, Language.Dutch);
|
||||
|
||||
// Should find something...
|
||||
expect(result.length).not.toBe(0);
|
||||
|
||||
// But not the example learning path.
|
||||
expect(
|
||||
result.filter(it =>
|
||||
it.hruid === example.learningPath.hruid && it.title === example.learningPath.title
|
||||
).length
|
||||
).toBe(0);
|
||||
expect(result.filter((it) => it.hruid === example.learningPath.hruid && it.title === example.learningPath.title).length).toBe(0);
|
||||
});
|
||||
it("should return an empty list if neither the Dwengo API nor the database contains matches", async () => {
|
||||
const result = await learningPathService.searchLearningPaths(
|
||||
TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES,
|
||||
Language.Dutch
|
||||
);
|
||||
it('should return an empty list if neither the Dwengo API nor the database contains matches', async () => {
|
||||
const result = await learningPathService.searchLearningPaths(TEST_SEARCH_QUERY_EXPECTING_NO_MATCHES, Language.Dutch);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {LearningObjectExample} from "./learning-object-example";
|
||||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import { LearningObjectExample } from './learning-object-example';
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
|
||||
export function createExampleLearningObjectWithAttachments(example: LearningObjectExample): LearningObject {
|
||||
const learningObject = example.createLearningObject();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {LearningObjectExample} from "../learning-object-example";
|
||||
import {LearningObject} from "../../../../src/entities/content/learning-object.entity";
|
||||
import {Language} from "../../../../src/entities/content/language";
|
||||
import {loadTestAsset} from "../../../test-utils/load-test-asset";
|
||||
import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type";
|
||||
import { LearningObjectExample } from '../learning-object-example';
|
||||
import { LearningObject } from '../../../../src/entities/content/learning-object.entity';
|
||||
import { Language } from '../../../../src/entities/content/language';
|
||||
import { loadTestAsset } from '../../../test-utils/load-test-asset';
|
||||
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type';
|
||||
|
||||
/**
|
||||
* Create a dummy learning object to be used in tests where multiple learning objects are needed (for example for use
|
||||
|
@ -16,12 +16,12 @@ export function dummyLearningObject(hruid: string, language: Language, title: st
|
|||
learningObject.language = language;
|
||||
learningObject.version = 1;
|
||||
learningObject.title = title;
|
||||
learningObject.description = "Just a dummy learning object for testing purposes";
|
||||
learningObject.description = 'Just a dummy learning object for testing purposes';
|
||||
learningObject.contentType = DwengoContentType.TEXT_PLAIN;
|
||||
learningObject.content = Buffer.from("Dummy content");
|
||||
learningObject.content = Buffer.from('Dummy content');
|
||||
return learningObject;
|
||||
},
|
||||
createAttachment: {},
|
||||
getHTMLRendering: () => loadTestAsset("learning-objects/dummy/rendering.html").toString()
|
||||
}
|
||||
getHTMLRendering: () => loadTestAsset('learning-objects/dummy/rendering.html').toString(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {LearningObject} from "../../../src/entities/content/learning-object.entity";
|
||||
import {Attachment} from "../../../src/entities/content/attachment.entity";
|
||||
import { LearningObject } from '../../../src/entities/content/learning-object.entity';
|
||||
import { Attachment } from '../../../src/entities/content/attachment.entity';
|
||||
|
||||
type LearningObjectExample = {
|
||||
createLearningObject: () => LearningObject,
|
||||
createAttachment: {[key: string]: (owner: LearningObject) => Attachment},
|
||||
getHTMLRendering: () => string
|
||||
createLearningObject: () => LearningObject;
|
||||
createAttachment: { [key: string]: (owner: LearningObject) => Attachment };
|
||||
getHTMLRendering: () => string;
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ Het lesmateriaal van 'Python in wiskunde en STEM' wordt aangeboden in de vorm va
|
|||
|
||||
_Nieuwe begrippen_ worden aangebracht via tekstuele uitleg, video en afbeeldingen.
|
||||
|
||||
Er zijn uitgewerkte *voorbeelden* met daarnaast ook kleine en grote *opdrachten*. In deze opdrachten zal je aangereikte code kunnen uitvoeren, maar ook zelf code opstellen.
|
||||
Er zijn uitgewerkte _voorbeelden_ met daarnaast ook kleine en grote _opdrachten_. In deze opdrachten zal je aangereikte code kunnen uitvoeren, maar ook zelf code opstellen.
|
||||
|
||||
De code die in de notebooks gebruikt wordt, is Python versie 3. We kozen voor Python omdat dit een heel toegankelijke programmeertaal is, die vaak ook intuïtief is.
|
||||
Python is bovendien bezig aan een opmars en wordt gebruikt door bedrijven, zoals Google, NASA, Netflix, Uber, AstraZeneca, Barco, Instagram en YouTube.
|
||||
|
@ -12,14 +12,15 @@ Python is bovendien bezig aan een opmars en wordt gebruikt door bedrijven, zoals
|
|||
We kozen voor notebooks omdat daar enkele belangrijke voordelen aan verbonden zijn: leerkrachten moeten geen geavanceerde installaties doen om de notebooks te gebruiken, leerkrachten kunnen verschillende soorten van lesinhouden aanbieden via één platform, de notebooks zijn interactief, leerlingen bouwen de oplossing van een probleem stap voor stap op in de notebook waardoor dat proces zichtbaar is voor de leerkracht ([Jeroen Van der Hooft, 2023](https://libstore.ugent.be/fulltxt/RUG01/003/151/437/RUG01-003151437_2023_0001_AC.pdf)).
|
||||
|
||||
---
|
||||
Klik je op onderstaande knop 'Open notebooks', dan word je doorgestuurd naar een andere website waar jouw persoonlijke notebooks ingeladen worden. (Dit kan even duren.)
|
||||
|
||||
Klik je op onderstaande knop 'Open notebooks', dan word je doorgestuurd naar een andere website waar jouw persoonlijke notebooks ingeladen worden. (Dit kan even duren.)
|
||||
|
||||
Links op het scherm vind je er twee bestanden met extensie _.ipynb_.
|
||||
Dit zijn de twee notebooks waarin je resp. een overzicht krijgt van de opbouw en mogelijkheden en hoe je er mee aan de slag kan. Dubbelklik op de bestandsnaam om een notebook te openen.
|
||||
|
||||
Je ziet er ook een map *images* met de afbeeldingen die in de notebooks getoond worden.
|
||||
Je ziet er ook een map _images_ met de afbeeldingen die in de notebooks getoond worden.
|
||||
|
||||
In deze eerste twee notebooks leer je hoe de notebooks zijn opgevat en hoe je ermee aan de slag kan.
|
||||
Na het doorlopen van beide notebooks heb je een goed idee van hoe onze Python notebooks zijn opgevat.
|
||||
|
||||
[](https://kiks.ilabt.imec.be/hub/tmplogin?id=0101 "Notebooks Werking")
|
||||
[](https://kiks.ilabt.imec.be/hub/tmplogin?id=0101 'Notebooks Werking')
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import {LearningObjectExample} from "../learning-object-example";
|
||||
import {Language} from "../../../../src/entities/content/language";
|
||||
import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type";
|
||||
import {loadTestAsset} from "../../../test-utils/load-test-asset";
|
||||
import {EducationalGoal, LearningObject, ReturnValue} from "../../../../src/entities/content/learning-object.entity";
|
||||
import {Attachment} from "../../../../src/entities/content/attachment.entity";
|
||||
import {EnvVars, getEnvVar} from "../../../../src/util/envvars";
|
||||
import { LearningObjectExample } from '../learning-object-example';
|
||||
import { Language } from '../../../../src/entities/content/language';
|
||||
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type';
|
||||
import { loadTestAsset } from '../../../test-utils/load-test-asset';
|
||||
import { EducationalGoal, LearningObject, ReturnValue } from '../../../../src/entities/content/learning-object.entity';
|
||||
import { Attachment } from '../../../../src/entities/content/attachment.entity';
|
||||
import { EnvVars, getEnvVar } from '../../../../src/util/envvars';
|
||||
|
||||
const ASSETS_PREFIX = "learning-objects/pn-werkingnotebooks/";
|
||||
const ASSETS_PREFIX = 'learning-objects/pn-werkingnotebooks/';
|
||||
|
||||
const example: LearningObjectExample = {
|
||||
createLearningObject: ()=> {
|
||||
createLearningObject: () => {
|
||||
const learningObject = new LearningObject();
|
||||
learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}pn_werkingnotebooks`;
|
||||
learningObject.version = 3;
|
||||
learningObject.language = Language.Dutch;
|
||||
learningObject.title = "Werken met notebooks";
|
||||
learningObject.description = "Leren werken met notebooks";
|
||||
learningObject.keywords = ["Python", "KIKS", "Wiskunde", "STEM", "AI"]
|
||||
learningObject.title = 'Werken met notebooks';
|
||||
learningObject.description = 'Leren werken met notebooks';
|
||||
learningObject.keywords = ['Python', 'KIKS', 'Wiskunde', 'STEM', 'AI'];
|
||||
|
||||
const educationalGoal1 = new EducationalGoal();
|
||||
educationalGoal1.source = "Source";
|
||||
educationalGoal1.id = "id";
|
||||
educationalGoal1.source = 'Source';
|
||||
educationalGoal1.id = 'id';
|
||||
|
||||
const educationalGoal2 = new EducationalGoal();
|
||||
educationalGoal2.source = "Source2";
|
||||
educationalGoal2.id = "id2";
|
||||
educationalGoal2.source = 'Source2';
|
||||
educationalGoal2.id = 'id2';
|
||||
|
||||
learningObject.educationalGoals = [educationalGoal1, educationalGoal2];
|
||||
learningObject.admins = [];
|
||||
|
@ -33,40 +33,40 @@ const example: LearningObjectExample = {
|
|||
learningObject.skosConcepts = [
|
||||
'http://ilearn.ilabt.imec.be/vocab/curr1/s-vaktaal',
|
||||
'http://ilearn.ilabt.imec.be/vocab/curr1/s-digitale-media-en-toepassingen',
|
||||
'http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen'
|
||||
'http://ilearn.ilabt.imec.be/vocab/curr1/s-computers-en-systemen',
|
||||
];
|
||||
learningObject.copyright = "dwengo";
|
||||
learningObject.license = "dwengo";
|
||||
learningObject.copyright = 'dwengo';
|
||||
learningObject.license = 'dwengo';
|
||||
learningObject.estimatedTime = 10;
|
||||
|
||||
const returnValue = new ReturnValue();
|
||||
returnValue.callbackUrl = "callback_url_example";
|
||||
returnValue.callbackUrl = 'callback_url_example';
|
||||
returnValue.callbackSchema = '{"att": "test", "att2": "test2"}';
|
||||
|
||||
learningObject.returnValue = returnValue;
|
||||
learningObject.available = true;
|
||||
learningObject.content = loadTestAsset(`${ASSETS_PREFIX}/content.md`);
|
||||
|
||||
return learningObject
|
||||
return learningObject;
|
||||
},
|
||||
createAttachment: {
|
||||
dwengoLogo: (learningObject) => {
|
||||
const att = new Attachment();
|
||||
att.learningObject = learningObject;
|
||||
att.name = "dwengo.png";
|
||||
att.mimeType = "image/png";
|
||||
att.content = loadTestAsset(`${ASSETS_PREFIX}/dwengo.png`)
|
||||
att.name = 'dwengo.png';
|
||||
att.mimeType = 'image/png';
|
||||
att.content = loadTestAsset(`${ASSETS_PREFIX}/dwengo.png`);
|
||||
return att;
|
||||
},
|
||||
knop: (learningObject) => {
|
||||
const att = new Attachment();
|
||||
att.learningObject = learningObject;
|
||||
att.name = "Knop.png";
|
||||
att.mimeType = "image/png";
|
||||
att.content = loadTestAsset(`${ASSETS_PREFIX}/Knop.png`)
|
||||
att.name = 'Knop.png';
|
||||
att.mimeType = 'image/png';
|
||||
att.content = loadTestAsset(`${ASSETS_PREFIX}/Knop.png`);
|
||||
return att;
|
||||
}
|
||||
},
|
||||
},
|
||||
getHTMLRendering: () => loadTestAsset(`${ASSETS_PREFIX}/rendering.html`).toString()
|
||||
}
|
||||
getHTMLRendering: () => loadTestAsset(`${ASSETS_PREFIX}/rendering.html`).toString(),
|
||||
};
|
||||
export default example;
|
||||
|
|
|
@ -4,17 +4,44 @@
|
|||
</a>
|
||||
Werken met notebooks
|
||||
</h1>
|
||||
<p>Het lesmateriaal van 'Python in wiskunde en STEM' wordt aangeboden in de vorm van interactieve <strong><em>notebooks</em></strong>. Notebooks zijn <em>digitale documenten</em> die zowel uitvoerbare code bevatten als tekst, afbeeldingen, video, hyperlinks ...</p>
|
||||
<p>
|
||||
Het lesmateriaal van 'Python in wiskunde en STEM' wordt aangeboden in de vorm van interactieve <strong><em>notebooks</em></strong
|
||||
>. Notebooks zijn <em>digitale documenten</em> die zowel uitvoerbare code bevatten als tekst, afbeeldingen, video, hyperlinks ...
|
||||
</p>
|
||||
<p><em>Nieuwe begrippen</em> worden aangebracht via tekstuele uitleg, video en afbeeldingen.</p>
|
||||
<p>Er zijn uitgewerkte <em>voorbeelden</em> met daarnaast ook kleine en grote <em>opdrachten</em>. In deze opdrachten zal je aangereikte code kunnen uitvoeren, maar ook zelf code opstellen.</p>
|
||||
<p>De code die in de notebooks gebruikt wordt, is Python versie 3. We kozen voor Python omdat dit een heel toegankelijke programmeertaal is, die vaak ook intuC/tief is.
|
||||
Python is bovendien bezig aan een opmars en wordt gebruikt door bedrijven, zoals Google, NASA, Netflix, Uber, AstraZeneca, Barco, Instagram en YouTube.</p>
|
||||
<p>We kozen voor notebooks omdat daar enkele belangrijke voordelen aan verbonden zijn: leerkrachten moeten geen geavanceerde installaties doen om de notebooks te gebruiken, leerkrachten kunnen verschillende soorten van lesinhouden aanbieden via C)C)n platform, de notebooks zijn interactief, leerlingen bouwen de oplossing van een probleem stap voor stap op in de notebook waardoor dat proces zichtbaar is voor de leerkracht (<a href="https://libstore.ugent.be/fulltxt/RUG01/003/151/437/RUG01-003151437_2023_0001_AC.pdf" target="_blank" title="">Jeroen Van der Hooft, 2023</a>).</p>
|
||||
<hr>
|
||||
<p>Klik je op onderstaande knop 'Open notebooks', dan word je doorgestuurd naar een andere website waar jouw persoonlijke notebooks ingeladen worden. (Dit kan even duren.)</p>
|
||||
<p>Links op het scherm vind je er twee bestanden met extensie <em>.ipynb</em>.
|
||||
Dit zijn de twee notebooks waarin je resp. een overzicht krijgt van de opbouw en mogelijkheden en hoe je er mee aan de slag kan. Dubbelklik op de bestandsnaam om een notebook te openen.</p>
|
||||
<p>
|
||||
Er zijn uitgewerkte <em>voorbeelden</em> met daarnaast ook kleine en grote <em>opdrachten</em>. In deze opdrachten zal je aangereikte code kunnen
|
||||
uitvoeren, maar ook zelf code opstellen.
|
||||
</p>
|
||||
<p>
|
||||
De code die in de notebooks gebruikt wordt, is Python versie 3. We kozen voor Python omdat dit een heel toegankelijke programmeertaal is, die vaak
|
||||
ook intuC/tief is. Python is bovendien bezig aan een opmars en wordt gebruikt door bedrijven, zoals Google, NASA, Netflix, Uber, AstraZeneca,
|
||||
Barco, Instagram en YouTube.
|
||||
</p>
|
||||
<p>
|
||||
We kozen voor notebooks omdat daar enkele belangrijke voordelen aan verbonden zijn: leerkrachten moeten geen geavanceerde installaties doen om de
|
||||
notebooks te gebruiken, leerkrachten kunnen verschillende soorten van lesinhouden aanbieden via C)C)n platform, de notebooks zijn interactief,
|
||||
leerlingen bouwen de oplossing van een probleem stap voor stap op in de notebook waardoor dat proces zichtbaar is voor de leerkracht (<a
|
||||
href="https://libstore.ugent.be/fulltxt/RUG01/003/151/437/RUG01-003151437_2023_0001_AC.pdf"
|
||||
target="_blank"
|
||||
title=""
|
||||
>Jeroen Van der Hooft, 2023</a
|
||||
>).
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
Klik je op onderstaande knop 'Open notebooks', dan word je doorgestuurd naar een andere website waar jouw persoonlijke notebooks ingeladen
|
||||
worden. (Dit kan even duren.)
|
||||
</p>
|
||||
<p>
|
||||
Links op het scherm vind je er twee bestanden met extensie <em>.ipynb</em>. Dit zijn de twee notebooks waarin je resp. een overzicht krijgt van de
|
||||
opbouw en mogelijkheden en hoe je er mee aan de slag kan. Dubbelklik op de bestandsnaam om een notebook te openen.
|
||||
</p>
|
||||
<p>Je ziet er ook een map <em>images</em> met de afbeeldingen die in de notebooks getoond worden.</p>
|
||||
<p>In deze eerste twee notebooks leer je hoe de notebooks zijn opgevat en hoe je ermee aan de slag kan.
|
||||
Na het doorlopen van beide notebooks heb je een goed idee van hoe onze Python notebooks zijn opgevat.</p>
|
||||
<p><a href="https://kiks.ilabt.imec.be/hub/tmplogin?id=0101" target="_blank" title="Notebooks Werking"><img alt="" src="Knop.png"></a></p>
|
||||
<p>
|
||||
In deze eerste twee notebooks leer je hoe de notebooks zijn opgevat en hoe je ermee aan de slag kan. Na het doorlopen van beide notebooks heb je
|
||||
een goed idee van hoe onze Python notebooks zijn opgevat.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://kiks.ilabt.imec.be/hub/tmplogin?id=0101" target="_blank" title="Notebooks Werking"><img alt="" src="Knop.png" /></a>
|
||||
</p>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {LearningObjectExample} from "../learning-object-example";
|
||||
import {LearningObject} from "../../../../src/entities/content/learning-object.entity";
|
||||
import {loadTestAsset} from "../../../test-utils/load-test-asset";
|
||||
import {EnvVars, getEnvVar} from "../../../../src/util/envvars";
|
||||
import {Language} from "../../../../src/entities/content/language";
|
||||
import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type";
|
||||
import { LearningObjectExample } from '../learning-object-example';
|
||||
import { LearningObject } from '../../../../src/entities/content/learning-object.entity';
|
||||
import { loadTestAsset } from '../../../test-utils/load-test-asset';
|
||||
import { EnvVars, getEnvVar } from '../../../../src/util/envvars';
|
||||
import { Language } from '../../../../src/entities/content/language';
|
||||
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type';
|
||||
|
||||
const example: LearningObjectExample = {
|
||||
createLearningObject: () => {
|
||||
|
@ -11,14 +11,14 @@ const example: LearningObjectExample = {
|
|||
learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_essay`;
|
||||
learningObject.language = Language.English;
|
||||
learningObject.version = 1;
|
||||
learningObject.title = "Essay question for testing";
|
||||
learningObject.description = "This essay question was only created for testing purposes.";
|
||||
learningObject.title = 'Essay question for testing';
|
||||
learningObject.description = 'This essay question was only created for testing purposes.';
|
||||
learningObject.contentType = DwengoContentType.GIFT;
|
||||
learningObject.content = loadTestAsset("learning-objects/test-essay/content.txt");
|
||||
learningObject.content = loadTestAsset('learning-objects/test-essay/content.txt');
|
||||
return learningObject;
|
||||
},
|
||||
createAttachment: {},
|
||||
getHTMLRendering: () => loadTestAsset("learning-objects/test-essay/rendering.html").toString()
|
||||
getHTMLRendering: () => loadTestAsset('learning-objects/test-essay/rendering.html').toString(),
|
||||
};
|
||||
|
||||
export default example;
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
<h2 id="gift-q1-title" class="gift-title">MC basic</h2>
|
||||
<p id="gift-q1-stem" class="gift-stem">Are you following along well with the class?</p>
|
||||
<div class="gift-choice-div">
|
||||
<input value="0" name="gift-q1-choices" id="gift-q1-choice-0" type="radio">
|
||||
<input value="0" name="gift-q1-choices" id="gift-q1-choice-0" type="radio" />
|
||||
<label for="gift-q1-choice-0">[object Object]</label>
|
||||
</div>
|
||||
<div class="gift-choice-div">
|
||||
<input value="1" name="gift-q1-choices" id="gift-q1-choice-1" type="radio">
|
||||
<input value="1" name="gift-q1-choices" id="gift-q1-choice-1" type="radio" />
|
||||
<label for="gift-q1-choice-1">[object Object]</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {LearningObjectExample} from "../learning-object-example";
|
||||
import {LearningObject} from "../../../../src/entities/content/learning-object.entity";
|
||||
import {loadTestAsset} from "../../../test-utils/load-test-asset";
|
||||
import {EnvVars, getEnvVar} from "../../../../src/util/envvars";
|
||||
import {Language} from "../../../../src/entities/content/language";
|
||||
import {DwengoContentType} from "../../../../src/services/learning-objects/processing/content-type";
|
||||
import { LearningObjectExample } from '../learning-object-example';
|
||||
import { LearningObject } from '../../../../src/entities/content/learning-object.entity';
|
||||
import { loadTestAsset } from '../../../test-utils/load-test-asset';
|
||||
import { EnvVars, getEnvVar } from '../../../../src/util/envvars';
|
||||
import { Language } from '../../../../src/entities/content/language';
|
||||
import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type';
|
||||
|
||||
const example: LearningObjectExample = {
|
||||
createLearningObject: () => {
|
||||
|
@ -11,14 +11,14 @@ const example: LearningObjectExample = {
|
|||
learningObject.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}test_multiple_choice`;
|
||||
learningObject.language = Language.English;
|
||||
learningObject.version = 1;
|
||||
learningObject.title = "Multiple choice question for testing";
|
||||
learningObject.description = "This multiple choice question was only created for testing purposes.";
|
||||
learningObject.title = 'Multiple choice question for testing';
|
||||
learningObject.description = 'This multiple choice question was only created for testing purposes.';
|
||||
learningObject.contentType = DwengoContentType.GIFT;
|
||||
learningObject.content = loadTestAsset("learning-objects/test-multiple-choice/content.txt");
|
||||
learningObject.content = loadTestAsset('learning-objects/test-multiple-choice/content.txt');
|
||||
return learningObject;
|
||||
},
|
||||
createAttachment: {},
|
||||
getHTMLRendering: () => loadTestAsset("learning-objects/test-multiple-choice/rendering.html").toString()
|
||||
getHTMLRendering: () => loadTestAsset('learning-objects/test-multiple-choice/rendering.html').toString(),
|
||||
};
|
||||
|
||||
export default example;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
type LearningPathExample = {
|
||||
createLearningPath: () => LearningPath
|
||||
createLearningPath: () => LearningPath;
|
||||
};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import {Language} from "../../../src/entities/content/language";
|
||||
import {LearningPathTransition} from "../../../src/entities/content/learning-path-transition.entity";
|
||||
import {LearningPathNode} from "../../../src/entities/content/learning-path-node.entity";
|
||||
import {LearningPath} from "../../../src/entities/content/learning-path.entity";
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity';
|
||||
import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity';
|
||||
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
|
||||
|
||||
export function createLearningPathTransition(node: LearningPathNode, transitionNumber: number, condition: string | null, to: LearningPathNode) {
|
||||
const trans = new LearningPathTransition();
|
||||
trans.node = node;
|
||||
trans.transitionNumber = transitionNumber;
|
||||
trans.condition = condition || "true";
|
||||
trans.condition = condition || 'true';
|
||||
trans.next = to;
|
||||
return trans;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import {LearningPath} from "../../../src/entities/content/learning-path.entity";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import {EnvVars, getEnvVar} from "../../../src/util/envvars";
|
||||
import {createLearningPathNode, createLearningPathTransition} from "./learning-path-utils";
|
||||
import {LearningPathNode} from "../../../src/entities/content/learning-path-node.entity";
|
||||
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
import { EnvVars, getEnvVar } from '../../../src/util/envvars';
|
||||
import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils';
|
||||
import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity';
|
||||
|
||||
function createNodes(learningPath: LearningPath): LearningPathNode[] {
|
||||
const nodes = [
|
||||
createLearningPathNode(learningPath, 0, "u_pn_werkingnotebooks", 3, Language.Dutch, true),
|
||||
createLearningPathNode(learningPath, 1, "pn_werkingnotebooks2", 3, Language.Dutch, false),
|
||||
createLearningPathNode(learningPath, 2, "pn_werkingnotebooks3", 3, Language.Dutch, false),
|
||||
createLearningPathNode(learningPath, 0, 'u_pn_werkingnotebooks', 3, Language.Dutch, true),
|
||||
createLearningPathNode(learningPath, 1, 'pn_werkingnotebooks2', 3, Language.Dutch, false),
|
||||
createLearningPathNode(learningPath, 2, 'pn_werkingnotebooks3', 3, Language.Dutch, false),
|
||||
];
|
||||
nodes[0].transitions.push(createLearningPathTransition(nodes[0], 0, "true", nodes[1]));
|
||||
nodes[1].transitions.push(createLearningPathTransition(nodes[1], 0, "true", nodes[2]));
|
||||
nodes[0].transitions.push(createLearningPathTransition(nodes[0], 0, 'true', nodes[1]));
|
||||
nodes[1].transitions.push(createLearningPathTransition(nodes[1], 0, 'true', nodes[2]));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,11 @@ const example: LearningPathExample = {
|
|||
const path = new LearningPath();
|
||||
path.language = Language.Dutch;
|
||||
path.hruid = `${getEnvVar(EnvVars.UserContentPrefix)}pn_werking`;
|
||||
path.title = "Werken met notebooks";
|
||||
path.description = "Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?";
|
||||
path.title = 'Werken met notebooks';
|
||||
path.description = 'Een korte inleiding tot Python notebooks. Hoe ga je gemakkelijk en efficiënt met de notebooks aan de slag?';
|
||||
path.nodes = createNodes(path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default example;
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
import {LearningPath} from "../../../src/entities/content/learning-path.entity";
|
||||
import {Language} from "../../../src/entities/content/language";
|
||||
import testMultipleChoiceExample from "../learning-objects/test-multiple-choice/test-multiple-choice-example";
|
||||
import {dummyLearningObject} from "../learning-objects/dummy/dummy-learning-object-example";
|
||||
import {createLearningPathNode, createLearningPathTransition} from "./learning-path-utils";
|
||||
import { LearningPath } from '../../../src/entities/content/learning-path.entity';
|
||||
import { Language } from '../../../src/entities/content/language';
|
||||
import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example';
|
||||
import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example';
|
||||
import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils';
|
||||
|
||||
const example: LearningPathExample = {
|
||||
createLearningPath: () => {
|
||||
const learningPath = new LearningPath();
|
||||
learningPath.hruid = "test_conditions";
|
||||
learningPath.hruid = 'test_conditions';
|
||||
learningPath.language = Language.English;
|
||||
learningPath.title = "Example learning path with conditional transitions";
|
||||
learningPath.description = "This learning path was made for the purpose of testing conditional transitions";
|
||||
learningPath.title = 'Example learning path with conditional transitions';
|
||||
learningPath.description = 'This learning path was made for the purpose of testing conditional transitions';
|
||||
|
||||
const branchingLearningObject = testMultipleChoiceExample.createLearningObject();
|
||||
const extraExerciseLearningObject = dummyLearningObject(
|
||||
"test_extra_exercise", Language.English, "Extra exercise (for students with difficulties)"
|
||||
'test_extra_exercise',
|
||||
Language.English,
|
||||
'Extra exercise (for students with difficulties)'
|
||||
).createLearningObject();
|
||||
const finalLearningObject = dummyLearningObject(
|
||||
"test_final_learning_object", Language.English, "Final exercise (for everyone)"
|
||||
'test_final_learning_object',
|
||||
Language.English,
|
||||
'Final exercise (for everyone)'
|
||||
).createLearningObject();
|
||||
|
||||
const branchingNode = createLearningPathNode(
|
||||
|
@ -48,21 +52,11 @@ const example: LearningPathExample = {
|
|||
const transitionToExtraExercise = createLearningPathTransition(
|
||||
branchingNode,
|
||||
0,
|
||||
"$[?(@[0] == 0)]", // The answer to the first question was the first one, which says that it is difficult for the student to follow along.
|
||||
'$[?(@[0] == 0)]', // The answer to the first question was the first one, which says that it is difficult for the student to follow along.
|
||||
extraExerciseNode
|
||||
);
|
||||
const directTransitionToFinal = createLearningPathTransition(
|
||||
branchingNode,
|
||||
1,
|
||||
"$[?(@[0] == 1)]",
|
||||
finalNode
|
||||
);
|
||||
const transitionExtraExerciseToFinal = createLearningPathTransition(
|
||||
extraExerciseNode,
|
||||
0,
|
||||
"true",
|
||||
finalNode
|
||||
);
|
||||
const directTransitionToFinal = createLearningPathTransition(branchingNode, 1, '$[?(@[0] == 1)]', finalNode);
|
||||
const transitionExtraExerciseToFinal = createLearningPathTransition(extraExerciseNode, 0, 'true', finalNode);
|
||||
|
||||
branchingNode.transitions = [transitionToExtraExercise, directTransitionToFinal];
|
||||
extraExerciseNode.transitions = [transitionExtraExerciseToFinal];
|
||||
|
@ -70,5 +64,5 @@ const example: LearningPathExample = {
|
|||
learningPath.nodes = [branchingNode, extraExerciseNode, finalNode];
|
||||
|
||||
return learningPath;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,63 +1,62 @@
|
|||
import {AssertionError} from "node:assert";
|
||||
import {LearningObject} from "../../src/entities/content/learning-object.entity";
|
||||
import {FilteredLearningObject, LearningPath} from "../../src/interfaces/learning-content";
|
||||
import {LearningPath as LearningPathEntity} from "../../src/entities/content/learning-path.entity"
|
||||
import { expect } from "vitest";
|
||||
import { AssertionError } from 'node:assert';
|
||||
import { LearningObject } from '../../src/entities/content/learning-object.entity';
|
||||
import { FilteredLearningObject, LearningPath } from '../../src/interfaces/learning-content';
|
||||
import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity';
|
||||
import { expect } from 'vitest';
|
||||
|
||||
// Ignored properties because they belang for example to the class, not to the entity itself.
|
||||
const IGNORE_PROPERTIES = ["parent"];
|
||||
const IGNORE_PROPERTIES = ['parent'];
|
||||
|
||||
/**
|
||||
* Checks if the actual entity from the database conforms to the entity that was added previously.
|
||||
* @param actual The actual entity retrieved from the database
|
||||
* @param expected The (previously added) entity we would expect to retrieve
|
||||
*/
|
||||
export function expectToBeCorrectEntity<T extends object>(
|
||||
actual: {entity: T, name?: string},
|
||||
expected: {entity: T, name?: string}
|
||||
): void {
|
||||
export function expectToBeCorrectEntity<T extends object>(actual: { entity: T; name?: string }, expected: { entity: T; name?: string }): void {
|
||||
if (!actual.name) {
|
||||
actual.name = "actual";
|
||||
actual.name = 'actual';
|
||||
}
|
||||
if (!expected.name) {
|
||||
expected.name = "expected";
|
||||
expected.name = 'expected';
|
||||
}
|
||||
for (const property in expected.entity) {
|
||||
if (
|
||||
property !in IGNORE_PROPERTIES
|
||||
&& expected.entity[property] !== undefined // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants.
|
||||
&& typeof expected.entity[property] !== "function" // Functions obviously are not persisted via the database
|
||||
property! in IGNORE_PROPERTIES &&
|
||||
expected.entity[property] !== undefined && // If we don't expect a certain value for a property, we assume it can be filled in by the database however it wants.
|
||||
typeof expected.entity[property] !== 'function' // Functions obviously are not persisted via the database
|
||||
) {
|
||||
if (!actual.entity.hasOwnProperty(property)) {
|
||||
throw new AssertionError({
|
||||
message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`
|
||||
message: `${expected.name} has defined property ${property}, but ${actual.name} is missing it.`,
|
||||
});
|
||||
}
|
||||
if (typeof expected.entity[property] === "boolean") { // Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database.
|
||||
if (typeof expected.entity[property] === 'boolean') {
|
||||
// Sometimes, booleans get represented by numbers 0 and 1 in the objects actual from the database.
|
||||
if (Boolean(expected.entity[property]) !== Boolean(actual.entity[property])) {
|
||||
throw new AssertionError({
|
||||
message: `${property} was ${expected.entity[property]} in ${expected.name},
|
||||
but ${actual.entity[property]} (${Boolean(expected.entity[property])}) in ${actual.name}`
|
||||
but ${actual.entity[property]} (${Boolean(expected.entity[property])}) in ${actual.name}`,
|
||||
});
|
||||
}
|
||||
} else if (typeof expected.entity[property] !== typeof actual.entity[property]) {
|
||||
throw new AssertionError({
|
||||
message: `${property} has type ${typeof expected.entity[property]} in ${expected.name}, but type ${typeof actual.entity[property]} in ${actual.name}.`
|
||||
message: `${property} has type ${typeof expected.entity[property]} in ${expected.name}, but type ${typeof actual.entity[property]} in ${actual.name}.`,
|
||||
});
|
||||
} else if (typeof expected.entity[property] === "object") {
|
||||
} else if (typeof expected.entity[property] === 'object') {
|
||||
expectToBeCorrectEntity(
|
||||
{
|
||||
name: actual.name + "." + property,
|
||||
entity: actual.entity[property] as object
|
||||
}, {
|
||||
name: expected.name + "." + property,
|
||||
entity: expected.entity[property] as object
|
||||
name: actual.name + '.' + property,
|
||||
entity: actual.entity[property] as object,
|
||||
},
|
||||
{
|
||||
name: expected.name + '.' + property,
|
||||
entity: expected.entity[property] as object,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (expected.entity[property] !== actual.entity[property]) {
|
||||
throw new AssertionError({
|
||||
message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${actual.entity[property]} in ${actual.name}`
|
||||
message: `${property} was ${expected.entity[property]} in ${expected.name}, but ${actual.entity[property]} in ${actual.name}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +77,7 @@ export function expectToBeCorrectFilteredLearningObject(filtered: FilteredLearni
|
|||
expect(filtered.key).toEqual(original.hruid);
|
||||
expect(filtered.targetAges).toEqual(original.targetAges);
|
||||
expect(filtered.title).toEqual(original.title);
|
||||
expect(Boolean(filtered.teacherExclusive)).toEqual(original.teacherExclusive) // !!: Workaround: MikroORM with SQLite returns 0 and 1 instead of booleans.
|
||||
expect(Boolean(filtered.teacherExclusive)).toEqual(original.teacherExclusive); // !!: Workaround: MikroORM with SQLite returns 0 and 1 instead of booleans.
|
||||
expect(filtered.skosConcepts).toEqual(original.skosConcepts);
|
||||
expect(filtered.estimatedTime).toEqual(original.estimatedTime);
|
||||
expect(filtered.educationalGoals).toEqual(original.educationalGoals);
|
||||
|
@ -112,10 +111,10 @@ export function expectToBeCorrectLearningPath(
|
|||
expect(learningPath.description).toEqual(expectedEntity.description);
|
||||
expect(learningPath.title).toEqual(expectedEntity.title);
|
||||
|
||||
const keywords = new Set(learningObjectsOnPath.flatMap(it => it.keywords || []));
|
||||
expect(new Set(learningPath.keywords.split(' '))).toEqual(keywords)
|
||||
const keywords = new Set(learningObjectsOnPath.flatMap((it) => it.keywords || []));
|
||||
expect(new Set(learningPath.keywords.split(' '))).toEqual(keywords);
|
||||
|
||||
const targetAges = new Set(learningObjectsOnPath.flatMap(it => it.targetAges || []));
|
||||
const targetAges = new Set(learningObjectsOnPath.flatMap((it) => it.targetAges || []));
|
||||
expect(new Set(learningPath.target_ages)).toEqual(targetAges);
|
||||
expect(learningPath.min_age).toEqual(Math.min(...targetAges));
|
||||
expect(learningPath.max_age).toEqual(Math.max(...targetAges));
|
||||
|
@ -124,9 +123,9 @@ export function expectToBeCorrectLearningPath(
|
|||
expect(learningPath.image || null).toEqual(expectedEntity.image);
|
||||
|
||||
const expectedLearningPathNodes = new Map(
|
||||
expectedEntity.nodes.map(node => [
|
||||
{learningObjectHruid: node.learningObjectHruid, language: node.language, version: node.version},
|
||||
{startNode: node.startNode, transitions: node.transitions}
|
||||
expectedEntity.nodes.map((node) => [
|
||||
{ learningObjectHruid: node.learningObjectHruid, language: node.language, version: node.version },
|
||||
{ startNode: node.startNode, transitions: node.transitions },
|
||||
])
|
||||
);
|
||||
|
||||
|
@ -134,31 +133,18 @@ export function expectToBeCorrectLearningPath(
|
|||
const nodeKey = {
|
||||
learningObjectHruid: node.learningobject_hruid,
|
||||
language: node.language,
|
||||
version: node.version
|
||||
version: node.version,
|
||||
};
|
||||
expect(expectedLearningPathNodes.keys()).toContainEqual(nodeKey);
|
||||
const expectedNode = [...expectedLearningPathNodes.entries()]
|
||||
.filter(([key, _]) =>
|
||||
key.learningObjectHruid === nodeKey.learningObjectHruid
|
||||
&& key.language === node.language
|
||||
&& key.version === node.version
|
||||
)[0][1]
|
||||
const expectedNode = [...expectedLearningPathNodes.entries()].filter(
|
||||
([key, _]) => key.learningObjectHruid === nodeKey.learningObjectHruid && key.language === node.language && key.version === node.version
|
||||
)[0][1];
|
||||
expect(node.start_node).toEqual(expectedNode?.startNode);
|
||||
|
||||
expect(
|
||||
new Set(node.transitions.map(it => it.next.hruid))
|
||||
).toEqual(
|
||||
new Set(expectedNode.transitions.map(it => it.next.learningObjectHruid))
|
||||
);
|
||||
expect(
|
||||
new Set(node.transitions.map(it => it.next.language))
|
||||
).toEqual(
|
||||
new Set(expectedNode.transitions.map(it => it.next.language))
|
||||
);
|
||||
expect(
|
||||
new Set(node.transitions.map(it => it.next.version))
|
||||
).toEqual(
|
||||
new Set(expectedNode.transitions.map(it => it.next.version))
|
||||
expect(new Set(node.transitions.map((it) => it.next.hruid))).toEqual(
|
||||
new Set(expectedNode.transitions.map((it) => it.next.learningObjectHruid))
|
||||
);
|
||||
expect(new Set(node.transitions.map((it) => it.next.language))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.language)));
|
||||
expect(new Set(node.transitions.map((it) => it.next.version))).toEqual(new Set(expectedNode.transitions.map((it) => it.next.version)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import fs from "fs";
|
||||
import path from "node:path";
|
||||
import fs from 'fs';
|
||||
import path from 'node:path';
|
||||
|
||||
/**
|
||||
* Load the asset at the given path.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue