feat(backend): Verwerking van leerobjecten in ZIP-formaat.

This commit is contained in:
Gerald Schmittinger 2025-05-05 23:15:22 +02:00
parent 509dd6bfab
commit 86ba4ea11e
4 changed files with 192 additions and 1 deletions

View file

@ -23,6 +23,8 @@
"@mikro-orm/postgresql": "6.4.12",
"@mikro-orm/reflection": "6.4.12",
"@mikro-orm/sqlite": "6.4.12",
"@types/mime-types": "^2.1.4",
"@types/unzipper": "^0.10.11",
"axios": "^1.8.2",
"cors": "^2.8.5",
"cross": "^1.0.0",
@ -37,8 +39,10 @@
"jwks-rsa": "^3.1.0",
"loki-logger-ts": "^1.0.2",
"marked": "^15.0.7",
"mime-types": "^3.0.1",
"response-time": "^2.3.3",
"swagger-ui-express": "^5.0.1",
"unzipper": "^0.12.3",
"uuid": "^11.1.0",
"winston": "^3.17.0",
"winston-loki": "^6.1.3"

View file

@ -2,7 +2,13 @@ import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provid
import { LearningObjectProvider } from './learning-object-provider.js';
import { envVars, getEnvVar } from '../../util/envVars.js';
import databaseLearningObjectProvider from './database-learning-object-provider.js';
import { FilteredLearningObject, LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content';
import {
FilteredLearningObject,
LearningObjectIdentifierDTO,
LearningPathIdentifier
} from '@dwengo-1/common/interfaces/learning-content';
import {getLearningObjectRepository} from "../../data/repositories";
import {processLearningObjectZip} from "./learning-object-zip-processing-service";
function getProvider(id: LearningObjectIdentifierDTO): LearningObjectProvider {
if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) {
@ -42,6 +48,21 @@ const learningObjectService = {
async getLearningObjectHTML(id: LearningObjectIdentifierDTO): Promise<string | null> {
return getProvider(id).getLearningObjectHTML(id);
},
/**
* Store the learning object in the given zip file in the database.
*/
async storeLearningObject(learningObjectPath: string): Promise<void> {
const learningObjectRepository = getLearningObjectRepository();
const learningObject = await processLearningObjectZip(learningObjectPath);
if (!learningObject.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) {
throw Error("Learning object name must start with the user content prefix!");
}
await learningObjectRepository.save(learningObject, {preventOverwrite: true});
}
};
export default learningObjectService;

View file

@ -0,0 +1,63 @@
import unzipper from 'unzipper';
import mime from 'mime-types';
import {LearningObjectMetadata} from "@dwengo-1/common/dist/interfaces/learning-content";
import {LearningObject} from "../../entities/content/learning-object.entity";
import {getAttachmentRepository, getLearningObjectRepository} from "../../data/repositories";
/**
* Process an uploaded zip file and construct a LearningObject from its contents.
* @param filePath Path of the zip file to process.
*/
export async function processLearningObjectZip(filePath: string): Promise<LearningObject> {
const learningObjectRepo = getLearningObjectRepository();
const attachmentRepo = getAttachmentRepository();
const zip = await unzipper.Open.file(filePath);
let metadata: LearningObjectMetadata | null = null;
const attachments: {name: string, content: Buffer}[] = [];
let content: Buffer | null = null;
for (const file of zip.files) {
if (file.type === "Directory") {
throw Error("The learning object zip file should not contain directories.");
} else if (file.path === "metadata.json") {
metadata = await processMetadataJson(file);
} else if (file.path.startsWith("index.")) {
content = await processFile(file);
} else {
attachments.push({
name: file.path,
content: await processFile(file)
});
}
}
if (!metadata) {
throw Error("Missing metadata.json file");
}
if (!content) {
throw Error("Missing index file");
}
const learningObject = learningObjectRepo.create(metadata);
const attachmentEntities = attachments.map(it => attachmentRepo.create({
name: it.name,
content: it.content,
mimeType: mime.lookup(it.name) || "text/plain",
learningObject
}))
learningObject.attachments.push(...attachmentEntities);
return learningObject;
}
async function processMetadataJson(file: unzipper.File): LearningObjectMetadata {
const buf = await file.buffer();
const content = buf.toString();
return JSON.parse(content);
}
async function processFile(file: unzipper.File): Promise<Buffer> {
return await file.buffer();
}