Merge branch 'dev' into refactor/common
This commit is contained in:
commit
a4408b5bc0
145 changed files with 3437 additions and 2822 deletions
|
@ -4,7 +4,7 @@ import { Attachment } from '../../entities/content/attachment.entity.js';
|
|||
import { LearningObjectIdentifier } from 'dwengo-1-common/src/interfaces/learning-content';
|
||||
|
||||
const attachmentService = {
|
||||
getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> {
|
||||
async getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> {
|
||||
const attachmentRepo = getAttachmentRepository();
|
||||
|
||||
if (learningObjectId.version) {
|
||||
|
|
|
@ -41,10 +41,10 @@ function convertLearningObject(learningObject: LearningObject | null): FilteredL
|
|||
};
|
||||
}
|
||||
|
||||
function findLearningObjectEntityById(id: LearningObjectIdentifier): Promise<LearningObject | null> {
|
||||
async 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,11 +65,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);
|
||||
if (!learningObject) {
|
||||
return null;
|
||||
}
|
||||
return await processingService.render(learningObject, (id) => findLearningObjectEntityById(id));
|
||||
return await processingService.render(learningObject, async (id) => findLearningObjectEntityById(id));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -96,7 +96,7 @@ const databaseLearningObjectProvider: LearningObjectProvider = {
|
|||
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(async (it) => {
|
||||
const learningObject = learningObjectService.getLearningObjectById({
|
||||
hruid: it.learningObjectHruid,
|
||||
language: it.language,
|
||||
|
|
|
@ -90,7 +90,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
metadataUrl,
|
||||
`Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`,
|
||||
{
|
||||
params: id,
|
||||
params: { ...id },
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -123,7 +123,7 @@ 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,
|
||||
params: { ...id },
|
||||
});
|
||||
|
||||
if (!html) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provider.js';
|
||||
import { LearningObjectProvider } from './learning-object-provider.js';
|
||||
import { EnvVars, getEnvVar } from '../../util/envvars.js';
|
||||
import { envVars, getEnvVar } from '../../util/envVars.js';
|
||||
import databaseLearningObjectProvider from './database-learning-object-provider.js';
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from 'dwengo-1-common/src/interfaces/learning-content';
|
||||
|
||||
function getProvider(id: LearningObjectIdentifier): LearningObjectProvider {
|
||||
if (id.hruid.startsWith(getEnvVar(EnvVars.UserContentPrefix))) {
|
||||
if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) {
|
||||
return databaseLearningObjectProvider;
|
||||
}
|
||||
return dwengoApiLearningObjectProvider;
|
||||
|
@ -18,28 +18,28 @@ const learningObjectService = {
|
|||
/**
|
||||
* Fetches a single learning object by its HRUID
|
||||
*/
|
||||
getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> {
|
||||
async getLearningObjectById(id: LearningObjectIdentifier): Promise<FilteredLearningObject | null> {
|
||||
return getProvider(id).getLearningObjectById(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch full learning object data (metadata)
|
||||
*/
|
||||
getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> {
|
||||
async getLearningObjectsFromPath(id: LearningPathIdentifier): Promise<FilteredLearningObject[]> {
|
||||
return getProvider(id).getLearningObjectsFromPath(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch only learning object HRUIDs
|
||||
*/
|
||||
getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> {
|
||||
async getLearningObjectIdsFromPath(id: LearningPathIdentifier): Promise<string[]> {
|
||||
return getProvider(id).getLearningObjectIdsFromPath(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain a HTML-rendering of the learning object with the given identifier (as a string).
|
||||
*/
|
||||
getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> {
|
||||
return getProvider(id).getLearningObjectHTML(id);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ class AudioProcessor extends StringProcessor {
|
|||
super(DwengoContentType.AUDIO_MPEG);
|
||||
}
|
||||
|
||||
protected renderFn(audioUrl: string): string {
|
||||
override renderFn(audioUrl: string): string {
|
||||
return DOMPurify.sanitize(`<audio controls>
|
||||
<source src="${audioUrl}" type=${type}>
|
||||
Your browser does not support the audio element.
|
||||
|
|
|
@ -15,7 +15,7 @@ class ExternProcessor extends StringProcessor {
|
|||
super(DwengoContentType.EXTERN);
|
||||
}
|
||||
|
||||
override renderFn(externURL: string) {
|
||||
override renderFn(externURL: string): string {
|
||||
if (!isValidHttpUrl(externURL)) {
|
||||
throw new ProcessingError('The url is not valid: ' + externURL);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class GiftProcessor extends StringProcessor {
|
|||
super(DwengoContentType.GIFT);
|
||||
}
|
||||
|
||||
override renderFn(giftString: string) {
|
||||
override renderFn(giftString: string): string {
|
||||
const quizQuestions: GIFTQuestion[] = parse(giftString);
|
||||
|
||||
let html = "<div class='learning-object-gift'>\n";
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Category } from 'gift-pegjs';
|
|||
import { ProcessingError } from '../../processing-error.js';
|
||||
|
||||
export class CategoryQuestionRenderer extends GIFTQuestionRenderer<Category> {
|
||||
render(question: Category, questionNumber: number): string {
|
||||
override render(_question: Category, _questionNumber: number): string {
|
||||
throw new ProcessingError("The question type 'Category' is not supported yet!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Description } from 'gift-pegjs';
|
|||
import { ProcessingError } from '../../processing-error.js';
|
||||
|
||||
export class DescriptionQuestionRenderer extends GIFTQuestionRenderer<Description> {
|
||||
render(question: Description, questionNumber: number): string {
|
||||
override render(_question: Description, _questionNumber: number): string {
|
||||
throw new ProcessingError("The question type 'Description' is not supported yet!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GIFTQuestionRenderer } from './gift-question-renderer.js';
|
|||
import { Essay } from 'gift-pegjs';
|
||||
|
||||
export class EssayQuestionRenderer extends GIFTQuestionRenderer<Essay> {
|
||||
render(question: Essay, questionNumber: number): string {
|
||||
override render(question: Essay, questionNumber: number): string {
|
||||
let renderedHtml = '';
|
||||
if (question.title) {
|
||||
renderedHtml += `<h2 class='gift-title' id='gift-q${questionNumber}-title'>${question.title}</h2>\n`;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Matching } from 'gift-pegjs';
|
|||
import { ProcessingError } from '../../processing-error.js';
|
||||
|
||||
export class MatchingQuestionRenderer extends GIFTQuestionRenderer<Matching> {
|
||||
render(question: Matching, questionNumber: number): string {
|
||||
override render(_question: Matching, _questionNumber: number): string {
|
||||
throw new ProcessingError("The question type 'Matching' is not supported yet!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GIFTQuestionRenderer } from './gift-question-renderer.js';
|
|||
import { MultipleChoice } from 'gift-pegjs';
|
||||
|
||||
export class MultipleChoiceQuestionRenderer extends GIFTQuestionRenderer<MultipleChoice> {
|
||||
render(question: MultipleChoice, questionNumber: number): string {
|
||||
override render(question: MultipleChoice, questionNumber: number): string {
|
||||
let renderedHtml = '';
|
||||
if (question.title) {
|
||||
renderedHtml += `<h2 class='gift-title' id='gift-q${questionNumber}-title'>${question.title}</h2>\n`;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Numerical } from 'gift-pegjs';
|
|||
import { ProcessingError } from '../../processing-error.js';
|
||||
|
||||
export class NumericalQuestionRenderer extends GIFTQuestionRenderer<Numerical> {
|
||||
render(question: Numerical, questionNumber: number): string {
|
||||
override render(_question: Numerical, _questionNumber: number): string {
|
||||
throw new ProcessingError("The question type 'Numerical' is not supported yet!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ShortAnswer } from 'gift-pegjs';
|
|||
import { ProcessingError } from '../../processing-error.js';
|
||||
|
||||
export class ShortQuestionRenderer extends GIFTQuestionRenderer<ShortAnswer> {
|
||||
render(question: ShortAnswer, questionNumber: number): string {
|
||||
override render(_question: ShortAnswer, _questionNumber: number): string {
|
||||
throw new ProcessingError("The question type 'ShortAnswer' is not supported yet!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { TrueFalse } from 'gift-pegjs';
|
|||
import { ProcessingError } from '../../processing-error.js';
|
||||
|
||||
export class TrueFalseQuestionRenderer extends GIFTQuestionRenderer<TrueFalse> {
|
||||
render(question: TrueFalse, questionNumber: number): string {
|
||||
override render(_question: TrueFalse, _questionNumber: number): string {
|
||||
throw new ProcessingError("The question type 'TrueFalse' is not supported yet!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class BlockImageProcessor extends InlineImageProcessor {
|
|||
super();
|
||||
}
|
||||
|
||||
override renderFn(imageUrl: string) {
|
||||
override renderFn(imageUrl: string): string {
|
||||
const inlineHtml = super.render(imageUrl);
|
||||
return DOMPurify.sanitize(`<div>${inlineHtml}</div>`);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class InlineImageProcessor extends StringProcessor {
|
|||
super(contentType);
|
||||
}
|
||||
|
||||
override renderFn(imageUrl: string) {
|
||||
override renderFn(imageUrl: string): string {
|
||||
if (!isValidHttpUrl(imageUrl)) {
|
||||
throw new ProcessingError(`Image URL is invalid: ${imageUrl}`);
|
||||
}
|
||||
|
|
|
@ -14,26 +14,24 @@ class MarkdownProcessor extends StringProcessor {
|
|||
super(DwengoContentType.TEXT_MARKDOWN);
|
||||
}
|
||||
|
||||
override renderFn(mdText: string) {
|
||||
let html = '';
|
||||
try {
|
||||
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);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
replaceLinks(html: string) {
|
||||
static replaceLinks(html: string): 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)
|
||||
(_match: string, src: string, _alt: string, _altText: string, _title: string, _titleText: string) => proc.render(src)
|
||||
);
|
||||
return html;
|
||||
}
|
||||
|
||||
override renderFn(mdText: string): string {
|
||||
try {
|
||||
marked.use({ renderer: dwengoMarkedRenderer });
|
||||
const html = marked(mdText, { async: false });
|
||||
return MarkdownProcessor.replaceLinks(html); // Replace html image links path
|
||||
} catch (e: unknown) {
|
||||
throw new ProcessingError('Unknown error while processing markdown: ' + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { MarkdownProcessor };
|
||||
|
|
|
@ -15,7 +15,7 @@ class PdfProcessor extends StringProcessor {
|
|||
super(DwengoContentType.APPLICATION_PDF);
|
||||
}
|
||||
|
||||
override renderFn(pdfUrl: string) {
|
||||
override renderFn(pdfUrl: string): string {
|
||||
if (!isValidHttpUrl(pdfUrl)) {
|
||||
throw new ProcessingError(`PDF URL is invalid: ${pdfUrl}`);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = /<learning-object hruid="([^"]+)" l
|
|||
const LEARNING_OBJECT_DOES_NOT_EXIST = "<div class='non-existing-learning-object' />";
|
||||
|
||||
class ProcessingService {
|
||||
private processors!: Map<DwengoContentType, Processor<any>>;
|
||||
private processors!: Map<DwengoContentType, Processor<DwengoContentType>>;
|
||||
|
||||
constructor() {
|
||||
const processors = [
|
||||
|
|
|
@ -9,7 +9,9 @@ import { DwengoContentType } from './content-type.js';
|
|||
* Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/processor.js
|
||||
*/
|
||||
abstract class Processor<T> {
|
||||
protected constructor(public contentType: DwengoContentType) {}
|
||||
protected constructor(public contentType: DwengoContentType) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the given object.
|
||||
|
|
|
@ -11,7 +11,7 @@ class TextProcessor extends StringProcessor {
|
|||
super(DwengoContentType.TEXT_PLAIN);
|
||||
}
|
||||
|
||||
override renderFn(text: string) {
|
||||
override renderFn(text: string): string {
|
||||
// Sanitize plain text to prevent xss.
|
||||
return DOMPurify.sanitize(text);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue