Merge branch 'feat/service-layer' into feat/service-layer-adriaan
# Conflicts: # backend/src/controllers/classes.ts # backend/src/controllers/students.ts # backend/src/data/users/teacher-repository.ts # backend/src/interfaces/assignment.ts # backend/src/interfaces/teacher.ts # backend/src/routes/classes.ts # backend/src/services/assignments.ts # backend/src/services/class.ts # backend/src/services/students.ts # backend/src/util/translation-helper.ts
This commit is contained in:
commit
6c4ea0eefb
33 changed files with 454 additions and 137 deletions
|
@ -3,21 +3,23 @@ import { initORM } from './orm.js';
|
||||||
import { EnvVars, getNumericEnvVar } from './util/envvars.js';
|
import { EnvVars, getNumericEnvVar } from './util/envvars.js';
|
||||||
|
|
||||||
import themeRoutes from './routes/themes.js';
|
import themeRoutes from './routes/themes.js';
|
||||||
import learningPathRoutes from './routes/learningPaths.js';
|
import learningPathRoutes from './routes/learning-paths.js';
|
||||||
import learningObjectRoutes from './routes/learningObjects.js';
|
import learningObjectRoutes from './routes/learning-objects.js';
|
||||||
|
|
||||||
import studentRouter from './routes/student.js';
|
import studentRoutes from './routes/students.js';
|
||||||
import groupRouter from './routes/group.js';
|
import teacherRoutes from './routes/teachers.js'
|
||||||
import submissionRouter from './routes/submission.js';
|
import groupRoutes from './routes/groups.js';
|
||||||
import classRouter from './routes/class.js';
|
import submissionRoutes from './routes/submissions.js';
|
||||||
import questionRouter from './routes/question.js';
|
import classRoutes from './routes/classes.js';
|
||||||
import loginRouter from './routes/login.js';
|
import questionRoutes from './routes/questions.js';
|
||||||
|
|
||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
const port: string | number = getNumericEnvVar(EnvVars.Port);
|
const port: string | number = getNumericEnvVar(EnvVars.Port);
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
// TODO Replace with Express routes
|
// TODO Replace with Express routes
|
||||||
app.get('/', (_, res: Response) => {
|
app.get('/', (_, res: Response) => {
|
||||||
res.json({
|
res.json({
|
||||||
|
@ -25,12 +27,12 @@ app.get('/', (_, res: Response) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/student', studentRouter);
|
app.use('/student', studentRoutes);
|
||||||
app.use('/group', groupRouter);
|
app.use('/teacher', teacherRoutes);
|
||||||
app.use('/submission', submissionRouter);
|
app.use('/group', groupRoutes);
|
||||||
app.use('/class', classRouter);
|
app.use('/submission', submissionRoutes);
|
||||||
app.use('/question', questionRouter);
|
app.use('/class', classRoutes);
|
||||||
app.use('/login', loginRouter);
|
app.use('/question', questionRoutes);
|
||||||
|
|
||||||
app.use('/theme', themeRoutes);
|
app.use('/theme', themeRoutes);
|
||||||
app.use('/learningPath', learningPathRoutes);
|
app.use('/learningPath', learningPathRoutes);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class.js';
|
import { getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/class.js';
|
||||||
|
import { ClassDTO } from '../interfaces/classes.js';
|
||||||
|
|
||||||
export async function getAllClassesHandler(
|
export async function getAllClassesHandler(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -47,7 +48,9 @@ export async function getClassStudentsHandler(
|
||||||
const classId = req.params.id;
|
const classId = req.params.id;
|
||||||
const full = req.query.full === "true";
|
const full = req.query.full === "true";
|
||||||
|
|
||||||
const students = await getClassStudents(classId, full);
|
let students = full
|
||||||
|
? await getClassStudents(classId)
|
||||||
|
: await getClassStudentsIds(classId);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
students: students,
|
students: students,
|
||||||
|
|
|
@ -3,9 +3,9 @@ import {
|
||||||
getLearningObjectById,
|
getLearningObjectById,
|
||||||
getLearningObjectIdsFromPath,
|
getLearningObjectIdsFromPath,
|
||||||
getLearningObjectsFromPath,
|
getLearningObjectsFromPath,
|
||||||
} from '../services/learningObjects.js';
|
} from '../services/learning-objects.js';
|
||||||
import { FALLBACK_LANG } from '../config.js';
|
import { FALLBACK_LANG } from '../config.js';
|
||||||
import { FilteredLearningObject } from '../interfaces/learningPath';
|
import { FilteredLearningObject } from '../interfaces/learning-path';
|
||||||
|
|
||||||
export async function getAllLearningObjects(
|
export async function getAllLearningObjects(
|
||||||
req: Request,
|
req: Request,
|
|
@ -4,7 +4,7 @@ import { FALLBACK_LANG } from '../config.js';
|
||||||
import {
|
import {
|
||||||
fetchLearningPaths,
|
fetchLearningPaths,
|
||||||
searchLearningPaths,
|
searchLearningPaths,
|
||||||
} from '../services/learningPaths.js';
|
} from '../services/learning-paths.js';
|
||||||
/**
|
/**
|
||||||
* Fetch learning paths based on query parameters.
|
* Fetch learning paths based on query parameters.
|
||||||
*/
|
*/
|
155
backend/src/controllers/teachers.ts
Normal file
155
backend/src/controllers/teachers.ts
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import {
|
||||||
|
createTeacher,
|
||||||
|
deleteTeacher,
|
||||||
|
getTeacherByUsername,
|
||||||
|
getClassesByTeacher,
|
||||||
|
getClassIdsByTeacher,
|
||||||
|
getAllTeachers,
|
||||||
|
getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher, getQuestionsByTeacher, getQuestionIdsByTeacher
|
||||||
|
} from '../services/teachers.js';
|
||||||
|
import {TeacherDTO} from "../interfaces/teacher";
|
||||||
|
import {ClassDTO} from "../interfaces/class";
|
||||||
|
import {StudentDTO} from "../interfaces/student";
|
||||||
|
import {QuestionDTO, QuestionId} from "../interfaces/question";
|
||||||
|
|
||||||
|
export async function getTeacherHandler(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const full = req.query.full === 'true';
|
||||||
|
const username = req.query.username as string;
|
||||||
|
|
||||||
|
if (username){
|
||||||
|
const teacher = await getTeacherByUsername(username);
|
||||||
|
if (!teacher){
|
||||||
|
res.status(404).json({ error: `Teacher with username '${username}' not found.` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.json(teacher);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let teachers: TeacherDTO[] | string[];
|
||||||
|
|
||||||
|
if (full) teachers = await getAllTeachers();
|
||||||
|
else teachers = await getAllTeachersIds();
|
||||||
|
|
||||||
|
res.json(teachers);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error fetching teachers:", error);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTeacherHandler(
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const teacherData = req.body as TeacherDTO;
|
||||||
|
|
||||||
|
if (!teacherData.username || !teacherData.firstName || !teacherData.lastName) {
|
||||||
|
res.status(400).json({ error: 'Missing required fields: username, firstName, lastName' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTeacher = await createTeacher(teacherData);
|
||||||
|
res.status(201).json(newTeacher);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating teacher:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteTeacherHandler(
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const username = req.params.username as string;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
res.status(400).json({ error: 'Missing required field: username' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedTeacher = await deleteTeacher(username);
|
||||||
|
|
||||||
|
if (!deletedTeacher) {
|
||||||
|
res.status(400).json({ error: `Did not find teacher with username ${username}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(201).json(deletedTeacher);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting teacher:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeacherClassHandler(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const username = req.params.username as string;
|
||||||
|
const full = req.query.full === 'true';
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
res.status(400).json({ error: 'Missing required field: username' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let classes: ClassDTO[] | string[];
|
||||||
|
|
||||||
|
if (full) classes = await getClassesByTeacher(username);
|
||||||
|
else classes = await getClassIdsByTeacher(username);
|
||||||
|
|
||||||
|
res.status(201).json(classes);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching classes by teacher:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeacherStudentHandler(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const username = req.params.username as string;
|
||||||
|
const full = req.query.full === 'true';
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
res.status(400).json({ error: 'Missing required field: username' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let students: StudentDTO[] | string[];
|
||||||
|
|
||||||
|
if (full) students = await getStudentsByTeacher(username);
|
||||||
|
else students = await getStudentIdsByTeacher(username);
|
||||||
|
|
||||||
|
res.status(201).json(students);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching students by teacher:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeacherQuestionHandler(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const username = req.params.username as string;
|
||||||
|
const full = req.query.full === 'true';
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
res.status(400).json({ error: 'Missing required field: username' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let questions: QuestionDTO[] | QuestionId[];
|
||||||
|
|
||||||
|
if (full) questions = await getQuestionsByTeacher(username);
|
||||||
|
else questions = await getQuestionIdsByTeacher(username);
|
||||||
|
|
||||||
|
res.status(201).json(questions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching questions by teacher:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { themes } from '../data/themes.js';
|
import { themes } from '../data/themes.js';
|
||||||
import { loadTranslations } from "../util/translationHelper.js";
|
import { loadTranslations } from "../util/translation-helper.js";
|
||||||
import { FALLBACK_LANG } from '../config.js';
|
import { FALLBACK_LANG } from '../config.js';
|
||||||
|
|
||||||
interface Translations {
|
interface Translations {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
||||||
import { Class } from '../../entities/classes/class.entity.js';
|
import { Class } from '../../entities/classes/class.entity.js';
|
||||||
import { Student } from '../../entities/users/student.entity.js';
|
import { Student } from '../../entities/users/student.entity.js';
|
||||||
|
import {Teacher} from "../../entities/users/teacher.entity";
|
||||||
|
|
||||||
export class ClassRepository extends DwengoEntityRepository<Class> {
|
export class ClassRepository extends DwengoEntityRepository<Class> {
|
||||||
public findById(id: string): Promise<Class | null> {
|
public findById(id: string): Promise<Class | null> {
|
||||||
|
@ -18,4 +19,11 @@ export class ClassRepository extends DwengoEntityRepository<Class> {
|
||||||
{ populate: ["students", "teachers"] } // voegt student en teacher objecten toe
|
{ populate: ["students", "teachers"] } // voegt student en teacher objecten toe
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public findByTeacher(teacher: Teacher): Promise<Class[]> {
|
||||||
|
return this.find(
|
||||||
|
{ teachers: teacher },
|
||||||
|
{ populate: ["students", "teachers"] }
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
||||||
import { LearningObject } from '../../entities/content/learning-object.entity.js';
|
import { LearningObject } from '../../entities/content/learning-object.entity.js';
|
||||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
||||||
|
import {Teacher} from "../../entities/users/teacher.entity";
|
||||||
|
|
||||||
export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> {
|
export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> {
|
||||||
public findByIdentifier(
|
public findByIdentifier(
|
||||||
|
@ -13,4 +14,11 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// This repository is read-only for now since creating own learning object is an extension feature.
|
// This repository is read-only for now since creating own learning object is an extension feature.
|
||||||
|
|
||||||
|
public findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> {
|
||||||
|
return this.find(
|
||||||
|
{ admins: teacher },
|
||||||
|
{ populate: ['admins'] } // Make sure to load admin relations
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
|
||||||
import { Question } from '../../entities/questions/question.entity.js';
|
import { Question } from '../../entities/questions/question.entity.js';
|
||||||
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
|
||||||
import { Student } from '../../entities/users/student.entity.js';
|
import { Student } from '../../entities/users/student.entity.js';
|
||||||
|
import {LearningObject} from "../../entities/content/learning-object.entity";
|
||||||
|
|
||||||
export class QuestionRepository extends DwengoEntityRepository<Question> {
|
export class QuestionRepository extends DwengoEntityRepository<Question> {
|
||||||
public createQuestion(question: {
|
public createQuestion(question: {
|
||||||
|
@ -42,4 +43,17 @@ export class QuestionRepository extends DwengoEntityRepository<Question> {
|
||||||
sequenceNumber: sequenceNumber,
|
sequenceNumber: sequenceNumber,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findAllByLearningObjects(learningObjects: LearningObject[]): Promise<Question[]> {
|
||||||
|
const objectIdentifiers = learningObjects.map(lo => ({
|
||||||
|
learningObjectHruid: lo.hruid,
|
||||||
|
learningObjectLanguage: lo.language,
|
||||||
|
learningObjectVersion: lo.version
|
||||||
|
}));
|
||||||
|
|
||||||
|
return this.findAll({
|
||||||
|
where: { $or: objectIdentifiers },
|
||||||
|
orderBy: { timestamp: 'ASC' },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
41
backend/src/interfaces/question.ts
Normal file
41
backend/src/interfaces/question.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import {Question} from "../entities/questions/question.entity";
|
||||||
|
import {Enum, PrimaryKey} from "@mikro-orm/core";
|
||||||
|
import {Language} from "../entities/content/language";
|
||||||
|
|
||||||
|
export interface QuestionDTO {
|
||||||
|
learningObjectHruid: string;
|
||||||
|
learningObjectLanguage: string;
|
||||||
|
learningObjectVersion: string;
|
||||||
|
sequenceNumber: number;
|
||||||
|
authorUsername: string;
|
||||||
|
timestamp: string;
|
||||||
|
content: string;
|
||||||
|
endpoints?: {
|
||||||
|
classes: string;
|
||||||
|
questions: string;
|
||||||
|
invitations: string;
|
||||||
|
groups: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Question entity to a DTO format.
|
||||||
|
*/
|
||||||
|
export function mapToQuestionDTO(question: Question): QuestionDTO {
|
||||||
|
return {
|
||||||
|
learningObjectHruid: question.learningObjectHruid,
|
||||||
|
learningObjectLanguage: question.learningObjectLanguage,
|
||||||
|
learningObjectVersion: question.learningObjectVersion,
|
||||||
|
sequenceNumber: question.sequenceNumber,
|
||||||
|
authorUsername: question.author.username,
|
||||||
|
timestamp: question.timestamp.toISOString(),
|
||||||
|
content: question.content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuestionId {
|
||||||
|
learningObjectHruid: string,
|
||||||
|
learningObjectLanguage: Language,
|
||||||
|
learningObjectVersion: string,
|
||||||
|
sequenceNumber: number
|
||||||
|
}
|
|
@ -1,23 +1,36 @@
|
||||||
import { Teacher } from "../entities/users/teacher.entity.js";
|
import { Teacher } from "../entities/users/teacher.entity.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teacher Data Transfer Object
|
||||||
|
*/
|
||||||
export interface TeacherDTO {
|
export interface TeacherDTO {
|
||||||
id: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
endpoints?: {
|
endpoints?: {
|
||||||
|
self: string;
|
||||||
classes: string;
|
classes: string;
|
||||||
questions: string;
|
questions: string;
|
||||||
invitations: string;
|
invitations: string;
|
||||||
groups: string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a Teacher entity to a TeacherDTO
|
||||||
|
*/
|
||||||
export function mapToTeacherDTO(teacher: Teacher): TeacherDTO {
|
export function mapToTeacherDTO(teacher: Teacher): TeacherDTO {
|
||||||
return {
|
return {
|
||||||
id: teacher.username,
|
|
||||||
username: teacher.username,
|
username: teacher.username,
|
||||||
firstName: teacher.firstName,
|
firstName: teacher.firstName,
|
||||||
lastName: teacher.lastName,
|
lastName: teacher.lastName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapToTeacher(teacherData: TeacherDTO): Teacher {
|
||||||
|
const teacher = new Teacher();
|
||||||
|
teacher.username = teacherData.username;
|
||||||
|
teacher.firstName = teacherData.firstName;
|
||||||
|
teacher.lastName = teacherData.lastName;
|
||||||
|
|
||||||
|
return teacher;
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import express from 'express';
|
||||||
import {
|
import {
|
||||||
getAllLearningObjects,
|
getAllLearningObjects,
|
||||||
getLearningObject,
|
getLearningObject,
|
||||||
} from '../controllers/learningObjects.js';
|
} from '../controllers/learning-objects.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getLearningPaths } from '../controllers/learningPaths.js';
|
import { getLearningPaths } from '../controllers/learning-paths.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import express from 'express'
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
// returns login paths for IDP
|
|
||||||
router.get('/', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
// dummy variables, needs to be changed
|
|
||||||
// with IDP endpoints
|
|
||||||
leerkracht: '/login-leerkracht',
|
|
||||||
leerling: '/login-leerling',
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
|
@ -1,58 +0,0 @@
|
||||||
import express from 'express'
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
// root endpoint used to search objects
|
|
||||||
router.get('/', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
teachers: [
|
|
||||||
'0',
|
|
||||||
'1',
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// information about a teacher
|
|
||||||
router.get('/:id', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
id: req.params.id,
|
|
||||||
firstName: 'John',
|
|
||||||
lastName: 'Doe',
|
|
||||||
username: 'JohnDoe1',
|
|
||||||
links: {
|
|
||||||
self: `${req.baseUrl}/${req.params.id}`,
|
|
||||||
classes: `${req.baseUrl}/${req.params.id}/classes`,
|
|
||||||
questions: `${req.baseUrl}/${req.params.id}/questions`,
|
|
||||||
invitations: `${req.baseUrl}/${req.params.id}/invitations`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
// the questions students asked a teacher
|
|
||||||
router.get('/:id/questions', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
questions: [
|
|
||||||
'0'
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// invitations to other classes a teacher received
|
|
||||||
router.get('/:id/invitations', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
invitations: [
|
|
||||||
'0'
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// a list with ids of classes a teacher is in
|
|
||||||
router.get('/:id/classes', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
classes: [
|
|
||||||
'0'
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export default router
|
|
34
backend/src/routes/teachers.ts
Normal file
34
backend/src/routes/teachers.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import express from 'express'
|
||||||
|
import {
|
||||||
|
createTeacherHandler,
|
||||||
|
deleteTeacherHandler,
|
||||||
|
getTeacherClassHandler,
|
||||||
|
getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler
|
||||||
|
} from "../controllers/teachers.js";
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// root endpoint used to search objects
|
||||||
|
router.get('/', getTeacherHandler);
|
||||||
|
|
||||||
|
router.post('/', createTeacherHandler);
|
||||||
|
|
||||||
|
router.delete('/:username', deleteTeacherHandler);
|
||||||
|
|
||||||
|
router.get('/:username/classes', getTeacherClassHandler);
|
||||||
|
|
||||||
|
router.get('/:username/students', getTeacherStudentHandler);
|
||||||
|
|
||||||
|
router.get('/:username/questions', getTeacherQuestionHandler);
|
||||||
|
|
||||||
|
// invitations to other classes a teacher received
|
||||||
|
router.get('/:id/invitations', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
invitations: [
|
||||||
|
'0'
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default router
|
|
@ -1,7 +1,7 @@
|
||||||
import { getClassRepository, getTeacherInvitationRepository } from "../data/repositories.js";
|
import { getClassRepository } from "../data/repositories";
|
||||||
import { ClassDTO, mapToClassDTO } from "../interfaces/classes.js";
|
import { Class } from "../entities/classes/class.entity";
|
||||||
import { mapToStudentDTO, StudentDTO } from "../interfaces/students.js";
|
import { ClassDTO, mapToClassDTO } from "../interfaces/class";
|
||||||
import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from "../interfaces/teacher-invitation.js";
|
import { mapToStudentDTO, StudentDTO } from "../interfaces/student";
|
||||||
|
|
||||||
export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> {
|
export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> {
|
||||||
const classRepository = getClassRepository();
|
const classRepository = getClassRepository();
|
||||||
|
@ -28,41 +28,21 @@ export async function getClass(classId: string): Promise<ClassDTO | null> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getClassStudents(classId: string, full: boolean): Promise<StudentDTO[] | string[]> {
|
async function fetchClassStudents(classId: string, full: boolean): Promise<StudentDTO[] | string[]> {
|
||||||
const classRepository = getClassRepository();
|
const classRepository = getClassRepository();
|
||||||
const cls = await classRepository.findById(classId);
|
const cls = await classRepository.findById(classId);
|
||||||
|
|
||||||
if (!cls) {
|
if (!cls)
|
||||||
return [];
|
return [];
|
||||||
}
|
|
||||||
|
|
||||||
if (full) {
|
return cls.students.map(mapToStudentDTO);
|
||||||
return cls.students.map(mapToStudentDTO);
|
|
||||||
} else {
|
|
||||||
return cls.students.map((student) => student.username);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getClassTeacherInvitations(classId: string, full: boolean): Promise<TeacherInvitationDTO[]> {
|
export async function getClassStudents(classId: string): Promise<StudentDTO[]> {
|
||||||
const classRepository = getClassRepository();
|
return await fetchClassStudents(classId);
|
||||||
const cls = await classRepository.findById(classId);
|
|
||||||
|
|
||||||
if (!cls) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const teacherInvitationRepository = getTeacherInvitationRepository();
|
|
||||||
const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls);
|
|
||||||
|
|
||||||
console.log(invitations);
|
|
||||||
|
|
||||||
if (!invitations) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (full) {
|
|
||||||
return invitations.map(mapToTeacherInvitationDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
return invitations.map(mapToTeacherInvitationDTOIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getClassStudentsIds(classId: string): Promise<string[]> {
|
||||||
|
return await fetchClassStudents(classId).map((student) => student.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { DWENGO_API_BASE } from '../config.js';
|
import { DWENGO_API_BASE } from '../config.js';
|
||||||
import { fetchWithLogging } from '../util/apiHelper.js';
|
import { fetchWithLogging } from '../util/api-helper.js';
|
||||||
import {
|
import {
|
||||||
FilteredLearningObject,
|
FilteredLearningObject,
|
||||||
LearningObjectMetadata,
|
LearningObjectMetadata,
|
||||||
LearningObjectNode,
|
LearningObjectNode,
|
||||||
LearningPathResponse,
|
LearningPathResponse,
|
||||||
} from '../interfaces/learningPath.js';
|
} from '../interfaces/learning-path.js';
|
||||||
import { fetchLearningPaths } from './learningPaths.js';
|
import { fetchLearningPaths } from './learning-paths.js';
|
||||||
|
|
||||||
function filterData(
|
function filterData(
|
||||||
data: LearningObjectMetadata,
|
data: LearningObjectMetadata,
|
|
@ -1,9 +1,9 @@
|
||||||
import { fetchWithLogging } from '../util/apiHelper.js';
|
import { fetchWithLogging } from '../util/api-helper.js';
|
||||||
import { DWENGO_API_BASE } from '../config.js';
|
import { DWENGO_API_BASE } from '../config.js';
|
||||||
import {
|
import {
|
||||||
LearningPath,
|
LearningPath,
|
||||||
LearningPathResponse,
|
LearningPathResponse,
|
||||||
} from '../interfaces/learningPath.js';
|
} from '../interfaces/learning-path.js';
|
||||||
|
|
||||||
export async function fetchLearningPaths(
|
export async function fetchLearningPaths(
|
||||||
hruids: string[],
|
hruids: string[],
|
131
backend/src/services/teachers.ts
Normal file
131
backend/src/services/teachers.ts
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import {
|
||||||
|
getClassRepository,
|
||||||
|
getLearningObjectRepository,
|
||||||
|
getQuestionRepository,
|
||||||
|
getTeacherRepository
|
||||||
|
} from "../data/repositories.js";
|
||||||
|
import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js";
|
||||||
|
import { Teacher } from "../entities/users/teacher.entity";
|
||||||
|
import {ClassDTO, mapToClassDTO} from "../interfaces/class";
|
||||||
|
import {getClassStudents, getClassStudentsIds} from "./class";
|
||||||
|
import {StudentDTO} from "../interfaces/student";
|
||||||
|
import {mapToQuestionDTO, QuestionDTO, QuestionId} from "../interfaces/question";
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchAllTeachers(): Promise<TeacherDTO[]> {
|
||||||
|
const teacherRepository = getTeacherRepository();
|
||||||
|
const teachers = await teacherRepository.find({});
|
||||||
|
|
||||||
|
return teachers.map(mapToTeacherDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllTeachers(): Promise<TeacherDTO[]> {
|
||||||
|
return await fetchAllTeachers();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllTeachersIds(): Promise<string[]> {
|
||||||
|
return await fetchAllTeachers().map((teacher) => teacher.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTeacher(teacherData: TeacherDTO): Promise<Teacher> {
|
||||||
|
const teacherRepository = getTeacherRepository();
|
||||||
|
const newTeacher = mapToTeacher(teacherData);
|
||||||
|
|
||||||
|
await teacherRepository.addTeacher(newTeacher);
|
||||||
|
return newTeacher;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeacherByUsername(username: string): Promise<TeacherDTO | null> {
|
||||||
|
const teacherRepository = getTeacherRepository();
|
||||||
|
const teacher = await teacherRepository.findByUsername(username);
|
||||||
|
|
||||||
|
return teacher ? mapToTeacherDTO(teacher) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteTeacher(username: string): Promise<TeacherDTO | null> {
|
||||||
|
const teacherRepository = getTeacherRepository();
|
||||||
|
const teacher = await teacherRepository.findByUsername(username);
|
||||||
|
|
||||||
|
if (!teacher)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
await teacherRepository.deleteByUsername(username);
|
||||||
|
return teacher;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchClassesByTeacher(username: string): Promise<ClassDTO[]> {
|
||||||
|
const teacherRepository = getTeacherRepository();
|
||||||
|
const classRepository = getClassRepository();
|
||||||
|
|
||||||
|
const teacher = await teacherRepository.findByUsername(username);
|
||||||
|
if (!teacher) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = await classRepository.findByTeacher(teacher);
|
||||||
|
return classes.map(mapToClassDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getClassesByTeacher(username: string): Promise<ClassDTO[]> {
|
||||||
|
return await fetchClassesByTeacher(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getClassIdsByTeacher(): Promise<string[]> {
|
||||||
|
return await fetchClassesByTeacher(username).map((cls) => cls.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchStudentsByTeacher(username: string) {
|
||||||
|
const classes = await getClassIdsByTeacher();
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
classes.map( async (id) => getClassStudents(id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStudentsByTeacher(username: string): Promise<StudentDTO[]> {
|
||||||
|
return await fetchStudentsByTeacher(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStudentIdsByTeacher(): Promise<string[]> {
|
||||||
|
return await fetchStudentsByTeacher(username).map((student) => student.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTeacherQuestions(username: string): Promise<QuestionDTO[]> {
|
||||||
|
const learningObjectRepository = getLearningObjectRepository();
|
||||||
|
const questionRepository = getQuestionRepository();
|
||||||
|
|
||||||
|
const teacher = getTeacherByUsername(username);
|
||||||
|
if (!teacher) {
|
||||||
|
throw new Error(`Teacher with username '${username}' not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all learning objects that this teacher manages
|
||||||
|
const learningObjects = await learningObjectRepository.findAllByTeacher(teacher);
|
||||||
|
|
||||||
|
// Fetch all questions related to these learning objects
|
||||||
|
const questions = await questionRepository.findAllByLearningObjects(learningObjects);
|
||||||
|
|
||||||
|
return questions.map(mapToQuestionDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getQuestionsByTeacher(username: string): Promise<QuestionDTO[]> {
|
||||||
|
return await fetchTeacherQuestions(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getQuestionIdsByTeacher(username: string): Promise<QuestionId[]> {
|
||||||
|
const questions = await fetchTeacherQuestions(username);
|
||||||
|
|
||||||
|
return questions.map((question) => ({
|
||||||
|
learningObjectHruid: question.learningObjectHruid,
|
||||||
|
learningObjectLanguage: question.learningObjectLanguage,
|
||||||
|
learningObjectVersion: question.learningObjectVersion,
|
||||||
|
sequenceNumber: question.sequenceNumber
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import {FALLBACK_LANG} from "../config.js";
|
import { FALLBACK_LANG } from "../config.js";
|
||||||
|
|
||||||
export function loadTranslations<T>(language: string): T {
|
export function loadTranslations<T>(language: string): T {
|
||||||
try {
|
try {
|
Loading…
Add table
Add a link
Reference in a new issue