merge: merged into feat/error-flow-backend
This commit is contained in:
commit
effaeb0277
249 changed files with 6832 additions and 3679 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment';
|
||||
import { getAssignmentRepository, getClassRepository, getGroupRepository, getQuestionRepository, getSubmissionRepository } from '../data/repositories.js';
|
||||
import { Assignment } from '../entities/assignments/assignment.entity.js';
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js';
|
||||
import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js';
|
||||
import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js';
|
||||
import { mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js';
|
||||
import { mapToQuestionDTO } from '../interfaces/question.js';
|
||||
import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js';
|
||||
import { fetchClass } from './classes.js';
|
||||
import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
|
||||
import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission';
|
||||
|
||||
export async function fetchAssignment(classid: string, assignmentNumber: number): Promise<Assignment> {
|
||||
const classRepository = getClassRepository();
|
||||
|
|
@ -37,35 +40,21 @@ export async function getAllAssignments(classid: string, full: boolean): Promise
|
|||
return assignments.map(mapToAssignmentDTOId);
|
||||
}
|
||||
|
||||
export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise<AssignmentDTO | null> {
|
||||
const classRepository = getClassRepository();
|
||||
const cls = await classRepository.findById(classid);
|
||||
|
||||
if (!cls) {
|
||||
return null;
|
||||
}
|
||||
export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise<AssignmentDTO> {
|
||||
const cls = await fetchClass(classid);
|
||||
|
||||
const assignment = mapToAssignment(assignmentData, cls);
|
||||
const assignmentRepository = getAssignmentRepository();
|
||||
|
||||
try {
|
||||
const newAssignment = assignmentRepository.create(assignment);
|
||||
await assignmentRepository.save(newAssignment);
|
||||
const newAssignment = assignmentRepository.create(assignment);
|
||||
await assignmentRepository.save(newAssignment, {preventOverwrite: true});
|
||||
|
||||
return mapToAssignmentDTO(newAssignment);
|
||||
|
||||
return mapToAssignmentDTO(newAssignment);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAssignment(classid: string, id: number): Promise<AssignmentDTO | null> {
|
||||
export async function getAssignment(classid: string, id: number): Promise<AssignmentDTO> {
|
||||
const assignment = await fetchAssignment(classid, id);
|
||||
|
||||
if (!assignment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mapToAssignmentDTO(assignment);
|
||||
}
|
||||
|
||||
|
|
@ -76,15 +65,15 @@ export async function getAssignmentsSubmissions(
|
|||
): Promise<SubmissionDTO[] | SubmissionDTOId[]> {
|
||||
const assignment = await fetchAssignment(classid, assignmentNumber);
|
||||
|
||||
if (!assignment) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const groupRepository = getGroupRepository();
|
||||
const groups = await groupRepository.findAllGroupsForAssignment(assignment);
|
||||
|
||||
if (groups.length === 0){
|
||||
throw new NotFoundException('No groups for assignment found');
|
||||
}
|
||||
|
||||
const submissionRepository = getSubmissionRepository();
|
||||
const submissions = (await Promise.all(groups.map((group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat();
|
||||
const submissions = (await Promise.all(groups.map(async (group) => submissionRepository.findAllSubmissionsForGroup(group)))).flat();
|
||||
|
||||
if (full) {
|
||||
return submissions.map(mapToSubmissionDTO);
|
||||
|
|
@ -100,10 +89,6 @@ export async function getAssignmentsQuestions(
|
|||
): Promise<QuestionDTO[] | QuestionId[]> {
|
||||
const assignment = await fetchAssignment(classid, assignmentNumber);
|
||||
|
||||
if (!assignment) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const questionRepository = getQuestionRepository();
|
||||
const questions = await questionRepository.findAllByAssignment(assignment);
|
||||
|
||||
|
|
@ -111,5 +96,5 @@ export async function getAssignmentsQuestions(
|
|||
return questions.map(mapToQuestionDTO);
|
||||
}
|
||||
|
||||
return questions.map(mapToQuestionDTO).map(mapToQuestionId); // mapToQuestionId should be updated
|
||||
return questions.map(mapToQuestionDTO); // mapToQuestionId should be updated
|
||||
}
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js';
|
||||
import { Class } from '../entities/classes/class.entity.js';
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
import { ClassDTO, mapToClassDTO } from '../interfaces/class.js';
|
||||
import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js';
|
||||
import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js';
|
||||
import { mapToClassDTO } from '../interfaces/class.js';
|
||||
import { mapToStudentDTO } from '../interfaces/student.js';
|
||||
import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds } from '../interfaces/teacher-invitation.js';
|
||||
import { getLogger } from '../logging/initalize.js';
|
||||
|
||||
const logger = getLogger();
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
import { Class } from '../entities/classes/class.entity.js';
|
||||
import { ClassDTO } from '@dwengo-1/common/interfaces/class';
|
||||
import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation';
|
||||
import { StudentDTO } from '@dwengo-1/common/interfaces/student';
|
||||
import {fetchTeacher} from "./teachers";
|
||||
import {fetchStudent} from "./students";
|
||||
import {TeacherDTO} from "@dwengo-1/common/interfaces/teacher";
|
||||
import {mapToTeacherDTO} from "../interfaces/teacher";
|
||||
|
||||
export async function fetchClass(classid: string): Promise<Class> {
|
||||
const classRepository = getClassRepository();
|
||||
|
|
@ -36,38 +41,27 @@ export async function getClass(classId: string): Promise<ClassDTO | null> {
|
|||
}
|
||||
|
||||
export async function createClass(classData: ClassDTO): Promise<ClassDTO | null> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
const teacherUsernames = classData.teachers || [];
|
||||
const teachers = (await Promise.all(teacherUsernames.map((id) => teacherRepository.findByUsername(id)))).filter((teacher) => teacher !== null);
|
||||
const teachers = (await Promise.all(teacherUsernames.map(async (id) => fetchTeacher(id) )));
|
||||
|
||||
const studentRepository = getStudentRepository();
|
||||
const studentUsernames = classData.students || [];
|
||||
const students = (await Promise.all(studentUsernames.map((id) => studentRepository.findByUsername(id)))).filter((student) => student !== null);
|
||||
const students = (await Promise.all(studentUsernames.map(async (id) => fetchStudent(id) )));
|
||||
|
||||
const classRepository = getClassRepository();
|
||||
|
||||
try {
|
||||
const newClass = classRepository.create({
|
||||
displayName: classData.displayName,
|
||||
teachers: teachers,
|
||||
students: students,
|
||||
});
|
||||
await classRepository.save(newClass);
|
||||
const newClass = classRepository.create({
|
||||
displayName: classData.displayName,
|
||||
teachers: teachers,
|
||||
students: students,
|
||||
});
|
||||
await classRepository.save(newClass, {preventOverwrite: true});
|
||||
|
||||
return mapToClassDTO(newClass);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return null;
|
||||
}
|
||||
return mapToClassDTO(newClass);
|
||||
}
|
||||
|
||||
export async function deleteClass(classId: string): Promise<ClassDTO> {
|
||||
const cls = await fetchClass(classId);
|
||||
|
||||
if (!cls) {
|
||||
throw new NotFoundException('Could not delete class because it does not exist');
|
||||
}
|
||||
|
||||
const classRepository = getClassRepository();
|
||||
await classRepository.deleteById(classId);
|
||||
|
||||
|
|
@ -80,10 +74,23 @@ export async function getClassStudents(classId: string, full: boolean): Promise<
|
|||
if (full) {
|
||||
return cls.students.map(mapToStudentDTO);
|
||||
}
|
||||
|
||||
return cls.students.map((student) => student.username);
|
||||
}
|
||||
|
||||
export async function getClassStudentsDTO(classId: string): Promise<StudentDTO[]> {
|
||||
const cls = await fetchClass(classId);
|
||||
return cls.students.map(mapToStudentDTO);
|
||||
}
|
||||
|
||||
export async function getClassTeachers(classId: string, full: boolean): Promise<TeacherDTO[] | string[]> {
|
||||
const cls = await fetchClass(classId);
|
||||
|
||||
if (full){
|
||||
return cls.teachers.map(mapToTeacherDTO);
|
||||
}
|
||||
return cls.teachers.map((student) => student.username);
|
||||
}
|
||||
|
||||
export async function getClassTeacherInvitations(classId: string, full: boolean): Promise<TeacherInvitationDTO[]> {
|
||||
const cls = await fetchClass(classId);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import {
|
|||
getSubmissionRepository,
|
||||
} from '../data/repositories.js';
|
||||
import { Group } from '../entities/assignments/group.entity.js';
|
||||
import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js';
|
||||
import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js';
|
||||
import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js';
|
||||
import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js';
|
||||
import { GroupDTO } from '@dwengo-1/common/interfaces/group';
|
||||
import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission';
|
||||
import { getLogger } from '../logging/initalize.js';
|
||||
import { fetchAssignment } from './assignments.js';
|
||||
import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js';
|
||||
|
||||
async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<Group | null> {
|
||||
const assignment = await fetchAssignment(classId, assignmentNumber);
|
||||
|
|
@ -44,9 +46,11 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme
|
|||
const studentRepository = getStudentRepository();
|
||||
|
||||
const memberUsernames = (groupData.members as string[]) || []; // TODO check if groupdata.members is a list
|
||||
const members = (await Promise.all([...memberUsernames].map((id) => studentRepository.findByUsername(id)))).filter((student) => student !== null);
|
||||
const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter(
|
||||
(student) => student !== null
|
||||
);
|
||||
|
||||
console.log(members);
|
||||
getLogger().debug(members);
|
||||
|
||||
const classRepository = getClassRepository();
|
||||
const cls = await classRepository.findById(classid);
|
||||
|
|
@ -72,7 +76,7 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme
|
|||
|
||||
return newGroup;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
getLogger().error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { DWENGO_API_BASE } from '../config.js';
|
||||
import { fetchWithLogging } from '../util/api-helper.js';
|
||||
import { FilteredLearningObject, LearningObjectMetadata, LearningObjectNode, LearningPathResponse } from '../interfaces/learning-content.js';
|
||||
|
||||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectMetadata,
|
||||
LearningObjectNode,
|
||||
LearningPathResponse,
|
||||
} from '@dwengo-1/common/interfaces/learning-content';
|
||||
import { getLogger } from '../logging/initalize.js';
|
||||
|
||||
function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject {
|
||||
return {
|
||||
|
|
@ -37,7 +44,7 @@ export async function getLearningObjectById(hruid: string, language: string): Pr
|
|||
);
|
||||
|
||||
if (!metadata) {
|
||||
console.error(`⚠️ WARNING: Learning object "${hruid}" not found.`);
|
||||
getLogger().error(`⚠️ WARNING: Learning object "${hruid}" not found.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +55,7 @@ export async function getLearningObjectById(hruid: string, language: string): Pr
|
|||
/**
|
||||
* Generic function to fetch learning paths
|
||||
*/
|
||||
function fetchLearningPaths(arg0: string[], language: string, arg2: string): LearningPathResponse | PromiseLike<LearningPathResponse> {
|
||||
function fetchLearningPaths(_arg0: string[], _language: string, _arg2: string): LearningPathResponse | PromiseLike<LearningPathResponse> {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +67,7 @@ async function fetchLearningObjects(hruid: string, full: boolean, language: stri
|
|||
const learningPathResponse: LearningPathResponse = await fetchLearningPaths([hruid], language, `Learning path for HRUID "${hruid}"`);
|
||||
|
||||
if (!learningPathResponse.success || !learningPathResponse.data?.length) {
|
||||
console.error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`);
|
||||
getLogger().error(`⚠️ WARNING: Learning path "${hruid}" exists but contains no learning objects.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +81,7 @@ async function fetchLearningObjects(hruid: string, full: boolean, language: stri
|
|||
objects.filter((obj): obj is FilteredLearningObject => obj !== null)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('❌ Error fetching learning objects:', error);
|
||||
getLogger().error('❌ Error fetching learning objects:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { getAttachmentRepository } from '../../data/repositories.js';
|
||||
import { Attachment } from '../../entities/content/attachment.entity.js';
|
||||
import { LearningObjectIdentifier } from '../../interfaces/learning-content.js';
|
||||
|
||||
import { LearningObjectIdentifier } from '@dwengo-1/common/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) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { LearningObjectProvider } from './learning-object-provider.js';
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js';
|
||||
import { getLearningObjectRepository, getLearningPathRepository } from '../../data/repositories.js';
|
||||
import { Language } from '../../entities/content/language.js';
|
||||
import { LearningObject } from '../../entities/content/learning-object.entity.js';
|
||||
import { getUrlStringForLearningObject } from '../../util/links.js';
|
||||
import processingService from './processing/processing-service.js';
|
||||
import { NotFoundError } from '@mikro-orm/core';
|
||||
import learningObjectService from './learning-object-service.js';
|
||||
import { getLogger, Logger } from '../../logging/initalize.js';
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
||||
|
||||
const logger: Logger = getLogger();
|
||||
|
||||
|
|
@ -41,10 +40,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 +64,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 +95,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,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { DWENGO_API_BASE } from '../../config.js';
|
||||
import { fetchWithLogging } from '../../util/api-helper.js';
|
||||
import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js';
|
||||
import { LearningObjectProvider } from './learning-object-provider.js';
|
||||
import { getLogger, Logger } from '../../logging/initalize.js';
|
||||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectIdentifier,
|
||||
|
|
@ -7,10 +10,7 @@ import {
|
|||
LearningObjectNode,
|
||||
LearningPathIdentifier,
|
||||
LearningPathResponse,
|
||||
} from '../../interfaces/learning-content.js';
|
||||
import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js';
|
||||
import { LearningObjectProvider } from './learning-object-provider.js';
|
||||
import { getLogger, Logger } from '../../logging/initalize.js';
|
||||
} from '@dwengo-1/common/interfaces/learning-content';
|
||||
|
||||
const logger: Logger = getLogger();
|
||||
|
||||
|
|
@ -66,12 +66,13 @@ async function fetchLearningObjects(learningPathId: LearningPathIdentifier, full
|
|||
}
|
||||
|
||||
const objects = await Promise.all(
|
||||
nodes.map(async (node) =>
|
||||
dwengoApiLearningObjectProvider.getLearningObjectById({
|
||||
nodes.map(async (node) => {
|
||||
const learningObjectId: LearningObjectIdentifier = {
|
||||
hruid: node.learningobject_hruid,
|
||||
language: learningPathId.language,
|
||||
})
|
||||
)
|
||||
};
|
||||
return dwengoApiLearningObjectProvider.getLearningObjectById(learningObjectId);
|
||||
})
|
||||
);
|
||||
return objects.filter((obj): obj is FilteredLearningObject => obj !== null);
|
||||
} catch (error) {
|
||||
|
|
@ -90,7 +91,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = {
|
|||
metadataUrl,
|
||||
`Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`,
|
||||
{
|
||||
params: id,
|
||||
params: { ...id },
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -123,7 +124,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,4 +1,4 @@
|
|||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js';
|
||||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
||||
|
||||
export interface LearningObjectProvider {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js';
|
||||
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/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}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,12 @@ import InlineImageProcessor from '../image/inline-image-processor.js';
|
|||
import * as marked from 'marked';
|
||||
import { getUrlStringForLearningObjectHTML, isValidHttpUrl } from '../../../../util/links.js';
|
||||
import { ProcessingError } from '../processing-error.js';
|
||||
import { LearningObjectIdentifier } from '../../../../interfaces/learning-content.js';
|
||||
import { Language } from '../../../../entities/content/language.js';
|
||||
|
||||
import Image = marked.Tokens.Image;
|
||||
import Heading = marked.Tokens.Heading;
|
||||
import Link = marked.Tokens.Link;
|
||||
import RendererObject = marked.RendererObject;
|
||||
import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
|
||||
const prefixes = {
|
||||
learningObject: '@learning-object',
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ import GiftProcessor from './gift/gift-processor.js';
|
|||
import { LearningObject } from '../../../entities/content/learning-object.entity.js';
|
||||
import Processor from './processor.js';
|
||||
import { DwengoContentType } from './content-type.js';
|
||||
import { LearningObjectIdentifier } from '../../../interfaces/learning-content.js';
|
||||
import { Language } from '../../../entities/content/language.js';
|
||||
import { replaceAsync } from '../../../util/async.js';
|
||||
import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
|
||||
const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = /<learning-object hruid="([^"]+)" language="([^"]+)" version="([^"]+)"\/>/g;
|
||||
const LEARNING_OBJECT_DOES_NOT_EXIST = "<div class='non-existing-learning-object' />";
|
||||
|
||||
class ProcessingService {
|
||||
private processors!: Map<DwengoContentType, Processor<any>>;
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { LearningPathProvider } from './learning-path-provider.js';
|
||||
import { FilteredLearningObject, LearningObjectNode, LearningPath, LearningPathResponse, Transition } from '../../interfaces/learning-content.js';
|
||||
import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity.js';
|
||||
import { getLearningPathRepository } from '../../data/repositories.js';
|
||||
import { Language } from '../../entities/content/language.js';
|
||||
import learningObjectService from '../learning-objects/learning-object-service.js';
|
||||
import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js';
|
||||
import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js';
|
||||
import { getLastSubmissionForCustomizationTarget, isTransitionPossible, PersonalizationTarget } from './learning-path-personalization-util.js';
|
||||
import {
|
||||
FilteredLearningObject,
|
||||
LearningObjectNode,
|
||||
LearningPath,
|
||||
LearningPathResponse,
|
||||
Transition,
|
||||
} from '@dwengo-1/common/interfaces/learning-content';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
|
||||
/**
|
||||
* Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its
|
||||
|
|
@ -18,14 +24,14 @@ async function getLearningObjectsForNodes(nodes: LearningPathNode[]): Promise<Ma
|
|||
// Its corresponding learning object.
|
||||
const nullableNodesToLearningObjects = new Map<LearningPathNode, FilteredLearningObject | null>(
|
||||
await Promise.all(
|
||||
nodes.map((node) =>
|
||||
nodes.map(async (node) =>
|
||||
learningObjectService
|
||||
.getLearningObjectById({
|
||||
hruid: node.learningObjectHruid,
|
||||
version: node.version,
|
||||
language: node.language,
|
||||
})
|
||||
.then((learningObject) => <[LearningPathNode, FilteredLearningObject | null]>[node, learningObject])
|
||||
.then((learningObject) => [node, learningObject] as [LearningPathNode, FilteredLearningObject | null])
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
@ -117,7 +123,7 @@ async function convertNodes(
|
|||
nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>,
|
||||
personalizedFor?: PersonalizationTarget
|
||||
): Promise<LearningObjectNode[]> {
|
||||
const nodesPromise = Array.from(nodesToLearningObjects.entries()).map((entry) =>
|
||||
const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) =>
|
||||
convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects)
|
||||
);
|
||||
return await Promise.all(nodesPromise);
|
||||
|
|
@ -152,7 +158,7 @@ function convertTransition(
|
|||
throw new Error(`Learning object ${transition.next.learningObjectHruid}/${transition.next.language}/${transition.next.version} not found!`);
|
||||
} else {
|
||||
return {
|
||||
_id: '' + index, // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path.
|
||||
_id: String(index), // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path.
|
||||
default: false, // We don't work with default transitions but retain this for backwards compatibility.
|
||||
next: {
|
||||
_id: nextNode._id + index, // Construct a unique ID for the transition for backwards compatibility.
|
||||
|
|
@ -179,11 +185,11 @@ const databaseLearningPathProvider: LearningPathProvider = {
|
|||
): Promise<LearningPathResponse> {
|
||||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
const learningPaths = (await Promise.all(hruids.map((hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter(
|
||||
const learningPaths = (await Promise.all(hruids.map(async (hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter(
|
||||
(learningPath) => learningPath !== null
|
||||
);
|
||||
const filteredLearningPaths = await Promise.all(
|
||||
learningPaths.map((learningPath, index) => convertLearningPath(learningPath, index, personalizedFor))
|
||||
learningPaths.map(async (learningPath, index) => convertLearningPath(learningPath, index, personalizedFor))
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
@ -200,7 +206,7 @@ const databaseLearningPathProvider: LearningPathProvider = {
|
|||
const learningPathRepo = getLearningPathRepository();
|
||||
|
||||
const searchResults = await learningPathRepo.findByQueryStringAndLanguage(query, language);
|
||||
return await Promise.all(searchResults.map((result, index) => convertLearningPath(result, index, personalizedFor)));
|
||||
return await Promise.all(searchResults.map(async (result, index) => convertLearningPath(result, index, personalizedFor)));
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { fetchWithLogging } from '../../util/api-helper.js';
|
||||
import { DWENGO_API_BASE } from '../../config.js';
|
||||
import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js';
|
||||
import { LearningPathProvider } from './learning-path-provider.js';
|
||||
import { getLogger, Logger } from '../../logging/initalize.js';
|
||||
import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content';
|
||||
|
||||
const logger: Logger = getLogger();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js';
|
||||
import { Language } from '../../entities/content/language.js';
|
||||
import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content';
|
||||
import { PersonalizationTarget } from './learning-path-personalization-util.js';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
|
||||
/**
|
||||
* Generic interface for a service which provides access to learning paths from a data source.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js';
|
||||
import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js';
|
||||
import databaseLearningPathProvider from './database-learning-path-provider.js';
|
||||
import { EnvVars, getEnvVar } from '../../util/envvars.js';
|
||||
import { Language } from '../../entities/content/language.js';
|
||||
import { envVars, getEnvVar } from '../../util/envVars.js';
|
||||
import { PersonalizationTarget } from './learning-path-personalization-util.js';
|
||||
import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
|
||||
const userContentPrefix = getEnvVar(EnvVars.UserContentPrefix);
|
||||
const userContentPrefix = getEnvVar(envVars.UserContentPrefix);
|
||||
const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider];
|
||||
|
||||
/**
|
||||
|
|
@ -49,7 +49,9 @@ const learningPathService = {
|
|||
* Search learning paths in the data source using the given search string.
|
||||
*/
|
||||
async searchLearningPaths(query: string, language: Language, personalizedFor?: PersonalizationTarget): Promise<LearningPath[]> {
|
||||
const providerResponses = await Promise.all(allProviders.map((provider) => provider.searchLearningPaths(query, language, personalizedFor)));
|
||||
const providerResponses = await Promise.all(
|
||||
allProviders.map(async (provider) => provider.searchLearningPaths(query, language, personalizedFor))
|
||||
);
|
||||
return providerResponses.flat();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { getAnswerRepository, getQuestionRepository } from '../data/repositories.js';
|
||||
import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js';
|
||||
import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js';
|
||||
import { Question } from '../entities/questions/question.entity.js';
|
||||
import { Answer } from '../entities/questions/answer.entity.js';
|
||||
import { mapToAnswerDTO, mapToAnswerId } from '../interfaces/answer.js';
|
||||
import { mapToAnswerDTO, mapToAnswerDTOId } from '../interfaces/answer.js';
|
||||
import { QuestionRepository } from '../data/questions/question-repository.js';
|
||||
import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js';
|
||||
import { mapToUser } from '../interfaces/user.js';
|
||||
import { Student } from '../entities/users/student.entity.js';
|
||||
import { mapToStudent } from '../interfaces/student.js';
|
||||
import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
|
||||
import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer';
|
||||
|
||||
export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise<QuestionDTO[] | QuestionId[]> {
|
||||
const questionRepository: QuestionRepository = getQuestionRepository();
|
||||
|
|
@ -17,13 +17,11 @@ export async function getAllQuestions(id: LearningObjectIdentifier, full: boolea
|
|||
return [];
|
||||
}
|
||||
|
||||
const questionsDTO: QuestionDTO[] = questions.map(mapToQuestionDTO);
|
||||
|
||||
if (full) {
|
||||
return questionsDTO;
|
||||
return questions.map(mapToQuestionDTO);
|
||||
}
|
||||
|
||||
return questionsDTO.map(mapToQuestionId);
|
||||
return questions.map(mapToQuestionDTOId);
|
||||
}
|
||||
|
||||
async function fetchQuestion(questionId: QuestionId): Promise<Question | null> {
|
||||
|
|
@ -47,7 +45,7 @@ export async function getQuestion(questionId: QuestionId): Promise<QuestionDTO |
|
|||
return mapToQuestionDTO(question);
|
||||
}
|
||||
|
||||
export async function getAnswersByQuestion(questionId: QuestionId, full: boolean) {
|
||||
export async function getAnswersByQuestion(questionId: QuestionId, full: boolean): Promise<AnswerDTO[] | AnswerId[]> {
|
||||
const answerRepository = getAnswerRepository();
|
||||
const question = await fetchQuestion(questionId);
|
||||
|
||||
|
|
@ -61,34 +59,37 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean
|
|||
return [];
|
||||
}
|
||||
|
||||
const answersDTO = answers.map(mapToAnswerDTO);
|
||||
|
||||
if (full) {
|
||||
return answersDTO;
|
||||
return answers.map(mapToAnswerDTO);
|
||||
}
|
||||
|
||||
return answersDTO.map(mapToAnswerId);
|
||||
return answers.map(mapToAnswerDTOId);
|
||||
}
|
||||
|
||||
export async function createQuestion(questionDTO: QuestionDTO) {
|
||||
export async function createQuestion(questionDTO: QuestionDTO): Promise<QuestionDTO | null> {
|
||||
const questionRepository = getQuestionRepository();
|
||||
|
||||
const author = mapToStudent(questionDTO.author);
|
||||
|
||||
const loId: LearningObjectIdentifier = {
|
||||
...questionDTO.learningObjectIdentifier,
|
||||
version: questionDTO.learningObjectIdentifier.version ?? 1,
|
||||
};
|
||||
|
||||
try {
|
||||
await questionRepository.createQuestion({
|
||||
loId: questionDTO.learningObjectIdentifier,
|
||||
loId,
|
||||
author,
|
||||
content: questionDTO.content,
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return questionDTO;
|
||||
}
|
||||
|
||||
export async function deleteQuestion(questionId: QuestionId) {
|
||||
export async function deleteQuestion(questionId: QuestionId): Promise<QuestionDTO | null> {
|
||||
const questionRepository = getQuestionRepository();
|
||||
|
||||
const question = await fetchQuestion(questionId);
|
||||
|
|
@ -97,9 +98,14 @@ export async function deleteQuestion(questionId: QuestionId) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const loId: LearningObjectIdentifier = {
|
||||
...questionId.learningObjectIdentifier,
|
||||
version: questionId.learningObjectIdentifier.version ?? 1,
|
||||
};
|
||||
|
||||
try {
|
||||
await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(questionId.learningObjectIdentifier, questionId.sequenceNumber);
|
||||
} catch (e) {
|
||||
await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(loId, questionId.sequenceNumber);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,62 +1,75 @@
|
|||
import { getClassRepository, getGroupRepository, getStudentRepository, getSubmissionRepository } from '../data/repositories.js';
|
||||
import { AssignmentDTO } from '../interfaces/assignment.js';
|
||||
import { ClassDTO, mapToClassDTO } from '../interfaces/class.js';
|
||||
import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js';
|
||||
import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student.js';
|
||||
import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js';
|
||||
import {
|
||||
getClassJoinRequestRepository,
|
||||
getClassRepository,
|
||||
getGroupRepository,
|
||||
getQuestionRepository,
|
||||
getStudentRepository,
|
||||
getSubmissionRepository,
|
||||
} from '../data/repositories.js';
|
||||
import { mapToClassDTO } from '../interfaces/class.js';
|
||||
import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js';
|
||||
import { mapToStudent, mapToStudentDTO } from '../interfaces/student.js';
|
||||
import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js';
|
||||
import { getAllAssignments } from './assignments.js';
|
||||
import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js';
|
||||
import { mapToStudentRequest, mapToStudentRequestDTO } from '../interfaces/student-request.js';
|
||||
import { Student } from '../entities/users/student.entity.js';
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
import { fetchClass } from './classes.js';
|
||||
import { StudentDTO } from '@dwengo-1/common/interfaces/student';
|
||||
import { ClassDTO } from '@dwengo-1/common/interfaces/class';
|
||||
import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment';
|
||||
import { GroupDTO } from '@dwengo-1/common/interfaces/group';
|
||||
import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission';
|
||||
import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
|
||||
import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request';
|
||||
|
||||
export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> {
|
||||
const studentRepository = getStudentRepository();
|
||||
const students = await studentRepository.findAll();
|
||||
const users = await studentRepository.findAll();
|
||||
|
||||
if (full) {
|
||||
return students.map(mapToStudentDTO);
|
||||
return users.map(mapToStudentDTO);
|
||||
}
|
||||
|
||||
return students.map((student) => student.username);
|
||||
return users.map((user) => user.username);
|
||||
}
|
||||
|
||||
export async function getStudent(username: string): Promise<StudentDTO | null> {
|
||||
export async function fetchStudent(username: string): Promise<Student> {
|
||||
const studentRepository = getStudentRepository();
|
||||
const user = await studentRepository.findByUsername(username);
|
||||
return user ? mapToStudentDTO(user) : null;
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('Student with username not found');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function createStudent(userData: StudentDTO): Promise<StudentDTO | null> {
|
||||
export async function getStudent(username: string): Promise<StudentDTO> {
|
||||
const user = await fetchStudent(username);
|
||||
return mapToStudentDTO(user);
|
||||
}
|
||||
|
||||
export async function createStudent(userData: StudentDTO): Promise<StudentDTO> {
|
||||
const studentRepository = getStudentRepository();
|
||||
|
||||
const newStudent = mapToStudent(userData);
|
||||
await studentRepository.save(newStudent, { preventOverwrite: true });
|
||||
return mapToStudentDTO(newStudent);
|
||||
return userData;
|
||||
}
|
||||
|
||||
export async function deleteStudent(username: string): Promise<StudentDTO | null> {
|
||||
export async function deleteStudent(username: string): Promise<StudentDTO> {
|
||||
const studentRepository = getStudentRepository();
|
||||
|
||||
const user = await studentRepository.findByUsername(username);
|
||||
const student = await fetchStudent(username); // Throws error if it does not exist
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
await studentRepository.deleteByUsername(username);
|
||||
|
||||
return mapToStudentDTO(user);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
await studentRepository.deleteByUsername(username);
|
||||
return mapToStudentDTO(student);
|
||||
}
|
||||
|
||||
export async function getStudentClasses(username: string, full: boolean): Promise<ClassDTO[] | string[]> {
|
||||
const studentRepository = getStudentRepository();
|
||||
const student = await studentRepository.findByUsername(username);
|
||||
|
||||
if (!student) {
|
||||
return [];
|
||||
}
|
||||
const student = await fetchStudent(username);
|
||||
|
||||
const classRepository = getClassRepository();
|
||||
const classes = await classRepository.findByStudent(student);
|
||||
|
|
@ -69,12 +82,7 @@ export async function getStudentClasses(username: string, full: boolean): Promis
|
|||
}
|
||||
|
||||
export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[]> {
|
||||
const studentRepository = getStudentRepository();
|
||||
const student = await studentRepository.findByUsername(username);
|
||||
|
||||
if (!student) {
|
||||
return [];
|
||||
}
|
||||
const student = await fetchStudent(username);
|
||||
|
||||
const classRepository = getClassRepository();
|
||||
const classes = await classRepository.findByStudent(student);
|
||||
|
|
@ -83,12 +91,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr
|
|||
}
|
||||
|
||||
export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[]> {
|
||||
const studentRepository = getStudentRepository();
|
||||
const student = await studentRepository.findByUsername(username);
|
||||
|
||||
if (!student) {
|
||||
return [];
|
||||
}
|
||||
const student = await fetchStudent(username);
|
||||
|
||||
const groupRepository = getGroupRepository();
|
||||
const groups = await groupRepository.findAllGroupsWithStudent(student);
|
||||
|
|
@ -101,12 +104,7 @@ export async function getStudentGroups(username: string, full: boolean): Promise
|
|||
}
|
||||
|
||||
export async function getStudentSubmissions(username: string, full: boolean): Promise<SubmissionDTO[] | SubmissionDTOId[]> {
|
||||
const studentRepository = getStudentRepository();
|
||||
const student = await studentRepository.findByUsername(username);
|
||||
|
||||
if (!student) {
|
||||
return [];
|
||||
}
|
||||
const student = await fetchStudent(username);
|
||||
|
||||
const submissionRepository = getSubmissionRepository();
|
||||
const submissions = await submissionRepository.findAllSubmissionsForStudent(student);
|
||||
|
|
@ -117,3 +115,66 @@ export async function getStudentSubmissions(username: string, full: boolean): Pr
|
|||
|
||||
return submissions.map(mapToSubmissionDTOId);
|
||||
}
|
||||
|
||||
export async function getStudentQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> {
|
||||
const student = await fetchStudent(username);
|
||||
|
||||
const questionRepository = getQuestionRepository();
|
||||
const questions = await questionRepository.findAllByAuthor(student);
|
||||
|
||||
if (full) {
|
||||
return questions.map(mapToQuestionDTO);
|
||||
}
|
||||
|
||||
return questions.map(mapToQuestionDTOId);
|
||||
}
|
||||
|
||||
export async function createClassJoinRequest(username: string, classId: string): Promise<ClassJoinRequestDTO> {
|
||||
const requestRepo = getClassJoinRequestRepository();
|
||||
|
||||
const student = await fetchStudent(username); // Throws error if student not found
|
||||
const cls = await fetchClass(classId);
|
||||
|
||||
const request = mapToStudentRequest(student, cls);
|
||||
await requestRepo.save(request, { preventOverwrite: true });
|
||||
return mapToStudentRequestDTO(request);
|
||||
}
|
||||
|
||||
export async function getJoinRequestsByStudent(username: string): Promise<ClassJoinRequestDTO[]> {
|
||||
const requestRepo = getClassJoinRequestRepository();
|
||||
|
||||
const student = await fetchStudent(username);
|
||||
|
||||
const requests = await requestRepo.findAllRequestsBy(student);
|
||||
return requests.map(mapToStudentRequestDTO);
|
||||
}
|
||||
|
||||
export async function getJoinRequestByStudentClass(username: string, classId: string): Promise<ClassJoinRequestDTO> {
|
||||
const requestRepo = getClassJoinRequestRepository();
|
||||
|
||||
const student = await fetchStudent(username);
|
||||
const cls = await fetchClass(classId);
|
||||
|
||||
const request = await requestRepo.findByStudentAndClass(student, cls);
|
||||
if (!request) {
|
||||
throw new NotFoundException('Join request not found');
|
||||
}
|
||||
|
||||
return mapToStudentRequestDTO(request);
|
||||
}
|
||||
|
||||
export async function deleteClassJoinRequest(username: string, classId: string): Promise<ClassJoinRequestDTO> {
|
||||
const requestRepo = getClassJoinRequestRepository();
|
||||
|
||||
const student = await fetchStudent(username);
|
||||
const cls = await fetchClass(classId);
|
||||
|
||||
const request = await requestRepo.findByStudentAndClass(student, cls);
|
||||
|
||||
if (!request) {
|
||||
throw new NotFoundException('Join request not found');
|
||||
}
|
||||
|
||||
await requestRepo.deleteBy(student, cls);
|
||||
return mapToStudentRequestDTO(request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { getGroupRepository, getSubmissionRepository } from '../data/repositories.js';
|
||||
import { Language } from '../entities/content/language.js';
|
||||
import { getSubmissionRepository } from '../data/repositories.js';
|
||||
import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js';
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js';
|
||||
import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js';
|
||||
import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission';
|
||||
import { Language } from '@dwengo-1/common/util/language';
|
||||
|
||||
export async function getSubmission(
|
||||
loId: LearningObjectIdentifier,
|
||||
|
|
@ -27,24 +28,24 @@ export async function getAllSubmissions(
|
|||
return submissions.map(mapToSubmissionDTO);
|
||||
}
|
||||
|
||||
export async function createSubmission(submissionDTO: SubmissionDTO) {
|
||||
export async function createSubmission(submissionDTO: SubmissionDTO): Promise<SubmissionDTO | null> {
|
||||
const submissionRepository = getSubmissionRepository();
|
||||
const submission = mapToSubmission(submissionDTO);
|
||||
|
||||
try {
|
||||
const newSubmission = await submissionRepository.create(submission);
|
||||
const newSubmission = submissionRepository.create(submission);
|
||||
await submissionRepository.save(newSubmission);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mapToSubmissionDTO(submission);
|
||||
}
|
||||
|
||||
export async function deleteSubmission(loId: LearningObjectIdentifier, submissionNumber: number) {
|
||||
export async function deleteSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise<SubmissionDTO> {
|
||||
const submissionRepository = getSubmissionRepository();
|
||||
|
||||
const submission = getSubmission(loId, submissionNumber);
|
||||
const submission = await getSubmission(loId, submissionNumber);
|
||||
|
||||
if (!submission) {
|
||||
throw new NotFoundException('Could not delete submission because it does not exist');
|
||||
|
|
|
|||
|
|
@ -1,127 +1,167 @@
|
|||
import { getClassRepository, getLearningObjectRepository, getQuestionRepository, getTeacherRepository } from '../data/repositories.js';
|
||||
import { ClassDTO, mapToClassDTO } from '../interfaces/class.js';
|
||||
import { getClassStudents } from './classes.js';
|
||||
import { StudentDTO } from '../interfaces/student.js';
|
||||
import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js';
|
||||
import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js';
|
||||
import {
|
||||
getClassJoinRequestRepository,
|
||||
getClassRepository,
|
||||
getLearningObjectRepository,
|
||||
getQuestionRepository,
|
||||
getTeacherRepository,
|
||||
} from '../data/repositories.js';
|
||||
import { mapToClassDTO } from '../interfaces/class.js';
|
||||
import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js';
|
||||
import { mapToTeacher, mapToTeacherDTO } from '../interfaces/teacher.js';
|
||||
import { Teacher } from '../entities/users/teacher.entity.js';
|
||||
import { fetchStudent } from './students.js';
|
||||
import { ClassJoinRequest } from '../entities/classes/class-join-request.entity.js';
|
||||
import { mapToStudentRequestDTO } from '../interfaces/student-request.js';
|
||||
import { TeacherRepository } from '../data/users/teacher-repository.js';
|
||||
import { ClassRepository } from '../data/classes/class-repository.js';
|
||||
import { Class } from '../entities/classes/class.entity.js';
|
||||
import { LearningObjectRepository } from '../data/content/learning-object-repository.js';
|
||||
import { LearningObject } from '../entities/content/learning-object.entity.js';
|
||||
import { QuestionRepository } from '../data/questions/question-repository.js';
|
||||
import { Question } from '../entities/questions/question.entity.js';
|
||||
import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js';
|
||||
import { Student } from '../entities/users/student.entity.js';
|
||||
import { NotFoundException } from '../exceptions/not-found-exception.js';
|
||||
import {getClassStudents, getClassStudentsDTO} from './classes.js';
|
||||
import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher';
|
||||
import { ClassDTO } from '@dwengo-1/common/interfaces/class';
|
||||
import { StudentDTO } from '@dwengo-1/common/interfaces/student';
|
||||
import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
|
||||
import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request';
|
||||
import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request';
|
||||
|
||||
export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
const teachers = await teacherRepository.findAll();
|
||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||
const users: Teacher[] = await teacherRepository.findAll();
|
||||
|
||||
if (full) {
|
||||
return teachers.map(mapToTeacherDTO);
|
||||
return users.map(mapToTeacherDTO);
|
||||
}
|
||||
return users.map((user) => user.username);
|
||||
}
|
||||
|
||||
export async function fetchTeacher(username: string): Promise<Teacher> {
|
||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||
const user: Teacher | null = await teacherRepository.findByUsername(username);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('Teacher with username not found');
|
||||
}
|
||||
|
||||
return teachers.map((teacher) => teacher.username);
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function getTeacher(username: string): Promise<TeacherDTO | null> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
const user = await teacherRepository.findByUsername(username);
|
||||
return user ? mapToTeacherDTO(user) : null;
|
||||
export async function getTeacher(username: string): Promise<TeacherDTO> {
|
||||
const user: Teacher = await fetchTeacher(username);
|
||||
return mapToTeacherDTO(user);
|
||||
}
|
||||
|
||||
export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO | null> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> {
|
||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||
|
||||
const newTeacher = mapToTeacher(userData);
|
||||
await teacherRepository.save(newTeacher, { preventOverwrite: true });
|
||||
|
||||
return mapToTeacherDTO(newTeacher);
|
||||
}
|
||||
|
||||
export async function deleteTeacher(username: string): Promise<TeacherDTO | null> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
export async function deleteTeacher(username: string): Promise<TeacherDTO> {
|
||||
const teacherRepository: TeacherRepository = getTeacherRepository();
|
||||
|
||||
const user = await teacherRepository.findByUsername(username);
|
||||
const teacher = await fetchTeacher(username); // Throws error if it does not exist
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
await teacherRepository.deleteByUsername(username);
|
||||
|
||||
return mapToTeacherDTO(user);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
await teacherRepository.deleteByUsername(username);
|
||||
return mapToTeacherDTO(teacher);
|
||||
}
|
||||
|
||||
export async function fetchClassesByTeacher(username: string): Promise<ClassDTO[] | null> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
const teacher = await teacherRepository.findByUsername(username);
|
||||
if (!teacher) {
|
||||
return null;
|
||||
}
|
||||
async function fetchClassesByTeacher(username: string): Promise<ClassDTO[]> {
|
||||
const teacher: Teacher = await fetchTeacher(username);
|
||||
|
||||
const classRepository = getClassRepository();
|
||||
const classes = await classRepository.findByTeacher(teacher);
|
||||
const classRepository: ClassRepository = getClassRepository();
|
||||
const classes: Class[] = await classRepository.findByTeacher(teacher);
|
||||
return classes.map(mapToClassDTO);
|
||||
}
|
||||
|
||||
export async function getClassesByTeacher(username: string, full: boolean): Promise<ClassDTO[] | string[] | null> {
|
||||
const classes = await fetchClassesByTeacher(username);
|
||||
|
||||
if (!classes) {
|
||||
return null;
|
||||
}
|
||||
export async function getClassesByTeacher(username: string, full: boolean): Promise<ClassDTO[] | string[]> {
|
||||
const classes: ClassDTO[] = await fetchClassesByTeacher(username);
|
||||
|
||||
if (full) {
|
||||
return classes;
|
||||
}
|
||||
|
||||
return classes.map((cls) => cls.id);
|
||||
}
|
||||
|
||||
export async function getStudentsByTeacher(username: string, full: boolean): Promise<StudentDTO[] | string[] | null> {
|
||||
const classes = (await getClassesByTeacher(username, false)) as string[];
|
||||
export async function getStudentsByTeacher(username: string, full: boolean): Promise<StudentDTO[] | string[]> {
|
||||
const classes: ClassDTO[] = await fetchClassesByTeacher(username);
|
||||
|
||||
if (!classes) {
|
||||
return null;
|
||||
if (!classes || classes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// workaround
|
||||
let students;
|
||||
const classIds: string[] = classes.map((cls) => cls.id);
|
||||
|
||||
const students: StudentDTO[] = (await Promise.all(classIds.map(async (username) => await getClassStudentsDTO(username)))).flat();
|
||||
|
||||
if (full) {
|
||||
students = (await Promise.all(classes.map(async (id) => await getClassStudents(id, full) as StudentDTO[]))).flat();
|
||||
} else {
|
||||
students = (await Promise.all(classes.map(async (id) => await getClassStudents(id, full) as string[]))).flat();
|
||||
return students
|
||||
}
|
||||
|
||||
return students;
|
||||
|
||||
return students.map((student) => student.username);
|
||||
}
|
||||
|
||||
export async function fetchTeacherQuestions(username: string): Promise<QuestionDTO[] | null> {
|
||||
const teacherRepository = getTeacherRepository();
|
||||
const teacher = await teacherRepository.findByUsername(username);
|
||||
if (!teacher) {
|
||||
return null;
|
||||
}
|
||||
export async function getTeacherQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> {
|
||||
const teacher: Teacher = await fetchTeacher(username);
|
||||
|
||||
// Find all learning objects that this teacher manages
|
||||
const learningObjectRepository = getLearningObjectRepository();
|
||||
const learningObjects = await learningObjectRepository.findAllByTeacher(teacher);
|
||||
const learningObjectRepository: LearningObjectRepository = getLearningObjectRepository();
|
||||
const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher);
|
||||
|
||||
if (!learningObjects || learningObjects.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Fetch all questions related to these learning objects
|
||||
const questionRepository = getQuestionRepository();
|
||||
const questions = await questionRepository.findAllByLearningObjects(learningObjects);
|
||||
|
||||
return questions.map(mapToQuestionDTO);
|
||||
}
|
||||
|
||||
export async function getQuestionsByTeacher(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[] | null> {
|
||||
const questions = await fetchTeacherQuestions(username);
|
||||
|
||||
if (!questions) {
|
||||
return null;
|
||||
}
|
||||
const questionRepository: QuestionRepository = getQuestionRepository();
|
||||
const questions: Question[] = await questionRepository.findAllByLearningObjects(learningObjects);
|
||||
|
||||
if (full) {
|
||||
return questions;
|
||||
return questions.map(mapToQuestionDTO);
|
||||
}
|
||||
|
||||
return questions.map(mapToQuestionId);
|
||||
return questions.map(mapToQuestionDTOId);
|
||||
}
|
||||
|
||||
export async function getJoinRequestsByClass(classId: string): Promise<ClassJoinRequestDTO[]> {
|
||||
const classRepository: ClassRepository = getClassRepository();
|
||||
const cls: Class | null = await classRepository.findById(classId);
|
||||
|
||||
if (!cls) {
|
||||
throw new NotFoundException('Class with id not found');
|
||||
}
|
||||
|
||||
const requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository();
|
||||
const requests: ClassJoinRequest[] = await requestRepo.findAllOpenRequestsTo(cls);
|
||||
return requests.map(mapToStudentRequestDTO);
|
||||
}
|
||||
|
||||
export async function updateClassJoinRequestStatus(studentUsername: string, classId: string, accepted = true): Promise<ClassJoinRequestDTO> {
|
||||
const requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository();
|
||||
const classRepo: ClassRepository = getClassRepository();
|
||||
|
||||
const student: Student = await fetchStudent(studentUsername);
|
||||
const cls: Class | null = await classRepo.findById(classId);
|
||||
|
||||
if (!cls) {
|
||||
throw new NotFoundException('Class not found');
|
||||
}
|
||||
|
||||
const request: ClassJoinRequest | null = await requestRepo.findByStudentAndClass(student, cls);
|
||||
|
||||
if (!request) {
|
||||
throw new NotFoundException('Join request not found');
|
||||
}
|
||||
|
||||
request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined;
|
||||
|
||||
await requestRepo.save(request);
|
||||
return mapToStudentRequestDTO(request);
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue