feat(backend): databaseLearningPathProvider.fetchLearningPaths geïmplementeerd
This commit is contained in:
parent
0fe42f73b2
commit
02be44fe53
6 changed files with 161 additions and 11 deletions
|
@ -4,6 +4,6 @@ export class LearningObjectIdentifier {
|
|||
constructor(
|
||||
public hruid: string,
|
||||
public language: Language,
|
||||
public version: string
|
||||
public version: number
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,12 @@ export class LearningPathNode {
|
|||
|
||||
@Embedded({ entity: () => LearningPathTransition, array: true })
|
||||
transitions!: LearningPathTransition[];
|
||||
|
||||
@Property({ length: 3 })
|
||||
createdAt: Date = new Date();
|
||||
|
||||
@Property({ length: 3, onUpdate: () => new Date() })
|
||||
updatedAt: Date = new Date();
|
||||
}
|
||||
|
||||
@Embeddable()
|
||||
|
|
|
@ -14,7 +14,7 @@ import {NotFoundError} from "@mikro-orm/core";
|
|||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
function filter(learningObject: LearningObject | null): FilteredLearningObject | null {
|
||||
function convertLearningObject(learningObject: LearningObject | null): FilteredLearningObject | null {
|
||||
if (!learningObject) {
|
||||
return null;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
*/
|
||||
async getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> {
|
||||
const learningObject = await findLearningObjectEntityById(id);
|
||||
return filter(learningObject);
|
||||
return convertLearningObject(learningObject);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -110,7 +110,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
return learningObject;
|
||||
})
|
||||
);
|
||||
return learningObjects.filter(it => it !== null);
|
||||
return learningObjects.filter(it => it !== null); // TODO: Determine this based on the submissions of the user.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,134 @@
|
|||
import {LearningPathProvider} from "./learning-path-provider";
|
||||
import {LearningPath, LearningPathResponse} from "../../interfaces/learning-content";
|
||||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectNode,
|
||||
LearningPath,
|
||||
LearningPathResponse,
|
||||
Transition
|
||||
} from "../../interfaces/learning-content";
|
||||
import {
|
||||
LearningPath as LearningPathEntity,
|
||||
LearningPathNode,
|
||||
LearningPathTransition
|
||||
} 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";
|
||||
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
/**
|
||||
* Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its
|
||||
* corresponding learning object.
|
||||
* @param nodes The nodes to find the learning object for.
|
||||
*/
|
||||
async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Map<LearningPathNode, FilteredLearningObject>> {
|
||||
// Fetching the corresponding learning object for each of the nodes and creating a map that maps each node to
|
||||
// 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]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
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>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 targetAges =
|
||||
nodesToLearningObjects.values().flatMap(it => it.targetAges || []).toArray();
|
||||
|
||||
const keywords =
|
||||
nodesToLearningObjects.values().flatMap(it => it.keywords || []).toArray();
|
||||
|
||||
return {
|
||||
_id: `${learningPath.hruid}/${learningPath.language}`, // for backwards compatibility with the original Dwengo API.
|
||||
__order: order,
|
||||
hruid: learningPath.hruid,
|
||||
language: learningPath.language,
|
||||
description: learningPath.description,
|
||||
image: learningPath.image,
|
||||
title: learningPath.title,
|
||||
nodes: convertNodes(nodesToLearningObjects),
|
||||
num_nodes: learningPath.nodes.length,
|
||||
num_nodes_left: learningPath.nodes.length, // TODO: Adjust when submissions are added.
|
||||
keywords: keywords.join(' '),
|
||||
target_ages: targetAges,
|
||||
max_age: Math.max(...targetAges),
|
||||
min_age: Math.min(...targetAges)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function converting pairs of learning path nodes (as represented in the database) and the corresponding
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function which converts a transition in the database representation to a transition in the representation
|
||||
* the Dwengo API uses.
|
||||
*
|
||||
* @param transition
|
||||
* @param index
|
||||
* @param nodesToLearningObjects
|
||||
*/
|
||||
function convertTransition(
|
||||
transition: LearningPathTransition,
|
||||
index: number,
|
||||
nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>
|
||||
): 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!`)
|
||||
} else {
|
||||
return {
|
||||
_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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Service providing access to data about learning paths from the database.
|
||||
|
@ -8,8 +137,21 @@ const databaseLearningPathProvider: LearningPathProvider = {
|
|||
/**
|
||||
* Fetch the learning paths with the given hruids from the database.
|
||||
*/
|
||||
fetchLearningPaths(hruids: string[], language: string, source: string): Promise<LearningPathResponse> {
|
||||
throw new Error("Not yet implemented"); // TODO
|
||||
async fetchLearningPaths(hruids: string[], language: Language, source: string): Promise<LearningPathResponse> {
|
||||
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))
|
||||
);
|
||||
|
||||
return {
|
||||
success: filteredLearningPaths.length > 0,
|
||||
data: await Promise.all(filteredLearningPaths),
|
||||
source
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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.
|
||||
|
@ -7,10 +8,10 @@ export interface LearningPathProvider {
|
|||
/**
|
||||
* Fetch the learning paths with the given hruids from the data source.
|
||||
*/
|
||||
fetchLearningPaths(hruids: string[], language: string, source: string): Promise<LearningPathResponse>;
|
||||
fetchLearningPaths(hruids: string[], language: Language, source: string): Promise<LearningPathResponse>;
|
||||
|
||||
/**
|
||||
* Search learning paths in the data source using the given search string.
|
||||
*/
|
||||
searchLearningPaths(query: string, language: string): Promise<LearningPath[]>;
|
||||
searchLearningPaths(query: string, language: Language): Promise<LearningPath[]>;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
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]
|
||||
|
@ -16,7 +17,7 @@ const learningPathService = {
|
|||
/**
|
||||
* Fetch the learning paths with the given hruids from the data source.
|
||||
*/
|
||||
async fetchLearningPaths(hruids: string[], language: string, source: string): Promise<LearningPathResponse> {
|
||||
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));
|
||||
|
||||
|
@ -35,7 +36,7 @@ const learningPathService = {
|
|||
/**
|
||||
* Search learning paths in the data source using the given search string.
|
||||
*/
|
||||
async searchLearningPaths(query: string, language: string): Promise<LearningPath[]> {
|
||||
async searchLearningPaths(query: string, language: Language): Promise<LearningPath[]> {
|
||||
const providerResponses = await Promise.all(
|
||||
allProviders.map(
|
||||
provider => provider.searchLearningPaths(query, language)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue