2025SELab2-project-Dwengo/frontend/src/data-objects/learning-path.ts
2025-04-01 01:31:11 +02:00

128 lines
4.3 KiB
TypeScript

import type {Language} from "@/data-objects/language.ts";
export interface LearningPathDTO {
language: string;
hruid: string;
title: string;
description: string;
image?: string; // Image might be missing, so it's optional
num_nodes: number;
num_nodes_left: number;
nodes: LearningPathNodeDTO[];
keywords: string;
target_ages: number[];
min_age: number;
max_age: number;
__order: number;
}
interface LearningPathNodeDTO {
_id: string;
learningobject_hruid: string;
version: number;
language: Language;
start_node?: boolean;
transitions: LearningPathTransitionDTO[];
created_at: string;
updatedAt: string;
done?: boolean; // True if a submission exists for this node by the user for whom the learning path is customized.
}
interface LearningPathTransitionDTO {
default: boolean;
_id: string;
next: {
_id: string;
hruid: string;
version: number;
language: string;
};
}
export class LearningPathNode {
constructor(
public readonly learningobjectHruid: string,
public readonly version: number,
public readonly language: Language,
public readonly transitions: {next: LearningPathNode, default: boolean}[],
public readonly createdAt: Date,
public readonly updatedAt: Date,
public readonly done: boolean = false
) {
}
static fromDTOAndOtherNodes(dto: LearningPathNodeDTO, otherNodes: LearningPathNodeDTO[]): LearningPathNode {
return new LearningPathNode(
dto.learningobject_hruid,
dto.version,
dto.language,
dto.transitions.map(transDto => {
let nextNodeDto = otherNodes.filter(it =>
it.learningobject_hruid === transDto.next.hruid
&& it.language === transDto.next.language
&& it.version === transDto.next.version
);
if (nextNodeDto.length !== 1) {
throw new Error(`Invalid learning path! There is a transition to node`
+ `${transDto.next.hruid}/${transDto.next.language}/${transDto.next.version}, but there are`
+ `${nextNodeDto.length} such nodes.`);
}
return {
next: LearningPathNode.fromDTOAndOtherNodes(nextNodeDto[0], otherNodes),
default: transDto.default
}
}),
new Date(dto.created_at),
new Date(dto.updatedAt),
dto.done
)
}
}
export class LearningPath {
constructor(
public readonly language: string,
public readonly hruid: string,
public readonly title: string,
public readonly description: string,
public readonly amountOfNodes: number,
public readonly amountOfNodesLeft: number,
public readonly keywords: string[],
public readonly targetAges: {min: number; max: number},
public readonly startNode: LearningPathNode,
public readonly image?: string // Image might be missing, so it's optional
) {
}
public get nodesAsList(): LearningPathNode[] {
let list: LearningPathNode[] = [];
let currentNode = this.startNode;
while (currentNode) {
list.push(currentNode);
currentNode = currentNode.transitions.filter(it => it.default)[0]?.next
|| currentNode.transitions[0]?.next;
}
return list;
}
static fromDTO(dto: LearningPathDTO): LearningPath {
let startNodeDto = dto.nodes.filter(it => it.start_node === true);
if (startNodeDto.length !== 1) {
throw new Error(`Invalid learning path: ${dto.hruid}/${dto.language}!
Expected precisely one start node, but there were ${startNodeDto.length}.`);
}
return new LearningPath(
dto.language,
dto.hruid,
dto.title,
dto.description,
dto.num_nodes,
dto.num_nodes_left,
dto.keywords.split(' '),
{min: dto.min_age, max: dto.max_age},
LearningPathNode.fromDTOAndOtherNodes(startNodeDto[0], dto.nodes),
dto.image
)
}
}