feat(backend): DatabaseLearningObjectProvider geïmplementeerd.
This commit is contained in:
parent
463c8c9fc0
commit
bbcf22e4ea
8 changed files with 157 additions and 32 deletions
|
@ -4,6 +4,51 @@ import {
|
|||
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";
|
||||
|
||||
const learningObjectRepo = getLearningObjectRepository();
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
function filter(learningObject: LearningObject | null): FilteredLearningObject | null {
|
||||
if (!learningObject) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
key: learningObject.hruid,
|
||||
_id: learningObject.uuid, // For backwards compatibility with the original Dwengo API, we also populate the _id field.
|
||||
uuid: learningObject.uuid,
|
||||
language: learningObject.language,
|
||||
version: learningObject.version,
|
||||
title: learningObject.title,
|
||||
description: learningObject.description,
|
||||
htmlUrl: getUrlStringForLearningObject(learningObject),
|
||||
available: learningObject.available,
|
||||
contentType: learningObject.contentType,
|
||||
contentLocation: learningObject.contentLocation,
|
||||
difficulty: learningObject.difficulty || 1,
|
||||
estimatedTime: learningObject.estimatedTime,
|
||||
keywords: learningObject.keywords,
|
||||
educationalGoals: learningObject.educationalGoals,
|
||||
returnValue: {
|
||||
callback_url: learningObject.returnValue.callbackUrl,
|
||||
callback_schema: JSON.parse(learningObject.returnValue.callbackSchema)
|
||||
},
|
||||
skosConcepts: learningObject.skosConcepts,
|
||||
targetAges: learningObject.targetAges || [],
|
||||
teacherExclusive: learningObject.teacherExclusive
|
||||
}
|
||||
}
|
||||
|
||||
function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise<LearningObject | null> {
|
||||
return learningObjectRepo.findLatestByHruidAndLanguage(
|
||||
id.hruid, id.language as Language
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Service providing access to data about learning objects from the database
|
||||
|
@ -12,31 +57,61 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
/**
|
||||
* Fetches a single learning object by its HRUID
|
||||
*/
|
||||
getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> {
|
||||
return Promise.resolve(null); // TODO
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch full learning object data (metadata)
|
||||
*/
|
||||
getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
return Promise.resolve(null); // TODO
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch only learning object HRUIDs
|
||||
*/
|
||||
getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> {
|
||||
return Promise.resolve([]);// TODO
|
||||
async getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> {
|
||||
const learningObject = await findLearningObjectEntityById(id);
|
||||
return filter(learningObject);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain a HTML-rendering of the learning object with the given identifier (as a string).
|
||||
*/
|
||||
getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> {
|
||||
return Promise.resolve([]); // TODO
|
||||
}
|
||||
async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
const learningObject = await learningObjectRepo.findLatestByHruidAndLanguage(
|
||||
id.hruid, id.language as Language
|
||||
);
|
||||
if (!learningObject) {
|
||||
return null;
|
||||
}
|
||||
return await processingService.render(
|
||||
learningObject,
|
||||
(id) => findLearningObjectEntityById(id)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the HRUIDs of all learning objects on this path.
|
||||
*/
|
||||
async getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> {
|
||||
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.");
|
||||
}
|
||||
return learningPath.nodes.map(it => it.learningObjectHruid); // TODO: Determine this based on the submissions of the user.
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the full metadata of all learning objects on this path.
|
||||
*/
|
||||
async getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> {
|
||||
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.");
|
||||
}
|
||||
const learningObjects = await Promise.all(
|
||||
learningPath.nodes.map(it => {
|
||||
const learningObject = this.getLearningObjectById({
|
||||
hruid: it.learningObjectHruid,
|
||||
language: it.language,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export default databaseLearningObjectProvider;
|
||||
|
|
|
@ -3,7 +3,7 @@ import AudioProcessor from "../audio/audio-processor.js";
|
|||
import ExternProcessor from "../extern/extern-processor.js";
|
||||
import InlineImageProcessor from "../image/inline-image-processor.js";
|
||||
import {RendererObject, Tokens} from "marked";
|
||||
import {getUrlStringForLearningObject, isValidHttpUrl} from "../../../../util/links";
|
||||
import {getUrlStringForLearningObjectHTML, isValidHttpUrl} from "../../../../util/links";
|
||||
import {ProcessingError} from "../processing-error";
|
||||
import {LearningObjectIdentifier} from "../../../../interfaces/learning-content";
|
||||
import {Language} from "../../../../entities/content/language";
|
||||
|
@ -58,7 +58,7 @@ function extractLearningObjectIdFromHref(href: string): LearningObjectIdentifier
|
|||
// link to learning-object
|
||||
const learningObjectId = extractLearningObjectIdFromHref(href);
|
||||
return `
|
||||
<a href="${getUrlStringForLearningObject(learningObjectId)}]" target="_blank" title="${title}">${text}</a>
|
||||
<a href="${getUrlStringForLearningObjectHTML(learningObjectId)}]" target="_blank" title="${title}">${text}</a>
|
||||
`;
|
||||
} else {
|
||||
// any other link
|
||||
|
|
|
@ -15,8 +15,10 @@ 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' />";
|
||||
|
||||
class ProcessingService {
|
||||
private processors!: Map<DwengoContentType, Processor<any>>;
|
||||
|
@ -48,15 +50,28 @@ class ProcessingService {
|
|||
* by placeholders.
|
||||
* @returns Rendered HTML for this LearningObject as a string.
|
||||
*/
|
||||
render(learningObject: LearningObject, fetchEmbeddedLearningObjects?: (loId: LearningObjectIdentifier) => LearningObject): string {
|
||||
async render(
|
||||
learningObject: LearningObject,
|
||||
fetchEmbeddedLearningObjects?: (loId: LearningObjectIdentifier) => Promise<LearningObject | null>
|
||||
): Promise<string> {
|
||||
let html = this.processors.get(learningObject.contentType)!.renderLearningObject(learningObject);
|
||||
if (fetchEmbeddedLearningObjects) {
|
||||
// Replace all embedded learning objects.
|
||||
return html.replace(
|
||||
return replaceAsync(
|
||||
html,
|
||||
EMBEDDED_LEARNING_OBJECT_PLACEHOLDER,
|
||||
(_, hruid: string, language: string, version: string): string => {
|
||||
async (_, hruid: string, language: string, version: string): Promise<string> => {
|
||||
// Fetch the embedded learning object...
|
||||
const learningObject = fetchEmbeddedLearningObjects({hruid, language: language as Language, version})
|
||||
const learningObject = await fetchEmbeddedLearningObjects({
|
||||
hruid,
|
||||
language: language as Language,
|
||||
version: parseInt(version)
|
||||
});
|
||||
|
||||
// If it does not exist, replace it by a placeholder.
|
||||
if (!learningObject) {
|
||||
return LEARNING_OBJECT_DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
// ... and render it.
|
||||
return this.render(learningObject);
|
||||
|
@ -67,4 +82,4 @@ class ProcessingService {
|
|||
}
|
||||
}
|
||||
|
||||
export default ProcessingService;
|
||||
export default new ProcessingService();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue