Merge branch 'feat/endpoints-in-backend-om-eigen-leerpaden-en-leerobjecten-toe-te-voegen-aan-de-databank-#248' of https://github.com/SELab-2/Dwengo-1 into feat/endpoints-in-backend-om-eigen-leerpaden-en-leerobjecten-toe-te-voegen-aan-de-databank-#248
This commit is contained in:
commit
226c9786dd
32 changed files with 357 additions and 303 deletions
|
@ -7,8 +7,8 @@ import { BadRequestException } from '../exceptions/bad-request-exception.js';
|
||||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||||
import { envVars, getEnvVar } from '../util/envVars.js';
|
import { envVars, getEnvVar } from '../util/envVars.js';
|
||||||
import { FilteredLearningObject, LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
import { FilteredLearningObject, LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
||||||
import {UploadedFile} from "express-fileupload";
|
import { UploadedFile } from 'express-fileupload';
|
||||||
import {AuthenticatedRequest} from "../middleware/auth/authenticated-request";
|
import { AuthenticatedRequest } from '../middleware/auth/authenticated-request';
|
||||||
|
|
||||||
function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifierDTO {
|
function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifierDTO {
|
||||||
if (!req.params.hruid) {
|
if (!req.params.hruid) {
|
||||||
|
@ -32,12 +32,13 @@ function getLearningPathIdentifierFromRequest(req: Request): LearningPathIdentif
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllLearningObjects(req: Request, res: Response): Promise<void> {
|
export async function getAllLearningObjects(req: Request, res: Response): Promise<void> {
|
||||||
if (req.query.admin) { // If the admin query parameter is present, the user wants to have all learning objects with this admin.
|
if (req.query.admin) {
|
||||||
const learningObjects =
|
// If the admin query parameter is present, the user wants to have all learning objects with this admin.
|
||||||
await learningObjectService.getLearningObjectsAdministratedBy(req.query.admin as string);
|
const learningObjects = await learningObjectService.getLearningObjectsAdministratedBy(req.query.admin as string);
|
||||||
|
|
||||||
res.json(learningObjects);
|
res.json(learningObjects);
|
||||||
} else { // Else he/she wants all learning objects on the path specified by the request parameters.
|
} else {
|
||||||
|
// Else he/she wants all learning objects on the path specified by the request parameters.
|
||||||
const learningPathId = getLearningPathIdentifierFromRequest(req);
|
const learningPathId = getLearningPathIdentifierFromRequest(req);
|
||||||
const full = req.query.full;
|
const full = req.query.full;
|
||||||
|
|
||||||
|
@ -86,10 +87,9 @@ export async function handlePostLearningObject(req: AuthenticatedRequest, res: R
|
||||||
if (!req.files || !req.files.learningObject) {
|
if (!req.files || !req.files.learningObject) {
|
||||||
throw new BadRequestException('No file uploaded');
|
throw new BadRequestException('No file uploaded');
|
||||||
}
|
}
|
||||||
const learningObject = await learningObjectService.storeLearningObject(
|
const learningObject = await learningObjectService.storeLearningObject((req.files.learningObject as UploadedFile).tempFilePath, [
|
||||||
(req.files.learningObject as UploadedFile).tempFilePath,
|
req.auth!.username,
|
||||||
[req.auth!.username]
|
]);
|
||||||
);
|
|
||||||
res.json(learningObject);
|
res.json(learningObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,17 +97,17 @@ export async function handleDeleteLearningObject(req: AuthenticatedRequest, res:
|
||||||
const learningObjectId = getLearningObjectIdentifierFromRequest(req);
|
const learningObjectId = getLearningObjectIdentifierFromRequest(req);
|
||||||
|
|
||||||
if (!learningObjectId.version) {
|
if (!learningObjectId.version) {
|
||||||
throw new BadRequestException("When deleting a learning object, a version must be specified.");
|
throw new BadRequestException('When deleting a learning object, a version must be specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedLearningObject = await learningObjectService.deleteLearningObject({
|
const deletedLearningObject = await learningObjectService.deleteLearningObject({
|
||||||
hruid: learningObjectId.hruid,
|
hruid: learningObjectId.hruid,
|
||||||
version: learningObjectId.version,
|
version: learningObjectId.version,
|
||||||
language: learningObjectId.language
|
language: learningObjectId.language,
|
||||||
});
|
});
|
||||||
if (deletedLearningObject) {
|
if (deletedLearningObject) {
|
||||||
res.json(deletedLearningObject);
|
res.json(deletedLearningObject);
|
||||||
} else {
|
} else {
|
||||||
throw new NotFoundException("Learning object not found");
|
throw new NotFoundException('Learning object not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,12 @@ export async function getLearningPaths(req: Request, res: Response): Promise<voi
|
||||||
hruidList = themes.flatMap((theme) => theme.hruids);
|
hruidList = themes.flatMap((theme) => theme.hruids);
|
||||||
}
|
}
|
||||||
|
|
||||||
const learningPaths = await learningPathService.fetchLearningPaths(hruidList, language as Language, `HRUIDs: ${hruidList.join(', ')}`, forGroup);
|
const learningPaths = await learningPathService.fetchLearningPaths(
|
||||||
|
hruidList,
|
||||||
|
language as Language,
|
||||||
|
`HRUIDs: ${hruidList.join(', ')}`,
|
||||||
|
forGroup
|
||||||
|
);
|
||||||
res.json(learningPaths.data);
|
res.json(learningPaths.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,12 +76,12 @@ function postOrPutLearningPath(isPut: boolean): (req: AuthenticatedRequest, res:
|
||||||
const teacher = await getTeacher(req.auth!.username);
|
const teacher = await getTeacher(req.auth!.username);
|
||||||
if (isPut) {
|
if (isPut) {
|
||||||
if (req.params.hruid !== path.hruid || req.params.language !== path.language) {
|
if (req.params.hruid !== path.hruid || req.params.language !== path.language) {
|
||||||
throw new BadRequestException("id_not_matching_query_params");
|
throw new BadRequestException('id_not_matching_query_params');
|
||||||
}
|
}
|
||||||
await learningPathService.deleteLearningPath({hruid: path.hruid, language: path.language as Language});
|
await learningPathService.deleteLearningPath({ hruid: path.hruid, language: path.language as Language });
|
||||||
}
|
}
|
||||||
res.json(await learningPathService.createNewLearningPath(path, [teacher]));
|
res.json(await learningPathService.createNewLearningPath(path, [teacher]));
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postLearningPath = postOrPutLearningPath(false);
|
export const postLearningPath = postOrPutLearningPath(false);
|
||||||
|
@ -85,12 +90,12 @@ export const putLearningPath = postOrPutLearningPath(true);
|
||||||
export async function deleteLearningPath(req: AuthenticatedRequest, res: Response): Promise<void> {
|
export async function deleteLearningPath(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
const id: LearningPathIdentifier = {
|
const id: LearningPathIdentifier = {
|
||||||
hruid: req.params.hruid,
|
hruid: req.params.hruid,
|
||||||
language: req.params.language as Language
|
language: req.params.language as Language,
|
||||||
};
|
};
|
||||||
const deletedPath = await learningPathService.deleteLearningPath(id);
|
const deletedPath = await learningPathService.deleteLearningPath(id);
|
||||||
if (deletedPath) {
|
if (deletedPath) {
|
||||||
res.json(deletedPath);
|
res.json(deletedPath);
|
||||||
} else {
|
} else {
|
||||||
throw new NotFoundException("The learning path could not be found.");
|
throw new NotFoundException('The learning path could not be found.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
||||||
return this.find(
|
return this.find(
|
||||||
{
|
{
|
||||||
admins: {
|
admins: {
|
||||||
username: adminUsername
|
username: adminUsername,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ populate: ['admins'] } // Make sure to load admin relations
|
{ populate: ['admins'] } // Make sure to load admin relations
|
||||||
);
|
);
|
||||||
|
@ -50,5 +50,4 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
||||||
}
|
}
|
||||||
return learningObject;
|
return learningObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,7 @@ import { LearningPathTransition } from '../../entities/content/learning-path-tra
|
||||||
|
|
||||||
export class LearningPathRepository extends DwengoEntityRepository<LearningPath> {
|
export class LearningPathRepository extends DwengoEntityRepository<LearningPath> {
|
||||||
public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> {
|
public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> {
|
||||||
return this.findOne(
|
return this.findOne({ hruid: hruid, language: language }, { populate: ['nodes', 'nodes.transitions', 'admins'] });
|
||||||
{ hruid: hruid, language: language },
|
|
||||||
{ populate: ['nodes', 'nodes.transitions', 'admins'] }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,10 +34,10 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath>
|
||||||
return this.findAll({
|
return this.findAll({
|
||||||
where: {
|
where: {
|
||||||
admins: {
|
admins: {
|
||||||
username: adminUsername
|
username: adminUsername,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
populate: ['nodes', 'nodes.transitions', 'admins']
|
populate: ['nodes', 'nodes.transitions', 'admins'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ export class Attachment {
|
||||||
@ManyToOne({
|
@ManyToOne({
|
||||||
entity: () => LearningObject,
|
entity: () => LearningObject,
|
||||||
primary: true,
|
primary: true,
|
||||||
deleteRule: 'cascade'
|
deleteRule: 'cascade',
|
||||||
})
|
})
|
||||||
learningObject!: LearningObject;
|
learningObject!: LearningObject;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,4 @@
|
||||||
import {
|
import { ArrayType, Collection, Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
|
||||||
ArrayType,
|
|
||||||
Collection,
|
|
||||||
Embedded,
|
|
||||||
Entity,
|
|
||||||
Enum,
|
|
||||||
ManyToMany,
|
|
||||||
OneToMany,
|
|
||||||
PrimaryKey,
|
|
||||||
Property
|
|
||||||
} from '@mikro-orm/core';
|
|
||||||
import { Attachment } from './attachment.entity.js';
|
import { Attachment } from './attachment.entity.js';
|
||||||
import { Teacher } from '../users/teacher.entity.js';
|
import { Teacher } from '../users/teacher.entity.js';
|
||||||
import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js';
|
import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js';
|
||||||
|
@ -92,7 +82,7 @@ export class LearningObject {
|
||||||
|
|
||||||
@OneToMany({
|
@OneToMany({
|
||||||
entity: () => Attachment,
|
entity: () => Attachment,
|
||||||
mappedBy: 'learningObject'
|
mappedBy: 'learningObject',
|
||||||
})
|
})
|
||||||
attachments: Collection<Attachment> = new Collection<Attachment>(this);
|
attachments: Collection<Attachment> = new Collection<Attachment>(this);
|
||||||
|
|
||||||
|
|
|
@ -116,14 +116,8 @@ export const authenticateUser = [verifyJwtToken, addAuthenticationInfo];
|
||||||
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
|
* @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
|
||||||
* to true.
|
* to true.
|
||||||
*/
|
*/
|
||||||
export function authorize(
|
export function authorize(accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean>): RequestHandler {
|
||||||
accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest) => boolean | Promise<boolean>
|
return async (req: AuthenticatedRequest, _res: express.Response, next: express.NextFunction): Promise<void> => {
|
||||||
): RequestHandler {
|
|
||||||
return async (
|
|
||||||
req: AuthenticatedRequest,
|
|
||||||
_res: express.Response,
|
|
||||||
next: express.NextFunction
|
|
||||||
): Promise<void> => {
|
|
||||||
if (!req.auth) {
|
if (!req.auth) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
} else if (!(await accessCondition(req.auth, req))) {
|
} else if (!(await accessCondition(req.auth, req))) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Language } from "@dwengo-1/common/util/language";
|
import { Language } from '@dwengo-1/common/util/language';
|
||||||
import learningObjectService from "../../../services/learning-objects/learning-object-service";
|
import learningObjectService from '../../../services/learning-objects/learning-object-service';
|
||||||
import { authorize } from "../auth";
|
import { authorize } from '../auth';
|
||||||
import { AuthenticatedRequest } from "../authenticated-request";
|
import { AuthenticatedRequest } from '../authenticated-request';
|
||||||
import { AuthenticationInfo } from "../authentication-info";
|
import { AuthenticationInfo } from '../authentication-info';
|
||||||
|
|
||||||
export const onlyAdminsForLearningObject = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
|
export const onlyAdminsForLearningObject = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
|
||||||
const { hruid } = req.params;
|
const { hruid } = req.params;
|
||||||
|
@ -10,7 +10,7 @@ export const onlyAdminsForLearningObject = authorize(async (auth: Authentication
|
||||||
const admins = await learningObjectService.getAdmins({
|
const admins = await learningObjectService.getAdmins({
|
||||||
hruid,
|
hruid,
|
||||||
language: language as Language,
|
language: language as Language,
|
||||||
version: parseInt(version as string)
|
version: parseInt(version as string),
|
||||||
});
|
});
|
||||||
return admins.includes(auth.username);
|
return admins.includes(auth.username);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Language } from "@dwengo-1/common/util/language";
|
import { Language } from '@dwengo-1/common/util/language';
|
||||||
import learningPathService from "../../../services/learning-paths/learning-path-service";
|
import learningPathService from '../../../services/learning-paths/learning-path-service';
|
||||||
import { authorize } from "../auth";
|
import { authorize } from '../auth';
|
||||||
import { AuthenticatedRequest } from "../authenticated-request";
|
import { AuthenticatedRequest } from '../authenticated-request';
|
||||||
import { AuthenticationInfo } from "../authentication-info";
|
import { AuthenticationInfo } from '../authentication-info';
|
||||||
|
|
||||||
export const onlyAdminsForLearningPath = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
|
export const onlyAdminsForLearningPath = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => {
|
||||||
const adminsForLearningPath = await learningPathService.getAdmins({
|
const adminsForLearningPath = await learningPathService.getAdmins({
|
||||||
hruid: req.params.hruid,
|
hruid: req.params.hruid,
|
||||||
language: req.params.language as Language
|
language: req.params.language as Language,
|
||||||
});
|
});
|
||||||
return adminsForLearningPath && adminsForLearningPath.includes(auth.username);
|
return adminsForLearningPath && adminsForLearningPath.includes(auth.username);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,12 +5,12 @@ import {
|
||||||
getLearningObject,
|
getLearningObject,
|
||||||
getLearningObjectHTML,
|
getLearningObjectHTML,
|
||||||
handleDeleteLearningObject,
|
handleDeleteLearningObject,
|
||||||
handlePostLearningObject
|
handlePostLearningObject,
|
||||||
} from '../controllers/learning-objects.js';
|
} from '../controllers/learning-objects.js';
|
||||||
|
|
||||||
import submissionRoutes from './submissions.js';
|
import submissionRoutes from './submissions.js';
|
||||||
import questionRoutes from './questions.js';
|
import questionRoutes from './questions.js';
|
||||||
import fileUpload from "express-fileupload";
|
import fileUpload from 'express-fileupload';
|
||||||
import { teachersOnly } from '../middleware/auth/auth.js';
|
import { teachersOnly } from '../middleware/auth/auth.js';
|
||||||
import { onlyAdminsForLearningObject } from '../middleware/auth/checks/learning-object-auth-checks.js';
|
import { onlyAdminsForLearningObject } from '../middleware/auth/checks/learning-object-auth-checks.js';
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ const router = express.Router();
|
||||||
// Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie
|
// Example 2: http://localhost:3000/learningObject?full=true&hruid=un_artificiele_intelligentie
|
||||||
router.get('/', getAllLearningObjects);
|
router.get('/', getAllLearningObjects);
|
||||||
|
|
||||||
router.post('/', teachersOnly, fileUpload({useTempFiles: true}), handlePostLearningObject)
|
router.post('/', teachersOnly, fileUpload({ useTempFiles: true }), handlePostLearningObject);
|
||||||
|
|
||||||
// Parameter: hruid of learning object
|
// Parameter: hruid of learning object
|
||||||
// Query: language
|
// Query: language
|
||||||
|
@ -40,7 +40,7 @@ router.get('/:hruid', getLearningObject);
|
||||||
// Query: language
|
// Query: language
|
||||||
// Route to delete a learning object based on its hruid.
|
// Route to delete a learning object based on its hruid.
|
||||||
// Example: http://localhost:3000/learningObject/un_ai7?language=nl&version=1
|
// Example: http://localhost:3000/learningObject/un_ai7?language=nl&version=1
|
||||||
router.delete('/:hruid', onlyAdminsForLearningObject, handleDeleteLearningObject)
|
router.delete('/:hruid', onlyAdminsForLearningObject, handleDeleteLearningObject);
|
||||||
|
|
||||||
router.use('/:hruid/submissions', submissionRoutes);
|
router.use('/:hruid/submissions', submissionRoutes);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ const router = express.Router();
|
||||||
// Example: http://localhost:3000/learningPath?theme=kiks
|
// Example: http://localhost:3000/learningPath?theme=kiks
|
||||||
|
|
||||||
router.get('/', getLearningPaths);
|
router.get('/', getLearningPaths);
|
||||||
router.post('/', teachersOnly, postLearningPath)
|
router.post('/', teachersOnly, postLearningPath);
|
||||||
|
|
||||||
router.put('/:hruid/:language', onlyAdminsForLearningPath, putLearningPath);
|
router.put('/:hruid/:language', onlyAdminsForLearningPath, putLearningPath);
|
||||||
router.delete('/:hruid/:language', onlyAdminsForLearningPath, deleteLearningPath);
|
router.delete('/:hruid/:language', onlyAdminsForLearningPath, deleteLearningPath);
|
||||||
|
|
|
@ -141,7 +141,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
||||||
*/
|
*/
|
||||||
async getLearningObjectsAdministratedBy(_adminUsername: string): Promise<FilteredLearningObject[]> {
|
async getLearningObjectsAdministratedBy(_adminUsername: string): Promise<FilteredLearningObject[]> {
|
||||||
return []; // The dwengo database does not contain any learning objects administrated by users.
|
return []; // The dwengo database does not contain any learning objects administrated by users.
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default dwengoApiLearningObjectProvider;
|
export default dwengoApiLearningObjectProvider;
|
||||||
|
|
|
@ -2,14 +2,10 @@ import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provid
|
||||||
import { LearningObjectProvider } from './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 databaseLearningObjectProvider from './database-learning-object-provider.js';
|
||||||
import {
|
import { FilteredLearningObject, LearningObjectIdentifierDTO, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
||||||
FilteredLearningObject,
|
import { getLearningObjectRepository, getTeacherRepository } from '../../data/repositories';
|
||||||
LearningObjectIdentifierDTO,
|
import { processLearningObjectZip } from './learning-object-zip-processing-service';
|
||||||
LearningPathIdentifier
|
import { LearningObject } from '../../entities/content/learning-object.entity';
|
||||||
} from '@dwengo-1/common/interfaces/learning-content';
|
|
||||||
import {getLearningObjectRepository, getTeacherRepository} from "../../data/repositories";
|
|
||||||
import {processLearningObjectZip} from "./learning-object-zip-processing-service";
|
|
||||||
import {LearningObject} from "../../entities/content/learning-object.entity";
|
|
||||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
||||||
import { NotFoundException } from '../../exceptions/not-found-exception.js';
|
import { NotFoundException } from '../../exceptions/not-found-exception.js';
|
||||||
|
|
||||||
|
@ -74,17 +70,15 @@ const learningObjectService = {
|
||||||
|
|
||||||
// Lookup the admin teachers based on their usernames and add them to the admins of the learning object.
|
// Lookup the admin teachers based on their usernames and add them to the admins of the learning object.
|
||||||
const teacherRepo = getTeacherRepository();
|
const teacherRepo = getTeacherRepository();
|
||||||
const adminTeachers = await Promise.all(
|
const adminTeachers = await Promise.all(admins.map(async (it) => teacherRepo.findByUsername(it)));
|
||||||
admins.map(async it => teacherRepo.findByUsername(it))
|
adminTeachers.forEach((it) => {
|
||||||
);
|
|
||||||
adminTeachers.forEach(it => {
|
|
||||||
if (it !== null) {
|
if (it !== null) {
|
||||||
learningObject.admins.add(it);
|
learningObject.admins.add(it);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await learningObjectRepository.save(learningObject, {preventOverwrite: true});
|
await learningObjectRepository.save(learningObject, { preventOverwrite: true });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
learningObjectRepository.getEntityManager().clear();
|
learningObjectRepository.getEntityManager().clear();
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -109,10 +103,10 @@ const learningObjectService = {
|
||||||
const learningObjectRepo = getLearningObjectRepository();
|
const learningObjectRepo = getLearningObjectRepository();
|
||||||
const learningObject = await learningObjectRepo.findByIdentifier(id);
|
const learningObject = await learningObjectRepo.findByIdentifier(id);
|
||||||
if (!learningObject) {
|
if (!learningObject) {
|
||||||
throw new NotFoundException("The specified learning object does not exist.");
|
throw new NotFoundException('The specified learning object does not exist.');
|
||||||
}
|
}
|
||||||
return learningObject.admins.map(admin => admin.username);
|
return learningObject.admins.map((admin) => admin.username);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default learningObjectService;
|
export default learningObjectService;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import unzipper from 'unzipper';
|
import unzipper from 'unzipper';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
import {LearningObject} from "../../entities/content/learning-object.entity";
|
import { LearningObject } from '../../entities/content/learning-object.entity';
|
||||||
import {getAttachmentRepository, getLearningObjectRepository} from "../../data/repositories";
|
import { getAttachmentRepository, getLearningObjectRepository } from '../../data/repositories';
|
||||||
import {BadRequestException} from "../../exceptions/bad-request-exception";
|
import { BadRequestException } from '../../exceptions/bad-request-exception';
|
||||||
import {LearningObjectMetadata} from "@dwengo-1/common/interfaces/learning-content";
|
import { LearningObjectMetadata } from '@dwengo-1/common/interfaces/learning-content';
|
||||||
import { DwengoContentType } from './processing/content-type';
|
import { DwengoContentType } from './processing/content-type';
|
||||||
|
|
||||||
const METADATA_PATH_REGEX = /.*[/^]metadata\.json$/;
|
const METADATA_PATH_REGEX = /.*[/^]metadata\.json$/;
|
||||||
|
@ -17,22 +17,21 @@ export async function processLearningObjectZip(filePath: string): Promise<Learni
|
||||||
let zip: unzipper.CentralDirectory;
|
let zip: unzipper.CentralDirectory;
|
||||||
try {
|
try {
|
||||||
zip = await unzipper.Open.file(filePath);
|
zip = await unzipper.Open.file(filePath);
|
||||||
} catch(_: unknown) {
|
} catch (_: unknown) {
|
||||||
throw new BadRequestException("invalidZip");
|
throw new BadRequestException('invalidZip');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let metadata: LearningObjectMetadata | undefined = undefined;
|
let metadata: LearningObjectMetadata | undefined = undefined;
|
||||||
const attachments: {name: string, content: Buffer}[] = [];
|
const attachments: { name: string; content: Buffer }[] = [];
|
||||||
let content: Buffer | undefined = undefined;
|
let content: Buffer | undefined = undefined;
|
||||||
|
|
||||||
if (zip.files.length === 0) {
|
if (zip.files.length === 0) {
|
||||||
throw new BadRequestException("emptyZip")
|
throw new BadRequestException('emptyZip');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
zip.files.map(async file => {
|
zip.files.map(async (file) => {
|
||||||
if (file.type !== "Directory") {
|
if (file.type !== 'Directory') {
|
||||||
if (METADATA_PATH_REGEX.test(file.path)) {
|
if (METADATA_PATH_REGEX.test(file.path)) {
|
||||||
metadata = await processMetadataJson(file);
|
metadata = await processMetadataJson(file);
|
||||||
} else if (CONTENT_PATH_REGEX.test(file.path)) {
|
} else if (CONTENT_PATH_REGEX.test(file.path)) {
|
||||||
|
@ -40,7 +39,7 @@ export async function processLearningObjectZip(filePath: string): Promise<Learni
|
||||||
} else {
|
} else {
|
||||||
attachments.push({
|
attachments.push({
|
||||||
name: file.path,
|
name: file.path,
|
||||||
content: await processFile(file)
|
content: await processFile(file),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,27 +47,24 @@ export async function processLearningObjectZip(filePath: string): Promise<Learni
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
throw new BadRequestException("missingMetadata");
|
throw new BadRequestException('missingMetadata');
|
||||||
}
|
}
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new BadRequestException("missingIndex");
|
throw new BadRequestException('missingIndex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const learningObject = createLearningObject(metadata, content, attachments);
|
const learningObject = createLearningObject(metadata, content, attachments);
|
||||||
|
|
||||||
return learningObject;
|
return learningObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLearningObject(
|
function createLearningObject(metadata: LearningObjectMetadata, content: Buffer, attachments: { name: string; content: Buffer }[]): LearningObject {
|
||||||
metadata: LearningObjectMetadata, content: Buffer, attachments: { name: string; content: Buffer; }[]
|
|
||||||
): LearningObject {
|
|
||||||
const learningObjectRepo = getLearningObjectRepository();
|
const learningObjectRepo = getLearningObjectRepository();
|
||||||
const attachmentRepo = getAttachmentRepository();
|
const attachmentRepo = getAttachmentRepository();
|
||||||
|
|
||||||
const returnValue = {
|
const returnValue = {
|
||||||
callbackUrl: metadata.return_value?.callback_url ?? "",
|
callbackUrl: metadata.return_value?.callback_url ?? '',
|
||||||
callbackSchema: metadata.return_value?.callback_schema ? JSON.stringify(metadata.return_value.callback_schema) : ""
|
callbackSchema: metadata.return_value?.callback_schema ? JSON.stringify(metadata.return_value.callback_schema) : '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const learningObject = learningObjectRepo.create({
|
const learningObject = learningObjectRepo.create({
|
||||||
|
@ -76,26 +72,30 @@ function createLearningObject(
|
||||||
available: metadata.available ?? true,
|
available: metadata.available ?? true,
|
||||||
content: content,
|
content: content,
|
||||||
contentType: metadata.content_type as DwengoContentType,
|
contentType: metadata.content_type as DwengoContentType,
|
||||||
copyright: metadata.copyright ?? "",
|
copyright: metadata.copyright ?? '',
|
||||||
description: metadata.description ?? "",
|
description: metadata.description ?? '',
|
||||||
educationalGoals: metadata.educational_goals ?? [],
|
educationalGoals: metadata.educational_goals ?? [],
|
||||||
hruid: metadata.hruid,
|
hruid: metadata.hruid,
|
||||||
keywords: metadata.keywords,
|
keywords: metadata.keywords,
|
||||||
language: metadata.language,
|
language: metadata.language,
|
||||||
license: metadata.license ?? "",
|
license: metadata.license ?? '',
|
||||||
returnValue,
|
returnValue,
|
||||||
skosConcepts: metadata.skos_concepts ?? [],
|
skosConcepts: metadata.skos_concepts ?? [],
|
||||||
teacherExclusive: metadata.teacher_exclusive,
|
teacherExclusive: metadata.teacher_exclusive,
|
||||||
title: metadata.title,
|
title: metadata.title,
|
||||||
version: metadata.version
|
version: metadata.version,
|
||||||
|
});
|
||||||
|
const attachmentEntities = attachments.map((it) =>
|
||||||
|
attachmentRepo.create({
|
||||||
|
name: it.name,
|
||||||
|
content: it.content,
|
||||||
|
mimeType: mime.lookup(it.name) || 'text/plain',
|
||||||
|
learningObject,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
attachmentEntities.forEach((it) => {
|
||||||
|
learningObject.attachments.add(it);
|
||||||
});
|
});
|
||||||
const attachmentEntities = attachments.map(it => attachmentRepo.create({
|
|
||||||
name: it.name,
|
|
||||||
content: it.content,
|
|
||||||
mimeType: mime.lookup(it.name) || "text/plain",
|
|
||||||
learningObject
|
|
||||||
}));
|
|
||||||
attachmentEntities.forEach(it => { learningObject.attachments.add(it); });
|
|
||||||
return learningObject;
|
return learningObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,12 +112,13 @@ async function convertNode(
|
||||||
)
|
)
|
||||||
.map((trans, i) => {
|
.map((trans, i) => {
|
||||||
try {
|
try {
|
||||||
return convertTransition(trans, i, nodesToLearningObjects)
|
return convertTransition(trans, i, nodesToLearningObjects);
|
||||||
} catch (_: unknown) {
|
} catch (_: unknown) {
|
||||||
logger.error(`Transition could not be resolved: ${JSON.stringify(trans)}`);
|
logger.error(`Transition could not be resolved: ${JSON.stringify(trans)}`);
|
||||||
return undefined; // Do not crash on invalid transitions, just ignore them so the rest of the learning path keeps working.
|
return undefined; // Do not crash on invalid transitions, just ignore them so the rest of the learning path keeps working.
|
||||||
}
|
}
|
||||||
}).filter(it => it !== undefined);
|
})
|
||||||
|
.filter((it) => it !== undefined);
|
||||||
return {
|
return {
|
||||||
_id: learningObject.uuid,
|
_id: learningObject.uuid,
|
||||||
language: learningObject.language,
|
language: learningObject.language,
|
||||||
|
|
|
@ -110,9 +110,7 @@ const learningPathService = {
|
||||||
* Fetch the learning paths administrated by the teacher with the given username.
|
* Fetch the learning paths administrated by the teacher with the given username.
|
||||||
*/
|
*/
|
||||||
async getLearningPathsAdministratedBy(adminUsername: string): Promise<LearningPath[]> {
|
async getLearningPathsAdministratedBy(adminUsername: string): Promise<LearningPath[]> {
|
||||||
const providerResponses = await Promise.all(
|
const providerResponses = await Promise.all(allProviders.map(async (provider) => provider.getLearningPathsAdministratedBy(adminUsername)));
|
||||||
allProviders.map(async (provider) => provider.getLearningPathsAdministratedBy(adminUsername))
|
|
||||||
);
|
|
||||||
return providerResponses.flat();
|
return providerResponses.flat();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -157,7 +155,7 @@ const learningPathService = {
|
||||||
if (deletedPath) {
|
if (deletedPath) {
|
||||||
return deletedPath;
|
return deletedPath;
|
||||||
}
|
}
|
||||||
throw new NotFoundException("No learning path with the given identifier found.");
|
throw new NotFoundException('No learning path with the given identifier found.');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,10 +166,10 @@ const learningPathService = {
|
||||||
const repo = getLearningPathRepository();
|
const repo = getLearningPathRepository();
|
||||||
const path = await repo.findByHruidAndLanguage(id.hruid, id.language);
|
const path = await repo.findByHruidAndLanguage(id.hruid, id.language);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
throw new NotFoundException("No learning path with the given identifier found.");
|
throw new NotFoundException('No learning path with the given identifier found.');
|
||||||
}
|
}
|
||||||
return path.admins.map(admin => admin.username);
|
return path.admins.map((admin) => admin.username);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default learningPathService;
|
export default learningPathService;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { AGE_TO_THEMES, THEMESITEMS } from "@/utils/constants.ts";
|
import { AGE_TO_THEMES, THEMESITEMS } from "@/utils/constants.ts";
|
||||||
import { useThemeQuery } from "@/queries/themes.ts";
|
import { useThemeQuery } from "@/queries/themes.ts";
|
||||||
import type { Theme } from "@/data-objects/theme.ts";
|
import type { Theme } from "@/data-objects/theme.ts";
|
||||||
import authService from "@/services/auth/auth-service";
|
import authService from "@/services/auth/auth-service";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
selectedTheme: { type: String, required: true },
|
selectedTheme: { type: String, required: true },
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
text: string,
|
text: string;
|
||||||
prependIcon?: string,
|
prependIcon?: string;
|
||||||
appendIcon?: string,
|
appendIcon?: string;
|
||||||
confirmQueryText: string,
|
confirmQueryText: string;
|
||||||
variant?: "flat" | "text" | "elevated" | "tonal" | "outlined" | "plain" | undefined,
|
variant?: "flat" | "text" | "elevated" | "tonal" | "outlined" | "plain" | undefined;
|
||||||
color?: string,
|
color?: string;
|
||||||
disabled?: boolean
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'confirm'): void }>()
|
const emit = defineEmits<{ (e: "confirm"): void }>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -22,40 +22,42 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-dialog max-width="500">
|
<v-dialog max-width="500">
|
||||||
<template v-slot:activator="{ props: activatorProps }">
|
<template v-slot:activator="{ props: activatorProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-bind="activatorProps"
|
v-bind="activatorProps"
|
||||||
:text="props.text"
|
:text="props.text"
|
||||||
:prependIcon="props.prependIcon"
|
:prependIcon="props.prependIcon"
|
||||||
:appendIcon="props.appendIcon"
|
:appendIcon="props.appendIcon"
|
||||||
:variant="props.variant"
|
:variant="props.variant"
|
||||||
:color="color"
|
:color="color"
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
></v-btn>
|
></v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:default="{ isActive }">
|
<template v-slot:default="{ isActive }">
|
||||||
<v-card :title="t('confirmDialogTitle')">
|
<v-card :title="t('confirmDialogTitle')">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
{{ props.confirmQueryText }}
|
{{ props.confirmQueryText }}
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
:text="t('yes')"
|
:text="t('yes')"
|
||||||
@click="confirm(); isActive.value = false"
|
@click="
|
||||||
></v-btn>
|
confirm();
|
||||||
<v-btn
|
isActive.value = false;
|
||||||
:text="t('cancel')"
|
"
|
||||||
@click="isActive.value = false"
|
></v-btn>
|
||||||
></v-btn>
|
<v-btn
|
||||||
</v-card-actions>
|
:text="t('cancel')"
|
||||||
</v-card>
|
@click="isActive.value = false"
|
||||||
</template>
|
></v-btn>
|
||||||
</v-dialog>
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
// Import assets
|
// Import assets
|
||||||
import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg";
|
import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg";
|
||||||
import { useLocale } from "vuetify";
|
import { useLocale } from "vuetify";
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const { current: vuetifyLocale } = useLocale();
|
const { current: vuetifyLocale } = useLocale();
|
||||||
|
|
|
@ -46,16 +46,21 @@ export abstract class BaseController {
|
||||||
* @param queryParams The query parameters.
|
* @param queryParams The query parameters.
|
||||||
* @returns The response the POST request generated.
|
* @returns The response the POST request generated.
|
||||||
*/
|
*/
|
||||||
protected async postFile<T>(path: string, formFieldName: string, file: File, queryParams?: QueryParams): Promise<T> {
|
protected async postFile<T>(
|
||||||
|
path: string,
|
||||||
|
formFieldName: string,
|
||||||
|
file: File,
|
||||||
|
queryParams?: QueryParams,
|
||||||
|
): Promise<T> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(formFieldName, file);
|
formData.append(formFieldName, file);
|
||||||
const response = await apiClient.post<T>(this.absolutePathFor(path), formData, {
|
const response = await apiClient.post<T>(this.absolutePathFor(path), formData, {
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
"Content-Type": "multipart/form-data",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
BaseController.assertSuccessResponse(response)
|
BaseController.assertSuccessResponse(response);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class LearningPathNode {
|
||||||
this.learningobjectHruid = options.learningobjectHruid;
|
this.learningobjectHruid = options.learningobjectHruid;
|
||||||
this.version = options.version;
|
this.version = options.version;
|
||||||
this.language = options.language;
|
this.language = options.language;
|
||||||
this.transitions = options.transitions.map(it => ({ next: it.next, default: it.default ?? false }));
|
this.transitions = options.transitions.map((it) => ({ next: it.next, default: it.default ?? false }));
|
||||||
this.createdAt = options.createdAt;
|
this.createdAt = options.createdAt;
|
||||||
this.updatedAt = options.updatedAt;
|
this.updatedAt = options.updatedAt;
|
||||||
this.done = options.done || false;
|
this.done = options.done || false;
|
||||||
|
|
|
@ -82,7 +82,7 @@ export class LearningPath {
|
||||||
keywords: dto.keywords.split(" "),
|
keywords: dto.keywords.split(" "),
|
||||||
targetAges: {
|
targetAges: {
|
||||||
min: dto.min_age ?? NaN,
|
min: dto.min_age ?? NaN,
|
||||||
max: dto.max_age ?? NaN
|
max: dto.max_age ?? NaN,
|
||||||
},
|
},
|
||||||
startNode: LearningPathNode.fromDTOAndOtherNodes(LearningPath.getStartNode(dto), dto.nodes),
|
startNode: LearningPathNode.fromDTOAndOtherNodes(LearningPath.getStartNode(dto), dto.nodes),
|
||||||
image: dto.image,
|
image: dto.image,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import * as directives from "vuetify/directives";
|
||||||
import i18n from "./i18n/i18n.ts";
|
import i18n from "./i18n/i18n.ts";
|
||||||
|
|
||||||
// JSON-editor
|
// JSON-editor
|
||||||
import JsonEditorVue from 'json-editor-vue';
|
import JsonEditorVue from "json-editor-vue";
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
@ -20,7 +20,7 @@ import { de, en, fr, nl } from "vuetify/locale";
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(JsonEditorVue, {})
|
app.use(JsonEditorVue, {});
|
||||||
|
|
||||||
const link = document.createElement("link");
|
const link = document.createElement("link");
|
||||||
link.rel = "stylesheet";
|
link.rel = "stylesheet";
|
||||||
|
@ -39,9 +39,9 @@ const vuetify = createVuetify({
|
||||||
},
|
},
|
||||||
locale: {
|
locale: {
|
||||||
locale: i18n.global.locale,
|
locale: i18n.global.locale,
|
||||||
fallback: 'en',
|
fallback: "en",
|
||||||
messages: { nl, en, de, fr }
|
messages: { nl, en, de, fr },
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { type MaybeRefOrGetter, toValue } from "vue";
|
import { type MaybeRefOrGetter, toValue } from "vue";
|
||||||
import type { Language } from "@/data-objects/language.ts";
|
import type { Language } from "@/data-objects/language.ts";
|
||||||
import { useMutation, useQuery, useQueryClient, type UseMutationReturnType, type UseQueryReturnType } from "@tanstack/vue-query";
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
type UseMutationReturnType,
|
||||||
|
type UseQueryReturnType,
|
||||||
|
} from "@tanstack/vue-query";
|
||||||
import { getLearningObjectController } from "@/controllers/controllers.ts";
|
import { getLearningObjectController } from "@/controllers/controllers.ts";
|
||||||
import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts";
|
import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts";
|
||||||
import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts";
|
import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts";
|
||||||
|
@ -58,7 +64,7 @@ export function useLearningObjectListForPathQuery(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLearningObjectListForAdminQuery(
|
export function useLearningObjectListForAdminQuery(
|
||||||
admin: MaybeRefOrGetter<string | undefined>
|
admin: MaybeRefOrGetter<string | undefined>,
|
||||||
): UseQueryReturnType<LearningObject[], Error> {
|
): UseQueryReturnType<LearningObject[], Error> {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [LEARNING_OBJECT_KEY, "forAdmin", admin],
|
queryKey: [LEARNING_OBJECT_KEY, "forAdmin", admin],
|
||||||
|
@ -66,24 +72,39 @@ export function useLearningObjectListForAdminQuery(
|
||||||
const adminVal = toValue(admin);
|
const adminVal = toValue(admin);
|
||||||
return await learningObjectController.getAllAdministratedBy(adminVal!);
|
return await learningObjectController.getAllAdministratedBy(adminVal!);
|
||||||
},
|
},
|
||||||
enabled: () => toValue(admin) !== undefined
|
enabled: () => toValue(admin) !== undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUploadLearningObjectMutation(): UseMutationReturnType<LearningObject, AxiosError, {learningObjectZip: File}, unknown> {
|
export function useUploadLearningObjectMutation(): UseMutationReturnType<
|
||||||
|
LearningObject,
|
||||||
|
AxiosError,
|
||||||
|
{ learningObjectZip: File },
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ learningObjectZip }) => await learningObjectController.upload(learningObjectZip),
|
mutationFn: async ({ learningObjectZip }) => await learningObjectController.upload(learningObjectZip),
|
||||||
onSuccess: async () => { await queryClient.invalidateQueries({queryKey: [LEARNING_OBJECT_KEY, "forAdmin"]}); }
|
onSuccess: async () => {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: [LEARNING_OBJECT_KEY, "forAdmin"] });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteLearningObjectMutation(): UseMutationReturnType<LearningObject, AxiosError, {hruid: string, language: Language, version: number}, unknown> {
|
export function useDeleteLearningObjectMutation(): UseMutationReturnType<
|
||||||
|
LearningObject,
|
||||||
|
AxiosError,
|
||||||
|
{ hruid: string; language: Language; version: number },
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ hruid, language, version }) => await learningObjectController.deleteLearningObject(hruid, language, version),
|
mutationFn: async ({ hruid, language, version }) =>
|
||||||
onSuccess: async () => { await queryClient.invalidateQueries({queryKey: [LEARNING_OBJECT_KEY, "forAdmin"]}); }
|
await learningObjectController.deleteLearningObject(hruid, language, version),
|
||||||
|
onSuccess: async () => {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: [LEARNING_OBJECT_KEY, "forAdmin"] });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { type MaybeRefOrGetter, toValue } from "vue";
|
import { type MaybeRefOrGetter, toValue } from "vue";
|
||||||
import type { Language } from "@/data-objects/language.ts";
|
import type { Language } from "@/data-objects/language.ts";
|
||||||
import { useMutation, useQuery, useQueryClient, type UseMutationReturnType, type UseQueryReturnType } from "@tanstack/vue-query";
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
type UseMutationReturnType,
|
||||||
|
type UseQueryReturnType,
|
||||||
|
} from "@tanstack/vue-query";
|
||||||
import { getLearningPathController } from "@/controllers/controllers";
|
import { getLearningPathController } from "@/controllers/controllers";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import type { LearningPath as LearningPathDTO } from "@dwengo-1/common/interfaces/learning-content";
|
import type { LearningPath as LearningPathDTO } from "@dwengo-1/common/interfaces/learning-content";
|
||||||
|
@ -35,42 +41,54 @@ export function useGetAllLearningPathsByThemeQuery(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetAllLearningPathsByAdminQuery(
|
export function useGetAllLearningPathsByAdminQuery(
|
||||||
admin: MaybeRefOrGetter<string | undefined>
|
admin: MaybeRefOrGetter<string | undefined>,
|
||||||
): UseQueryReturnType<LearningPathDTO[], AxiosError> {
|
): UseQueryReturnType<LearningPathDTO[], AxiosError> {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [LEARNING_PATH_KEY, "getAllByAdmin", admin],
|
queryKey: [LEARNING_PATH_KEY, "getAllByAdmin", admin],
|
||||||
queryFn: async () => learningPathController.getAllByAdminRaw(toValue(admin)!),
|
queryFn: async () => learningPathController.getAllByAdminRaw(toValue(admin)!),
|
||||||
enabled: () => Boolean(toValue(admin))
|
enabled: () => Boolean(toValue(admin)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePostLearningPathMutation():
|
export function usePostLearningPathMutation(): UseMutationReturnType<
|
||||||
UseMutationReturnType<LearningPathDTO, AxiosError, { learningPath: LearningPathDTO }, unknown> {
|
LearningPathDTO,
|
||||||
|
AxiosError,
|
||||||
|
{ learningPath: LearningPathDTO },
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ learningPath }) => learningPathController.postLearningPath(learningPath),
|
mutationFn: async ({ learningPath }) => learningPathController.postLearningPath(learningPath),
|
||||||
onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] })
|
onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePutLearningPathMutation():
|
export function usePutLearningPathMutation(): UseMutationReturnType<
|
||||||
UseMutationReturnType<LearningPathDTO, AxiosError, { learningPath: LearningPathDTO }, unknown> {
|
LearningPathDTO,
|
||||||
|
AxiosError,
|
||||||
|
{ learningPath: LearningPathDTO },
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ learningPath }) => learningPathController.putLearningPath(learningPath),
|
mutationFn: async ({ learningPath }) => learningPathController.putLearningPath(learningPath),
|
||||||
onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] })
|
onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteLearningPathMutation():
|
export function useDeleteLearningPathMutation(): UseMutationReturnType<
|
||||||
UseMutationReturnType<LearningPathDTO, AxiosError, { hruid: string, language: Language }, unknown> {
|
LearningPathDTO,
|
||||||
|
AxiosError,
|
||||||
|
{ hruid: string; language: Language },
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ hruid, language }) => learningPathController.deleteLearningPath(hruid, language),
|
mutationFn: async ({ hruid, language }) => learningPathController.deleteLearningPath(hruid, language),
|
||||||
onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] })
|
onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ const router = createRouter({
|
||||||
path: "/my-content",
|
path: "/my-content",
|
||||||
name: "OwnLearningContentPage",
|
name: "OwnLearningContentPage",
|
||||||
component: OwnLearningContentPage,
|
component: OwnLearningContentPage,
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/learningPath",
|
path: "/learningPath",
|
||||||
|
@ -126,7 +126,7 @@ const router = createRouter({
|
||||||
name: "LearningPath",
|
name: "LearningPath",
|
||||||
component: LearningPathPage,
|
component: LearningPathPage,
|
||||||
props: true,
|
props: true,
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useLearningObjectListForAdminQuery} from "@/queries/learning-objects.ts";
|
import { useLearningObjectListForAdminQuery } from "@/queries/learning-objects.ts";
|
||||||
import OwnLearningObjectsView from "@/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue"
|
import OwnLearningObjectsView from "@/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue";
|
||||||
import OwnLearningPathsView from "@/views/own-learning-content/learning-paths/OwnLearningPathsView.vue"
|
import OwnLearningPathsView from "@/views/own-learning-content/learning-paths/OwnLearningPathsView.vue";
|
||||||
import authService from "@/services/auth/auth-service.ts";
|
import authService from "@/services/auth/auth-service.ts";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
import type { LearningObject } from "@/data-objects/learning-objects/learning-object";
|
import type { LearningObject } from "@/data-objects/learning-objects/learning-object";
|
||||||
|
@ -12,11 +12,13 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const learningObjectsQuery =
|
const learningObjectsQuery = useLearningObjectListForAdminQuery(
|
||||||
useLearningObjectListForAdminQuery(authService.authState.user?.profile.preferred_username);
|
authService.authState.user?.profile.preferred_username,
|
||||||
|
);
|
||||||
|
|
||||||
const learningPathsQuery =
|
const learningPathsQuery = useGetAllLearningPathsByAdminQuery(
|
||||||
useGetAllLearningPathsByAdminQuery(authService.authState.user?.profile.preferred_username);
|
authService.authState.user?.profile.preferred_username,
|
||||||
|
);
|
||||||
|
|
||||||
type Tab = "learningObjects" | "learningPaths";
|
type Tab = "learningObjects" | "learningPaths";
|
||||||
const tab: Ref<Tab> = ref("learningObjects");
|
const tab: Ref<Tab> = ref("learningObjects");
|
||||||
|
@ -27,12 +29,18 @@
|
||||||
<h1 class="title">{{ t("ownLearningContentTitle") }}</h1>
|
<h1 class="title">{{ t("ownLearningContentTitle") }}</h1>
|
||||||
|
|
||||||
<v-tabs v-model="tab">
|
<v-tabs v-model="tab">
|
||||||
<v-tab value="learningObjects">{{ t('learningObjects') }}</v-tab>
|
<v-tab value="learningObjects">{{ t("learningObjects") }}</v-tab>
|
||||||
<v-tab value="learningPaths">{{ t('learningPaths') }}</v-tab>
|
<v-tab value="learningPaths">{{ t("learningPaths") }}</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
|
|
||||||
<v-tabs-window v-model="tab" class="main-content">
|
<v-tabs-window
|
||||||
<v-tabs-window-item value="learningObjects" class="main-content">
|
v-model="tab"
|
||||||
|
class="main-content"
|
||||||
|
>
|
||||||
|
<v-tabs-window-item
|
||||||
|
value="learningObjects"
|
||||||
|
class="main-content"
|
||||||
|
>
|
||||||
<using-query-result
|
<using-query-result
|
||||||
:query-result="learningObjectsQuery"
|
:query-result="learningObjectsQuery"
|
||||||
v-slot="response: { data: LearningObject[] }"
|
v-slot="response: { data: LearningObject[] }"
|
||||||
|
@ -45,7 +53,7 @@
|
||||||
:query-result="learningPathsQuery"
|
:query-result="learningPathsQuery"
|
||||||
v-slot="response: { data: LearningPathDTO[] }"
|
v-slot="response: { data: LearningPathDTO[] }"
|
||||||
>
|
>
|
||||||
<own-learning-paths-view :learningPaths="response.data"/>
|
<own-learning-paths-view :learningPaths="response.data" />
|
||||||
</using-query-result>
|
</using-query-result>
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
</v-tabs-window>
|
</v-tabs-window>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LearningObject } from '@/data-objects/learning-objects/learning-object';
|
import type { LearningObject } from "@/data-objects/learning-objects/learning-object";
|
||||||
import UsingQueryResult from '@/components/UsingQueryResult.vue';
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
import LearningObjectContentView from '../../learning-paths/learning-object/content/LearningObjectContentView.vue';
|
import LearningObjectContentView from "../../learning-paths/learning-object/content/LearningObjectContentView.vue";
|
||||||
import ButtonWithConfirmation from '@/components/ButtonWithConfirmation.vue';
|
import ButtonWithConfirmation from "@/components/ButtonWithConfirmation.vue";
|
||||||
import { useDeleteLearningObjectMutation, useLearningObjectHTMLQuery } from '@/queries/learning-objects';
|
import { useDeleteLearningObjectMutation, useLearningObjectHTMLQuery } from "@/queries/learning-objects";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selectedLearningObject?: LearningObject
|
selectedLearningObject?: LearningObject;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const learningObjectQueryResult = useLearningObjectHTMLQuery(
|
const learningObjectQueryResult = useLearningObjectHTMLQuery(
|
||||||
() => props.selectedLearningObject?.key,
|
() => props.selectedLearningObject?.key,
|
||||||
() => props.selectedLearningObject?.language,
|
() => props.selectedLearningObject?.language,
|
||||||
() => props.selectedLearningObject?.version
|
() => props.selectedLearningObject?.version,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isPending, mutate } = useDeleteLearningObjectMutation();
|
const { isPending, mutate } = useDeleteLearningObjectMutation();
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
mutate({
|
mutate({
|
||||||
hruid: props.selectedLearningObject.key,
|
hruid: props.selectedLearningObject.key,
|
||||||
language: props.selectedLearningObject.language,
|
language: props.selectedLearningObject.language,
|
||||||
version: props.selectedLearningObject.version
|
version: props.selectedLearningObject.version,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,10 @@
|
||||||
:title="t('previewFor') + selectedLearningObject.title"
|
:title="t('previewFor') + selectedLearningObject.title"
|
||||||
>
|
>
|
||||||
<template v-slot:text>
|
<template v-slot:text>
|
||||||
<using-query-result :query-result="learningObjectQueryResult" v-slot="response: { data: Document }">
|
<using-query-result
|
||||||
|
:query-result="learningObjectQueryResult"
|
||||||
|
v-slot="response: { data: Document }"
|
||||||
|
>
|
||||||
<learning-object-content-view :learning-object-content="response.data"></learning-object-content-view>
|
<learning-object-content-view :learning-object-content="response.data"></learning-object-content-view>
|
||||||
</using-query-result>
|
</using-query-result>
|
||||||
</template>
|
</template>
|
||||||
|
@ -53,5 +56,4 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUploadLearningObjectMutation } from '@/queries/learning-objects';
|
import { useUploadLearningObjectMutation } from "@/queries/learning-objects";
|
||||||
import { ref, watch, type Ref } from 'vue';
|
import { ref, watch, type Ref } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
import { VFileUpload } from 'vuetify/labs/VFileUpload';
|
import { VFileUpload } from "vuetify/labs/VFileUpload";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -25,19 +25,23 @@
|
||||||
|
|
||||||
function uploadFile() {
|
function uploadFile() {
|
||||||
if (fileToUpload.value) {
|
if (fileToUpload.value) {
|
||||||
mutate({learningObjectZip: fileToUpload.value});
|
mutate({ learningObjectZip: fileToUpload.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<v-dialog max-width="500" v-model="dialogOpen">
|
<v-dialog
|
||||||
|
max-width="500"
|
||||||
|
v-model="dialogOpen"
|
||||||
|
>
|
||||||
<template v-slot:activator="{ props: activatorProps }">
|
<template v-slot:activator="{ props: activatorProps }">
|
||||||
<v-btn
|
<v-btn
|
||||||
prepend-icon="mdi mdi-plus"
|
prepend-icon="mdi mdi-plus"
|
||||||
:text="t('newLearningObject')"
|
:text="t('newLearningObject')"
|
||||||
v-bind="activatorProps"
|
v-bind="activatorProps"
|
||||||
color="rgb(14, 105, 66)"
|
color="rgb(14, 105, 66)"
|
||||||
size="large">
|
size="large"
|
||||||
|
>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -75,5 +79,4 @@
|
||||||
</template>
|
</template>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,36 +1,38 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LearningObject } from '@/data-objects/learning-objects/learning-object';
|
import type { LearningObject } from "@/data-objects/learning-objects/learning-object";
|
||||||
import LearningObjectUploadButton from '@/views/own-learning-content/learning-objects/LearningObjectUploadButton.vue'
|
import LearningObjectUploadButton from "@/views/own-learning-content/learning-objects/LearningObjectUploadButton.vue";
|
||||||
import LearningObjectPreviewCard from './LearningObjectPreviewCard.vue';
|
import LearningObjectPreviewCard from "./LearningObjectPreviewCard.vue";
|
||||||
import { computed, ref, watch, type Ref } from 'vue';
|
import { computed, ref, watch, type Ref } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningObjects: LearningObject[]
|
learningObjects: LearningObject[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tableHeaders = [
|
const tableHeaders = [
|
||||||
{ title: t("hruid"), width: "250px", key: "key" },
|
{ title: t("hruid"), width: "250px", key: "key" },
|
||||||
{ title: t("language"), width: "50px", key: "language" },
|
{ title: t("language"), width: "50px", key: "language" },
|
||||||
{ title: t("version"), width: "50px", key: "version" },
|
{ title: t("version"), width: "50px", key: "version" },
|
||||||
{ title: t("title"), key: "title" }
|
{ title: t("title"), key: "title" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const selectedLearningObjects: Ref<LearningObject[]> = ref([]);
|
const selectedLearningObjects: Ref<LearningObject[]> = ref([]);
|
||||||
|
|
||||||
watch(() => props.learningObjects, () => selectedLearningObjects.value = []);
|
watch(
|
||||||
|
() => props.learningObjects,
|
||||||
const selectedLearningObject = computed(() =>
|
() => (selectedLearningObjects.value = []),
|
||||||
selectedLearningObjects.value ? selectedLearningObjects.value[0] : undefined
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedLearningObject = computed(() =>
|
||||||
|
selectedLearningObjects.value ? selectedLearningObjects.value[0] : undefined,
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<learning-object-upload-button/>
|
<learning-object-upload-button />
|
||||||
<v-data-table
|
<v-data-table
|
||||||
class="table"
|
class="table"
|
||||||
v-model="selectedLearningObjects"
|
v-model="selectedLearningObjects"
|
||||||
|
@ -41,8 +43,14 @@
|
||||||
return-object
|
return-object
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-container" v-if="selectedLearningObject">
|
<div
|
||||||
<learning-object-preview-card class="preview" :selectedLearningObject="selectedLearningObject"/>
|
class="preview-container"
|
||||||
|
v-if="selectedLearningObject"
|
||||||
|
>
|
||||||
|
<learning-object-preview-card
|
||||||
|
class="preview"
|
||||||
|
:selectedLearningObject="selectedLearningObject"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,30 +1,34 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
import { computed, ref, watch, type Ref } from 'vue';
|
import { computed, ref, watch, type Ref } from "vue";
|
||||||
import JsonEditorVue from 'json-editor-vue'
|
import JsonEditorVue from "json-editor-vue";
|
||||||
import ButtonWithConfirmation from '@/components/ButtonWithConfirmation.vue'
|
import ButtonWithConfirmation from "@/components/ButtonWithConfirmation.vue";
|
||||||
import { useDeleteLearningPathMutation, usePostLearningPathMutation, usePutLearningPathMutation } from '@/queries/learning-paths';
|
import {
|
||||||
import { Language } from '@/data-objects/language';
|
useDeleteLearningPathMutation,
|
||||||
import type { LearningPath } from '@dwengo-1/common/interfaces/learning-content';
|
usePostLearningPathMutation,
|
||||||
import type { AxiosError } from 'axios';
|
usePutLearningPathMutation,
|
||||||
import { parse } from 'uuid';
|
} from "@/queries/learning-paths";
|
||||||
|
import { Language } from "@/data-objects/language";
|
||||||
|
import type { LearningPath } from "@dwengo-1/common/interfaces/learning-content";
|
||||||
|
import type { AxiosError } from "axios";
|
||||||
|
import { parse } from "uuid";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selectedLearningPath?: LearningPath
|
selectedLearningPath?: LearningPath;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { isPending, mutate, error: deleteError, isSuccess: deleteSuccess } = useDeleteLearningPathMutation();
|
const { isPending, mutate, error: deleteError, isSuccess: deleteSuccess } = useDeleteLearningPathMutation();
|
||||||
|
|
||||||
const DEFAULT_LEARNING_PATH: LearningPath = {
|
const DEFAULT_LEARNING_PATH: LearningPath = {
|
||||||
language: 'en',
|
language: "en",
|
||||||
hruid: '...',
|
hruid: "...",
|
||||||
title: '...',
|
title: "...",
|
||||||
description: '...',
|
description: "...",
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
learningobject_hruid: '...',
|
learningobject_hruid: "...",
|
||||||
language: Language.English,
|
language: Language.English,
|
||||||
version: 1,
|
version: 1,
|
||||||
start_node: true,
|
start_node: true,
|
||||||
|
@ -33,17 +37,17 @@ import { parse } from 'uuid';
|
||||||
default: true,
|
default: true,
|
||||||
condition: "(remove if the transition should be unconditinal)",
|
condition: "(remove if the transition should be unconditinal)",
|
||||||
next: {
|
next: {
|
||||||
hruid: '...',
|
hruid: "...",
|
||||||
version: 1,
|
version: 1,
|
||||||
language: '...'
|
language: "...",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
keywords: 'Keywords separated by spaces',
|
keywords: "Keywords separated by spaces",
|
||||||
target_ages: []
|
target_ages: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
const { isPending: isPostPending, error: postError, mutate: doPost } = usePostLearningPathMutation();
|
const { isPending: isPostPending, error: postError, mutate: doPost } = usePostLearningPathMutation();
|
||||||
const { isPending: isPutPending, error: putError, mutate: doPut } = usePutLearningPathMutation();
|
const { isPending: isPutPending, error: putError, mutate: doPut } = usePutLearningPathMutation();
|
||||||
|
@ -51,11 +55,13 @@ import { parse } from 'uuid';
|
||||||
const learningPath: Ref<LearningPath | string> = ref(DEFAULT_LEARNING_PATH);
|
const learningPath: Ref<LearningPath | string> = ref(DEFAULT_LEARNING_PATH);
|
||||||
|
|
||||||
const parsedLearningPath = computed(() =>
|
const parsedLearningPath = computed(() =>
|
||||||
typeof learningPath.value === "string" ? JSON.parse(learningPath.value) as LearningPath
|
typeof learningPath.value === "string" ? (JSON.parse(learningPath.value) as LearningPath) : learningPath.value,
|
||||||
: learningPath.value
|
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(() => props.selectedLearningPath, () => learningPath.value = props.selectedLearningPath ?? DEFAULT_LEARNING_PATH);
|
watch(
|
||||||
|
() => props.selectedLearningPath,
|
||||||
|
() => (learningPath.value = props.selectedLearningPath ?? DEFAULT_LEARNING_PATH),
|
||||||
|
);
|
||||||
|
|
||||||
function uploadLearningPath(): void {
|
function uploadLearningPath(): void {
|
||||||
if (props.selectedLearningPath) {
|
if (props.selectedLearningPath) {
|
||||||
|
@ -69,20 +75,20 @@ import { parse } from 'uuid';
|
||||||
if (props.selectedLearningPath) {
|
if (props.selectedLearningPath) {
|
||||||
mutate({
|
mutate({
|
||||||
hruid: props.selectedLearningPath.hruid,
|
hruid: props.selectedLearningPath.hruid,
|
||||||
language: props.selectedLearningPath.language as Language
|
language: props.selectedLearningPath.language as Language,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractErrorMessage(error: AxiosError): string {
|
function extractErrorMessage(error: AxiosError): string {
|
||||||
return (error.response?.data as {error: string}).error ?? error.message;
|
return (error.response?.data as { error: string }).error ?? error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isIdModified = computed(() =>
|
const isIdModified = computed(
|
||||||
props.selectedLearningPath !== undefined && (
|
() =>
|
||||||
props.selectedLearningPath.hruid !== parsedLearningPath.value.hruid
|
props.selectedLearningPath !== undefined &&
|
||||||
|| props.selectedLearningPath.language !== parsedLearningPath.value.language
|
(props.selectedLearningPath.hruid !== parsedLearningPath.value.hruid ||
|
||||||
)
|
props.selectedLearningPath.language !== parsedLearningPath.value.language),
|
||||||
);
|
);
|
||||||
|
|
||||||
function getErrorMessage(): string | null {
|
function getErrorMessage(): string | null {
|
||||||
|
@ -93,24 +99,22 @@ import { parse } from 'uuid';
|
||||||
} else if (deleteError.value) {
|
} else if (deleteError.value) {
|
||||||
return t(extractErrorMessage(deleteError.value));
|
return t(extractErrorMessage(deleteError.value));
|
||||||
} else if (isIdModified.value) {
|
} else if (isIdModified.value) {
|
||||||
return t('learningPathCantModifyId');
|
return t("learningPathCantModifyId");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card
|
<v-card :title="props.selectedLearningPath ? t('editLearningPath') : t('newLearningPath')">
|
||||||
:title="props.selectedLearningPath ? t('editLearningPath') : t('newLearningPath')"
|
|
||||||
>
|
|
||||||
<template v-slot:text>
|
<template v-slot:text>
|
||||||
<json-editor-vue v-model="learningPath"></json-editor-vue>
|
<json-editor-vue v-model="learningPath"></json-editor-vue>
|
||||||
<v-alert
|
<v-alert
|
||||||
v-if="postError || putError || deleteError || isIdModified"
|
v-if="postError || putError || deleteError || isIdModified"
|
||||||
icon="mdi mdi-alert-circle"
|
icon="mdi mdi-alert-circle"
|
||||||
type="error"
|
type="error"
|
||||||
:title="t('error')"
|
:title="t('error')"
|
||||||
:text="getErrorMessage()!"
|
:text="getErrorMessage()!"
|
||||||
></v-alert>
|
></v-alert>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:actions>
|
<template v-slot:actions>
|
||||||
|
@ -120,7 +124,7 @@ import { parse } from 'uuid';
|
||||||
:loading="isPostPending || isPutPending"
|
:loading="isPostPending || isPutPending"
|
||||||
:disabled="parsedLearningPath.hruid === DEFAULT_LEARNING_PATH.hruid || isIdModified"
|
:disabled="parsedLearningPath.hruid === DEFAULT_LEARNING_PATH.hruid || isIdModified"
|
||||||
>
|
>
|
||||||
{{ props.selectedLearningPath ? t('saveChanges') : t('create') }}
|
{{ props.selectedLearningPath ? t("saveChanges") : t("create") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<button-with-confirmation
|
<button-with-confirmation
|
||||||
@confirm="deleteLearningPath"
|
@confirm="deleteLearningPath"
|
||||||
|
@ -136,11 +140,10 @@ import { parse } from 'uuid';
|
||||||
prepend-icon="mdi mdi-open-in-new"
|
prepend-icon="mdi mdi-open-in-new"
|
||||||
:disabled="!props.selectedLearningPath"
|
:disabled="!props.selectedLearningPath"
|
||||||
>
|
>
|
||||||
{{ t('open') }}
|
{{ t("open") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningPathPreviewCard from './LearningPathPreviewCard.vue';
|
import LearningPathPreviewCard from "./LearningPathPreviewCard.vue";
|
||||||
import { computed, ref, watch, type Ref } from 'vue';
|
import { computed, ref, watch, type Ref } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
import type { LearningPath as LearningPathDTO } from '@dwengo-1/common/interfaces/learning-content';
|
import type { LearningPath as LearningPathDTO } from "@dwengo-1/common/interfaces/learning-content";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningPaths: LearningPathDTO[]
|
learningPaths: LearningPathDTO[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tableHeaders = [
|
const tableHeaders = [
|
||||||
{ title: t("hruid"), width: "250px", key: "hruid" },
|
{ title: t("hruid"), width: "250px", key: "hruid" },
|
||||||
{ title: t("language"), width: "50px", key: "language" },
|
{ title: t("language"), width: "50px", key: "language" },
|
||||||
{ title: t("title"), key: "title" }
|
{ title: t("title"), key: "title" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const selectedLearningPaths: Ref<LearningPathDTO[]> = ref([]);
|
const selectedLearningPaths: Ref<LearningPathDTO[]> = ref([]);
|
||||||
|
|
||||||
const selectedLearningPath = computed(() =>
|
const selectedLearningPath = computed(() =>
|
||||||
selectedLearningPaths.value ? selectedLearningPaths.value[0] : undefined
|
selectedLearningPaths.value ? selectedLearningPaths.value[0] : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(() => props.learningPaths, () => selectedLearningPaths.value = []);
|
watch(
|
||||||
|
() => props.learningPaths,
|
||||||
|
() => (selectedLearningPaths.value = []),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -38,7 +41,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-container">
|
<div class="preview-container">
|
||||||
<learning-path-preview-card class="preview" :selectedLearningPath="selectedLearningPath"/>
|
<learning-path-preview-card
|
||||||
|
class="preview"
|
||||||
|
:selectedLearningPath="selectedLearningPath"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue