feat: add, delete student route met user logic + .js in files

This commit is contained in:
Gabriellvl 2025-03-09 20:18:11 +01:00
parent e0a5596994
commit ecad27ea4d
29 changed files with 301 additions and 159 deletions

View file

@ -16,6 +16,7 @@ import loginRouter from './routes/login.js';
const app: Express = express();
const port: string | number = getNumericEnvVar(EnvVars.Port);
app.use(express.json());
// TODO Replace with Express routes
app.get('/', (_, res: Response) => {

View file

@ -1,5 +1,5 @@
import { Request, Response } from 'express'
import { getAllAssignments, getAssignment } from '../services/assignments';
import { getAllAssignments, getAssignment } from '../services/assignments.js';
// typescript is annoy with with parameter forwarding from class.ts
interface AssignmentParams {
@ -41,4 +41,4 @@ export async function getAssignmentHandler(
}
res.json(assignment);
}
}

View file

@ -1,6 +1,5 @@
import { Request, Response } from 'express';
import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class';
import { ClassDTO } from '../interfaces/classes';
import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class.js';
export async function getAllClassesHandler(
req: Request,
@ -61,10 +60,10 @@ export async function getTeacherInvitationsHandler(
): Promise<void> {
const classId = req.params.id;
const full = req.query.full === "true"; // TODO: not implemented yet
const invitations = await getClassTeacherInvitations(classId, full);
res.json({
invitations: invitations,
});
}
}

View file

@ -1,5 +1,5 @@
import { Request, Response } from 'express';
import { getAllGroups, getGroup } from '../services/groups';
import { getAllGroups, getGroup } from '../services/groups.js';
// typescript is annoywith with parameter forwarding from class.ts
interface GroupParams {
@ -52,4 +52,4 @@ export async function getAllGroupsHandler(
res.json({
groups: groups,
});
}
}

View file

@ -1,53 +1,33 @@
import { Request, Response } from 'express';
import { getAllStudents, getStudent, getStudentClasses, getStudentClassIds } from '../services/students';
import { ClassDTO } from '../interfaces/classes';
import { getAllAssignments } from '../services/assignments';
import {
getStudentClasses,
getStudentClassIds,
StudentService
} from '../services/students.js';
import { ClassDTO } from '../interfaces/classes.js';
import { getAllAssignments } from '../services/assignments.js';
import {createUserHandler, deleteUserHandler, getAllUsersHandler, getUserHandler} from "./users.js";
import { Student } from "../entities/users/student.entity.js";
// TODO: accept arguments (full, ...)
// TODO: endpoints
export async function getAllStudentsHandler (
req: Request,
res: Response,
): Promise<void> {
try {
const students = await getAllStudents();
res.json({
students: students
});
} catch (error) {
console.error('Error fetching learning objects:', error);
res.status(500).json({ error: 'Internal server error' });
}
export async function getAllStudentsHandler (req: Request, res: Response): Promise<void> {
await getAllUsersHandler<Student>(req, res, new StudentService());
}
export async function getStudentHandler(
req: Request,
res: Response,
): Promise<void> {
try {
const username = req.params.id;
const student = await getStudent(username);
if (!student) {
res.status(404).json({ error: "Student not found" });
return;
} else {
student.endpoints = {
classes: `/student/${req.params.id}/classes`,
questions: `/student/${req.params.id}/submissions`,
invitations: `/student/${req.params.id}/assignments`,
groups: `/student/${req.params.id}/groups`,
}
res.json(student);
}
} catch (error) {
console.error('Error fetching learning objects:', error);
res.status(500).json({ error: 'Internal server error' });
}
export async function getStudentHandler(req: Request, res: Response): Promise<void> {
await getUserHandler<Student>(req, res, new StudentService());
}
export async function createStudentHandler(req: Request, res: Response): Promise<void> {
await createUserHandler<Student>(req, res, new StudentService(), Student);
}
export async function deleteStudentHandler(req: Request, res: Response): Promise<void> {
await deleteUserHandler<Student>(req, res, new StudentService());
}
export async function getStudentClassesHandler (
req: Request,
res: Response,
@ -75,6 +55,7 @@ export async function getStudentClassesHandler (
}
}
// TODO
// Might not be fully correct depending on if
// a class has an assignment, that all students
// have this assignment.
@ -92,4 +73,6 @@ export async function getStudentAssignmentsHandler(
res.json({
assignments: assignments
});
}
}

View file

@ -0,0 +1,106 @@
import { Request, Response } from 'express';
import { UserService } from "../services/users.js";
import {UserDTO} from "../interfaces/user.js";
import {User} from "../entities/users/user.entity.js";
export async function getAllUsersHandler<T extends User>(
req: Request,
res: Response,
service: UserService<T>
): Promise<void> {
try {
const full = req.query.full === 'true';
let users: UserDTO[] | string[] = full
? await service.getAllUsers()
: await service.getAllUserIds();
if (!users){
res.status(404).json({ error: `Users not found.` });
return;
}
res.status(201).json(users);
} catch (error) {
console.error("❌ Error fetching users:", error);
res.status(500).json({ error: "Internal server error" });
}
}
export async function getUserHandler<T extends User>(
req: Request,
res: Response,
service: UserService<T>
): Promise<void> {
try {
const username = req.params.username as string;
if (!username) {
res.status(400).json({ error: 'Missing required field: username' });
return;
}
const user = await service.getUserByUsername(username);
if (!user){
res.status(404).json({ error: `User with username '${username}' not found.` });
return;
}
res.status(201).json(user);
} catch (error) {
console.error("❌ Error fetching users:", error);
res.status(500).json({ error: "Internal server error" });
}
}
export async function createUserHandler<T extends User>(
req: Request,
res: Response,
service: UserService<T>,
UserClass: new () => T
) {
try {
console.log("req", req)
const userData = req.body as UserDTO;
if (!userData.username || !userData.firstName || !userData.lastName) {
res.status(400).json({ error: "Missing required fields: username, firstName, lastName" });
return;
}
const newUser = await service.createUser(userData, UserClass);
res.status(201).json(newUser);
} catch (error) {
console.error("❌ Error creating user:", error);
res.status(500).json({ error: "Internal server error" });
}
}
export async function deleteUserHandler<T extends User> (
req: Request,
res: Response,
service: UserService<T>
) {
try {
const username = req.params.username;
if (!username) {
res.status(400).json({ error: "Missing required field: username" });
return;
}
const deletedUser = await service.deleteUser(username);
if (!deletedUser) {
res.status(404).json({ error: `User with username '${username}' not found.` });
return;
}
res.status(200).json(deletedUser);
} catch (error) {
console.error("❌ Error deleting user:", error);
res.status(500).json({ error: "Internal server error" });
}
}

View file

@ -59,7 +59,7 @@ function repositoryGetter<T extends AnyEntity, R extends EntityRepository<T>>(
}
/* Users */
export const getUserRepository = repositoryGetter<User, UserRepository>(User);
export const getUserRepository = repositoryGetter<User, UserRepository<User>>(User);
export const getStudentRepository = repositoryGetter<
Student,
StudentRepository

View file

@ -1,11 +1,4 @@
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
import { Student } from '../../entities/users/student.entity.js';
import {UserRepository} from "./user-repository.js";
export class StudentRepository extends DwengoEntityRepository<Student> {
public findByUsername(username: string): Promise<Student | null> {
return this.findOne({ username: username });
}
public deleteByUsername(username: string): Promise<void> {
return this.deleteWhere({ username: username });
}
}
export class StudentRepository extends UserRepository<Student> {}

View file

@ -1,11 +1,4 @@
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
import { Teacher } from '../../entities/users/teacher.entity.js';
import {UserRepository} from "./user-repository.js";
export class TeacherRepository extends DwengoEntityRepository<Teacher> {
public findByUsername(username: string): Promise<Teacher | null> {
return this.findOne({ username: username });
}
public deleteByUsername(username: string): Promise<void> {
return this.deleteWhere({ username: username });
}
}
export class TeacherRepository extends UserRepository<Teacher> {}

View file

@ -1,11 +1,11 @@
import { DwengoEntityRepository } from '../dwengo-entity-repository.js';
import { User } from '../../entities/users/user.entity.js';
export class UserRepository extends DwengoEntityRepository<User> {
public findByUsername(username: string): Promise<User | null> {
return this.findOne({ username: username });
export class UserRepository<T extends User> extends DwengoEntityRepository<T> {
public findByUsername(username: string): Promise<T | null> {
return this.findOne({ username } as Partial<T>);
}
public deleteByUsername(username: string): Promise<void> {
return this.deleteWhere({ username: username });
return this.deleteWhere({ username } as Partial<T>);
}
}

View file

@ -13,6 +13,15 @@ import { Attachment } from './attachment.entity.js';
import { Teacher } from '../users/teacher.entity.js';
import { LearningObjectRepository } from '../../data/content/learning-object-repository.js';
@Embeddable()
export class ReturnValue {
@Property({ type: 'string' })
callbackUrl!: string;
@Property({ type: 'json' })
callbackSchema!: string;
}
@Entity({ repository: () => LearningObjectRepository })
export class LearningObject {
@PrimaryKey({ type: 'string' })
@ -88,15 +97,6 @@ export class EducationalGoal {
id!: string;
}
@Embeddable()
export class ReturnValue {
@Property({ type: 'string' })
callbackUrl!: string;
@Property({ type: 'json' })
callbackSchema!: string;
}
export enum ContentType {
Markdown = 'text/markdown',
Image = 'image/image',

View file

@ -11,12 +11,4 @@ export class Student extends User {
@ManyToMany(() => Group)
groups!: Collection<Group>;
constructor(
public username: string,
public firstName: string,
public lastName: string
) {
super();
}
}

View file

@ -1,6 +1,5 @@
import { Assignment } from "../entities/assignments/assignment.entity";
import { Class } from "../entities/classes/class.entity";
import { GroupDTO, mapToGroupDTO } from "./groups";
import { Assignment } from "../entities/assignments/assignment.entity.js";
import { GroupDTO, mapToGroupDTO } from "./groups.js";
export interface AssignmentDTO {
id: number,
@ -34,4 +33,4 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO {
language: assignment.learningPathLanguage,
// groups: assignment.groups.map(mapToGroupDTO),
};
}
}

View file

@ -1,4 +1,4 @@
import { Class } from "../entities/classes/class.entity";
import { Class } from "../entities/classes/class.entity.js";
export interface ClassDTO {
id: string;

View file

@ -1,6 +1,6 @@
import { Group } from "../entities/assignments/group.entity";
import { AssignmentDTO, mapToAssignmentDTO } from "./assignments";
import { mapToStudentDTO, StudentDTO } from "./students";
import { Group } from "../entities/assignments/group.entity.js";
import { AssignmentDTO, mapToAssignmentDTO } from "./assignments.js";
import { mapToStudentDTO, StudentDTO } from "./students.js";
export interface GroupDTO {
assignment: number | AssignmentDTO,
@ -10,7 +10,7 @@ export interface GroupDTO {
export function mapToGroupDTO(group: Group): GroupDTO {
return {
assignment: mapToAssignmentDTO(group.assignment, group.assignment.within),
assignment: mapToAssignmentDTO(group.assignment), // ERROR: , group.assignment.within),
groupNumber: group.groupNumber,
members: group.members.map(mapToStudentDTO),
}
@ -22,4 +22,4 @@ export function mapToGroupDTOId(group: Group): GroupDTO {
groupNumber: group.groupNumber,
members: group.members.map(member => member.username),
}
}
}

View file

@ -1,4 +1,4 @@
import { Student } from "../entities/users/student.entity";
import { Student } from "../entities/users/student.entity.js";
export interface StudentDTO {
id: string;
@ -20,4 +20,13 @@ export function mapToStudentDTO(student: Student): StudentDTO {
firstName: student.firstName,
lastName: student.lastName,
};
}
}
export function mapToStudent(studentData: StudentDTO): Student {
const student = new Student();
student.username = studentData.username;
student.firstName = studentData.firstName;
student.lastName = studentData.lastName;
return student;
}

View file

@ -1,6 +1,6 @@
import { TeacherInvitation } from "../entities/classes/teacher-invitation.entity";
import { ClassDTO, mapToClassDTO } from "./classes";
import { mapToTeacherDTO, TeacherDTO } from "./teacher";
import { TeacherInvitation } from "../entities/classes/teacher-invitation.entity.js";
import { ClassDTO, mapToClassDTO } from "./classes.js";
import { mapToTeacherDTO, TeacherDTO } from "./teacher.js";
export interface TeacherInvitationDTO {
sender: string | TeacherDTO,
@ -22,4 +22,4 @@ export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): Tea
receiver: invitation.receiver.username,
class: invitation.class.classId,
};
}
}

View file

@ -1,4 +1,4 @@
import { Teacher } from "../entities/users/teacher.entity";
import { Teacher } from "../entities/users/teacher.entity.js";
export interface TeacherDTO {
id: string;
@ -20,4 +20,4 @@ export function mapToTeacherDTO(teacher: Teacher): TeacherDTO {
firstName: teacher.firstName,
lastName: teacher.lastName,
};
}
}

View file

@ -0,0 +1,30 @@
import { User } from "../entities/users/user.entity.js";
export interface UserDTO {
id?: string,
username: string;
firstName: string;
lastName: string;
endpoints?: {
self: string;
classes: string;
questions: string;
invitations: string;
};
}
export function mapToUserDTO(user: User): UserDTO {
return {
id: user.username,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
};
}
export function mapToUser<T extends User>(userData: UserDTO, userInstance: T): T {
userInstance.username = userData.username;
userInstance.firstName = userData.firstName;
userInstance.lastName = userData.lastName;
return userInstance;
}

View file

@ -1,5 +1,5 @@
import express from 'express'
import { getAllAssignmentsHandler, getAssignmentHandler } from '../controllers/assignments';
import { getAllAssignmentsHandler, getAssignmentHandler } from '../controllers/assignments.js';
import groupRouter from './group.js';
const router = express.Router({ mergeParams: true });
@ -28,4 +28,4 @@ router.get('/:id/questions', (req, res) => {
router.use('/:assignmentid/groups', groupRouter);
export default router
export default router

View file

@ -1,5 +1,5 @@
import express from 'express'
import { getAllClassesHandler, getClassHandler, getClassStudentsHandler, getTeacherInvitationsHandler } from '../controllers/classes';
import { getAllClassesHandler, getClassHandler, getClassStudentsHandler, getTeacherInvitationsHandler } from '../controllers/classes.js';
import assignmentRouter from './assignment.js';
const router = express.Router();
@ -16,4 +16,4 @@ router.get('/:id/students', getClassStudentsHandler);
router.use('/:classid/assignments', assignmentRouter);
export default router
export default router

View file

@ -1,5 +1,5 @@
import express from 'express'
import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups';
import { getAllGroupsHandler, getGroupHandler } from '../controllers/groups.js';
const router = express.Router({ mergeParams: true });
// root endpoint used to search objects
@ -15,4 +15,4 @@ router.get('/:id/question', (req, res) => {
});
})
export default router
export default router

View file

@ -1,12 +1,24 @@
import express from 'express'
import { getAllStudentsHandler, getStudentAssignmentsHandler, getStudentClassesHandler, getStudentHandler } from '../controllers/students';
import {
createStudentHandler, deleteStudentHandler,
getAllStudentsHandler,
getStudentAssignmentsHandler,
getStudentClassesHandler,
getStudentHandler
} from '../controllers/students.js';
const router = express.Router();
// root endpoint used to search objects
router.get('/', getAllStudentsHandler);
router.post('/', createStudentHandler);
router.delete('/:username', deleteStudentHandler);
// information about a student's profile
router.get('/:id', getStudentHandler);
router.get('/:username', getStudentHandler);
// the list of classes a student is in
router.get('/:id/classes', getStudentClassesHandler);
@ -18,10 +30,10 @@ router.get('/:id/submissions', (req, res) => {
});
})
// the list of assignments a student has
router.get('/:id/assignments', getStudentAssignmentsHandler);
// the list of groups a student is in
router.get('/:id/groups', (req, res) => {
res.json({
@ -36,4 +48,4 @@ router.get('/:id/questions', (req, res) => {
});
})
export default router
export default router

View file

@ -1,5 +1,5 @@
import { getAssignmentRepository, getClassRepository } from "../data/repositories";
import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId } from "../interfaces/assignments";
import { getAssignmentRepository, getClassRepository } from "../data/repositories.js";
import { AssignmentDTO, mapToAssignmentDTO, mapToAssignmentDTOId } from "../interfaces/assignments.js";
export async function getAllAssignments(classid: string, full: boolean): Promise<AssignmentDTO[]> {
const classRepository = getClassRepository();
@ -8,7 +8,7 @@ export async function getAllAssignments(classid: string, full: boolean): Promise
if (!cls) {
return [];
}
const assignmentRepository = getAssignmentRepository();
const assignments = await assignmentRepository.findAllAssignmentsInClass(cls);
@ -26,7 +26,7 @@ export async function getAssignment(classid: string, id: number): Promise<Assign
if (!cls) {
return null;
}
const assignmentRepository = getAssignmentRepository();
const assignment = await assignmentRepository.findByClassAndId(cls, id);
@ -35,4 +35,4 @@ export async function getAssignment(classid: string, id: number): Promise<Assign
}
return mapToAssignmentDTO(assignment);
}
}

View file

@ -1,8 +1,7 @@
import { getClassRepository, getTeacherInvitationRepository } from "../data/repositories";
import { Class } from "../entities/classes/class.entity";
import { ClassDTO, mapToClassDTO } from "../interfaces/classes";
import { mapToStudentDTO, StudentDTO } from "../interfaces/students";
import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from "../interfaces/teacher-invitation";
import { getClassRepository, getTeacherInvitationRepository } from "../data/repositories.js";
import { ClassDTO, mapToClassDTO } from "../interfaces/classes.js";
import { mapToStudentDTO, StudentDTO } from "../interfaces/students.js";
import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from "../interfaces/teacher-invitation.js";
export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> {
const classRepository = getClassRepository();
@ -11,7 +10,7 @@ export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[
if (!classes) {
return [];
}
if (full) {
return classes.map(mapToClassDTO);
} else {
@ -66,4 +65,4 @@ export async function getClassTeacherInvitations(classId: string, full: boolean)
}
return invitations.map(mapToTeacherInvitationDTOIds);
}
}

View file

@ -1,9 +1,9 @@
import { getAssignmentRepository, getClassRepository, getGroupRepository } from "../data/repositories";
import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from "../interfaces/groups";
import { getAssignmentRepository, getClassRepository, getGroupRepository } from "../data/repositories.js";
import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from "../interfaces/groups.js";
export async function getGroup(
classId: string,
assignmentNumber: number,
classId: string,
assignmentNumber: number,
groupNumber: number,
full: boolean,
): Promise<GroupDTO | null> {
@ -64,4 +64,4 @@ export async function getAllGroups(
}
return groups.map(mapToGroupDTOId);
}
}

View file

@ -1,26 +1,13 @@
import { getClassRepository, getStudentRepository } from "../data/repositories";
import { Class } from "../entities/classes/class.entity";
import { Student } from "../entities/users/student.entity";
import { ClassDTO, mapToClassDTO } from "../interfaces/classes";
import { StudentDTO, mapToStudentDTO } from "../interfaces/students";
import { getClassRepository, getStudentRepository } from "../data/repositories.js";
import { Class } from "../entities/classes/class.entity.js";
import { Student } from "../entities/users/student.entity.js";
import { ClassDTO, mapToClassDTO } from "../interfaces/classes.js";
import {UserService} from "./users.js";
export async function getAllStudents(): Promise<StudentDTO[]> {
const studentRepository = getStudentRepository();
const students = await studentRepository.find({});
return students.map(mapToStudentDTO);
}
export async function getStudent(username: string): Promise<StudentDTO | null> {
const studentRepository = getStudentRepository();
const student = await studentRepository.findByUsername(username);
if (!student) {
return null;
export class StudentService extends UserService<Student> {
constructor() {
super(getStudentRepository());
}
return mapToStudentDTO(student);
}
async function fetchStudentClasses(username: string): Promise<Class[]> {

View file

@ -0,0 +1,39 @@
import { UserRepository } from "../data/users/user-repository.js";
import { UserDTO, mapToUser, mapToUserDTO } from "../interfaces/user.js";
import {User} from "../entities/users/user.entity.js";
export class UserService<T extends User> {
protected repository: UserRepository<T>;
constructor(repository: UserRepository<T>) {
this.repository = repository;
}
async getAllUsers(): Promise<UserDTO[]> {
const users = await this.repository.findAll();
return users.map(mapToUserDTO);
}
async getAllUserIds(): Promise<string[]> {
const users = await this.getAllUsers();
return users.map((user) => user.username);
}
async getUserByUsername(username: string): Promise<UserDTO | null> {
const user = await this.repository.findByUsername(username)
return user ? mapToUserDTO(user) : null;
}
async createUser(userData: UserDTO, UserClass: new () => T): Promise<T> {
const newUser = mapToUser(userData, new UserClass());
await this.repository.save(newUser);
return newUser;
}
async deleteUser(username: string): Promise<UserDTO | null> {
const user = await this.getUserByUsername(username);
if (!user) return null;
await this.repository.deleteByUsername(username)
return mapToUserDTO(user);
}
}

View file

@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import {FALLBACK_LANG} from "../../config";
import {FALLBACK_LANG} from "../config.js";
export function loadTranslations<T>(language: string): T {
try {