feat: question-teacher route

This commit is contained in:
Gabriellvl 2025-03-08 10:19:22 +01:00
parent 4968d7cb07
commit 16b73b9e18
6 changed files with 138 additions and 22 deletions

View file

@ -2,15 +2,16 @@ import { Request, Response } from 'express';
import {
createTeacher,
deleteTeacher,
fetchTeacherByUsername,
getTeacherByUsername,
getClassesByTeacher,
getClassIdsByTeacher,
getAllTeachers,
getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher
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 {
@ -18,7 +19,7 @@ export async function getTeacherHandler(req: Request, res: Response): Promise<vo
const username = req.query.username as string;
if (username){
const teacher = await fetchTeacherByUsername(username);
const teacher = await getTeacherByUsername(username);
if (!teacher){
res.status(404).json({ error: `Teacher with username '${username}' not found.` });
return;
@ -128,3 +129,27 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro
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' });
}
}

View file

@ -1,6 +1,7 @@
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
import { LearningObject } from '../../entities/content/learning-object.entity.js';
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
import {Teacher} from "../../entities/users/teacher.entity";
export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> {
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.
public findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> {
return this.find(
{ admins: teacher },
{ populate: ['admins'] } // Make sure to load admin relations
);
}
}

View file

@ -2,6 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
import { Question } from '../../entities/questions/question.entity.js';
import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js';
import { Student } from '../../entities/users/student.entity.js';
import {LearningObject} from "../../entities/content/learning-object.entity";
export class QuestionRepository extends DwengoEntityRepository<Question> {
public createQuestion(question: {
@ -42,4 +43,17 @@ export class QuestionRepository extends DwengoEntityRepository<Question> {
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' },
});
}
}

View 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
}

View file

@ -3,7 +3,7 @@ import {
createTeacherHandler,
deleteTeacherHandler,
getTeacherClassHandler,
getTeacherHandler, getTeacherStudentHandler
getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler
} from "../controllers/teachers.js";
const router = express.Router();
@ -18,14 +18,7 @@ router.get('/:username/classes', getTeacherClassHandler);
router.get('/:username/students', getTeacherStudentHandler);
// the questions students asked a teacher
router.get('/:id/questions', (req, res) => {
res.json({
questions: [
'0'
],
});
});
router.get('/:username/questions', getTeacherQuestionHandler);
// invitations to other classes a teacher received
router.get('/:id/invitations', (req, res) => {
@ -36,14 +29,6 @@ router.get('/:id/invitations', (req, res) => {
});
});
// a list with ids of classes a teacher is in
router.get('/:id/classes', (req, res) => {
res.json({
classes: [
'0'
],
});
});
export default router

View file

@ -1,9 +1,15 @@
import {getClassRepository, getTeacherRepository} from "../data/repositories.js";
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[]> {
@ -29,7 +35,7 @@ export async function createTeacher(teacherData: TeacherDTO): Promise<Teacher> {
return newTeacher;
}
export async function fetchTeacherByUsername(username: string): Promise<TeacherDTO | null> {
export async function getTeacherByUsername(username: string): Promise<TeacherDTO | null> {
const teacherRepository = getTeacherRepository();
const teacher = await teacherRepository.findByUsername(username);
@ -84,5 +90,42 @@ 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
}));
}