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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue