From 986a6c900ea045d9c76fce29ddd86ece9bad7085 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Fri, 14 Mar 2025 08:31:32 +0100 Subject: [PATCH 01/28] Implemented possibility to delete submitted assignments --- .../de/gaz/eedu/course/CourseController.java | 3 +- .../appointment/AppointmentController.java | 41 +-------- .../appointment/AppointmentService.java | 2 + .../entry/AppointmentEntryEntity.java | 36 +++++--- .../entry/AppointmentEntryRepository.java | 7 +- .../assignment/AssignmentController.java | 63 +++++++++++++ .../AssignmentCreateModel.java | 2 +- .../assignment/AssignmentInsightModel.java | 12 +++ .../AssignmentModel.java | 2 +- .../assessment/AssessmentController.java | 77 ++++++++++++++++ .../assessment/AssessmentEntity.java | 43 +++++++++ .../assessment/AssessmentRepository.java | 7 ++ .../assessment/AssessmentService.java | 92 +++++++++++++++++++ .../model/AssessmentCreateModel.java | 16 ++++ .../assessment/model/AssessmentModel.java | 7 ++ .../model/AppointmentEntryCreateModel.java | 1 + .../entry/model/AppointmentEntryModel.java | 1 + .../entry/model/AppointmentUpdateModel.java | 1 + .../entry/model/AssignmentInsightModel.java | 5 - .../user/privileges/SystemPrivileges.java | 1 + EEDU-Backend/src/main/resources/schema.sql | 11 +++ .../selection-input.component.ts | 10 +- .../assignment-card.component.ts | 8 +- .../src/app/entity/entity-service.ts | 3 +- .../assignment-student-view.component.ts | 12 +-- .../assignment-teacher-view.component.ts | 11 +-- .../event-data/event-data-dialog.component.ts | 4 +- .../appointment/appointment.service.ts | 36 +------- .../entry/appointment-entry-model.ts | 6 +- .../entry/appointment-update-model.ts | 6 +- .../assessment/assessment-create-model.ts | 42 +++++++++ .../assignment/assessment/assessment-model.ts | 48 ++++++++++ .../assessment/assessment.service.spec.ts | 16 ++++ .../assessment/assessment.service.ts | 88 ++++++++++++++++++ .../assignment-create-model.ts | 0 .../assignment-insight-model.ts | 0 .../{ => assignment}/assignment-model.ts | 0 .../assignment/assignment.service.spec.ts | 16 ++++ .../entry/assignment/assignment.service.ts | 65 +++++++++++++ .../create-group-dialog.component.html | 2 +- .../create-group-dialog.component.ts | 9 +- 41 files changed, 683 insertions(+), 129 deletions(-) create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java rename EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/{model => assignment}/AssignmentCreateModel.java (94%) create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java rename EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/{model => assignment}/AssignmentModel.java (87%) create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentService.java create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentCreateModel.java create mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java delete mode 100644 EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentInsightModel.java create mode 100644 EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts create mode 100644 EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts create mode 100644 EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.spec.ts create mode 100644 EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts rename EEDU-Frontend/src/app/user/courses/appointment/entry/{ => assignment}/assignment-create-model.ts (100%) rename EEDU-Frontend/src/app/user/courses/appointment/entry/{ => assignment}/assignment-insight-model.ts (100%) rename EEDU-Frontend/src/app/user/courses/appointment/entry/{ => assignment}/assignment-model.ts (100%) create mode 100644 EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.spec.ts create mode 100644 EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/CourseController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/CourseController.java index 4f8b6ad0..3af98217 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/CourseController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/CourseController.java @@ -26,7 +26,8 @@ public class CourseController extends EntityController setSubject(@PathVariable long course, @PathVariable String subject) { log.info("Received incoming request for setting the subject of course {} to {}.", course, subject); diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java index 224fe553..60bba574 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java @@ -3,7 +3,6 @@ import de.gaz.eedu.course.appointment.entry.model.AppointmentEntryCreateModel; import de.gaz.eedu.course.appointment.entry.model.AppointmentEntryModel; import de.gaz.eedu.course.appointment.entry.model.AppointmentUpdateModel; -import de.gaz.eedu.course.appointment.entry.model.AssignmentInsightModel; import de.gaz.eedu.course.appointment.frequent.FrequentAppointmentEntity; import de.gaz.eedu.course.appointment.frequent.model.FrequentAppointmentCreateModel; import de.gaz.eedu.course.appointment.frequent.model.FrequentAppointmentModel; @@ -17,9 +16,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Set; @@ -54,42 +51,6 @@ public class AppointmentController extends EntityController submitStatus(@PathVariable long appointment) - { - return ResponseEntity.ok(getService().getInsight(appointment).toArray(AssignmentInsightModel[]::new)); - } - - @PreAuthorize("hasRole('teacher')") - @GetMapping("/assignment/{appointment}/status/{user}") - public @NotNull ResponseEntity submitStatus(@PathVariable long appointment, @PathVariable long user) - { - ResponseEntity notFound = ResponseEntity.notFound().build(); - return getService().getInsight(appointment, user).map(ResponseEntity::ok).orElse(notFound); - } - - @PreAuthorize("hasRole('student')") - @GetMapping("/assignment/{appointment}/status") - public @NotNull ResponseEntity ownSubmitStatus(@AuthenticationPrincipal long userId, @PathVariable long appointment) - { - ResponseEntity notFound = ResponseEntity.notFound().build(); - return getService().getInsight(appointment, userId).map(ResponseEntity::ok).orElse(notFound); - } - - @DeleteMapping("/assignment/{appointment}/delete/{files}") - public @NotNull ResponseEntity deleteAssignment(@AuthenticationPrincipal long userId, @PathVariable long appointment, @PathVariable @NotNull String[] files) - { - return empty(getService().deleteAssignment(userId, appointment, files) ? HttpStatus.OK : HttpStatus.INTERNAL_SERVER_ERROR); - } - - @PostMapping("/assignment/{appointment}/submit") - public @NotNull ResponseEntity submitAssignment(@AuthenticationPrincipal long userId, @PathVariable long appointment, @NotNull @RequestPart("file") MultipartFile[] files) - { - getService().submitAssignment(userId, appointment, files); - return empty(HttpStatus.CREATED); - } - @PostMapping("/update/standalone/{appointment}") @PreAuthorize("hasRole('teacher') or hasRole('administrator')") public @NotNull ResponseEntity updateAppointment(@PathVariable long appointment, @NotNull @RequestBody AppointmentUpdateModel updateModel) { @@ -102,7 +63,7 @@ public class AppointmentController extends EntityController assessments = new HashSet<>(); + /** * This constructor creates a new instance of this entity. *

@@ -133,18 +135,22 @@ public AppointmentEntryEntity(long id) String uploadPath = getUploadPath(user.getId()); File file = new File(uploadPath); File[] files = file.listFiles(); + + AssessmentModel assessment = getAssessment(user).map(AssessmentEntity::toModel).orElse(null); + if (!hasSubmitted(user) || !file.isDirectory() || Objects.isNull(files) || files.length == 0) { - return new AssignmentInsightModel(user.getLoginName(), false, new String[0]); + return new AssignmentInsightModel(user.getLoginName(), false, new String[0], assessment); } String[] paths = Arrays.stream(files).map(File::getName).toArray(String[]::new); - return new AssignmentInsightModel(user.getLoginName(), true, paths); + return new AssignmentInsightModel(user.getLoginName(), true, paths, assessment); } // method not allowed when submitHomework is false // bad gateway when any file is malicious // bad request when + public void submitAssignment(long user, @NotNull MultipartFile... files) throws ResponseStatusException { if (!this.isAssignmentValid()) @@ -183,6 +189,12 @@ public boolean deleteAssignment(long user, String @NotNull ... files) return allDeleted; } + private @NotNull Optional getAssessment(@NotNull UserEntity user) + { + Predicate userEquals = current -> Objects.equals(user, current.getUser()); + return getAssessments().stream().filter(userEquals).findFirst(); + } + private @NotNull String getUploadPath(long user) { FileEntity repository = getCourse().getRepository(); diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryRepository.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryRepository.java index ea57d7b7..777569a6 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryRepository.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryRepository.java @@ -13,9 +13,10 @@ public interface AppointmentEntryRepository extends JpaRepository findById(@NotNull Long id); } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java new file mode 100644 index 00000000..5ff98f07 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java @@ -0,0 +1,63 @@ +package de.gaz.eedu.course.appointment.entry.assignment; + +import de.gaz.eedu.course.appointment.AppointmentService; +import de.gaz.eedu.course.appointment.entry.assignment.assessment.AssessmentService; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RequestMapping("/api/v1/course/appointment/assignment") +@RequiredArgsConstructor +@Getter(AccessLevel.PROTECTED) +public class AssignmentController +{ + private final AssessmentService service; + private final AppointmentService appointmentService; + + @PreAuthorize("hasRole('teacher')") @GetMapping("/{appointment}/status/all") + public @NotNull ResponseEntity submitStatus(@PathVariable long appointment) + { + return ResponseEntity.ok(getAppointmentService().getInsight(appointment).toArray(AssignmentInsightModel[]::new)); + } + + @PreAuthorize("hasRole('teacher')") @GetMapping("/{appointment}/status/{user}") + public @NotNull ResponseEntity submitStatus(@PathVariable long appointment, @PathVariable long user) + { + ResponseEntity notFound = ResponseEntity.notFound().build(); + return getAppointmentService().getInsight(appointment, user).map(ResponseEntity::ok).orElse(notFound); + } + + @PreAuthorize("hasRole('student')") @GetMapping("/{appointment}/status") + public @NotNull ResponseEntity ownSubmitStatus(@AuthenticationPrincipal long userId, @PathVariable long appointment) + { + ResponseEntity notFound = ResponseEntity.notFound().build(); + return getAppointmentService().getInsight(appointment, userId).map(ResponseEntity::ok).orElse(notFound); + } + + @DeleteMapping("/{appointment}/delete/{files}") + public @NotNull ResponseEntity deleteAssignment(@AuthenticationPrincipal long userId, @PathVariable long appointment, @PathVariable @NotNull String[] files) + { + return ResponseEntity.status(getAppointmentService().deleteAssignment( + userId, + appointment, + files) ? HttpStatus.OK : HttpStatus.INTERNAL_SERVER_ERROR + ).build(); + } + + @PostMapping("/{appointment}/submit") + public @NotNull ResponseEntity submitAssignment(@AuthenticationPrincipal long userId, @PathVariable long appointment, @NotNull @RequestPart( + "file" + ) MultipartFile[] files) + { + getAppointmentService().submitAssignment(userId, appointment, files); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + +} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentCreateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentCreateModel.java similarity index 94% rename from EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentCreateModel.java rename to EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentCreateModel.java index eada8f75..72fb6f2c 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentCreateModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentCreateModel.java @@ -1,4 +1,4 @@ -package de.gaz.eedu.course.appointment.entry.model; +package de.gaz.eedu.course.appointment.entry.assignment; import de.gaz.eedu.course.appointment.entry.AppointmentEntryEntity; import org.jetbrains.annotations.NotNull; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java new file mode 100644 index 00000000..70ecd919 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java @@ -0,0 +1,12 @@ +package de.gaz.eedu.course.appointment.entry.assignment; + +import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record AssignmentInsightModel( + @NotNull String name, + boolean submitted, + @NotNull String[] files, + @Nullable AssessmentModel assessment +) {} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentModel.java similarity index 87% rename from EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentModel.java rename to EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentModel.java index f18adaaf..dfe7b310 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentModel.java @@ -1,4 +1,4 @@ -package de.gaz.eedu.course.appointment.entry.model; +package de.gaz.eedu.course.appointment.entry.assignment; import org.jetbrains.annotations.NotNull; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java new file mode 100644 index 00000000..7ddc1958 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java @@ -0,0 +1,77 @@ +package de.gaz.eedu.course.appointment.entry.assignment.assessment; + +import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentCreateModel; +import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentModel; +import de.gaz.eedu.entity.EntityController; +import de.gaz.eedu.exception.CreationException; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("/api/v1/course/appointment/assignment/assessment") +@RequiredArgsConstructor @Getter(AccessLevel.PROTECTED) +public class AssessmentController extends EntityController +{ + private final AssessmentService service; + + @PreAuthorize("hasRole('teacher')") + @PutMapping("/{assessment}/set/feedback/{feedback}") + public @NotNull ResponseEntity setFeedback(@PathVariable long assessment, @PathVariable @NotNull String feedback) + { + return ResponseEntity.ok(getService().setFeedback(assessment, feedback)); + } + + @PreAuthorize("hasRole('teacher')") + @PutMapping("/{assessment}/unset/feedback") + public @NotNull ResponseEntity unsetFeedback(@PathVariable long assessment) + { + return ResponseEntity.ok(getService().setFeedback(assessment, null)); + } + + @PreAuthorize("hasRole('teacher')") + @PutMapping("/{assessment}/set/grade/{grade}") + public @NotNull ResponseEntity setGrade(@PathVariable long assessment, @PathVariable float grade) + { + return ResponseEntity.ok(getService().setGrade(assessment, grade)); + } + + @PreAuthorize("hasRole('teacher')") + @PutMapping("/{assessment}/unset/grade") + public @NotNull ResponseEntity unsetGrade(@PathVariable long assessment) + { + return ResponseEntity.ok(getService().setGrade(assessment, null)); + } + + @PreAuthorize("hasRole('teacher')") + @Override @PostMapping("/create") + public @NotNull ResponseEntity create(@NotNull @RequestBody AssessmentCreateModel[] model) throws CreationException + { + return super.create(model); + } + + @PreAuthorize("hasRole('teacher')") + @DeleteMapping("/delete/{id}") @Override + public @NotNull ResponseEntity delete(@NotNull @PathVariable Long[] id) + { + return super.delete(id); + } + + @PreAuthorize("hasRole('student')") + @GetMapping("/get/{appointment}") + public @NotNull ResponseEntity getOwnData(@NotNull @PathVariable Long appointment, @AuthenticationPrincipal long user) + { + return getData(appointment, user); + } + + @PreAuthorize("hasRole('teacher')") + @GetMapping("/get/{appointment}/{user}") + public @NotNull ResponseEntity getData(@NotNull @PathVariable Long appointment , @PathVariable long user) + { + return super.getData(getService().getId(user, appointment)); + } +} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java new file mode 100644 index 00000000..8fe239f5 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java @@ -0,0 +1,43 @@ +package de.gaz.eedu.course.appointment.entry.assignment.assessment; + +import com.fasterxml.jackson.annotation.JsonManagedReference; +import de.gaz.eedu.course.appointment.entry.AppointmentEntryEntity; +import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentModel; +import de.gaz.eedu.entity.model.EntityModelRelation; +import de.gaz.eedu.user.UserEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class AssessmentEntity implements EntityModelRelation +{ + @Id private @NotNull Long id; + @JsonManagedReference @ManyToOne @Setter(AccessLevel.NONE) + + @JoinColumn(name = "appointment_id", referencedColumnName = "id", nullable = false) + private AppointmentEntryEntity appointment; + @ManyToOne @JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false) @Setter(AccessLevel.NONE) + private UserEntity user; + + @Column(length = 200) private String feedback; + private Float grade; + + public AssessmentEntity(long id, @NotNull AppointmentEntryEntity appointment, @NotNull UserEntity user) + { + this.id = id; + this.appointment = appointment; + this.user = user; + } + + @Override public @NotNull AssessmentModel toModel() + { + return new AssessmentModel(getId(), getGrade(), getFeedback()); + } +} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java new file mode 100644 index 00000000..086cf307 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java @@ -0,0 +1,7 @@ +package de.gaz.eedu.course.appointment.entry.assignment.assessment; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AssessmentRepository extends JpaRepository {} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentService.java new file mode 100644 index 00000000..7968b484 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentService.java @@ -0,0 +1,92 @@ +package de.gaz.eedu.course.appointment.entry.assignment.assessment; + +import de.gaz.eedu.course.appointment.entry.AppointmentEntryEntity; +import de.gaz.eedu.course.appointment.entry.AppointmentEntryRepository; +import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentCreateModel; +import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentModel; +import de.gaz.eedu.entity.EntityService; +import de.gaz.eedu.exception.CreationException; +import de.gaz.eedu.user.UserEntity; +import de.gaz.eedu.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Service @Getter(AccessLevel.PROTECTED) @RequiredArgsConstructor +public class AssessmentService extends EntityService +{ + private final AssessmentRepository repository; + private final AppointmentEntryRepository appointmentRepository; + private final UserRepository userRepository; + + @Contract(pure = true, value = "_, _ -> _") + private static long generateId(long entity, long user) + { + // okay so, this is a bit complex, so I'll break it down + // Firstly, I bitshift the appointmentEntryId into the lower 32 bits, because a long is 64 bits in total + + // Secondly, I shift the userid into the 32 remaining bits + // The value 0xFFFFFFFFFL is a 32-bit mask in hexadecimal format + + // The 0x indicates it's a hexadecimal number, where each F are 4 bits (there are 8), + // making up a total of 32 bits. The L suffix defines that the value is of type Long + + // This allows me to encode both the appointment id and the userid into a single id + return (entity << 32) | (user & 0xFFFFFFFFL); + } + + public long getId(long entity, long user) + { + return generateId(entity, user); + } + + @Transactional @Override + public @NotNull List createEntity(@NotNull Set model) throws CreationException + { + return saveEntity(model.stream().map(current -> { + long aId = current.appointment(); + AppointmentEntryEntity appointment = getAppointmentRepository().findById(aId).orElseThrow(entityUnknown(aId)); + UserEntity user = getUserRepository().findById(current.user()).orElseThrow(entityUnknown(current.user())); + return current.toEntity(new AssessmentEntity(generateId(appointment.getId(), user.getId()), appointment, user)); + }).toList()); + } + + @Transactional + public @NotNull AssessmentModel setGrade(long assessment, @Nullable Float grade) + { + AssessmentEntity assessmentEntity = loadEntityByIDSafe(assessment); + + if (Objects.equals(assessmentEntity.getGrade(), grade)) + { + throw new ResponseStatusException(HttpStatus.CONFLICT); + } + + assessmentEntity.setGrade(grade); + return saveEntity(assessmentEntity).toModel(); + } + + @Transactional + public @NotNull AssessmentModel setFeedback(long assessment, @Nullable String feedback) + { + AssessmentEntity assessmentEntity = loadEntityByIDSafe(assessment); + + if (Objects.equals(assessmentEntity.getFeedback(), feedback)) + { + throw new ResponseStatusException(HttpStatus.CONFLICT); + } + + assessmentEntity.setFeedback(feedback); + return saveEntity(assessmentEntity).toModel(); + } +} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentCreateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentCreateModel.java new file mode 100644 index 00000000..a54a50a1 --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentCreateModel.java @@ -0,0 +1,16 @@ +package de.gaz.eedu.course.appointment.entry.assignment.assessment.model; + +import de.gaz.eedu.course.appointment.entry.assignment.assessment.AssessmentEntity; +import de.gaz.eedu.entity.model.CreationModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record AssessmentCreateModel(long appointment, long user, float grade, @Nullable String feedback) implements CreationModel +{ + @Override public @NotNull AssessmentEntity toEntity(@NotNull AssessmentEntity entity) + { + entity.setGrade(grade()); + entity.setFeedback(feedback()); + return entity; + } +} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java new file mode 100644 index 00000000..688a47bb --- /dev/null +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java @@ -0,0 +1,7 @@ +package de.gaz.eedu.course.appointment.entry.assignment.assessment.model; + +import de.gaz.eedu.entity.model.EntityModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record AssessmentModel(@NotNull Long id, float grade, @Nullable String feedback) implements EntityModel {} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryCreateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryCreateModel.java index 4845a14c..5e70df5e 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryCreateModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryCreateModel.java @@ -1,6 +1,7 @@ package de.gaz.eedu.course.appointment.entry.model; import de.gaz.eedu.course.appointment.entry.AppointmentEntryEntity; +import de.gaz.eedu.course.appointment.entry.assignment.AssignmentCreateModel; import de.gaz.eedu.entity.model.CreationModel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryModel.java index 55a6e078..35ad1ef2 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentEntryModel.java @@ -1,5 +1,6 @@ package de.gaz.eedu.course.appointment.entry.model; +import de.gaz.eedu.course.appointment.entry.assignment.AssignmentModel; import de.gaz.eedu.course.room.model.RoomModel; import de.gaz.eedu.entity.model.EntityModel; import org.jetbrains.annotations.NotNull; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentUpdateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentUpdateModel.java index 19df8ef5..a231deb3 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentUpdateModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AppointmentUpdateModel.java @@ -1,5 +1,6 @@ package de.gaz.eedu.course.appointment.entry.model; +import de.gaz.eedu.course.appointment.entry.assignment.AssignmentCreateModel; import org.jetbrains.annotations.Nullable; public record AppointmentUpdateModel( diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentInsightModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentInsightModel.java deleted file mode 100644 index b9174a4a..00000000 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/model/AssignmentInsightModel.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.gaz.eedu.course.appointment.entry.model; - -import org.jetbrains.annotations.NotNull; - -public record AssignmentInsightModel(@NotNull String name, boolean submitted, @NotNull String[] files) {} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/privileges/SystemPrivileges.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/privileges/SystemPrivileges.java index f67d6a73..105c0b25 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/privileges/SystemPrivileges.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/privileges/SystemPrivileges.java @@ -28,6 +28,7 @@ public enum SystemPrivileges COURSE_CREATE, COURSE_ATTACH_USER, COURSE_DETACH_USER, + COURSE_ALTER_SUBJECT, COURSE_DELETE, COURSE_GET, diff --git a/EEDU-Backend/src/main/resources/schema.sql b/EEDU-Backend/src/main/resources/schema.sql index cf543c7a..bf27c0f5 100644 --- a/EEDU-Backend/src/main/resources/schema.sql +++ b/EEDU-Backend/src/main/resources/schema.sql @@ -108,6 +108,17 @@ CREATE TABLE IF NOT EXISTS user_entity FOREIGN KEY (class_room_id) REFERENCES class_room_entity (id) ); +CREATE TABLE IF NOT EXISTS assessment_entity +( + id BIGINT PRIMARY KEY NOT NULL, + appointment_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + grade FLOAT NULL, + feedback VARCHAR(200) NULL, + FOREIGN KEY (appointment_id) REFERENCES appointment_entry_entity (id), + FOREIGN KEY (user_id) REFERENCES user_entity (id) +); + CREATE TABLE IF NOT EXISTS credential_entity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, diff --git a/EEDU-Frontend/src/app/common/selection-input/selection-input.component.ts b/EEDU-Frontend/src/app/common/selection-input/selection-input.component.ts index 84794081..7c0f5439 100644 --- a/EEDU-Frontend/src/app/common/selection-input/selection-input.component.ts +++ b/EEDU-Frontend/src/app/common/selection-input/selection-input.component.ts @@ -185,6 +185,11 @@ export class SelectionInput implement return; } + if(Array.isArray(value)) { + this.selectedValues.set(value as T[]); + return; + } + this.currentValue.set(this.toName(value)); } @@ -254,11 +259,6 @@ export class SelectionInput implement protected add(event: MatChipInputEvent): string { const value: T[] = this.filter((event.value || '').trim()); - if(value.length != 1) - { - return event.value; - } - this.value = value[0]; return ''; } diff --git a/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts b/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts index 12a63743..427a7842 100644 --- a/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import {AssignmentModel} from "../../user/courses/appointment/entry/assignment-model"; -import {AppointmentService} from "../../user/courses/appointment/appointment.service"; +import {AssignmentService} from "../../user/courses/appointment/entry/assignment/assignment.service"; +import {AssignmentModel} from "../../user/courses/appointment/entry/assignment/assignment-model"; @Component({ selector: 'app-assignment-card', @@ -11,9 +11,9 @@ import {AppointmentService} from "../../user/courses/appointment/appointment.ser }) export class AssignmentCardComponent { - public constructor(private readonly _appointmentService: AppointmentService) {} + public constructor(private readonly _assignmentService: AssignmentService) {} protected get assignments(): readonly AssignmentModel[] { - return this._appointmentService.nextAssignments.slice(0, 5); + return this._assignmentService.nextAssignments.slice(0, 5); } } diff --git a/EEDU-Frontend/src/app/entity/entity-service.ts b/EEDU-Frontend/src/app/entity/entity-service.ts index d723630d..dbcaa216 100644 --- a/EEDU-Frontend/src/app/entity/entity-service.ts +++ b/EEDU-Frontend/src/app/entity/entity-service.ts @@ -19,7 +19,8 @@ export abstract class EntityService { private readonly _http: HttpClient, private readonly _location: string, private readonly _defaultRequiredPrivileges: DefaultRequiredPrivileges, - private readonly _createDialog: ComponentType) {} + private readonly _createDialog: ComponentType + ) {} private _fetched: boolean = false diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts index 2382716d..b4025131 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts @@ -6,9 +6,9 @@ import {MatButton} from "@angular/material/button"; import {MatList, MatListItem, MatListItemLine, MatListItemTitle} from "@angular/material/list"; import {NgIf} from "@angular/common"; import {MatIcon} from "@angular/material/icon"; -import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment-model"; -import {AppointmentService} from "../../../../user/courses/appointment/appointment.service"; -import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment-insight-model"; +import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; +import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; +import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; @Component({ selector: 'app-assignment-student-view', @@ -38,12 +38,12 @@ export class AssignmentStudentViewComponent implements AfterViewInit { return this._insight; } - public constructor(private readonly _appointmentService: AppointmentService, form: FormBuilder) { + public constructor(private readonly _assignmentService: AssignmentService, form: FormBuilder) { this._form = form.group({files: [[], Validators.required]}); } public ngAfterViewInit(): void { - this._appointmentService.fetchInsight(this.appointment.id).subscribe((insights: AssignmentInsightModel): void => { + this._assignmentService.fetchInsight(this.appointment.id).subscribe((insights: AssignmentInsightModel): void => { this._insight = insights; }); } @@ -65,7 +65,7 @@ export class AssignmentStudentViewComponent implements AfterViewInit { } // -- - this._appointmentService.submitAssignment(this.appointment.id, fileArray).subscribe((): void => {}); + this._assignmentService.submitAssignment(this.appointment.id, fileArray).subscribe((): void => {}); } protected onFileSelected(event: FileList): void { diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index 6f0ee027..687d720c 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -1,11 +1,11 @@ import {Component, Input, input, InputSignal} from '@angular/core'; import {NgIf} from "@angular/common"; import {AppointmentEntryModel} from "../../../../user/courses/appointment/entry/appointment-entry-model"; -import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment-model"; import {SelectionInput} from "../../../../common/selection-input/selection-input.component"; -import {AppointmentService} from "../../../../user/courses/appointment/appointment.service"; -import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment-insight-model"; import {MatList, MatListItem} from "@angular/material/list"; +import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; +import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; +import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; @Component({ selector: 'app-assignment-teacher-view', @@ -27,15 +27,14 @@ export class AssignmentTeacherViewComponent { private _currentInsight: AssignmentInsightModel | null = null; - - public constructor(private readonly _appointmentService: AppointmentService) { + public constructor(private readonly _assignmentService: AssignmentService) { } @Input() public set appointment(appointment: AppointmentEntryModel) { this._appointment = appointment; - this._appointmentService.fetchInsights(appointment.id).subscribe((response: AssignmentInsightModel[]): void => + this._assignmentService.fetchInsights(appointment.id).subscribe((response: AssignmentInsightModel[]): void => { this._assignmentInsightModels = response; }) diff --git a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts index 2f7bc0b6..096e4c37 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts @@ -10,8 +10,6 @@ import {MatGridList, MatGridTile} from "@angular/material/grid-list"; import {MatButton} from "@angular/material/button"; import {MatFormField, MatHint} from "@angular/material/form-field"; import {MatInput} from "@angular/material/input"; -import {AssignmentModel} from "../../user/courses/appointment/entry/assignment-model"; -import {GenericAssignmentCreateModel} from "../../user/courses/appointment/entry/assignment-create-model"; import {AssignmentTabComponent} from "./assignment-tab/assignment-tab.component"; import { DateTimePickerComponent @@ -28,6 +26,8 @@ import {MatSnackBar} from "@angular/material/snack-bar"; import {CourseService} from "../../user/courses/course.service"; import {CourseModel} from "../../user/courses/course-model"; import {GeneralCardComponent} from "../../common/general-card-component/general-card.component"; +import {AssignmentModel} from "../../user/courses/appointment/entry/assignment/assignment-model"; +import {GenericAssignmentCreateModel} from "../../user/courses/appointment/entry/assignment/assignment-create-model"; @Component({ standalone: true, diff --git a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts index 8e156320..92c0d698 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {HttpClient, HttpEvent} from "@angular/common/http"; +import {HttpClient} from "@angular/common/http"; import {environment} from "../../../../environment/environment"; import {CourseService} from "../course.service"; import {map, Observable, tap} from "rxjs"; @@ -11,9 +11,6 @@ import { import {FrequentAppointmentModel} from "./frequent/frequent-appointment-model"; import {AppointmentUpdateModel} from "./entry/appointment-update-model"; import {CourseModel} from "../course-model"; -import {FileService} from "../../../file/file.service"; -import {AssignmentModel} from "./entry/assignment-model"; -import {AssignmentInsightModel, GenericAssignmentInsightModel} from "./entry/assignment-insight-model"; @Injectable({ providedIn: 'root' @@ -22,16 +19,12 @@ export class AppointmentService { private readonly BACKEND_URL: string = `${environment.backendUrl}/course/appointment`; - constructor(private readonly _http: HttpClient, private readonly _courseService: CourseService, private readonly _fileService: FileService) { } + constructor(private readonly _http: HttpClient, private readonly _courseService: CourseService) { } public get nextAppointments(): readonly AppointmentEntryModel[] { return this.courseService.value.flatMap((course: CourseModel): readonly AppointmentEntryModel[] => course.appointmentEntries).filter((appointment: AppointmentEntryModel): boolean => appointment.start > new Date()).sort((a: AppointmentEntryModel, b: AppointmentEntryModel): number => a.start.getTime() - b.start.getTime()); } - public get nextAssignments(): readonly AssignmentModel[] { - return this.nextAppointments.filter((appointment: AppointmentEntryModel): boolean => !!appointment.assignment).map((appointment: AppointmentEntryModel): AssignmentModel => appointment.assignment); - } - protected get http(): HttpClient { return this._http; } @@ -40,31 +33,6 @@ export class AppointmentService { return this._courseService; } - public fetchInsights(appointment: bigint): Observable { - const url: string = `${this.BACKEND_URL}/assignment/${appointment}/status/all`; - return this.http.get(url, {withCredentials: true}).pipe(map((response: GenericAssignmentInsightModel[]): AssignmentInsightModel[] => response.map((item: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(item)))); - } - - public fetchInsight(appointment: bigint): Observable { - const url: string = `${this.BACKEND_URL}/assignment/${appointment}/status`; - return this.http.get(url, {withCredentials: true}).pipe(map((response: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(response))); - } - - public fetchUsersInsight(appointment: bigint, user: bigint): Observable { - const url: string = `${this.BACKEND_URL}/assignment/${appointment}/status/${user}`; - return this.http.get(url, {withCredentials: true}).pipe(map((response: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(response))); - } - - public submitAssignment(appointment: bigint, assignmentFiles: File[]): Observable> { - const url: string = `${this.BACKEND_URL}/assignment/${appointment}/submit`; - return this._fileService.uploadFiles(url, assignmentFiles); - } - - public deleteAssignment(appointment: bigint, assignmentFiles: string[]): Observable { - const url: string = `${this.BACKEND_URL}/assignment/${appointment}/delete/${assignmentFiles.toString()}`; - return this.http.delete(url, {withCredentials: true}); - } - /** * Creates appointments for a specified course. * diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts index cb365081..ccf8770b 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts @@ -1,14 +1,14 @@ import {CalendarEvent} from "angular-calendar"; -import {AssignmentModel, GenericAssignment} from "./assignment-model"; import {GenericRoom, RoomModel} from "../../room/room-model"; +import {AssignmentModel, GenericAssignment} from "./assignment/assignment-model"; export interface GenericAppointmentEntry { id: bigint, duration: number, description: string, attachedScheduled: bigint, - room: GenericRoom, - assignment: GenericAssignment + room?: GenericRoom, + assignment?: GenericAssignment } export class AppointmentEntryModel { diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-update-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-update-model.ts index a974c9b4..223582ed 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-update-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-update-model.ts @@ -1,4 +1,8 @@ -import {AssignmentCreateModel, AssignmentCreatePacket, GenericAssignmentCreateModel} from "./assignment-create-model"; +import { + AssignmentCreateModel, + AssignmentCreatePacket, + GenericAssignmentCreateModel +} from "./assignment/assignment-create-model"; export interface GenericAppointmentUpdate { description: string; diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts new file mode 100644 index 00000000..74f7356f --- /dev/null +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts @@ -0,0 +1,42 @@ +export interface GenericAssessmentCreateModel +{ + appointment: number; + user: number; + grade: number; + feedback?: string; +} + +export class AssessmentCreateModel +{ + public constructor( + private readonly _appointment: number, + private readonly _user: number, + private readonly _grade: number, + private readonly _feedback: string | null, + ) {} + + public static fromObject(obj: GenericAssessmentCreateModel): AssessmentCreateModel { + return new AssessmentCreateModel( + obj.appointment, + obj.user, + obj.grade, + obj.feedback || null, + ) + } + + public get appointment(): number { + return this._appointment; + } + + public get user(): number { + return this._user; + } + + public get grade(): number { + return this._grade; + } + + public get feedback(): string | null { + return this._feedback; + } +} diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts new file mode 100644 index 00000000..5c2ca88b --- /dev/null +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts @@ -0,0 +1,48 @@ +export interface GenericAssessment +{ + id: bigint; + grade: number; + feedback?: string +} + +export class AssessmentModel { + + private static readonly BIT_MASK: 0xFFFFFFFFn = 0xFFFFFFFFn; + + public constructor( + private _id: bigint, + private _grade: number, + private _feedback: string | null + ) {} + + public static fromObject(obj: GenericAssessment): AssessmentModel + { + return new AssessmentModel( + obj.id, + obj.grade, + obj.feedback || null, + ) + } + + public get id(): bigint { + return this._id; + } + + public get appointment(): bigint + { + return (this.id >> 32n) & AssessmentModel.BIT_MASK; + } + + public get user(): bigint + { + return this.id & AssessmentModel.BIT_MASK; + } + + public get grade(): number { + return this._grade; + } + + public get feedback(): string | null { + return this._feedback; + } +} diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.spec.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.spec.ts new file mode 100644 index 00000000..b2320e03 --- /dev/null +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AssessmentService } from './assessment.service'; + +describe('AssessmentService', () => { + let service: AssessmentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AssessmentService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts new file mode 100644 index 00000000..19ff6dcf --- /dev/null +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {environment} from "../../../../../../../environment/environment"; +import {GenericAssessmentCreateModel} from "./assessment-create-model"; +import {BehaviorSubject, map, Observable, OperatorFunction} from "rxjs"; +import {AssessmentModel, GenericAssessment} from "./assessment-model"; + +@Injectable({ + providedIn: 'root' +}) +export class AssessmentService { + + private readonly _assessmentSubject: BehaviorSubject = new BehaviorSubject([]); + + public constructor(private readonly _http: HttpClient) {} + + protected get http(): HttpClient { + return this._http; + } + + protected get BACKEND_URL(): string + { + return `${environment.backendUrl}/course/appointment/assignment/assessment`; + } + + public assess(obj: GenericAssessmentCreateModel[]): Observable + { + const url: string = `${this.BACKEND_URL}/create`; + return this.http.post(url, obj, { withCredentials: true }).pipe( + map((response: GenericAssessment[]): AssessmentModel[] => + response.map((item: GenericAssessment): AssessmentModel => + { + const assessment: AssessmentModel = AssessmentModel.fromObject(item) + this.pushAssessment(assessment); + return assessment; + }) + ) + ); + } + + public updateFeedback(id: bigint, feedback: string): Observable + { + const url: string = `${this.BACKEND_URL}/${id}/set/feedback/${feedback}`; + return this.http.put(url, { withCredentials: true }).pipe(this.translateAndPush); + } + + public updateGrade(id: bigint, grade: number): Observable + { + const url: string = `${this.BACKEND_URL}/${id}/set/grade/${grade}`; + return this.http.put(url, { withCredentials: true }).pipe(this.translateAndPush); + } + + private get translateAndPush(): OperatorFunction + { + return map((response: GenericAssessment): AssessmentModel => + { + const model: AssessmentModel = AssessmentModel.fromObject(response); + this.pushAssessment(model); + return model; + }) + } + + private pushAssessment(obj: AssessmentModel): void { + let replaced: boolean = false; + this.value.map((item: AssessmentModel): AssessmentModel => { + if (item.id !== obj.id) { + return item; + } + replaced = true; + return obj; + }); + + if (replaced) { + return; + } + + this._assessmentSubject.next([...this.value, obj]); + } + + public get value(): readonly AssessmentModel[] + { + return this._assessmentSubject.value; + } + + public get values$(): Observable { + return this._assessmentSubject.asObservable(); + } +} diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment-create-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-create-model.ts similarity index 100% rename from EEDU-Frontend/src/app/user/courses/appointment/entry/assignment-create-model.ts rename to EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-create-model.ts diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment-insight-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-insight-model.ts similarity index 100% rename from EEDU-Frontend/src/app/user/courses/appointment/entry/assignment-insight-model.ts rename to EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-insight-model.ts diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-model.ts similarity index 100% rename from EEDU-Frontend/src/app/user/courses/appointment/entry/assignment-model.ts rename to EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-model.ts diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.spec.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.spec.ts new file mode 100644 index 00000000..2c8e2c6a --- /dev/null +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AssignmentService } from './assignment.service'; + +describe('AssignmentService', () => { + let service: AssignmentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AssignmentService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts new file mode 100644 index 00000000..19794b92 --- /dev/null +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts @@ -0,0 +1,65 @@ +import {Injectable} from '@angular/core'; +import {HttpClient, HttpEvent} from "@angular/common/http"; +import {map, Observable} from "rxjs"; +import {AssignmentInsightModel, GenericAssignmentInsightModel} from "./assignment-insight-model"; +import {FileService} from "../../../../../file/file.service"; +import {AssignmentModel} from "./assignment-model"; +import {AppointmentService} from "../../appointment.service"; +import {AppointmentEntryModel} from "../appointment-entry-model"; +import {environment} from "../../../../../../environment/environment"; + +@Injectable({ + providedIn: 'root' +}) +export class AssignmentService { + + + public constructor(private readonly _http: HttpClient, + private readonly _fileService: FileService, + private readonly _appointmentService: AppointmentService) { + } + + public get nextAssignments(): readonly AssignmentModel[] { + return this._appointmentService.nextAppointments.filter( + (appointment: AppointmentEntryModel): boolean => !!appointment.assignment + ).map((appointment: AppointmentEntryModel): AssignmentModel => appointment.assignment); + } + + public fetchInsights(appointment: bigint): Observable { + const url: string = `${this.BACKEND_URL}/${appointment}/status/all`; + return this.http.get(url, {withCredentials: true}).pipe( + map((response: GenericAssignmentInsightModel[]): AssignmentInsightModel[] => + response.map((item: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(item)) + ) + ); + } + + public fetchInsight(appointment: bigint): Observable { + const url: string = `${this.BACKEND_URL}/${appointment}/status`; + return this.http.get(url, {withCredentials: true}).pipe(map((response: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(response))); + } + + public fetchUsersInsight(appointment: bigint, user: bigint): Observable { + const url: string = `${this.BACKEND_URL}/${appointment}/status/${user}`; + return this.http.get(url, {withCredentials: true}).pipe(map((response: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(response))); + } + + public submitAssignment(appointment: bigint, assignmentFiles: File[]): Observable> { + const url: string = `${this.BACKEND_URL}/${appointment}/submit`; + return this._fileService.uploadFiles(url, assignmentFiles); + } + + public deleteAssignment(appointment: bigint, assignmentFiles: string[]): Observable { + const url: string = `${this.BACKEND_URL}/${appointment}/delete/${assignmentFiles.toString()}`; + return this.http.delete(url, {withCredentials: true}); + } + + private get BACKEND_URL(): string + { + return `${environment.backendUrl}/course/appointment`; + } + + private get http(): HttpClient { + return this._http; + } +} diff --git a/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.html b/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.html index 95c03d93..9ba6b97e 100644 --- a/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.html +++ b/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.html @@ -11,7 +11,7 @@ [values]="privileges" [allowNull]="true" [multiple]="true" - title="Select Privileges" + label="Select Privileges" formControlName="privileges"> diff --git a/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.ts b/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.ts index eab686c4..0366cab2 100644 --- a/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.ts +++ b/EEDU-Frontend/src/app/user/group/create-group-dialog/create-group-dialog.component.ts @@ -23,7 +23,9 @@ export class CreateGroupDialogComponent extends AbstractCreateEntity { public constructor(service: GroupService, dialogRef: DialogRef, formBuilder: FormBuilder, private readonly _privilegeService: PrivilegeService) { super(service, dialogRef, formBuilder, "Create Group"); - this._privilegeService.value$.subscribe((value: PrivilegeModel[]): void => {this._privileges = value; }); + this._privilegeService.value$.subscribe((value: PrivilegeModel[]): void => { + this._privileges = value; + }); } private _privileges: readonly PrivilegeModel[] = []; @@ -37,12 +39,13 @@ export class CreateGroupDialogComponent extends AbstractCreateEntity { } protected override get loading(): boolean { - return this._privilegeService.fetched; + return !this._privilegeService.fetched; } protected override getForm(formBuilder: FormBuilder): FormGroup { return formBuilder.group({ - id: [null, Validators.required], privileges: [[]] + id: [null, Validators.required], + privileges: [[]] }); } } From 3fd495745cc4cb1ba59422936ad2b90d4fc62a87 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Fri, 14 Mar 2025 14:22:34 +0100 Subject: [PATCH 02/28] Populated news card, not finished --- .../eedu/livechat/WebsocketController.java | 1 - .../eedu/user/theming/ThemeCreateModel.java | 1 - .../de/gaz/eedu/user/theming/ThemeEntity.java | 5 ---- .../src/app/abstract/abstract.component.ts | 12 -------- .../app/dashboard/dashboard.component.html | 2 +- .../app/dashboard/dashboard.component.scss | 3 +- .../news-card/news-card.component.html | 7 ++++- .../news-card/news-card.component.scss | 25 ++++++++++++++++ .../news-card/news-card.component.ts | 30 +++++++++++++++++-- EEDU-Frontend/src/app/news/news.service.ts | 5 +++- 10 files changed, 65 insertions(+), 26 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WebsocketController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WebsocketController.java index cf2b8838..9d54f344 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WebsocketController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WebsocketController.java @@ -1,7 +1,6 @@ package de.gaz.eedu.livechat; import com.fasterxml.jackson.core.JsonProcessingException; -import de.gaz.eedu.livechat.DTO.WebsocketChatEdit; import de.gaz.eedu.livechat.chat.ChatModel; import de.gaz.eedu.livechat.chat.ChatService; import de.gaz.eedu.livechat.message.MessageModel; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java index 0f806e24..f7202793 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java @@ -23,7 +23,6 @@ public record ThemeCreateModel(String name, byte[] backgroundColor, byte[] widge themeEntity.setWidgetColorG(widgetColor[1]); themeEntity.setWidgetColorB(widgetColor[2]); - themeEntity.setUsers(new HashSet<>()); return themeEntity; } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java index a88350c5..a9002922 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java @@ -24,11 +24,6 @@ @Column(name = "widget_color_g") private byte widgetColorG; @Column(name = "widget_color_b") private byte widgetColorB; - @OneToMany(mappedBy = "themeEntity", cascade = { - CascadeType.REFRESH, - CascadeType.PERSIST - }) @JsonBackReference private Set users; - @Override @Contract(pure = true) public @NotNull ThemeModel toModel() { return new ThemeModel(getId(), getName(), diff --git a/EEDU-Frontend/src/app/abstract/abstract.component.ts b/EEDU-Frontend/src/app/abstract/abstract.component.ts index 562e8b12..c256cc9a 100644 --- a/EEDU-Frontend/src/app/abstract/abstract.component.ts +++ b/EEDU-Frontend/src/app/abstract/abstract.component.ts @@ -29,7 +29,6 @@ import {MatButton, MatIconButton} from "@angular/material/button"; export class AbstractComponent implements OnInit { private _mobile: boolean = false; - private _portrait: boolean = false; constructor(public websocketService: WebsocketService, public router: Router, public userService: UserService) { } @@ -67,27 +66,16 @@ export class AbstractComponent implements OnInit { this._mobile = window.innerWidth <= 600; } - private isPortrait() - { - this._portrait = window.innerHeight > window.innerWidth; - } - get mobile(): boolean { return this._mobile; } - get portrait(): boolean { - return this._portrait; - } - @HostListener("window:resize") public onResize() { this.isMobile(); - this.isPortrait(); } ngOnInit(): void { this.isMobile(); - this.isPortrait(); } } diff --git a/EEDU-Frontend/src/app/dashboard/dashboard.component.html b/EEDU-Frontend/src/app/dashboard/dashboard.component.html index cc0bc85b..0354e0eb 100644 --- a/EEDU-Frontend/src/app/dashboard/dashboard.component.html +++ b/EEDU-Frontend/src/app/dashboard/dashboard.component.html @@ -11,6 +11,6 @@

My Dashboard

{{ card.title }}

-

+
diff --git a/EEDU-Frontend/src/app/dashboard/dashboard.component.scss b/EEDU-Frontend/src/app/dashboard/dashboard.component.scss index 9a4cba79..80d9e1d5 100644 --- a/EEDU-Frontend/src/app/dashboard/dashboard.component.scss +++ b/EEDU-Frontend/src/app/dashboard/dashboard.component.scss @@ -62,11 +62,12 @@ body { } .card { + width: 100%; color: var(--text-color); background: var(--widget-color); border-radius: 8px; padding: 20px; - text-align: center; + text-align: left; transition: transform 0.3s ease, box-shadow 0.3s ease; display: flex; flex-direction: column; diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html index 0e958cef..786bb4a3 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html @@ -1 +1,6 @@ -

news-card works!

+
+
+

{{ x.title }}

+ +
+
diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss index e69de29b..cee93a7a 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss @@ -0,0 +1,25 @@ +.post-item { + width: 100%; + padding: 8px 0; + border-bottom: 1px solid var(--text-color); + &:last-child { + border-bottom: none; + } +} + +.post-header { + display: flex; + justify-content: space-between; +} + +.post-title { + font-size: 15px; + margin: 0; + color: var(--text-color); +} + +.post-author { + font-size: 12px; + color: gray; + margin: 0; +} diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts index 3454616d..f29da5fa 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts @@ -1,12 +1,36 @@ -import { Component } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; +import {NewsService} from "../../news/news.service"; +import {PostModel} from "../../news/post-model"; +import {Observable} from "rxjs"; +import {NgForOf, NgIf} from "@angular/common"; +import {MatCard, MatCardContent, MatCardHeader, MatCardSubtitle, MatCardTitle} from "@angular/material/card"; @Component({ selector: 'app-news-card', standalone: true, - imports: [], + imports: [ + NgForOf, + NgIf, + MatCard, + MatCardHeader, + MatCardContent, + MatCardTitle, + MatCardSubtitle + ], templateUrl: './news-card.component.html', styleUrl: './news-card.component.scss' }) -export class NewsCardComponent { +export class NewsCardComponent implements OnInit { + postList: PostModel[] = []; + + constructor(public newsService: NewsService) { + } + + ngOnInit(): void { + this.newsService.getPosts().subscribe(posts => { + this.postList = posts.splice(0, 5); + console.log(this.postList); + }); + } } diff --git a/EEDU-Frontend/src/app/news/news.service.ts b/EEDU-Frontend/src/app/news/news.service.ts index 63348d32..c63da256 100644 --- a/EEDU-Frontend/src/app/news/news.service.ts +++ b/EEDU-Frontend/src/app/news/news.service.ts @@ -11,7 +11,10 @@ import {UserService} from "../user/user.service"; export class NewsService implements OnInit { constructor(public router: Router, public http: HttpClient, public userService: UserService) { console.log("Getting posts...") - this.getPosts(); + this.getPosts().subscribe(posts => { + this.articleList = posts; + console.log(this.articleList); + }); } articleList: PostModel[] = []; From 3abcc7c3bce94e690d75e4926eab1045c2ab1cd6 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Fri, 14 Mar 2025 14:39:00 +0100 Subject: [PATCH 03/28] Outsourced illness notification requests to service and removed all unused imports --- .../src/main/java/de/gaz/eedu/DataLoader.java | 2 -- .../eedu/adapter/PeriodArgumentAdapter.java | 2 -- .../de/gaz/eedu/blogging/PostCreateModel.java | 8 ------ .../course/classroom/ClassRoomController.java | 1 - .../course/classroom/ClassRoomService.java | 3 -- .../eedu/course/subject/SubjectService.java | 1 - .../java/de/gaz/eedu/file/FileRepository.java | 1 - .../de/gaz/eedu/livechat/WSInterceptor.java | 6 ---- .../de/gaz/eedu/livechat/chat/ChatEntity.java | 2 -- .../de/gaz/eedu/livechat/chat/ChatModel.java | 1 - .../java/de/gaz/eedu/user/UserController.java | 1 - .../repository/GroupEntityRepository.java | 3 -- .../illnessnotifications/IllnessRequest.java | 1 - .../eedu/user/theming/ThemeCreateModel.java | 1 - .../de/gaz/eedu/user/theming/ThemeEntity.java | 3 -- .../chat-creation/chat-creation.component.ts | 2 +- .../src/app/chat/websocket.service.ts | 4 +-- .../news-card/news-card.component.ts | 1 - .../file-upload-button.component.ts | 1 - .../illness-notification.component.ts | 19 ++++--------- .../illness-notification.service.spec.ts | 16 +++++++++++ .../illness-notification.service.ts | 28 +++++++++++++++++++ .../app/management/management.component.ts | 2 -- 23 files changed, 52 insertions(+), 57 deletions(-) create mode 100644 EEDU-Frontend/src/app/illness-notification/illness-notification.service.spec.ts create mode 100644 EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java b/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java index c56b40b3..6e2b74fe 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java @@ -3,7 +3,6 @@ import de.gaz.eedu.user.AccountType; import de.gaz.eedu.user.UserEntity; import de.gaz.eedu.user.UserService; -import de.gaz.eedu.user.UserStatus; import de.gaz.eedu.user.group.GroupEntity; import de.gaz.eedu.user.group.GroupService; import de.gaz.eedu.user.group.model.GroupCreateModel; @@ -34,7 +33,6 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; @Component @RequiredArgsConstructor @Slf4j @Getter(AccessLevel.PROTECTED) public class DataLoader implements CommandLineRunner { diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/adapter/PeriodArgumentAdapter.java b/EEDU-Backend/src/main/java/de/gaz/eedu/adapter/PeriodArgumentAdapter.java index 4925ec4e..8ee6f869 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/adapter/PeriodArgumentAdapter.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/adapter/PeriodArgumentAdapter.java @@ -4,8 +4,6 @@ import jakarta.persistence.Converter; import org.jetbrains.annotations.Nullable; -import java.time.Duration; -import java.time.Instant; import java.time.Period; import java.util.Objects; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/blogging/PostCreateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/blogging/PostCreateModel.java index 4f8e52d2..8ded21e5 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/blogging/PostCreateModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/blogging/PostCreateModel.java @@ -1,16 +1,8 @@ package de.gaz.eedu.blogging; import de.gaz.eedu.entity.model.CreationModel; -import de.gaz.eedu.file.FileCreateModel; -import jakarta.validation.constraints.Null; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; public record PostCreateModel(@NotNull String author, @NotNull String title, @Nullable String thumbnailURL, @NotNull String body, @NotNull String[] editPrivileges, @NotNull String[] tags) implements CreationModel diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomController.java index 985a337c..696ad1b9 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomController.java @@ -1,6 +1,5 @@ package de.gaz.eedu.course.classroom; -import de.gaz.eedu.course.CourseService; import de.gaz.eedu.course.classroom.model.ClassRoomCreateModel; import de.gaz.eedu.course.classroom.model.ClassRoomModel; import de.gaz.eedu.course.model.CourseModel; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomService.java index c825ccf5..5c77396e 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/classroom/ClassRoomService.java @@ -22,11 +22,8 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.server.ResponseStatusException; import java.util.*; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/subject/SubjectService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/subject/SubjectService.java index 760c453f..2dd06593 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/subject/SubjectService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/subject/SubjectService.java @@ -15,7 +15,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Stream; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileRepository.java b/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileRepository.java index ebf7d66a..73b95d7c 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileRepository.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileRepository.java @@ -3,7 +3,6 @@ import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; import java.util.Optional; public interface FileRepository extends JpaRepository diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WSInterceptor.java b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WSInterceptor.java index 2e86305f..4c6f06b8 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WSInterceptor.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/WSInterceptor.java @@ -1,22 +1,16 @@ package de.gaz.eedu.livechat; import de.gaz.eedu.user.UserService; -import de.gaz.eedu.user.verification.VerificationService; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.messaging.simp.SimpMessagingTemplate; -import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @RequiredArgsConstructor diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatEntity.java index 49b31f65..691ab542 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatEntity.java @@ -1,9 +1,7 @@ package de.gaz.eedu.livechat.chat; import de.gaz.eedu.entity.model.EntityModelRelation; -import de.gaz.eedu.entity.model.EntityObject; import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatModel.java index 3cef8af5..2274b4dd 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/livechat/chat/ChatModel.java @@ -1,7 +1,6 @@ package de.gaz.eedu.livechat.chat; import de.gaz.eedu.entity.model.EntityModel; -import de.gaz.eedu.entity.model.Model; import org.jetbrains.annotations.NotNull; import java.util.Arrays; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserController.java index 716f82d8..4e071f16 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserController.java @@ -18,7 +18,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/repository/GroupEntityRepository.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/repository/GroupEntityRepository.java index 9b30c662..53100c91 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/repository/GroupEntityRepository.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/repository/GroupEntityRepository.java @@ -2,8 +2,5 @@ import de.gaz.eedu.entity.EntityRepository; import de.gaz.eedu.user.group.GroupEntity; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; public interface GroupEntityRepository extends EntityRepository {} diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessRequest.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessRequest.java index 9de5acfc..f281b43b 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessRequest.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessRequest.java @@ -1,7 +1,6 @@ package de.gaz.eedu.user.illnessnotifications; import org.jetbrains.annotations.NotNull; -import org.springframework.web.multipart.MultipartFile; public record IllnessRequest(@NotNull String reason, @NotNull Long expirationTime) { diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java index f7202793..497dd7a7 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeCreateModel.java @@ -5,7 +5,6 @@ import org.jetbrains.annotations.NotNull; import java.util.Arrays; -import java.util.HashSet; public record ThemeCreateModel(String name, byte[] backgroundColor, byte[] widgetColor) implements CreationModel { diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java index a9002922..6516a55f 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeEntity.java @@ -1,8 +1,6 @@ package de.gaz.eedu.user.theming; -import com.fasterxml.jackson.annotation.JsonBackReference; import de.gaz.eedu.entity.model.EntityModelRelation; -import de.gaz.eedu.user.UserEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -10,7 +8,6 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import java.util.Set; @Setter @Getter @Entity @Table(name = "theme_entity") public class ThemeEntity implements EntityModelRelation { diff --git a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts index a0b3d140..b48f35cc 100644 --- a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts +++ b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component} from '@angular/core'; import {ReducedUserModel} from "../../user/reduced-user-model"; import {HttpClient} from "@angular/common/http"; import {MatListSubheaderCssMatStyler} from "@angular/material/list"; diff --git a/EEDU-Frontend/src/app/chat/websocket.service.ts b/EEDU-Frontend/src/app/chat/websocket.service.ts index bbb8242a..bb9e5cef 100644 --- a/EEDU-Frontend/src/app/chat/websocket.service.ts +++ b/EEDU-Frontend/src/app/chat/websocket.service.ts @@ -1,9 +1,9 @@ -import {Injectable, OnInit} from '@angular/core'; +import {Injectable} from '@angular/core'; import {Stomp} from "@stomp/stompjs"; import {AuthenticationService} from "../user/authentication/authentication.service"; import {UserService} from "../user/user.service"; import {HttpClient} from "@angular/common/http"; -import {map, merge, Observable} from "rxjs"; +import {Observable} from "rxjs"; @Injectable({ providedIn: 'root' diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts index f29da5fa..100693bf 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts @@ -1,7 +1,6 @@ import {Component, OnInit} from '@angular/core'; import {NewsService} from "../../news/news.service"; import {PostModel} from "../../news/post-model"; -import {Observable} from "rxjs"; import {NgForOf, NgIf} from "@angular/common"; import {MatCard, MatCardContent, MatCardHeader, MatCardSubtitle, MatCardTitle} from "@angular/material/card"; diff --git a/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.ts b/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.ts index 24226fc4..71ccdd21 100644 --- a/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.ts +++ b/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.ts @@ -1,7 +1,6 @@ import {Component, EventEmitter, Output} from '@angular/core'; import {NgForOf} from "@angular/common"; import {MatButton} from "@angular/material/button"; -import {ArticleCreationComponent} from "../../news/article-creation/article-creation.component"; import {ArticleCreationService} from "../../news/article-creation/article-creation.service"; @Component({ diff --git a/EEDU-Frontend/src/app/illness-notification/illness-notification.component.ts b/EEDU-Frontend/src/app/illness-notification/illness-notification.component.ts index f30b600d..7a0f577d 100644 --- a/EEDU-Frontend/src/app/illness-notification/illness-notification.component.ts +++ b/EEDU-Frontend/src/app/illness-notification/illness-notification.component.ts @@ -13,9 +13,9 @@ import {MatIcon} from "@angular/material/icon"; import {NgForOf, NgIf} from "@angular/common"; import {HttpClient} from "@angular/common/http"; import {ReducedIllnessNotificationModel} from "./model/reduced-illness-notification-model"; -import {Observable, Subscription} from "rxjs"; import {IllnessNotificationStatus} from "./illness-notification-status"; import {FileUploadComponent} from "../common/file-upload/file-upload.component"; +import {IllnessNotificationService} from "./illness-notification.service"; @Component({ selector: 'app-illness-notification', @@ -43,27 +43,20 @@ import {FileUploadComponent} from "../common/file-upload/file-upload.component"; export class IllnessNotificationComponent implements OnInit { selectedFile: File | null = null; - prefix: string = "http://localhost:8080/api/v1/illness/me"; reason!: string; until!: Date | null; illnessNotifications!: ReducedIllnessNotificationModel[]; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient, private service: IllnessNotificationService) {} ngOnInit(): void { - this.getOwnSickNotes().subscribe(list => { + this.service.getOwnSickNotes().subscribe(list => { this.illnessNotifications = list.sort(model => Number(model.id)); console.log(this.illnessNotifications); }) } - getOwnSickNotes(): Observable { - return this.http.get(`${this.prefix}/my-notifications`, { - withCredentials: true - }); - } - onFilesSelected(files: FileList): void { this.selectedFile = files.item(0); } @@ -92,7 +85,7 @@ export class IllnessNotificationComponent implements OnInit { this.until = event.value; } - onRequestSent(): Subscription | undefined { + onRequestSent(): void { console.log(this.until); if (!this.until) { @@ -114,8 +107,6 @@ export class IllnessNotificationComponent implements OnInit { formData.append("file", this.selectedFile); - return this.http.post(`${this.prefix}/excuse`, formData, { - withCredentials: true - }).subscribe(() => location.reload()); + this.service.sendSickNoteRequest(formData); } } diff --git a/EEDU-Frontend/src/app/illness-notification/illness-notification.service.spec.ts b/EEDU-Frontend/src/app/illness-notification/illness-notification.service.spec.ts new file mode 100644 index 00000000..7f12f26f --- /dev/null +++ b/EEDU-Frontend/src/app/illness-notification/illness-notification.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { IllnessNotificationService } from './illness-notification.service'; + +describe('IllnessNotificationService', () => { + let service: IllnessNotificationService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(IllnessNotificationService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts b/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts new file mode 100644 index 00000000..4f147ab4 --- /dev/null +++ b/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import {ReducedIllnessNotificationModel} from "./model/reduced-illness-notification-model"; +import {HttpClient} from "@angular/common/http"; +import {environment} from "../../environment/environment"; +import {Observable, Subscription} from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class IllnessNotificationService { + protected backendUrl = environment.backendUrl; + protected prefix: string = `http://${this.backendUrl}/api/v1/illness/me`; + + constructor(public http: HttpClient) { } + + getOwnSickNotes(): Observable { + return this.http.get(`${this.prefix}/my-notifications`, { + withCredentials: true + }); + } + + sendSickNoteRequest(formData: FormData): Subscription + { + return this.http.post(`${this.prefix}/excuse`, formData, { + withCredentials: true + }).subscribe(() => location.reload()); + } +} diff --git a/EEDU-Frontend/src/app/management/management.component.ts b/EEDU-Frontend/src/app/management/management.component.ts index 7718a71b..01fd5317 100644 --- a/EEDU-Frontend/src/app/management/management.component.ts +++ b/EEDU-Frontend/src/app/management/management.component.ts @@ -22,8 +22,6 @@ import {IllnessNotificationStatus} from "../illness-notification/illness-notific import {ListItemInfo} from "../common/abstract-list/abstract-list.component"; import {CourseModel} from "../user/courses/course-model"; import {ClassRoomModel} from "../user/courses/classroom/class-room-model"; -import {RoomModel} from "../user/courses/room/room-model"; -import {SubjectModel} from "../user/courses/subject/subject-model"; import {EntityListComponent} from "../entity/entity-list/entity-list.component"; import {SubjectService} from "../user/courses/subject/subject.service"; import {RoomService} from "../user/courses/room/room.service"; From 44a5ec7f93c8d43489a490681dd029b19b6a2476 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Fri, 14 Mar 2025 14:51:26 +0100 Subject: [PATCH 04/28] Saved Ivo's mental health --- .../chat/chat-creation/chat-creation.component.ts | 5 +++-- EEDU-Frontend/src/app/chat/chat.component.ts | 5 +++-- EEDU-Frontend/src/app/chat/websocket.service.ts | 5 +++-- EEDU-Frontend/src/app/file/file.service.ts | 12 ++---------- .../article-creation/article-creation.service.ts | 3 ++- EEDU-Frontend/src/app/news/news.service.ts | 7 ++++--- EEDU-Frontend/src/app/settings/settings.component.ts | 8 ++++---- 7 files changed, 21 insertions(+), 24 deletions(-) diff --git a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts index b48f35cc..b46a7ed6 100644 --- a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts +++ b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts @@ -11,6 +11,7 @@ import {NgForOf} from "@angular/common"; import {FormsModule} from "@angular/forms"; import {UserService} from "../../user/user.service"; import {ChatModel} from "../models/chat-model"; +import {environment} from "../../../environment/environment"; @Component({ selector: 'app-chat-creation', @@ -39,7 +40,7 @@ export class ChatCreationComponent { } private getAllUsers() { - this.http.get("http://localhost:8080/api/v1/user/all/reduced", { withCredentials: true }) + this.http.get(`http://${environment.backendUrl}/api/v1/user/all/reduced`, { withCredentials: true }) .subscribe(list => { this.originalUserList = [...list]; this.userList = list; @@ -61,7 +62,7 @@ export class ChatCreationComponent { public createChat(userId: bigint) { let chatUsers = [this.userService.getUserData.id, userId] - return this.http.post("http://localhost:8080/api/v1/chat/create", chatUsers, { + return this.http.post(`http://${environment.backendUrl}/api/v1/chat/create`, chatUsers, { withCredentials: true }).subscribe(model => { console.log(model); diff --git a/EEDU-Frontend/src/app/chat/chat.component.ts b/EEDU-Frontend/src/app/chat/chat.component.ts index 218fd6b9..15c402fc 100644 --- a/EEDU-Frontend/src/app/chat/chat.component.ts +++ b/EEDU-Frontend/src/app/chat/chat.component.ts @@ -18,6 +18,7 @@ import { MatDrawer, MatDrawerContainer, } from "@angular/material/sidenav"; +import {environment} from "../../environment/environment"; @Component({ selector: 'app-chat', @@ -67,7 +68,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { } public getAllChats() { - return this.http.get("http://localhost:8080/api/v1/chat/getChatList", { + return this.http.get(`http://${environment.backendUrl}/api/v1/chat/getChatList`, { withCredentials: true }).subscribe(models => { this.chatList = models; @@ -99,7 +100,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { } public getChat(chatId: number) { - this.http.post("http://localhost:8080/api/v1/chat/get/chat", chatId, { + this.http.post(`http://${environment.backendUrl}/api/v1/chat/get/chat`, chatId, { withCredentials: true }).subscribe(model => { this.currentChatHistory = model; diff --git a/EEDU-Frontend/src/app/chat/websocket.service.ts b/EEDU-Frontend/src/app/chat/websocket.service.ts index bb9e5cef..e2e6e993 100644 --- a/EEDU-Frontend/src/app/chat/websocket.service.ts +++ b/EEDU-Frontend/src/app/chat/websocket.service.ts @@ -4,6 +4,7 @@ import {AuthenticationService} from "../user/authentication/authentication.servi import {UserService} from "../user/user.service"; import {HttpClient} from "@angular/common/http"; import {Observable} from "rxjs"; +import {environment} from "../../environment/environment"; @Injectable({ providedIn: 'root' @@ -20,7 +21,7 @@ export class WebsocketService { private connect(onConnectCallback: () => void) { this.authenticateWebsocket().subscribe(token => { - const socket = new WebSocket(`ws://localhost:8080/ws-endpoint?token=${token}`); + const socket = new WebSocket(`ws://${environment.backendUrl}/ws-endpoint?token=${token}`); this.stompClient = Stomp.over(socket); this.stompClient.connect({}, (frame: any): void => { this.listen('test'); @@ -51,7 +52,7 @@ export class WebsocketService { private authenticateWebsocket(): Observable { - return this.http.get('http://localhost:8080/api/v1/chat/authenticate', { + return this.http.get(`http://${environment.backendUrl}/api/v1/chat/authenticate`, { withCredentials: true, responseType: "text" as "json" }); diff --git a/EEDU-Frontend/src/app/file/file.service.ts b/EEDU-Frontend/src/app/file/file.service.ts index 11c86f9e..3e6e24b8 100644 --- a/EEDU-Frontend/src/app/file/file.service.ts +++ b/EEDU-Frontend/src/app/file/file.service.ts @@ -2,6 +2,7 @@ import {HttpClient, HttpEvent} from "@angular/common/http"; import {FileModel} from "./file-model"; import {Observable} from "rxjs"; import {Injectable} from '@angular/core'; +import {environment} from "../../environment/environment"; interface FileResponse { blob: Uint8Array; @@ -16,21 +17,12 @@ interface FileResponse { * https://blog.angular-university.io/angular-file-upload/ */ export class FileService { - URL_PREFIX: string = "http://localhost:8080/api/v1/file"; + URL_PREFIX: string = `http://${environment.backendUrl}/api/v1/file`; public selectedFiles!: File[] | null; constructor(private http: HttpClient) { } - public uploadImageToPost() - { - this.uploadSelection("http://localhost:8080/api/v1/blog/post"); - } - - public testDownload(): void { - this.downloadFile(BigInt(1)); - } - // ------------------------------ UPLOAD ----------------------------------- public uploadSelection(url: string, additionalData?: { [key: string]: any }): void { if(this.selectedFiles){ diff --git a/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts b/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts index 06038518..6d23aebc 100644 --- a/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts +++ b/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import {PostCreateModel} from "./post-create-model"; import {PostModel} from "../post-model"; import {HttpClient} from "@angular/common/http"; +import {environment} from "../../../environment/environment"; @Injectable({ providedIn: 'root' @@ -48,7 +49,7 @@ export class ArticleCreationService { sendPostCreationRequest(formData: FormData) { console.log(formData); this.http - .post('http://localhost:8080/api/v1/blog/post', formData, { + .post(`http://${environment.backendUrl}/api/v1/blog/post`, formData, { withCredentials: true }) .subscribe({ diff --git a/EEDU-Frontend/src/app/news/news.service.ts b/EEDU-Frontend/src/app/news/news.service.ts index c63da256..e5ddc956 100644 --- a/EEDU-Frontend/src/app/news/news.service.ts +++ b/EEDU-Frontend/src/app/news/news.service.ts @@ -4,6 +4,7 @@ import {Router} from "@angular/router"; import {HttpClient} from "@angular/common/http"; import {Observable, tap} from "rxjs"; import {UserService} from "../user/user.service"; +import {environment} from "../../environment/environment"; @Injectable({ providedIn: 'root' @@ -30,7 +31,7 @@ export class NewsService implements OnInit { pageIndex = 0; } - return this.http.get(`http://localhost:8080/api/v1/blog/get/list?pageNumber=${pageIndex}`, { + return this.http.get(`http://${environment.backendUrl}/api/v1/blog/get/list?pageNumber=${pageIndex}`, { withCredentials: true }).pipe( tap((list) => { @@ -42,14 +43,14 @@ export class NewsService implements OnInit { public getCount(): Observable { - return this.http.get('http://localhost:8080/api/v1/blog/get/length', { + return this.http.get(`http://${environment.backendUrl}/api/v1/blog/get/length`, { withCredentials: true }); } public getArticle(id: number): Observable { - return this.http.get(`http://localhost:8080/api/v1/blog/get/${id}`, { + return this.http.get(`http://${environment.backendUrl}/api/v1/blog/get/${id}`, { withCredentials: true }); } diff --git a/EEDU-Frontend/src/app/settings/settings.component.ts b/EEDU-Frontend/src/app/settings/settings.component.ts index e3226f63..a5b2f642 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.ts +++ b/EEDU-Frontend/src/app/settings/settings.component.ts @@ -17,6 +17,7 @@ import { MatExpansionModule, } from "@angular/material/expansion"; import {FullUserListComponent} from "../user/user-list/full-user-list/full-user-list.component"; +import {environment} from "../../environment/environment"; @Component({ selector: 'app-settings', @@ -33,8 +34,7 @@ import {FullUserListComponent} from "../user/user-list/full-user-list/full-user- MatButton, FormsModule, MatDivider, - MatExpansionModule, - FullUserListComponent + MatExpansionModule ], templateUrl: './settings.component.html', styleUrl: './settings.component.scss', @@ -130,7 +130,7 @@ export class SettingsComponent implements OnInit { * @returns Observable carrying the full newly selected theme. */ public setTheme(themeId: bigint): Observable { - const url: string = `http://localhost:8080/${this.THEME_URL}/me/theme/set`; + const url: string = `http://${environment.backendUrl}/${this.THEME_URL}/me/theme/set`; return this.http.put(url, themeId, { withCredentials: true }).pipe(map(model => { @@ -148,7 +148,7 @@ export class SettingsComponent implements OnInit { * id, name format. */ public fetchAllThemes() : Observable { - const url: string = `http://localhost:8080/${this.THEME_URL}/theme/all`; + const url: string = `http://${environment.backendUrl}/${this.THEME_URL}/theme/all`; return this.http.get(url, {withCredentials: true}); } From ffdc5e91a774562479f645acf5d69c325376e8c9 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Fri, 14 Mar 2025 15:20:37 +0100 Subject: [PATCH 05/28] Fixed some issues --- .../course/appointment/entry/AppointmentEntryEntity.java | 5 +++-- .../appointment/entry/assignment/AssignmentController.java | 1 + .../entry/assignment/assessment/AssessmentController.java | 1 + .../main/java/de/gaz/eedu/course/room/RoomController.java | 2 +- .../common/selection-input/selection-input.component.html | 1 - .../appointment/entry/assignment/assignment.service.ts | 3 +-- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java index 8d3a210a..48da26ee 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java @@ -165,7 +165,7 @@ public void submitAssignment(long user, @NotNull MultipartFile... files) throws throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE, "The maximum amount of files exceeded."); } - getCourse().getRepository().uploadBatch(uploadPath, files); + getCourse().getRepository().uploadBatch(uploadPath(user), files); log.info("User {} has uploaded files to appointment entry {}.", user, getId()); } @@ -208,7 +208,8 @@ public boolean hasSubmitted(@NotNull UserEntity user) return false; } - return new File(getCourse().getRepository().getFilePath(uploadPath(user.getId()))).exists(); + File file = new File(getUploadPath(user.getId())); + return file.exists() && file.isDirectory(); } private @NotNull String uploadPath(long user) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java index 5ff98f07..e36f6eb7 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +@RestController @RequestMapping("/api/v1/course/appointment/assignment") @RequiredArgsConstructor @Getter(AccessLevel.PROTECTED) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java index 7ddc1958..0af94de8 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java @@ -13,6 +13,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +@RestController @RequestMapping("/api/v1/course/appointment/assignment/assessment") @RequiredArgsConstructor @Getter(AccessLevel.PROTECTED) public class AssessmentController extends EntityController diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/room/RoomController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/room/RoomController.java index 4964a9e1..d8d7ce98 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/room/RoomController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/room/RoomController.java @@ -37,6 +37,6 @@ public class RoomController extends EntityController> fetchAll() {return super.fetchAll();} } diff --git a/EEDU-Frontend/src/app/common/selection-input/selection-input.component.html b/EEDU-Frontend/src/app/common/selection-input/selection-input.component.html index 20911237..c3cb3970 100644 --- a/EEDU-Frontend/src/app/common/selection-input/selection-input.component.html +++ b/EEDU-Frontend/src/app/common/selection-input/selection-input.component.html @@ -35,7 +35,6 @@ [(ngModel)]="currentValue" [placeholder]="placeholder()" [matAutocomplete]="complete" - [disabled]="accessibleValues().length == 1" (blur)="onTouched()" /> } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts index 19794b92..c317b436 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts @@ -13,7 +13,6 @@ import {environment} from "../../../../../../environment/environment"; }) export class AssignmentService { - public constructor(private readonly _http: HttpClient, private readonly _fileService: FileService, private readonly _appointmentService: AppointmentService) { @@ -56,7 +55,7 @@ export class AssignmentService { private get BACKEND_URL(): string { - return `${environment.backendUrl}/course/appointment`; + return `${environment.backendUrl}/course/appointment/assignment`; } private get http(): HttpClient { From e7ee023effad8b31d30b30ecaaf73d23edd2a3e3 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Fri, 14 Mar 2025 15:28:37 +0100 Subject: [PATCH 06/28] Fixed endpoint issues --- .../src/app/chat/chat-creation/chat-creation.component.ts | 4 ++-- EEDU-Frontend/src/app/chat/chat.component.html | 2 +- EEDU-Frontend/src/app/chat/chat.component.ts | 4 ++-- EEDU-Frontend/src/app/chat/websocket.service.ts | 4 ++-- .../src/app/common/abstract-list/abstract-list.component.ts | 1 + EEDU-Frontend/src/app/file/file.service.ts | 2 +- .../illness-notification/illness-notification.service.ts | 2 +- .../app/news/article-creation/article-creation.service.ts | 2 +- EEDU-Frontend/src/app/news/news.service.ts | 6 +++--- EEDU-Frontend/src/app/settings/settings.component.ts | 3 ++- .../user-list/full-user-list/full-user-list.component.ts | 2 +- EEDU-Frontend/src/environment/environment.ts | 3 ++- 12 files changed, 19 insertions(+), 16 deletions(-) diff --git a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts index b46a7ed6..d8911203 100644 --- a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts +++ b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.ts @@ -40,7 +40,7 @@ export class ChatCreationComponent { } private getAllUsers() { - this.http.get(`http://${environment.backendUrl}/api/v1/user/all/reduced`, { withCredentials: true }) + this.http.get(`${environment.backendUrl}/user/all/reduced`, { withCredentials: true }) .subscribe(list => { this.originalUserList = [...list]; this.userList = list; @@ -62,7 +62,7 @@ export class ChatCreationComponent { public createChat(userId: bigint) { let chatUsers = [this.userService.getUserData.id, userId] - return this.http.post(`http://${environment.backendUrl}/api/v1/chat/create`, chatUsers, { + return this.http.post(`${environment.backendUrl}/chat/create`, chatUsers, { withCredentials: true }).subscribe(model => { console.log(model); diff --git a/EEDU-Frontend/src/app/chat/chat.component.html b/EEDU-Frontend/src/app/chat/chat.component.html index 94caaed6..1ded0887 100644 --- a/EEDU-Frontend/src/app/chat/chat.component.html +++ b/EEDU-Frontend/src/app/chat/chat.component.html @@ -1,7 +1,7 @@
-

Open new chat

+

Open new chat

menu
diff --git a/EEDU-Frontend/src/app/chat/chat.component.ts b/EEDU-Frontend/src/app/chat/chat.component.ts index 15c402fc..de070f14 100644 --- a/EEDU-Frontend/src/app/chat/chat.component.ts +++ b/EEDU-Frontend/src/app/chat/chat.component.ts @@ -68,7 +68,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { } public getAllChats() { - return this.http.get(`http://${environment.backendUrl}/api/v1/chat/getChatList`, { + return this.http.get(`${environment.backendUrl}/chat/getChatList`, { withCredentials: true }).subscribe(models => { this.chatList = models; @@ -100,7 +100,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { } public getChat(chatId: number) { - this.http.post(`http://${environment.backendUrl}/api/v1/chat/get/chat`, chatId, { + this.http.post(`${environment.backendUrl}/chat/get/chat`, chatId, { withCredentials: true }).subscribe(model => { this.currentChatHistory = model; diff --git a/EEDU-Frontend/src/app/chat/websocket.service.ts b/EEDU-Frontend/src/app/chat/websocket.service.ts index e2e6e993..99fc2ad2 100644 --- a/EEDU-Frontend/src/app/chat/websocket.service.ts +++ b/EEDU-Frontend/src/app/chat/websocket.service.ts @@ -21,7 +21,7 @@ export class WebsocketService { private connect(onConnectCallback: () => void) { this.authenticateWebsocket().subscribe(token => { - const socket = new WebSocket(`ws://${environment.backendUrl}/ws-endpoint?token=${token}`); + const socket = new WebSocket(`${environment.websocketUrl}?token=${token}`); this.stompClient = Stomp.over(socket); this.stompClient.connect({}, (frame: any): void => { this.listen('test'); @@ -52,7 +52,7 @@ export class WebsocketService { private authenticateWebsocket(): Observable { - return this.http.get(`http://${environment.backendUrl}/api/v1/chat/authenticate`, { + return this.http.get(`${environment.backendUrl}/chat/authenticate`, { withCredentials: true, responseType: "text" as "json" }); diff --git a/EEDU-Frontend/src/app/common/abstract-list/abstract-list.component.ts b/EEDU-Frontend/src/app/common/abstract-list/abstract-list.component.ts index 189c85cd..0b14960a 100644 --- a/EEDU-Frontend/src/app/common/abstract-list/abstract-list.component.ts +++ b/EEDU-Frontend/src/app/common/abstract-list/abstract-list.component.ts @@ -35,6 +35,7 @@ export interface GeneralListInfo { selector: 'list', imports: [MatChipSet, MatChip, MatExpansionPanel, MatAccordion, MatExpansionPanelTitle, MatExpansionPanelDescription, MatExpansionPanelHeader, MatFormField, MatInput, MatLabel, NgIf, FormsModule, NgForOf, AllCheckBoxComponent, SingleCheckBoxComponent, NgComponentOutlet, MatList, MatListItem, NgTemplateOutlet,], templateUrl: './abstract-list.component.html', + standalone: true, styleUrl: './abstract-list.component.scss' }) export class AbstractList { diff --git a/EEDU-Frontend/src/app/file/file.service.ts b/EEDU-Frontend/src/app/file/file.service.ts index 3e6e24b8..38d01e5a 100644 --- a/EEDU-Frontend/src/app/file/file.service.ts +++ b/EEDU-Frontend/src/app/file/file.service.ts @@ -17,7 +17,7 @@ interface FileResponse { * https://blog.angular-university.io/angular-file-upload/ */ export class FileService { - URL_PREFIX: string = `http://${environment.backendUrl}/api/v1/file`; + URL_PREFIX: string = `${environment.backendUrl}/file`; public selectedFiles!: File[] | null; diff --git a/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts b/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts index 4f147ab4..108caf9b 100644 --- a/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts +++ b/EEDU-Frontend/src/app/illness-notification/illness-notification.service.ts @@ -9,7 +9,7 @@ import {Observable, Subscription} from "rxjs"; }) export class IllnessNotificationService { protected backendUrl = environment.backendUrl; - protected prefix: string = `http://${this.backendUrl}/api/v1/illness/me`; + protected prefix: string = `${this.backendUrl}/illness/me`; constructor(public http: HttpClient) { } diff --git a/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts b/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts index 6d23aebc..0dbabf69 100644 --- a/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts +++ b/EEDU-Frontend/src/app/news/article-creation/article-creation.service.ts @@ -49,7 +49,7 @@ export class ArticleCreationService { sendPostCreationRequest(formData: FormData) { console.log(formData); this.http - .post(`http://${environment.backendUrl}/api/v1/blog/post`, formData, { + .post(`${environment.backendUrl}/blog/post`, formData, { withCredentials: true }) .subscribe({ diff --git a/EEDU-Frontend/src/app/news/news.service.ts b/EEDU-Frontend/src/app/news/news.service.ts index e5ddc956..5f4e8be1 100644 --- a/EEDU-Frontend/src/app/news/news.service.ts +++ b/EEDU-Frontend/src/app/news/news.service.ts @@ -31,7 +31,7 @@ export class NewsService implements OnInit { pageIndex = 0; } - return this.http.get(`http://${environment.backendUrl}/api/v1/blog/get/list?pageNumber=${pageIndex}`, { + return this.http.get(`${environment.backendUrl}/blog/get/list?pageNumber=${pageIndex}`, { withCredentials: true }).pipe( tap((list) => { @@ -43,14 +43,14 @@ export class NewsService implements OnInit { public getCount(): Observable { - return this.http.get(`http://${environment.backendUrl}/api/v1/blog/get/length`, { + return this.http.get(`${environment.backendUrl}/blog/get/length`, { withCredentials: true }); } public getArticle(id: number): Observable { - return this.http.get(`http://${environment.backendUrl}/api/v1/blog/get/${id}`, { + return this.http.get(`${environment.backendUrl}/blog/get/${id}`, { withCredentials: true }); } diff --git a/EEDU-Frontend/src/app/settings/settings.component.ts b/EEDU-Frontend/src/app/settings/settings.component.ts index a5b2f642..930c3eb2 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.ts +++ b/EEDU-Frontend/src/app/settings/settings.component.ts @@ -34,7 +34,8 @@ import {environment} from "../../environment/environment"; MatButton, FormsModule, MatDivider, - MatExpansionModule + MatExpansionModule, + FullUserListComponent ], templateUrl: './settings.component.html', styleUrl: './settings.component.scss', diff --git a/EEDU-Frontend/src/app/user/user-list/full-user-list/full-user-list.component.ts b/EEDU-Frontend/src/app/user/user-list/full-user-list/full-user-list.component.ts index 9f57a206..ee05701c 100644 --- a/EEDU-Frontend/src/app/user/user-list/full-user-list/full-user-list.component.ts +++ b/EEDU-Frontend/src/app/user/user-list/full-user-list/full-user-list.component.ts @@ -7,10 +7,10 @@ import {AccountType} from "../../account-type"; @Component({ selector: 'app-full-user-list', imports: [ - AbstractList, AbstractList ], templateUrl: './full-user-list.component.html', + standalone: true, styleUrl: './full-user-list.component.scss' }) export class FullUserListComponent { diff --git a/EEDU-Frontend/src/environment/environment.ts b/EEDU-Frontend/src/environment/environment.ts index a15a1e0c..738a531c 100644 --- a/EEDU-Frontend/src/environment/environment.ts +++ b/EEDU-Frontend/src/environment/environment.ts @@ -1,5 +1,6 @@ export const environment = { production: false, timeZone: 'Europe/Berlin', - backendUrl: 'http://localhost:8080/api/v1' + backendUrl: 'http://localhost:8080/api/v1', + websocketUrl: 'ws://localhost:8080/ws-endpoint' }; From a837a7f008daa901c1df6fd91c0fc4ab9e5c6c77 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Fri, 14 Mar 2025 21:32:12 +0100 Subject: [PATCH 07/28] Made some qol --- .../appointment/AppointmentService.java | 29 +++++++++++++++++ .../entry/AppointmentEntryEntity.java | 19 +++++++++-- .../assignment/AssignmentController.java | 13 ++++++++ .../assignment-teacher-view.component.html | 20 +++++++++--- .../assignment-teacher-view.component.ts | 32 +++++++++++++------ .../entry/assignment/assignment.service.ts | 4 +++ 6 files changed, 102 insertions(+), 15 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java index e95872bb..ca41b43a 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java @@ -16,8 +16,10 @@ import de.gaz.eedu.entity.EntityService; import de.gaz.eedu.exception.CreationException; import de.gaz.eedu.exception.EntityUnknownException; +import de.gaz.eedu.file.FileService; import de.gaz.eedu.user.UserEntity; import de.gaz.eedu.user.repository.UserRepository; +import io.jsonwebtoken.io.IOException; import jakarta.transaction.Transactional; import lombok.AccessLevel; import lombok.Getter; @@ -25,7 +27,9 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; @@ -41,6 +45,7 @@ @RequiredArgsConstructor public class AppointmentService extends EntityService { + private final FileService fileService; private final FrequentAppointmentRepository repository; @Getter(AccessLevel.PUBLIC) private final AppointmentEntryRepository entryRepository; @@ -219,6 +224,30 @@ private AppointmentEntryEntity getAppointmentEntry(long id) throws EntityUnknown return entryReference.orElseThrow(entityUnknown(id)); } + public @NotNull ResponseEntity downloadAssignments(long appointment, long user) + { + AppointmentEntryEntity entry = getEntryRepository().findById(appointment).orElseThrow(entityUnknown(appointment)); + return getFileService().zipAndSend(entry.loadAssignmentFiles(user)); + } + + public @NotNull ResponseEntity downloadAssignment(long appointment, long user, @NotNull String file) + { + try + { + AppointmentEntryEntity entry = getEntryRepository().findById(appointment).orElseThrow(entityUnknown(appointment)); + return getFileService().sendSingle(entry.loadAssignmentFile(user, file).orElseThrow(() -> + { + String message = String.format("Could not find file %s in appointment: %s", file, appointment); + return new IOException(message); + })); + } + catch (java.io.IOException exception) + { + String message = "An exception occurred while trying to prepare downloading assignment: %s"; + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, String.format(message, file), exception); + } + } + /** * Creates a list of {@link AppointmentEntryEntity} based on the given course and creation models. *

diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java index 48da26ee..091760b3 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java @@ -132,8 +132,7 @@ public AppointmentEntryEntity(long id) public @NotNull AssignmentInsightModel getInsight(@NotNull UserEntity user) { - String uploadPath = getUploadPath(user.getId()); - File file = new File(uploadPath); + File file = new File(getUploadPath(user.getId())); File[] files = file.listFiles(); AssessmentModel assessment = getAssessment(user).map(AssessmentEntity::toModel).orElse(null); @@ -147,6 +146,22 @@ public AppointmentEntryEntity(long id) return new AssignmentInsightModel(user.getLoginName(), true, paths, assessment); } + public @NotNull File[] loadAssignmentFiles(long user) + { + return Objects.requireNonNullElse(new File(getUploadPath(user)).listFiles(), new File[0]); + } + + public @NotNull Optional loadAssignmentFile(long user, @NotNull String file) + { + File couldBe = loadFileSave(getUploadPath(user), file); + if(!couldBe.exists() || !couldBe.isDirectory() || !couldBe.canRead()) + { + return Optional.empty(); + } + + return Optional.of(couldBe); + } + // method not allowed when submitHomework is false // bad gateway when any file is malicious // bad request when diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java index e36f6eb7..e5288835 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentController.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; +import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -22,6 +23,18 @@ public class AssignmentController private final AssessmentService service; private final AppointmentService appointmentService; + @GetMapping("/{appointment}/download/{user}") + public @NotNull ResponseEntity downloadAssignments(@PathVariable long appointment, @PathVariable long user) + { + return getAppointmentService().downloadAssignments(appointment, user); + } + + @GetMapping("/{appointment}/download/{user}/{file}") + public @NotNull ResponseEntity downloadAssignment(@PathVariable long appointment, @PathVariable long user, @PathVariable String file) + { + return getAppointmentService().downloadAssignment(appointment, user, file); + } + @PreAuthorize("hasRole('teacher')") @GetMapping("/{appointment}/status/all") public @NotNull ResponseEntity submitStatus(@PathVariable long appointment) { diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index a5a558d0..a20ed73b 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -4,12 +4,24 @@ - - -

Submitted {{ currentInsight.submitted }}

+ + Current User + + @for (insight of assignmentInsightModels; track assignmentInsightModels) + { + + {{ toIcon(insight) }} + {{ insight.name }} + + } + + + + +

Submitted {{ select.value.submitted }}

- @for (file of currentInsight.files; track file) { + @for (file of toArray(select.value); track file) { {{ file }} } diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index 687d720c..4e349f6e 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -1,20 +1,26 @@ import {Component, Input, input, InputSignal} from '@angular/core'; import {NgIf} from "@angular/common"; import {AppointmentEntryModel} from "../../../../user/courses/appointment/entry/appointment-entry-model"; -import {SelectionInput} from "../../../../common/selection-input/selection-input.component"; import {MatList, MatListItem} from "@angular/material/list"; import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatOption, MatSelect} from "@angular/material/select"; +import {MatIcon} from "@angular/material/icon"; @Component({ selector: 'app-assignment-teacher-view', standalone: true, imports: [ NgIf, - SelectionInput, + MatLabel, MatList, - MatListItem + MatFormField, + MatSelect, + MatOption, + MatListItem, + MatIcon ], templateUrl: './assignment-teacher-view.component.html', styleUrl: './assignment-teacher-view.component.scss' @@ -25,8 +31,6 @@ export class AssignmentTeacherViewComponent { private _assignmentInsightModels: readonly AssignmentInsightModel[] = []; public readonly editing: InputSignal = input(false); - private _currentInsight: AssignmentInsightModel | null = null; - public constructor(private readonly _assignmentService: AssignmentService) { } @@ -52,11 +56,21 @@ export class AssignmentTeacherViewComponent { return this.appointment!.assignment || null; } - protected valueUpdate(event: readonly AssignmentInsightModel[] | AssignmentInsightModel): void { - this._currentInsight = event as AssignmentInsightModel + protected toArray(value: any): readonly string[] + { + if(value instanceof AssignmentInsightModel) + { + return value.files; + } + return []; } - protected get currentInsight(): AssignmentInsightModel | null { - return this._currentInsight; + protected toIcon(insight: AssignmentInsightModel): 'assignment_turned_in' | 'assignment_late' | 'pending' + { + if(this.assignment?.submitUntil.getTime() && (this.assignment?.submitUntil.getTime()) < new Date().getTime()) + { + return insight.submitted ? 'assignment_turned_in' : 'assignment_late'; + } + return insight.submitted ? 'assignment_turned_in' : 'pending'; } } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts index c317b436..ccece08e 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts @@ -18,6 +18,10 @@ export class AssignmentService { private readonly _appointmentService: AppointmentService) { } + public downloadAssignment(appointment: bigint, user: bigint, file: string): void { + //TODO + } + public get nextAssignments(): readonly AssignmentModel[] { return this._appointmentService.nextAppointments.filter( (appointment: AppointmentEntryModel): boolean => !!appointment.assignment From 2cb97be3b9637936dbcea0a289745bffb68bdc0c Mon Sep 17 00:00:00 2001 From: SortyFix Date: Sun, 16 Mar 2025 17:24:11 +0100 Subject: [PATCH 08/28] Finished news card --- .../IllnessNotificationService.java | 2 +- .../src/app/dashboard/dashboard.component.html | 8 +++++--- .../src/app/dashboard/dashboard.component.ts | 18 ++++++++++++------ .../news-card/news-card.component.html | 12 ++++++++---- .../news-card/news-card.component.scss | 16 ++++++++++------ .../dashboard/news-card/news-card.component.ts | 2 +- .../src/app/settings/settings.component.ts | 6 ++---- 7 files changed, 39 insertions(+), 25 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java index 2ef6b204..58e631f5 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java @@ -65,7 +65,7 @@ public boolean excuse(@NotNull Long userId, @NotNull String reason, @NotNull Lon FileEntity fileEntity = fileService.createEntity(new FileCreateModel( "illness_notifications", - new String[] { "Management", "ADMINISTRATOR" }, + new String[] { "Management", "ADMINISTRATOR", "ROLE_administrator", "USER_CREATE" }, new String[] { "illness_notification" })); try diff --git a/EEDU-Frontend/src/app/dashboard/dashboard.component.html b/EEDU-Frontend/src/app/dashboard/dashboard.component.html index 0354e0eb..f147db74 100644 --- a/EEDU-Frontend/src/app/dashboard/dashboard.component.html +++ b/EEDU-Frontend/src/app/dashboard/dashboard.component.html @@ -9,8 +9,10 @@

My Dashboard

-
-

{{ card.title }}

- +
+
+

{{ card.title }}

+ +
diff --git a/EEDU-Frontend/src/app/dashboard/dashboard.component.ts b/EEDU-Frontend/src/app/dashboard/dashboard.component.ts index 25df273c..b21e8bf2 100644 --- a/EEDU-Frontend/src/app/dashboard/dashboard.component.ts +++ b/EEDU-Frontend/src/app/dashboard/dashboard.component.ts @@ -7,6 +7,7 @@ import {NewsCardComponent} from "./news-card/news-card.component"; import {ChatCardComponent} from "./chat-card/chat-card.component"; import {MatIcon} from "@angular/material/icon"; import {NgComponentOutlet, NgForOf} from "@angular/common"; +import {Router, RouterLink} from "@angular/router"; @Component({ selector: 'app-dashboard', @@ -14,23 +15,28 @@ import {NgComponentOutlet, NgForOf} from "@angular/common"; imports: [ MatIcon, NgForOf, - NgComponentOutlet + NgComponentOutlet, + RouterLink ], standalone: true, styleUrls: ['./dashboard.component.scss'] }) export class DashboardComponent { - constructor(public themeService: ThemeService, public userService: UserService) { + constructor(public userService: UserService, public router: Router) { } cards = [ - { title: 'Next appointments', content: 'This is the content of card 1.', component: AppointmentCardComponent }, - { title: 'Homework', content: 'This is the content of card 2.', component: AssignmentCardComponent }, - { title: 'Latest news', content: 'This is the content of card 3.', component: NewsCardComponent }, - { title: 'Latest contacts', content: 'This is the content of card 4.', component: ChatCardComponent } + { title: 'Next appointments', component: AppointmentCardComponent, route: 'timetable' }, + { title: 'Homework', component: AssignmentCardComponent, route: 'timetable'}, + { title: 'Latest news', component: NewsCardComponent, route: 'news' }, + { title: 'Latest contacts', component: ChatCardComponent, route: 'chat' } ]; public get user() { return this.userService.getUserData; } + + public navigateTo(route: string): void { + this.router.navigate([route]); + } } diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html index 786bb4a3..643ffc12 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html @@ -1,6 +1,10 @@ -
-
-

{{ x.title }}

- +
+
+
+
+

{{ x.title }}

+ +
+
diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss index cee93a7a..cf2fa830 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.scss @@ -1,7 +1,9 @@ .post-item { - width: 100%; - padding: 8px 0; - border-bottom: 1px solid var(--text-color); + padding: 12px 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + transition: background 0.3s ease-in-out; + border-radius: 6px; + &:last-child { border-bottom: none; } @@ -13,13 +15,15 @@ } .post-title { - font-size: 15px; + font-size: 16px; + font-weight: 600; margin: 0; color: var(--text-color); } .post-author { - font-size: 12px; - color: gray; + font-size: 13px; + color: var(--text-color); margin: 0; + font-style: italic; } diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts index 100693bf..ba5f77b5 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts @@ -28,7 +28,7 @@ export class NewsCardComponent implements OnInit { ngOnInit(): void { this.newsService.getPosts().subscribe(posts => { - this.postList = posts.splice(0, 5); + this.postList = posts.splice(0, 6); console.log(this.postList); }); } diff --git a/EEDU-Frontend/src/app/settings/settings.component.ts b/EEDU-Frontend/src/app/settings/settings.component.ts index 930c3eb2..750fb52c 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.ts +++ b/EEDU-Frontend/src/app/settings/settings.component.ts @@ -51,8 +51,6 @@ export class SettingsComponent implements OnInit { themeForm = new FormControl(null, Validators.required); public feedbackText = ""; - public THEME_URL: string = "api/v1/user"; - ngOnInit(): void { this.themes = this.fetchAllThemes(); this.themeForm.valueChanges.subscribe((selectedTheme) => { @@ -131,7 +129,7 @@ export class SettingsComponent implements OnInit { * @returns Observable carrying the full newly selected theme. */ public setTheme(themeId: bigint): Observable { - const url: string = `http://${environment.backendUrl}/${this.THEME_URL}/me/theme/set`; + const url: string = `${environment.backendUrl}/user/me/theme/set`; return this.http.put(url, themeId, { withCredentials: true }).pipe(map(model => { @@ -149,7 +147,7 @@ export class SettingsComponent implements OnInit { * id, name format. */ public fetchAllThemes() : Observable { - const url: string = `http://${environment.backendUrl}/${this.THEME_URL}/theme/all`; + const url: string = `${environment.backendUrl}/user/theme/all`; return this.http.get(url, {withCredentials: true}); } From 1f4fb45f7f26c7474cf2d83a9266fd47ac9614d2 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Sun, 16 Mar 2025 17:25:04 +0100 Subject: [PATCH 09/28] Removed obsolete code --- .../app/dashboard/news-card/news-card.component.html | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html index 643ffc12..f98d0d03 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html @@ -1,10 +1,8 @@ -
-
-
-
-

{{ x.title }}

- -
+
+
+
+

{{ x.title }}

+
From e16746333c52efedf152e93986b441cdecc0752d Mon Sep 17 00:00:00 2001 From: SortyFix Date: Mon, 17 Mar 2025 21:30:34 +0100 Subject: [PATCH 10/28] Enhanced user experience - Made file retrieval by file name possible in front and backend - Outsourced some code - Created fallback for news card --- .../java/de/gaz/eedu/file/FileController.java | 12 +++++++++ .../java/de/gaz/eedu/file/FileService.java | 13 ++++++++++ EEDU-Frontend/src/app/chat/chat.component.ts | 11 +++----- .../src/app/chat/chat.service.spec.ts | 16 ++++++++++++ EEDU-Frontend/src/app/chat/chat.service.ts | 25 +++++++++++++++++++ .../appointment-card.component.html | 13 +++++++++- .../appointment-card.component.ts | 9 ++++--- .../app/dashboard/dashboard.component.scss | 1 + .../news-card/news-card.component.html | 4 +++ .../news-card/news-card.component.ts | 8 +++--- EEDU-Frontend/src/app/file/file.service.ts | 4 +-- 11 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 EEDU-Frontend/src/app/chat/chat.service.spec.ts create mode 100644 EEDU-Frontend/src/app/chat/chat.service.ts diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileController.java index 95d32f00..b84bfbe9 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileController.java @@ -83,4 +83,16 @@ } throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } + + @PreAuthorize("@verificationService.isFullyAuthenticated()") @GetMapping("/get/{fileId}/{fileName}") public ResponseEntity downloadFileWithName( + @AuthenticationPrincipal Long userId, @PathVariable Long fileId, @PathVariable String fileName) throws IOException + { + Function access = file -> file.hasAccess(userService.loadEntityByIDSafe(userId)); + if (fileService.getRepository().findById(fileId).map(access).orElse(false)) + { + return fileService.loadResourceByIdAndName(fileId, fileName); + } + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + } + } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileService.java index 65fcf4c3..110d0765 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/file/FileService.java @@ -99,6 +99,19 @@ public boolean delete(long id, @NotNull Runnable deleteTask) return zipAndSend(files); } + public @NotNull ResponseEntity loadResourceByIdAndName(@NotNull Long id, @NotNull String fileName) throws IOException + { + File directory = getDirectoryFromId(id); + File[] files = directory.listFiles((dir, name) -> name.equals(fileName)); + + if (files == null || files.length == 0) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + + return sendSingle(files[0]); + } + + public ResponseEntity zipAndSend(@NotNull File[] files) { ContentDisposition contentDisposition = ContentDisposition.builder("attachment") diff --git a/EEDU-Frontend/src/app/chat/chat.component.ts b/EEDU-Frontend/src/app/chat/chat.component.ts index de070f14..f656b8ce 100644 --- a/EEDU-Frontend/src/app/chat/chat.component.ts +++ b/EEDU-Frontend/src/app/chat/chat.component.ts @@ -19,6 +19,7 @@ import { MatDrawerContainer, } from "@angular/material/sidenav"; import {environment} from "../../environment/environment"; +import {ChatService} from "./chat.service"; @Component({ selector: 'app-chat', @@ -51,7 +52,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { notificationList: bigint[] = []; messageContent!: string; - constructor(public dialog: Dialog, public websocketService: WebsocketService, public http: HttpClient, public userService: UserService, private cdr: ChangeDetectorRef) { + constructor(public dialog: Dialog, public chatService: ChatService, public websocketService: WebsocketService, public http: HttpClient, public userService: UserService, private cdr: ChangeDetectorRef) { } public ngOnInit() { @@ -68,9 +69,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { } public getAllChats() { - return this.http.get(`${environment.backendUrl}/chat/getChatList`, { - withCredentials: true - }).subscribe(models => { + this.chatService.getAllChats().subscribe((models: ChatModel[]): void => { this.chatList = models; console.log(models); }); @@ -100,9 +99,7 @@ export class ChatComponent implements OnInit, AfterViewChecked { } public getChat(chatId: number) { - this.http.post(`${environment.backendUrl}/chat/get/chat`, chatId, { - withCredentials: true - }).subscribe(model => { + this.chatService.getChat(chatId).subscribe(model => { this.currentChatHistory = model; console.log(this.currentChatHistory); }); diff --git a/EEDU-Frontend/src/app/chat/chat.service.spec.ts b/EEDU-Frontend/src/app/chat/chat.service.spec.ts new file mode 100644 index 00000000..4d8abdfc --- /dev/null +++ b/EEDU-Frontend/src/app/chat/chat.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ChatService } from './chat.service'; + +describe('ChatService', () => { + let service: ChatService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ChatService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/EEDU-Frontend/src/app/chat/chat.service.ts b/EEDU-Frontend/src/app/chat/chat.service.ts new file mode 100644 index 00000000..c85da7f3 --- /dev/null +++ b/EEDU-Frontend/src/app/chat/chat.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {ChatModel} from "./models/chat-model"; +import {environment} from "../../environment/environment"; +import {Observable} from "rxjs"; +import {MessageModel} from "./models/message-model"; + +@Injectable({ + providedIn: 'root' +}) +export class ChatService { + constructor(public http: HttpClient) { } + + public getAllChats(): Observable { + return this.http.get(`${environment.backendUrl}/chat/getChatList`, { + withCredentials: true + }); + } + + public getChat(chatId: number): Observable { + return this.http.post(`${environment.backendUrl}/chat/get/chat`, chatId, { + withCredentials: true + }); + } +} diff --git a/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.html b/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.html index e040c587..f2864c72 100644 --- a/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.html +++ b/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.html @@ -1 +1,12 @@ -

appointment-card works!

+
+
+
+

{{ x.description }}

+ +
+
+
+ +
+ No future assignments! +
diff --git a/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts b/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts index 92796f40..0ab43fd8 100644 --- a/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts @@ -1,16 +1,19 @@ -import { Component } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; import {AppointmentService} from "../../user/courses/appointment/appointment.service"; import {AppointmentEntryModel} from "../../user/courses/appointment/entry/appointment-entry-model"; +import {NgForOf, NgIf} from "@angular/common"; @Component({ selector: 'app-appointment-card', standalone: true, - imports: [], + imports: [ + NgForOf, + NgIf + ], templateUrl: './appointment-card.component.html', styleUrl: './appointment-card.component.scss' }) export class AppointmentCardComponent { - public constructor(private readonly _appointmentService: AppointmentService) {} protected get appointments(): readonly AppointmentEntryModel[] { diff --git a/EEDU-Frontend/src/app/dashboard/dashboard.component.scss b/EEDU-Frontend/src/app/dashboard/dashboard.component.scss index 80d9e1d5..12a5fbc3 100644 --- a/EEDU-Frontend/src/app/dashboard/dashboard.component.scss +++ b/EEDU-Frontend/src/app/dashboard/dashboard.component.scss @@ -72,6 +72,7 @@ body { display: flex; flex-direction: column; align-items: center; + cursor: pointer; } .card:hover { diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html index f98d0d03..fd51aa89 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.html @@ -6,3 +6,7 @@

{{ x.title }}

+ +
+ No articles yet! +
diff --git a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts index ba5f77b5..6e0d7f23 100644 --- a/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/news-card/news-card.component.ts @@ -5,8 +5,8 @@ import {NgForOf, NgIf} from "@angular/common"; import {MatCard, MatCardContent, MatCardHeader, MatCardSubtitle, MatCardTitle} from "@angular/material/card"; @Component({ - selector: 'app-news-card', - standalone: true, + selector: 'app-news-card', + standalone: true, imports: [ NgForOf, NgIf, @@ -16,8 +16,8 @@ import {MatCard, MatCardContent, MatCardHeader, MatCardSubtitle, MatCardTitle} f MatCardTitle, MatCardSubtitle ], - templateUrl: './news-card.component.html', - styleUrl: './news-card.component.scss' + templateUrl: './news-card.component.html', + styleUrl: './news-card.component.scss' }) export class NewsCardComponent implements OnInit { diff --git a/EEDU-Frontend/src/app/file/file.service.ts b/EEDU-Frontend/src/app/file/file.service.ts index 38d01e5a..fa5802ec 100644 --- a/EEDU-Frontend/src/app/file/file.service.ts +++ b/EEDU-Frontend/src/app/file/file.service.ts @@ -81,9 +81,9 @@ export class FileService { } // ------------------------------ DOWNLOAD ----------------------------------- - public async fetchFile(id: bigint, index?: number): Promise { + public async fetchFile(id: bigint, identifier?: number | string): Promise { console.log("Fetching file binaries..."); - const url: string = index == null ? `${(this.URL_PREFIX)}/get/${id}` : `${(this.URL_PREFIX)}/get/${id}/${index}` + const url: string = identifier == null ? `${(this.URL_PREFIX)}/get/${id}` : `${(this.URL_PREFIX)}/get/${id}/${identifier}` const response: Response = await fetch(url, { method: 'GET', headers: { From 65a9ce3d76da8ba758bc4054ceaaff03ad6ad1cc Mon Sep 17 00:00:00 2001 From: SortyFix Date: Mon, 17 Mar 2025 21:36:27 +0100 Subject: [PATCH 11/28] Removed imports --- EEDU-Frontend/src/app/chat/chat.component.ts | 1 - EEDU-Frontend/src/app/dashboard/dashboard.component.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/EEDU-Frontend/src/app/chat/chat.component.ts b/EEDU-Frontend/src/app/chat/chat.component.ts index f656b8ce..d575dae1 100644 --- a/EEDU-Frontend/src/app/chat/chat.component.ts +++ b/EEDU-Frontend/src/app/chat/chat.component.ts @@ -18,7 +18,6 @@ import { MatDrawer, MatDrawerContainer, } from "@angular/material/sidenav"; -import {environment} from "../../environment/environment"; import {ChatService} from "./chat.service"; @Component({ diff --git a/EEDU-Frontend/src/app/dashboard/dashboard.component.ts b/EEDU-Frontend/src/app/dashboard/dashboard.component.ts index b21e8bf2..28e347ee 100644 --- a/EEDU-Frontend/src/app/dashboard/dashboard.component.ts +++ b/EEDU-Frontend/src/app/dashboard/dashboard.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import {ThemeService} from "../theming/theme.service"; import {UserService} from "../user/user.service"; import {AppointmentCardComponent} from "./appointment-card/appointment-card.component"; import {AssignmentCardComponent} from "./assignment-card/assignment-card.component"; From c8f531b9b6a5d6a65c8bb8fa9e50d25ea9be8d45 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Mon, 17 Mar 2025 22:31:53 +0100 Subject: [PATCH 12/28] did some things --- .../appointment/AppointmentService.java | 5 +- .../assignment-student-view.component.html | 11 ++- .../assignment-student-view.component.ts | 8 ++- .../assignment-teacher-view.component.html | 47 +++++++++--- .../assignment-teacher-view.component.scss | 3 + .../assignment-teacher-view.component.ts | 33 +++------ .../event-data/event-data-dialog.component.ts | 72 +++++-------------- 7 files changed, 88 insertions(+), 91 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java index ca41b43a..3e33ad0c 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentService.java @@ -120,9 +120,8 @@ private static boolean setAssignment(@NotNull AppointmentEntryEntity entity, @Nu Function equals = (room -> !Objects.equals(updateModel.room(), room.getId())); if (entity.getRoom().map(equals).orElseGet(() -> Objects.nonNull(updateModel.room()))) { - entity.setRoom( - Objects.isNull(updateModel.room()) ? null : - roomRepository.findById(updateModel.room()).orElseThrow(entityUnknown(updateModel.room())) + entity.setRoom(Objects.isNull(updateModel.room()) ? null : + roomRepository.findById(updateModel.room()).orElseThrow(entityUnknown(updateModel.room())) ); } diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html index af098343..3bc49f08 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html @@ -6,10 +6,15 @@
{{ insight.submitted }} - {{ insight.files }} + + + description + {{ file }} + +
-
+
Deadline: @@ -17,7 +22,7 @@ -
+
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts index b4025131..ec668052 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts @@ -4,11 +4,12 @@ import {FileUploadComponent} from "../../../../common/file-upload/file-upload.co import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {MatButton} from "@angular/material/button"; import {MatList, MatListItem, MatListItemLine, MatListItemTitle} from "@angular/material/list"; -import {NgIf} from "@angular/common"; +import {NgForOf, NgIf} from "@angular/common"; import {MatIcon} from "@angular/material/icon"; import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; +import {MatTree, MatTreeNode} from "@angular/material/tree"; @Component({ selector: 'app-assignment-student-view', @@ -21,7 +22,10 @@ import {AssignmentModel} from "../../../../user/courses/appointment/entry/assign MatListItemTitle, MatListItem, NgIf, - MatIcon + MatIcon, + MatTree, + MatTreeNode, + NgForOf ], templateUrl: './assignment-student-view.component.html', styleUrl: './assignment-student-view.component.scss' diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index a20ed73b..8f0a3437 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -17,13 +17,44 @@ - -

Submitted {{ select.value.submitted }}

+
+ +

Submitted {{ select.value.submitted }}

+
+

+ {{ assignment?.description }} +

+ +
    + @for (file of toArray(select.value); track file) + { +
  • {{file}}
  • + } +
+
+ + +
+ +
+ +
+ + Grade + + + + Feedback + + + +
- - @for (file of toArray(select.value); track file) { - {{ file }} - } - -
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.scss b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.scss index e69de29b..ce3ff15c 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.scss +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.scss @@ -0,0 +1,3 @@ +.mat-list .mat-list-item { + height: 50px; /* default is 72px */ +} diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index 4e349f6e..b0735ac1 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -1,27 +1,20 @@ import {Component, Input, input, InputSignal} from '@angular/core'; import {NgIf} from "@angular/common"; import {AppointmentEntryModel} from "../../../../user/courses/appointment/entry/appointment-entry-model"; -import {MatList, MatListItem} from "@angular/material/list"; import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; import {MatFormField, MatLabel} from "@angular/material/form-field"; import {MatOption, MatSelect} from "@angular/material/select"; import {MatIcon} from "@angular/material/icon"; +import {MatButton} from "@angular/material/button"; +import {MatInput} from "@angular/material/input"; +import {MatDivider} from "@angular/material/divider"; @Component({ selector: 'app-assignment-teacher-view', standalone: true, - imports: [ - NgIf, - MatLabel, - MatList, - MatFormField, - MatSelect, - MatOption, - MatListItem, - MatIcon - ], + imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, MatDivider,], templateUrl: './assignment-teacher-view.component.html', styleUrl: './assignment-teacher-view.component.scss' }) @@ -34,9 +27,11 @@ export class AssignmentTeacherViewComponent { public constructor(private readonly _assignmentService: AssignmentService) { } - @Input() - public set appointment(appointment: AppointmentEntryModel) - { + public get appointment(): AppointmentEntryModel | null { + return this._appointment; + } + + @Input() public set appointment(appointment: AppointmentEntryModel) { this._appointment = appointment; this._assignmentService.fetchInsights(appointment.id).subscribe((response: AssignmentInsightModel[]): void => { @@ -44,10 +39,6 @@ export class AssignmentTeacherViewComponent { }) } - public get appointment(): AppointmentEntryModel | null { - return this._appointment; - } - protected get assignmentInsightModels(): readonly AssignmentInsightModel[] { return this._assignmentInsightModels; } @@ -65,10 +56,8 @@ export class AssignmentTeacherViewComponent { return []; } - protected toIcon(insight: AssignmentInsightModel): 'assignment_turned_in' | 'assignment_late' | 'pending' - { - if(this.assignment?.submitUntil.getTime() && (this.assignment?.submitUntil.getTime()) < new Date().getTime()) - { + protected toIcon(insight: AssignmentInsightModel): 'assignment_turned_in' | 'assignment_late' | 'pending' { + if (this.assignment?.submitUntil.getTime() && (this.assignment?.submitUntil.getTime()) < new Date().getTime()) { return insight.submitted ? 'assignment_turned_in' : 'assignment_late'; } return insight.submitted ? 'assignment_turned_in' : 'pending'; diff --git a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts index 096e4c37..99fa8567 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts @@ -18,9 +18,7 @@ import {EventTileContentComponent} from "./event-tile-content/event-tile-content import {RoomTabComponent} from "./room-tab/room-tab.component"; import {SelectionInput} from "../../common/selection-input/selection-input.component"; import { - MAT_DIALOG_DATA, - MatDialogActions, - MatDialogContent, + MAT_DIALOG_DATA, MatDialogActions, MatDialogContent, } from "@angular/material/dialog"; import {MatSnackBar} from "@angular/material/snack-bar"; import {CourseService} from "../../user/courses/course.service"; @@ -31,24 +29,7 @@ import {GenericAssignmentCreateModel} from "../../user/courses/appointment/entry @Component({ standalone: true, - imports: [ - ReactiveFormsModule, - NgIf, - MatHint, - MatGridList, - MatGridTile, - MatFormField, - MatInput, - MatButton, - AssignmentTabComponent, - DateTimePickerComponent, - EventTileContentComponent, - RoomTabComponent, - SelectionInput, - MatDialogContent, - MatDialogActions, - GeneralCardComponent - ], + imports: [ReactiveFormsModule, NgIf, MatHint, MatGridList, MatGridTile, MatFormField, MatInput, MatButton, AssignmentTabComponent, DateTimePickerComponent, EventTileContentComponent, RoomTabComponent, SelectionInput, MatDialogContent, MatDialogActions, GeneralCardComponent], templateUrl: './event-data-dialog.component.html', styleUrl: './event-data-dialog.component.scss' }) @@ -58,14 +39,10 @@ export class EventDataDialogComponent { private readonly _form: FormGroup; private readonly _rooms: RoomModel[] = []; - public constructor( - formBuilder: FormBuilder, - roomService: RoomService, - @Inject(MAT_DIALOG_DATA) data: { title: string, appointment: AppointmentEntryModel }, - private readonly _appointmentService: AppointmentService, - private readonly _courseService: CourseService, - private readonly _matSnackBar: MatSnackBar, - ) { + public constructor(formBuilder: FormBuilder, roomService: RoomService, @Inject(MAT_DIALOG_DATA) data: { + title: string, + appointment: AppointmentEntryModel + }, private readonly _appointmentService: AppointmentService, private readonly _courseService: CourseService, private readonly _matSnackBar: MatSnackBar,) { roomService.value$.subscribe((rooms: RoomModel[]): void => { this._rooms.length = 0; this._rooms.push(...rooms); @@ -76,20 +53,12 @@ export class EventDataDialogComponent { console.log(data.appointment) this._form = formBuilder.group({ - description: [null], - room: [null], - assignment: [null], - publish: [null], - submitUntil: [null] + description: [null], room: [null], assignment: [null], publish: [null], submitUntil: [null] }); this.appointment = data.appointment; } - protected get course(): CourseModel { - return this._courseService.findCourseLazily(this.event.course) as CourseModel; // expect the course to exist - } - @Input() public set appointment(value: AppointmentEntryModel) { this._event = value; @@ -107,17 +76,19 @@ export class EventDataDialogComponent { // Default values when creating a new assignment this.form.get('publish')?.setValue(new Date()); - // TODO also include frequent appointments const appointments: readonly AppointmentEntryModel[] = this._appointmentService.nextAppointments; let start: Date = new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 7)); - if(appointments.length !== 0) - { + if (appointments.length !== 0) { start = appointments[0].start; } this.form.get('submitUntil')?.setValue(start); } + protected get course(): CourseModel { + return this._courseService.findCourseLazily(this.event.course) as CourseModel; // expect the course to exist + } + protected get title(): string { return this._title; } @@ -140,18 +111,10 @@ export class EventDataDialogComponent { return this._form; } - protected get assignmentModel(): AssignmentModel { - return AssignmentModel.fromObject({ - description: this.form.get('assignment')?.value, - publish: this.form.get('publish')?.value, - submitUntil: this.form.get('submitUntil')?.value - }); - } - private get assignmentCreateModel(): GenericAssignmentCreateModel { + // if the 'assignment' field is undefined, these field below will be ignored return { description: this.form.get("assignment")?.value, - // if the 'assignment' field is undefined, these will be ignored publish: (this.form.get('publish')?.value as Date), submitUntil: (this.form.get('submitUntil')?.value as Date) } @@ -178,14 +141,17 @@ export class EventDataDialogComponent { } protected onSubmit(): void { + if (!this.anyEdit) { + return; + } + this._appointmentService.updateAppointment(this.event.id, AppointmentUpdateModel.fromObject({ description: this.form.get('description')?.value, - room: this.form.get('room')?.value, - // undefined means not updating !!! + room: this.form.get('room')?.value, // undefined means not updating !!! assignment: this.hasEdited('assignment') ? this.assignmentCreateModel : undefined })).subscribe((response: AppointmentEntryModel): void => { this.appointment = response; - this._matSnackBar.open("The changes have been saved!", "", { duration: 2000 }); + this._matSnackBar.open("The changes have been saved!", "", {duration: 2000}); }); } } From d734c4c948c3f765dc39ccb5d35c2dbdd0908263 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Mon, 17 Mar 2025 23:06:57 +0100 Subject: [PATCH 13/28] Renamed default themes --- EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java b/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java index 6e2b74fe..804813ca 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java @@ -118,10 +118,10 @@ private void createDefaultGroup() private @NotNull ThemeEntity createDefaultTheme() { - ThemeCreateModel defaultDark = new ThemeCreateModel("defaultDark", + ThemeCreateModel defaultDark = new ThemeCreateModel("Dark", new byte[]{Byte.MIN_VALUE + 5, Byte.MIN_VALUE + 5, Byte.MIN_VALUE + 5}, new byte[]{Byte.MIN_VALUE + 10, Byte.MIN_VALUE + 10, Byte.MIN_VALUE + 10}); - ThemeCreateModel defaultLight = new ThemeCreateModel("defaultLight", + ThemeCreateModel defaultLight = new ThemeCreateModel("Light", new byte[]{Byte.MIN_VALUE + 255, Byte.MIN_VALUE + 255, Byte.MIN_VALUE + 255}, new byte[]{Byte.MIN_VALUE + 235, Byte.MIN_VALUE + 235, Byte.MIN_VALUE + 235}); From acb17af5ad04d6ac63c2f50a9320d0eef21dbbe4 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Mon, 17 Mar 2025 23:22:13 +0100 Subject: [PATCH 14/28] Fixed some issues --- .../appointment-card.component.ts | 26 ++++--- .../assignment-card.component.ts | 12 +++- .../app/management/management.component.ts | 23 ------ .../assignment-teacher-view.component.html | 72 +++++++++---------- .../event-data/event-data-dialog.component.ts | 18 ++--- .../appointment/appointment.service.ts | 7 +- .../create-appointment.component.ts | 2 +- .../entry/assignment/assignment.service.ts | 11 +-- .../src/app/user/courses/course.service.ts | 4 ++ 9 files changed, 84 insertions(+), 91 deletions(-) diff --git a/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts b/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts index 0ab43fd8..b706f920 100644 --- a/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/appointment-card/appointment-card.component.ts @@ -1,22 +1,26 @@ -import {Component, OnInit} from '@angular/core'; +import {Component} from '@angular/core'; import {AppointmentService} from "../../user/courses/appointment/appointment.service"; import {AppointmentEntryModel} from "../../user/courses/appointment/entry/appointment-entry-model"; import {NgForOf, NgIf} from "@angular/common"; @Component({ - selector: 'app-appointment-card', - standalone: true, - imports: [ - NgForOf, - NgIf - ], - templateUrl: './appointment-card.component.html', - styleUrl: './appointment-card.component.scss' + selector: 'app-appointment-card', + standalone: true, + imports: [NgForOf, NgIf], + templateUrl: './appointment-card.component.html', + styleUrl: './appointment-card.component.scss' }) export class AppointmentCardComponent { - public constructor(private readonly _appointmentService: AppointmentService) {} + + public constructor(appointmentService: AppointmentService) { + appointmentService.nextAppointments.subscribe((appointments: readonly AppointmentEntryModel[]): void => { + this._appointments = appointments; + }); + } + + private _appointments: readonly AppointmentEntryModel[] = []; protected get appointments(): readonly AppointmentEntryModel[] { - return this._appointmentService.nextAppointments.slice(0, 5); + return this._appointments; } } diff --git a/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts b/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts index 427a7842..213d769d 100644 --- a/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts +++ b/EEDU-Frontend/src/app/dashboard/assignment-card/assignment-card.component.ts @@ -11,9 +11,17 @@ import {AssignmentModel} from "../../user/courses/appointment/entry/assignment/a }) export class AssignmentCardComponent { - public constructor(private readonly _assignmentService: AssignmentService) {} + private _assignments: readonly AssignmentModel[] = []; + + public constructor(assignmentService: AssignmentService) + { + assignmentService.nextAssignments.subscribe((assignment: readonly AssignmentModel[]): void => + { + this._assignments = assignment; + }) + } protected get assignments(): readonly AssignmentModel[] { - return this._assignmentService.nextAssignments.slice(0, 5); + return this._assignments.slice(0, 5); } } diff --git a/EEDU-Frontend/src/app/management/management.component.ts b/EEDU-Frontend/src/app/management/management.component.ts index ada4098d..af7926d0 100644 --- a/EEDU-Frontend/src/app/management/management.component.ts +++ b/EEDU-Frontend/src/app/management/management.component.ts @@ -17,29 +17,6 @@ import {MatButton} from "@angular/material/button"; import {MatIcon} from "@angular/material/icon"; import {ManagementService} from "./management.service"; import {IllnessNotificationStatus} from "../illness-notification/illness-notification-status"; -import {ListItemInfo} from "../common/abstract-list/abstract-list.component"; -import {CourseModel} from "../user/courses/course-model"; -import {ClassRoomModel} from "../user/courses/classroom/class-room-model"; -import {EntityListComponent} from "../entity/entity-list/entity-list.component"; -import {SubjectService} from "../user/courses/subject/subject.service"; -import {RoomService} from "../user/courses/room/room.service"; -import {ClassRoomService} from "../user/courses/classroom/class-room.service"; -import {CourseService} from "../user/courses/course.service"; -import {EntityService} from "../entity/entity-service"; -import {ListItemContent} from "../common/abstract-list/list-item-content"; -import {CourseListItemComponent} from "../user/courses/course-dialogs/course-list-item/course-list-item.component"; -import {ComponentType} from "@angular/cdk/overlay"; -import {CreateCourseComponent} from "../user/courses/course-dialogs/course-dialogs.component"; -import {DeleteDialogComponent} from "../common/delete-dialog/delete-dialog.component"; -import { - CreateSubjectComponent, - DeleteSubjectComponent -} from "../user/courses/subject/subject-dialogs/subject-dialogs.component"; -import { - CreateClassRoomComponent, - DeleteClassRoomComponent -} from "../user/courses/classroom/class-room-dialogs/class-room-dialogs.component"; -import {CreateRoomComponent, DeleteRoomComponent} from "../user/courses/room/room-dialogs/room-list.component"; import {MatDialog} from "@angular/material/dialog"; import {ManagementCourseSectionComponent} from "./management-course-section/management-course-section.component"; import {ManagementUserSectionComponent} from "./management-user-section/management-user-section.component"; diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index 8f0a3437..df6dbe67 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -7,8 +7,7 @@ Current User - @for (insight of assignmentInsightModels; track assignmentInsightModels) - { + @for (insight of assignmentInsightModels; track assignmentInsightModels) { {{ toIcon(insight) }} {{ insight.name }} @@ -17,44 +16,37 @@ -
- -

Submitted {{ select.value.submitted }}

-
-

- {{ assignment?.description }} -

- -
    - @for (file of toArray(select.value); track file) - { -
  • {{file}}
  • - } -
-
+ +
+

+ {{ assignment?.description }} +

+ +
    + @for (file of toArray(select.value); track file) { +
  • {{ file }}
  • + } +
+
+ +
+ + Grade + + + + Feedback + + +
+
-
- -
- -
- - Grade - - - - Feedback - - - -
- diff --git a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts index 99fa8567..bd34d671 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.ts @@ -75,14 +75,16 @@ export class EventDataDialogComponent { // Default values when creating a new assignment this.form.get('publish')?.setValue(new Date()); - - const appointments: readonly AppointmentEntryModel[] = this._appointmentService.nextAppointments; - let start: Date = new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 7)); - if (appointments.length !== 0) { - start = appointments[0].start; - } - - this.form.get('submitUntil')?.setValue(start); + this.form.get('submitUntil')?.setValue(new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 7))); + + this._appointmentService.nextAppointments.subscribe((appointments: readonly AppointmentEntryModel[]): void => + { + if(appointments.length === 0) + { + return; + } + this.form.get('submitUntil')?.setValue(appointments[0].start); + }) } protected get course(): CourseModel { diff --git a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts index 92c0d698..e2156bd7 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts @@ -21,8 +21,11 @@ export class AppointmentService { constructor(private readonly _http: HttpClient, private readonly _courseService: CourseService) { } - public get nextAppointments(): readonly AppointmentEntryModel[] { - return this.courseService.value.flatMap((course: CourseModel): readonly AppointmentEntryModel[] => course.appointmentEntries).filter((appointment: AppointmentEntryModel): boolean => appointment.start > new Date()).sort((a: AppointmentEntryModel, b: AppointmentEntryModel): number => a.start.getTime() - b.start.getTime()); + public get nextAppointments(): Observable { + const currentDate: Date = new Date(); + return this.courseService.ownCourses$.pipe(map((courses: CourseModel[]): AppointmentEntryModel[] => { + return courses.flatMap((course: CourseModel): readonly AppointmentEntryModel[] => course.appointmentEntries).filter((appointment: AppointmentEntryModel): boolean => appointment.start > currentDate).sort((a: AppointmentEntryModel, b: AppointmentEntryModel): number => a.start.getTime() - b.start.getTime()) + })); } protected get http(): HttpClient { diff --git a/EEDU-Frontend/src/app/user/courses/appointment/create-appointment/create-appointment.component.ts b/EEDU-Frontend/src/app/user/courses/appointment/create-appointment/create-appointment.component.ts index 45f53548..2658867b 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/create-appointment/create-appointment.component.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/create-appointment/create-appointment.component.ts @@ -80,7 +80,7 @@ export class CreateAppointmentComponent { formBuilder: FormBuilder ) { - courseService.value$.subscribe((value: CourseModel[]): any => this._courses = value); + courseService.ownCourses$.subscribe((value: CourseModel[]): any => this._courses = value); this._form = formBuilder.group({ course: [undefined, Validators.required], selected: [0, Validators.required], diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts index ccece08e..7b01274c 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts @@ -7,6 +7,7 @@ import {AssignmentModel} from "./assignment-model"; import {AppointmentService} from "../../appointment.service"; import {AppointmentEntryModel} from "../appointment-entry-model"; import {environment} from "../../../../../../environment/environment"; +import {AssessmentModel} from "./assessment/assessment-model"; @Injectable({ providedIn: 'root' @@ -22,10 +23,12 @@ export class AssignmentService { //TODO } - public get nextAssignments(): readonly AssignmentModel[] { - return this._appointmentService.nextAppointments.filter( - (appointment: AppointmentEntryModel): boolean => !!appointment.assignment - ).map((appointment: AppointmentEntryModel): AssignmentModel => appointment.assignment); + public get nextAssignments(): Observable { + return this._appointmentService.nextAppointments.pipe(map((response: readonly AppointmentEntryModel[]): readonly AssignmentModel[] => { + return response.filter( + (appointment: AppointmentEntryModel): boolean => !!appointment.assignment + ).map((appointment: AppointmentEntryModel): AssignmentModel => appointment.assignment); + })); } public fetchInsights(appointment: bigint): Observable { diff --git a/EEDU-Frontend/src/app/user/courses/course.service.ts b/EEDU-Frontend/src/app/user/courses/course.service.ts index 676a982f..40844e14 100644 --- a/EEDU-Frontend/src/app/user/courses/course.service.ts +++ b/EEDU-Frontend/src/app/user/courses/course.service.ts @@ -47,6 +47,10 @@ export class CourseService extends EntityService { if (!this.fetchedOwn) { this.fetchOwnCourses.subscribe(); From 36f9cb17bd5d907d9a94335a6d58d6875bf8f4fe Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Tue, 18 Mar 2025 00:22:15 +0100 Subject: [PATCH 15/28] Added possibility to assess --- .../assignment-teacher-view.component.html | 66 +++++++++++-------- .../assignment-teacher-view.component.ts | 34 +++++++++- .../event-data-dialog.component.html | 2 +- .../entry/appointment-entry-model.ts | 18 ++++- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index df6dbe67..0751276a 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -4,6 +4,10 @@ +

+ {{ assignment?.description }} +

+ Current User @@ -17,34 +21,42 @@ -
-

- {{ assignment?.description }} -

- -
    - @for (file of toArray(select.value); track file) { -
  • {{ file }}
  • - } -
-
-
- - Grade - - - - Feedback - - + +
+ + +
    + @for (file of toArray(select.value); track file) { +
  • {{ file }}
  • + } +
+ + Feedback + + +
+
+ + Give Grade + +
+ + + + {{ slider.value }} +
+ +
+ + +
+ +
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index b0735ac1..4f032745 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -9,12 +9,16 @@ import {MatOption, MatSelect} from "@angular/material/select"; import {MatIcon} from "@angular/material/icon"; import {MatButton} from "@angular/material/button"; import {MatInput} from "@angular/material/input"; -import {MatDivider} from "@angular/material/divider"; +import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; +import {AssessmentService} from "../../../../user/courses/appointment/entry/assignment/assessment/assessment.service"; +import {UserService} from "../../../../user/user.service"; +import {MatSlider, MatSliderThumb} from "@angular/material/slider"; +import {MatCheckbox} from "@angular/material/checkbox"; @Component({ selector: 'app-assignment-teacher-view', standalone: true, - imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, MatDivider,], + imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule, MatSlider, MatSliderThumb, MatCheckbox,], templateUrl: './assignment-teacher-view.component.html', styleUrl: './assignment-teacher-view.component.scss' }) @@ -24,7 +28,31 @@ export class AssignmentTeacherViewComponent { private _assignmentInsightModels: readonly AssignmentInsightModel[] = []; public readonly editing: InputSignal = input(false); - public constructor(private readonly _assignmentService: AssignmentService) { + private readonly _assessForm: FormGroup; + + public constructor( + private readonly _assignmentService: AssignmentService, + private readonly _assessmentService: AssessmentService, + private readonly _userService: UserService, + formBuilder: FormBuilder) { + this._assessForm = formBuilder.group({ + grade: [null], + feedback: [null] + }) + } + + protected get assessForm(): FormGroup { + return this._assessForm; + } + + protected onAssess(): void + { + this._assessmentService.assess([{ + appointment: Number(this.appointment?.id), + user: Number(this._userService.getUserData.id), + feedback: this.assessForm.get('feedback')?.value, + grade: this.assessForm.get('grade')?.value + }]).subscribe(); } public get appointment(): AppointmentEntryModel | null { diff --git a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html index b91425b9..85639c28 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html @@ -2,7 +2,7 @@
- + diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts index ccf8770b..547b3fc4 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/appointment-entry-model.ts @@ -72,6 +72,20 @@ export class AppointmentEntryModel { } public asEvent(name: string): CalendarEvent { + + const currentDate: Date = new Date(); + let color: string = '#1f3eda'; + + if(this.start < currentDate) { + color = '#888'; + } + + if(this.assignment) + { + const tomorrow: Date = new Date(currentDate.getTime() + 1000 * 60 * 60 * 24); + color = tomorrow > this.assignment.submitUntil ? '#d00' : '#da5410' + } + return { id: Number(this.id), title: name, @@ -82,8 +96,8 @@ export class AppointmentEntryModel { afterEnd: false }, color: { - primary: '#f00', - secondary: '#0f0', + primary: color, + secondary: '#fff', }, draggable: false, meta: { From a03f30505f4137c184f5d6ddb307be4d97b8b890 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Tue, 18 Mar 2025 19:07:43 +0100 Subject: [PATCH 16/28] Removed BS --- EEDU-Frontend/src/app/theming/theme.service.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/EEDU-Frontend/src/app/theming/theme.service.ts b/EEDU-Frontend/src/app/theming/theme.service.ts index 333cbf07..7dd9c0c3 100644 --- a/EEDU-Frontend/src/app/theming/theme.service.ts +++ b/EEDU-Frontend/src/app/theming/theme.service.ts @@ -5,11 +5,7 @@ import {ThemeModel} from "./theme-model"; @Injectable({ providedIn: 'root' }) -/** - * This ThemeService provides methods to retrieve theme-related information - * for UI elements. Apart from getter methods, this service also includes text color - * logic based on the luminance of the given background for better readability. - */ + export class ThemeService { constructor(public userService: UserService) { } From c7e93b782615eb990845f0a32fdb6a070870b744 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Wed, 19 Mar 2025 19:46:27 +0100 Subject: [PATCH 17/28] Removed grades. I am not going to deal with this --- .../assessment/AssessmentController.java | 14 --------- .../assessment/AssessmentEntity.java | 4 +-- .../assessment/AssessmentService.java | 14 --------- .../model/AssessmentCreateModel.java | 3 +- .../assessment/model/AssessmentModel.java | 2 +- .../src/app/entity/entity-service.ts | 12 ++++---- .../assignment-teacher-view.component.html | 22 ++------------ .../assignment-teacher-view.component.ts | 6 +--- .../authentication/authentication.service.ts | 2 +- .../appointment/appointment.service.ts | 30 +++++++++++++++++-- .../assessment/assessment-create-model.ts | 7 ----- .../assignment/assessment/assessment-model.ts | 7 ----- .../assessment/assessment.service.ts | 6 ---- .../src/app/user/courses/course.service.ts | 14 +++++++-- 14 files changed, 52 insertions(+), 91 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java index 0af94de8..cd786b9a 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentController.java @@ -34,20 +34,6 @@ public class AssessmentController extends EntityController setGrade(@PathVariable long assessment, @PathVariable float grade) - { - return ResponseEntity.ok(getService().setGrade(assessment, grade)); - } - - @PreAuthorize("hasRole('teacher')") - @PutMapping("/{assessment}/unset/grade") - public @NotNull ResponseEntity unsetGrade(@PathVariable long assessment) - { - return ResponseEntity.ok(getService().setGrade(assessment, null)); - } - @PreAuthorize("hasRole('teacher')") @Override @PostMapping("/create") public @NotNull ResponseEntity create(@NotNull @RequestBody AssessmentCreateModel[] model) throws CreationException diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java index 8fe239f5..6a96e6d7 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentEntity.java @@ -27,8 +27,6 @@ public class AssessmentEntity implements EntityModelRelation +public record AssessmentCreateModel(long appointment, long user, @Nullable String feedback) implements CreationModel { @Override public @NotNull AssessmentEntity toEntity(@NotNull AssessmentEntity entity) { - entity.setGrade(grade()); entity.setFeedback(feedback()); return entity; } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java index 688a47bb..080e39b5 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/model/AssessmentModel.java @@ -4,4 +4,4 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public record AssessmentModel(@NotNull Long id, float grade, @Nullable String feedback) implements EntityModel {} +public record AssessmentModel(@NotNull Long id, @Nullable String feedback) implements EntityModel {} diff --git a/EEDU-Frontend/src/app/entity/entity-service.ts b/EEDU-Frontend/src/app/entity/entity-service.ts index dbcaa216..c3fce23d 100644 --- a/EEDU-Frontend/src/app/entity/entity-service.ts +++ b/EEDU-Frontend/src/app/entity/entity-service.ts @@ -42,14 +42,14 @@ export abstract class EntityService { } public get value(): T[] { - return this.value$.value; + return this._subject.value; } - public get value$(): BehaviorSubject { + public get value$(): Observable { if (!this.fetched) { this.fetchAll.subscribe(); } - return this._subject; + return this._subject.asObservable(); } public get fetchAll(): Observable { @@ -101,7 +101,7 @@ export abstract class EntityService { } public update(): void { - this.value$.next([...this.value]); + this._subject.next([...this.value]); } protected toPackets(models: C[]): any[] { @@ -114,11 +114,11 @@ export abstract class EntityService { }); } - protected pushCreated(response: T[]): void { + protected pushCreated(response: readonly T[]): void { this._subject.next([...this.value, ...response]); } protected postDelete(id: P[]): void { - this.value$.next(this.value.filter(((value: T): boolean => !id.includes(value.id)))); + this._subject.next(this.value.filter(((value: T): boolean => !id.includes(value.id)))); } } diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index 0751276a..5ad5167b 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -25,12 +25,12 @@
-
    @for (file of toArray(select.value); track file) {
  • {{ file }}
  • }
+ Feedback -
-
- - Give Grade - -
- - - - {{ slider.value }} -
- -
- - -
- +
- - diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index 4f032745..50afadfd 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -12,13 +12,11 @@ import {MatInput} from "@angular/material/input"; import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; import {AssessmentService} from "../../../../user/courses/appointment/entry/assignment/assessment/assessment.service"; import {UserService} from "../../../../user/user.service"; -import {MatSlider, MatSliderThumb} from "@angular/material/slider"; -import {MatCheckbox} from "@angular/material/checkbox"; @Component({ selector: 'app-assignment-teacher-view', standalone: true, - imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule, MatSlider, MatSliderThumb, MatCheckbox,], + imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule], templateUrl: './assignment-teacher-view.component.html', styleUrl: './assignment-teacher-view.component.scss' }) @@ -36,7 +34,6 @@ export class AssignmentTeacherViewComponent { private readonly _userService: UserService, formBuilder: FormBuilder) { this._assessForm = formBuilder.group({ - grade: [null], feedback: [null] }) } @@ -51,7 +48,6 @@ export class AssignmentTeacherViewComponent { appointment: Number(this.appointment?.id), user: Number(this._userService.getUserData.id), feedback: this.assessForm.get('feedback')?.value, - grade: this.assessForm.get('grade')?.value }]).subscribe(); } diff --git a/EEDU-Frontend/src/app/user/authentication/authentication.service.ts b/EEDU-Frontend/src/app/user/authentication/authentication.service.ts index d70dac30..7664ef31 100644 --- a/EEDU-Frontend/src/app/user/authentication/authentication.service.ts +++ b/EEDU-Frontend/src/app/user/authentication/authentication.service.ts @@ -64,7 +64,7 @@ export class AuthenticationService { const url = `${this.BACKEND_URL}/user/login` + (advanced ? "/advanced" : ""); // send credential in case of advanced login return this.http.post(url, data, { - responseType: "text" as "json", withCredentials: true + responseType: 'text' as 'json', withCredentials: true }).pipe(map((token: string): void => {this._loginData = new LoginData(data.loginName, token)})); } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts index e2156bd7..6186e812 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts @@ -11,6 +11,7 @@ import { import {FrequentAppointmentModel} from "./frequent/frequent-appointment-model"; import {AppointmentUpdateModel} from "./entry/appointment-update-model"; import {CourseModel} from "../course-model"; +import {UserService} from "../../user.service"; @Injectable({ providedIn: 'root' @@ -19,7 +20,10 @@ export class AppointmentService { private readonly BACKEND_URL: string = `${environment.backendUrl}/course/appointment`; - constructor(private readonly _http: HttpClient, private readonly _courseService: CourseService) { } + constructor( + private readonly _http: HttpClient, + private readonly _courseService: CourseService, + private readonly _userService: UserService) { } public get nextAppointments(): Observable { const currentDate: Date = new Date(); @@ -92,7 +96,12 @@ export class AppointmentService { private pushAppointment(objects: AppointmentEntryModel[]): void { for (const appointment of objects) { - this.courseService.findCourseLazily(appointment.course)?.attachAppointment(appointment); + if(this.hasFetchCoursePrivilege) + { + this.courseService.findCourseLazily(appointment.course)?.attachAppointment(appointment); + } + + this.courseService.findOwnCourseLazily(appointment.course)?.attachAppointment(appointment); } this.courseService.update(); @@ -100,9 +109,24 @@ export class AppointmentService { private pushFrequent(course: bigint, objects: FrequentAppointmentModel[]) { for (const appointment of objects) { - this.courseService.findCourseLazily(course)?.attachFrequentAppointment(appointment); + if(this.hasFetchCoursePrivilege) + { + this.courseService.findCourseLazily(course)?.attachFrequentAppointment(appointment); + } + + this.courseService.findOwnCourseLazily(course)?.attachFrequentAppointment(appointment); } this.courseService.update(); } + + private get hasFetchCoursePrivilege(): boolean + { + const privilege: string | null = this._courseService.privileges.fetchPrivilege; + if(!privilege) + { + return true; + } + return this._userService.getUserData.hasPrivilege(privilege) + } } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts index 74f7356f..5fd7ff4c 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts @@ -2,7 +2,6 @@ export interface GenericAssessmentCreateModel { appointment: number; user: number; - grade: number; feedback?: string; } @@ -11,7 +10,6 @@ export class AssessmentCreateModel public constructor( private readonly _appointment: number, private readonly _user: number, - private readonly _grade: number, private readonly _feedback: string | null, ) {} @@ -19,7 +17,6 @@ export class AssessmentCreateModel return new AssessmentCreateModel( obj.appointment, obj.user, - obj.grade, obj.feedback || null, ) } @@ -32,10 +29,6 @@ export class AssessmentCreateModel return this._user; } - public get grade(): number { - return this._grade; - } - public get feedback(): string | null { return this._feedback; } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts index 5c2ca88b..a108b78e 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-model.ts @@ -1,7 +1,6 @@ export interface GenericAssessment { id: bigint; - grade: number; feedback?: string } @@ -11,7 +10,6 @@ export class AssessmentModel { public constructor( private _id: bigint, - private _grade: number, private _feedback: string | null ) {} @@ -19,7 +17,6 @@ export class AssessmentModel { { return new AssessmentModel( obj.id, - obj.grade, obj.feedback || null, ) } @@ -38,10 +35,6 @@ export class AssessmentModel { return this.id & AssessmentModel.BIT_MASK; } - public get grade(): number { - return this._grade; - } - public get feedback(): string | null { return this._feedback; } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts index 19ff6dcf..8c50ea32 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts @@ -44,12 +44,6 @@ export class AssessmentService { return this.http.put(url, { withCredentials: true }).pipe(this.translateAndPush); } - public updateGrade(id: bigint, grade: number): Observable - { - const url: string = `${this.BACKEND_URL}/${id}/set/grade/${grade}`; - return this.http.put(url, { withCredentials: true }).pipe(this.translateAndPush); - } - private get translateAndPush(): OperatorFunction { return map((response: GenericAssessment): AssessmentModel => diff --git a/EEDU-Frontend/src/app/user/courses/course.service.ts b/EEDU-Frontend/src/app/user/courses/course.service.ts index 40844e14..61ba77bc 100644 --- a/EEDU-Frontend/src/app/user/courses/course.service.ts +++ b/EEDU-Frontend/src/app/user/courses/course.service.ts @@ -105,6 +105,10 @@ export class CourseService extends EntityService course.id === id) || null; } - protected override pushCreated(response: CourseModel[]): void { + protected override pushCreated(response: readonly CourseModel[]): void { super.pushCreated(response); this._ownCourses.next([...this._ownCourses.value, ...response]); } @@ -129,4 +133,10 @@ export class CourseService extends EntityService !id.includes(value.id))); } + + + override update(): void { + super.update(); + this._ownCourses.next([...this.ownCourses]) + } } From f3d9cba51d873eacee87e1d1d80073a5da5446ed Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Wed, 19 Mar 2025 23:19:26 +0100 Subject: [PATCH 18/28] Implemented some more assessment stuff --- .../entry/AppointmentEntryEntity.java | 10 +-- .../assignment/AssignmentInsightModel.java | 3 +- .../assessment/AssessmentRepository.java | 10 ++- EEDU-Backend/src/main/resources/schema.sql | 1 - .../general-error-box.component.html | 2 +- .../general-error-box.component.scss | 5 ++ .../general-error-box.component.ts | 1 + .../calendar-controls.component.ts | 8 +-- .../assignment-student-view.component.html | 10 +-- .../assignment-student-view.component.ts | 6 +- .../assignment-teacher-view.component.html | 44 ++++++++---- .../assignment-teacher-view.component.ts | 68 +++++++++++++------ .../event-data-dialog.component.html | 2 +- .../assessment/assessment-create-model.ts | 22 +++++- .../assessment/assessment.service.ts | 10 ++- .../assignment/assignment-insight-model.ts | 33 +++++++-- .../entry/assignment/assignment.service.ts | 5 +- 17 files changed, 164 insertions(+), 76 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java index 091760b3..06d7fc78 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/AppointmentEntryEntity.java @@ -16,6 +16,7 @@ import de.gaz.eedu.entity.model.EntityModelRelation; import de.gaz.eedu.file.FileEntity; import de.gaz.eedu.user.UserEntity; +import de.gaz.eedu.user.model.ReducedUserModel; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -67,7 +68,7 @@ public class AppointmentEntryEntity implements EntityModelRelation assessments = new HashSet<>(); /** @@ -136,14 +137,15 @@ public AppointmentEntryEntity(long id) File[] files = file.listFiles(); AssessmentModel assessment = getAssessment(user).map(AssessmentEntity::toModel).orElse(null); + ReducedUserModel reducedUserModel = user.toReducedModel(); if (!hasSubmitted(user) || !file.isDirectory() || Objects.isNull(files) || files.length == 0) { - return new AssignmentInsightModel(user.getLoginName(), false, new String[0], assessment); + return new AssignmentInsightModel(reducedUserModel, false, new String[0], assessment); } String[] paths = Arrays.stream(files).map(File::getName).toArray(String[]::new); - return new AssignmentInsightModel(user.getLoginName(), true, paths, assessment); + return new AssignmentInsightModel(reducedUserModel, true, paths, assessment); } public @NotNull File[] loadAssignmentFiles(long user) @@ -175,7 +177,7 @@ public void submitAssignment(long user, @NotNull MultipartFile... files) throws String uploadPath = getUploadPath(user); File[] file = new File(uploadPath).listFiles(); - if (file != null && (file.length + files.length) > 5) + if (file != null && (file.length + files.length) > 3) { throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE, "The maximum amount of files exceeded."); } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java index 70ecd919..e45a2c5f 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/AssignmentInsightModel.java @@ -1,11 +1,12 @@ package de.gaz.eedu.course.appointment.entry.assignment; import de.gaz.eedu.course.appointment.entry.assignment.assessment.model.AssessmentModel; +import de.gaz.eedu.user.model.ReducedUserModel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public record AssignmentInsightModel( - @NotNull String name, + @NotNull ReducedUserModel user, boolean submitted, @NotNull String[] files, @Nullable AssessmentModel assessment diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java index 086cf307..5ffe410b 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/entry/assignment/assessment/AssessmentRepository.java @@ -1,7 +1,15 @@ package de.gaz.eedu.course.appointment.entry.assignment.assessment; +import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository -public interface AssessmentRepository extends JpaRepository {} +public interface AssessmentRepository extends JpaRepository +{ + @Query("SELECT a FROM AssessmentEntity a LEFT JOIN FETCH a.appointment ap LEFT JOIN FETCH a.user u WHERE a.id = :id") + @Override @NotNull Optional findById(@NotNull Long id); +} diff --git a/EEDU-Backend/src/main/resources/schema.sql b/EEDU-Backend/src/main/resources/schema.sql index bf27c0f5..e643a2f2 100644 --- a/EEDU-Backend/src/main/resources/schema.sql +++ b/EEDU-Backend/src/main/resources/schema.sql @@ -113,7 +113,6 @@ CREATE TABLE IF NOT EXISTS assessment_entity id BIGINT PRIMARY KEY NOT NULL, appointment_id BIGINT NOT NULL, user_id BIGINT NOT NULL, - grade FLOAT NULL, feedback VARCHAR(200) NULL, FOREIGN KEY (appointment_id) REFERENCES appointment_entry_entity (id), FOREIGN KEY (user_id) REFERENCES user_entity (id) diff --git a/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.html b/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.html index 5ab98241..b6a8c9c4 100644 --- a/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.html +++ b/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.html @@ -1,6 +1,6 @@
- warning + {{ icon() }}

{{ message() }}

diff --git a/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.scss b/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.scss index 30ce2f8f..a1fc2c61 100644 --- a/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.scss +++ b/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.scss @@ -8,6 +8,11 @@ .error-icon { margin-bottom: 16px; + + .actual-icon + { + font-size: 64px; height: 64px; width: 64px; + } } .error-message p strong { diff --git a/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.ts b/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.ts index 7cd5255c..3ea7ce16 100644 --- a/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.ts +++ b/EEDU-Frontend/src/app/common/general-error-box/general-error-box.component.ts @@ -9,6 +9,7 @@ import {NgIf} from "@angular/common"; styleUrl: './general-error-box.component.scss' }) export class GeneralErrorBoxComponent { + public readonly icon: InputSignal = input('warning'); public readonly message: InputSignal = input(''); public readonly subMessage: InputSignal = input(null); } diff --git a/EEDU-Frontend/src/app/timetable/calendar-controls/calendar-controls.component.ts b/EEDU-Frontend/src/app/timetable/calendar-controls/calendar-controls.component.ts index 88363c16..cefbf22b 100644 --- a/EEDU-Frontend/src/app/timetable/calendar-controls/calendar-controls.component.ts +++ b/EEDU-Frontend/src/app/timetable/calendar-controls/calendar-controls.component.ts @@ -70,10 +70,10 @@ export class CalendarControlsComponent { protected get title(): string { switch (this._viewType) { case CalendarView.Month: - return this.viewDate.toLocaleDateString('de-DE', {month: 'long', year: 'numeric'}); + return this.viewDate.toLocaleDateString('en-GB', {month: 'long', year: 'numeric'}); case CalendarView.Day: - return this.viewDate.toLocaleDateString('de-DE', { + return this.viewDate.toLocaleDateString('en-GB', { weekday: 'long', month: 'long', day: 'numeric', @@ -89,10 +89,10 @@ export class CalendarControlsComponent { startOfWeek.setDate(this.viewDate.getDate() + daysToMonday); // Go to Monday endOfWeek.setDate(startOfWeek.getDate() + 6); // Go to Sunday (end of the week) - return `${startOfWeek.toLocaleDateString('de-DE', { + return `${startOfWeek.toLocaleDateString('en-GB', { month: 'short', day: 'numeric' - })} – ${endOfWeek.toLocaleDateString('de-DE', {month: 'short', day: 'numeric', year: 'numeric'})}`; + })} – ${endOfWeek.toLocaleDateString('en-GB', {month: 'short', day: 'numeric', year: 'numeric'})}`; } } diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html index 3bc49f08..c1511af8 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.html @@ -6,15 +6,9 @@
{{ insight.submitted }} - - - description - {{ file }} - -
-
+
Deadline: @@ -23,7 +17,7 @@
- +
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts index ec668052..5a67ea7f 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts @@ -4,12 +4,11 @@ import {FileUploadComponent} from "../../../../common/file-upload/file-upload.co import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {MatButton} from "@angular/material/button"; import {MatList, MatListItem, MatListItemLine, MatListItemTitle} from "@angular/material/list"; -import {NgForOf, NgIf} from "@angular/common"; +import {NgIf} from "@angular/common"; import {MatIcon} from "@angular/material/icon"; import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; -import {MatTree, MatTreeNode} from "@angular/material/tree"; @Component({ selector: 'app-assignment-student-view', @@ -23,9 +22,6 @@ import {MatTree, MatTreeNode} from "@angular/material/tree"; MatListItem, NgIf, MatIcon, - MatTree, - MatTreeNode, - NgForOf ], templateUrl: './assignment-student-view.component.html', styleUrl: './assignment-student-view.component.scss' diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index 5ad5167b..e28fde36 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -10,37 +10,51 @@ Current User - - @for (insight of assignmentInsightModels; track assignmentInsightModels) { + + @for (insight of assignmentInsight; track assignmentInsight) { {{ toIcon(insight) }} - {{ insight.name }} + {{ insight.user.name }} } - + - -
+
    - @for (file of toArray(select.value); track file) { + @for (file of currentInsight.files; track file) {
  • {{ file }}
  • }
- - Feedback - - - + + +
+ +
+

{{ currentInsight.assessment.feedback }}

+
+
+ +
+ + +
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index 50afadfd..008f5b48 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -12,18 +12,24 @@ import {MatInput} from "@angular/material/input"; import {FormBuilder, FormGroup, ReactiveFormsModule} from "@angular/forms"; import {AssessmentService} from "../../../../user/courses/appointment/entry/assignment/assessment/assessment.service"; import {UserService} from "../../../../user/user.service"; +import {AssessmentModel} from "../../../../user/courses/appointment/entry/assignment/assessment/assessment-model"; +import { + AssessmentCreateModel +} from "../../../../user/courses/appointment/entry/assignment/assessment/assessment-create-model"; +import {GeneralErrorBoxComponent} from "../../../../common/general-error-box/general-error-box.component"; @Component({ selector: 'app-assignment-teacher-view', standalone: true, - imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule], + imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule, GeneralErrorBoxComponent], templateUrl: './assignment-teacher-view.component.html', styleUrl: './assignment-teacher-view.component.scss' }) export class AssignmentTeacherViewComponent { private _appointment: AppointmentEntryModel | null = null; - private _assignmentInsightModels: readonly AssignmentInsightModel[] = []; + private _assignmentInsight: readonly AssignmentInsightModel[] = []; + private _insight: AssignmentInsightModel | null = null; public readonly editing: InputSignal = input(false); private readonly _assessForm: FormGroup; @@ -42,42 +48,64 @@ export class AssignmentTeacherViewComponent { return this._assessForm; } + protected set currentInsight(assignmentInsight: AssignmentInsightModel) + { + this._insight = assignmentInsight; + } + + protected get currentInsight(): AssignmentInsightModel | null + { + return this._insight; + } + protected onAssess(): void { - this._assessmentService.assess([{ + if(!this.currentInsight) + { + return; + } + + const insight: AssignmentInsightModel = this.currentInsight; + this._assessmentService.assess([AssessmentCreateModel.fromObject({ appointment: Number(this.appointment?.id), - user: Number(this._userService.getUserData.id), + user: insight.user.id, feedback: this.assessForm.get('feedback')?.value, - }]).subscribe(); - } + })]).subscribe((assessmentModel: readonly AssessmentModel[]): void => { + this._assignmentInsight.map((current: AssignmentInsightModel): AssignmentInsightModel => { - public get appointment(): AppointmentEntryModel | null { - return this._appointment; + if(current === insight) + { + const newInsight: AssignmentInsightModel = AssignmentInsightModel.pushAssessment(current, assessmentModel[0]); + if(this.currentInsight === insight) + { + this.currentInsight = newInsight; + } + return newInsight; + } + + return current; + }) + }); } @Input() public set appointment(appointment: AppointmentEntryModel) { this._appointment = appointment; this._assignmentService.fetchInsights(appointment.id).subscribe((response: AssignmentInsightModel[]): void => { - this._assignmentInsightModels = response; + this._assignmentInsight = response; }) } - protected get assignmentInsightModels(): readonly AssignmentInsightModel[] { - return this._assignmentInsightModels; + public get appointment(): AppointmentEntryModel | null { + return this._appointment; } - protected get assignment(): AssignmentModel | null { - return this.appointment!.assignment || null; + protected get assignmentInsight(): readonly AssignmentInsightModel[] { + return this._assignmentInsight; } - protected toArray(value: any): readonly string[] - { - if(value instanceof AssignmentInsightModel) - { - return value.files; - } - return []; + protected get assignment(): AssignmentModel | null { + return this.appointment!.assignment || null; } protected toIcon(insight: AssignmentInsightModel): 'assignment_turned_in' | 'assignment_late' | 'pending' { diff --git a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html index 85639c28..b91425b9 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/event-data-dialog.component.html @@ -2,7 +2,7 @@
- + diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts index 5fd7ff4c..3b3e2318 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment-create-model.ts @@ -1,15 +1,22 @@ export interface GenericAssessmentCreateModel { appointment: number; - user: number; + user: bigint; feedback?: string; } +export interface AssessmentCreateModelPacket +{ + appointment: number; + user: number; + feedback: string | null; +} + export class AssessmentCreateModel { public constructor( private readonly _appointment: number, - private readonly _user: number, + private readonly _user: bigint, private readonly _feedback: string | null, ) {} @@ -25,11 +32,20 @@ export class AssessmentCreateModel return this._appointment; } - public get user(): number { + public get user(): bigint { return this._user; } public get feedback(): string | null { return this._feedback; } + + public get toPacket(): AssessmentCreateModelPacket + { + return { + user: Number(this.user), + appointment: this.appointment, + feedback: this.feedback + } + } } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts index 8c50ea32..190e4f1e 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assessment/assessment.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {environment} from "../../../../../../../environment/environment"; -import {GenericAssessmentCreateModel} from "./assessment-create-model"; +import { + AssessmentCreateModel, + AssessmentCreateModelPacket, +} from "./assessment-create-model"; import {BehaviorSubject, map, Observable, OperatorFunction} from "rxjs"; import {AssessmentModel, GenericAssessment} from "./assessment-model"; @@ -23,10 +26,10 @@ export class AssessmentService { return `${environment.backendUrl}/course/appointment/assignment/assessment`; } - public assess(obj: GenericAssessmentCreateModel[]): Observable + public assess(obj: AssessmentCreateModel[]): Observable { const url: string = `${this.BACKEND_URL}/create`; - return this.http.post(url, obj, { withCredentials: true }).pipe( + return this.http.post(url, obj.map((current: AssessmentCreateModel): AssessmentCreateModelPacket => current.toPacket), { withCredentials: true }).pipe( map((response: GenericAssessment[]): AssessmentModel[] => response.map((item: GenericAssessment): AssessmentModel => { @@ -55,6 +58,7 @@ export class AssessmentService { } private pushAssessment(obj: AssessmentModel): void { + let replaced: boolean = false; this.value.map((item: AssessmentModel): AssessmentModel => { if (item.id !== obj.id) { diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-insight-model.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-insight-model.ts index f0f3bfdd..c5e64729 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-insight-model.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment-insight-model.ts @@ -1,23 +1,38 @@ +import {AssessmentModel, GenericAssessment} from "./assessment/assessment-model"; +import {GenericReducedUserModel, ReducedUserModel} from "../../../../reduced-user-model"; + export interface GenericAssignmentInsightModel { - name: string, + user: GenericReducedUserModel, submitted: boolean, - files: string[] + files: readonly string[], + assessment?: GenericAssessment } export class AssignmentInsightModel { public constructor( - private readonly _name: string, + private readonly _user: ReducedUserModel, private readonly _submitted: boolean, - private readonly _files: readonly string[] + private readonly _files: readonly string[], + private readonly _assessment: AssessmentModel | null ) {} public static fromObject(obj: GenericAssignmentInsightModel): AssignmentInsightModel { - return new AssignmentInsightModel(obj.name, obj.submitted, obj.files); + return new AssignmentInsightModel( + ReducedUserModel.fromObject(obj.user), + obj.submitted, + obj.files, + obj.assessment ? AssessmentModel.fromObject(obj.assessment) : null + ); + } + + public static pushAssessment(assignment: AssignmentInsightModel, assessment: AssessmentModel): AssignmentInsightModel + { + return new AssignmentInsightModel(assignment.user, assignment.submitted, assignment.files, assessment); } - public get name(): string { - return this._name; + public get user(): ReducedUserModel { + return this._user; } public get submitted(): boolean { @@ -27,4 +42,8 @@ export class AssignmentInsightModel { public get files(): readonly string[] { return this._files; } + + public get assessment(): AssessmentModel | null { + return this._assessment; + } } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts index 7b01274c..3dc53ec0 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/entry/assignment/assignment.service.ts @@ -7,7 +7,6 @@ import {AssignmentModel} from "./assignment-model"; import {AppointmentService} from "../../appointment.service"; import {AppointmentEntryModel} from "../appointment-entry-model"; import {environment} from "../../../../../../environment/environment"; -import {AssessmentModel} from "./assessment/assessment-model"; @Injectable({ providedIn: 'root' @@ -42,7 +41,9 @@ export class AssignmentService { public fetchInsight(appointment: bigint): Observable { const url: string = `${this.BACKEND_URL}/${appointment}/status`; - return this.http.get(url, {withCredentials: true}).pipe(map((response: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(response))); + return this.http.get(url, {withCredentials: true}).pipe( + map((response: GenericAssignmentInsightModel): AssignmentInsightModel => AssignmentInsightModel.fromObject(response)) + ); } public fetchUsersInsight(appointment: bigint, user: bigint): Observable { From 91ba17427b48c8b0e3c6087cc32f8d6fc1791308 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Sun, 23 Mar 2025 10:07:40 +0100 Subject: [PATCH 19/28] Adjusted some minor things --- .../src/main/java/de/gaz/eedu/user/UserEntity.java | 11 +++++++++-- .../de/gaz/eedu/user/group/GroupController.java | 4 ++-- .../java/de/gaz/eedu/user/group/GroupService.java | 1 - .../de/gaz/eedu/user/model/ReducedUserModel.java | 13 +++++++++---- .../test/java/de/gaz/eedu/user/UserServiceTest.java | 13 +++++++++---- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserEntity.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserEntity.java index da73f81c..e2959dab 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserEntity.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/UserEntity.java @@ -247,7 +247,7 @@ public boolean attachGroups(@NotNull GroupEntity... groupEntities) throws StateT if(!Collections.disjoint(this.getGroups(), entities)) { - throw new StateTransitionException("The user already has group(s)."); + throw new StateTransitionException("The user already is part in any of the given group(s)."); } return this.groups.addAll(entities); @@ -271,7 +271,7 @@ private static boolean containsGroup(@NotNull Collection entities, * @param ids The IDs of the groups to be detached. * @return true if a group was successfully detached and the user entity was saved, false otherwise. */ - public boolean detachGroups(@NotNull UserService userService, @NotNull String... ids) + public boolean detachGroups(@NotNull UserService userService, @NotNull String... ids) throws StateTransitionException { return saveEntityIfPredicateTrue(userService, ids, this::detachGroups); } @@ -292,6 +292,13 @@ public boolean detachGroups(@NotNull UserService userService, @NotNull String... public boolean detachGroups(@NotNull String... ids) { List detachGroupIds = Arrays.asList(ids); + Set allGroups = this.groups.stream().map(GroupEntity::getId).collect(Collectors.toSet()); + + if(!allGroups.containsAll(detachGroupIds)) + { + throw new StateTransitionException("The user already is not part of some given group(s)."); + } + return this.groups.removeIf(groupEntity -> detachGroupIds.contains(groupEntity.getId())); } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupController.java index 81646504..d0f52391 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupController.java @@ -41,7 +41,7 @@ public class GroupController extends EntityController attachGroups(@PathVariable long user, @PathVariable @NotNull String... groups) { log.info("Received incoming request for attaching group(s) {} to user {}.", groups, user); - return empty(getService().attachGroups(user, groups) ? HttpStatus.OK : HttpStatus.NOT_MODIFIED); + return empty(getService().attachGroups(user, groups) ? HttpStatus.OK : HttpStatus.BAD_REQUEST); } @@ -50,7 +50,7 @@ public class GroupController extends EntityController detachGroups(@PathVariable long user, @PathVariable @NotNull String... groups) { log.info("Received incoming request for detaching group(s) {} to user {}.", groups, user); - return empty(getService().detachGroups(user, groups) ? HttpStatus.OK : HttpStatus.NOT_MODIFIED); + return empty(getService().detachGroups(user, groups) ? HttpStatus.OK : HttpStatus.BAD_REQUEST); } @PostMapping("/create") diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupService.java index 967737ef..d3856437 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/group/GroupService.java @@ -88,7 +88,6 @@ private static void validateGroups(String @NotNull [] entities) throws ResponseS @Transactional public boolean attachGroups(long userId, @NotNull String[] groups) throws GroupUnprocessableException { validateGroups(groups); - GroupEntity[] entities = loadEntityById(Arrays.asList(groups)).toArray(GroupEntity[]::new); return getUserService().loadEntityByIDSafe(userId).attachGroups(getUserService(), entities); } diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/model/ReducedUserModel.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/model/ReducedUserModel.java index ee364e42..f1c236f4 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/model/ReducedUserModel.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/model/ReducedUserModel.java @@ -1,14 +1,19 @@ package de.gaz.eedu.user.model; +import de.gaz.eedu.entity.model.Model; import de.gaz.eedu.user.AccountType; -import jakarta.validation.constraints.NotNull; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.Objects; -public record ReducedUserModel(@NotNull Long id, @NotNull String firstName, @NotNull String lastName, @NotNull AccountType accountType) -{ - @Contract(pure = true) @Override public @org.jetbrains.annotations.NotNull String toString() +public record ReducedUserModel( + @NotNull Long id, + @NotNull String firstName, + @NotNull String lastName, + @NotNull AccountType accountType +) implements Model { + @Contract(pure = true) @Override public @NotNull String toString() { // Automatically generated by IntelliJ return "ReducedUserModel{" + "id=" + id + diff --git a/EEDU-Backend/src/test/java/de/gaz/eedu/user/UserServiceTest.java b/EEDU-Backend/src/test/java/de/gaz/eedu/user/UserServiceTest.java index fb65272d..4c0279b0 100644 --- a/EEDU-Backend/src/test/java/de/gaz/eedu/user/UserServiceTest.java +++ b/EEDU-Backend/src/test/java/de/gaz/eedu/user/UserServiceTest.java @@ -97,8 +97,8 @@ public class UserServiceTest extends ServiceTest test(Eval.eval(groupEntity, true, Validator.equals()), userEntity::attachGroups); if(userID == 1) @@ -133,7 +133,12 @@ public void testAttachGroup(long userID) { @ValueSource(longs = {1, 2}) @Transactional(Transactional.TxType.REQUIRES_NEW) public void testDetachGroup(long userID) { - UserEntity userEntity = getService().loadEntityById(userID).orElseThrow(IllegalStateException::new); - test(Eval.eval("group0", userID == 1, Validator.equals()), userEntity::detachGroups); + UserEntity userEntity = getService().loadEntityById(userID).orElseThrow(); + if(userID == 1) + { + test(Eval.eval("group0", true, Validator.equals()), userEntity::detachGroups); + return; + } + Assertions.assertThrowsExactly(StateTransitionException.class, () -> userEntity.detachGroups("group0")); } } From f40fce046bee1d268992a0b45683ad7d4b136532 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Sun, 23 Mar 2025 10:10:17 +0100 Subject: [PATCH 20/28] Use SHA256 instead of SHA1 --- .../credentials/implementations/CredentialMethod.java | 2 +- .../credentials/implementations/TOTPCredential.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/CredentialMethod.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/CredentialMethod.java index 1f2646bc..799b5923 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/CredentialMethod.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/CredentialMethod.java @@ -15,7 +15,7 @@ public enum CredentialMethod PASSWORD(new PasswordCredential(new BCryptPasswordEncoder()), false), EMAIL(new EmailCredential(), true), SMS(new SMSCredential(), true), - TOTP(new TOTPCredential(new TOPTHandler(HashingAlgorithm.SHA1)), true); + TOTP(new TOTPCredential(new TOPTHandler(HashingAlgorithm.SHA256)), true); private final Credential credential; private final boolean enablingRequired; diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/TOTPCredential.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/TOTPCredential.java index 98b7258b..40c920be 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/TOTPCredential.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/credentials/implementations/TOTPCredential.java @@ -32,7 +32,7 @@ public class TOTPCredential implements Credential { UserEntity userEntity = credentialEntity.getUser(); String secret = credentialEntity.getSecret(); - return new TOTPData(userEntity.getLoginName(), secret, HashingAlgorithm.SHA1.getFriendlyName(), 6, 30); + return new TOTPData(userEntity.getLoginName(), secret, HashingAlgorithm.SHA256.getFriendlyName(), 6, 30); } @Override public boolean verify(@NotNull CredentialEntity credentialEntity, @NotNull String code) From 3b572ba515f818e8f4933b4df9839da2596612b5 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Tue, 25 Mar 2025 17:02:52 +0100 Subject: [PATCH 21/28] Minor changes --- .../src/main/java/de/gaz/eedu/DataLoader.java | 7 ++++-- ...lnessNotificationManagementController.java | 1 - .../IllnessNotificationService.java | 15 ++++-------- .../eedu/user/theming/ThemeController.java | 11 +++++++++ .../src/app/settings/settings.component.html | 3 ++- .../src/app/settings/settings.component.ts | 23 ++++++++++++++++++- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java b/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java index 804813ca..9ef9db77 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/DataLoader.java @@ -1,5 +1,6 @@ package de.gaz.eedu; +import de.gaz.eedu.exception.EntityUnknownException; import de.gaz.eedu.user.AccountType; import de.gaz.eedu.user.UserEntity; import de.gaz.eedu.user.UserService; @@ -125,11 +126,13 @@ private void createDefaultGroup() new byte[]{Byte.MIN_VALUE + 255, Byte.MIN_VALUE + 255, Byte.MIN_VALUE + 255}, new byte[]{Byte.MIN_VALUE + 235, Byte.MIN_VALUE + 235, Byte.MIN_VALUE + 235}); + String defaultThemeName = "Dark"; + return getThemeService().createEntity(Set.of(defaultDark, defaultLight)).stream().filter(theme -> { // Dark will be set as default - return Objects.equals(theme.getName(), "defaultDark"); - }).findFirst().orElseThrow(); + return Objects.equals(theme.getName(), defaultThemeName); + }).findFirst().orElseThrow(() -> new EntityUnknownException(defaultThemeName)); } private @NotNull UserEntity createDefaultUser(@NotNull ThemeEntity themeEntity) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/management/illnessnotifications/IllnessNotificationManagementController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/management/illnessnotifications/IllnessNotificationManagementController.java index 748d5fef..d0231c80 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/management/illnessnotifications/IllnessNotificationManagementController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/management/illnessnotifications/IllnessNotificationManagementController.java @@ -30,7 +30,6 @@ public ResponseEntity getNotificationsOfDate(@NotNul return illnessNotificationManagementService.getNotificationsOfDate(date); } - @PreAuthorize("hasAuthority(T(de.gaz.eedu.user.privileges.SystemPrivileges).USER_CREATE.toString())") @GetMapping("/get-pending") public ResponseEntity> getPendingNotifications() diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java index 58e631f5..011d72f1 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/illnessnotifications/IllnessNotificationService.java @@ -53,11 +53,11 @@ public boolean excuse(@NotNull Long userId, @NotNull String reason, @NotNull Lon LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond(), expirationTime, fileId))); - return true; } - public @Nullable Long uploadNotification(@Nullable MultipartFile file){ + public @Nullable Long uploadNotification(@Nullable MultipartFile file) throws MaliciousFileException + { if(!(file != null && !file.isEmpty())) { return null; @@ -68,15 +68,8 @@ public boolean excuse(@NotNull Long userId, @NotNull String reason, @NotNull Lon new String[] { "Management", "ADMINISTRATOR", "ROLE_administrator", "USER_CREATE" }, new String[] { "illness_notification" })); - try - { - fileEntity.uploadBatch("", file); - return fileEntity.getId(); - } - catch (MaliciousFileException e) - { - throw new RuntimeException(e); - } + fileEntity.uploadBatch("", file); + return fileEntity.getId(); } @Transactional diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeController.java index b4fe1f85..756d2fd2 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/theming/ThemeController.java @@ -1,5 +1,6 @@ package de.gaz.eedu.user.theming; +import de.gaz.eedu.exception.EntityUnknownException; import de.gaz.eedu.exception.NameOccupiedException; import de.gaz.eedu.user.UserEntity; import de.gaz.eedu.user.UserService; @@ -66,6 +67,16 @@ public ResponseEntity setTheme(@AuthenticationPrincipal Long id, @Re return ResponseEntity.ok(fallbackTheme.toEntity(new ThemeEntity()).toModel()); } + @PreAuthorize("@verificationService.isFullyAuthenticated()") + @GetMapping("/theme/get/{themeId}") public ResponseEntity getTheme(@AuthenticationPrincipal Long id, @NotNull @PathVariable Long themeId){ + if(userService.loadEntityById(id).isPresent()) + { + return ResponseEntity.ok(themeService.getRepository().findById(themeId).orElseThrow(() -> new EntityUnknownException(themeId)).toModel()); + } + + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + /** * Returns all themes in the database as SimpleThemeModels. * @return SimpleThemeModel diff --git a/EEDU-Frontend/src/app/settings/settings.component.html b/EEDU-Frontend/src/app/settings/settings.component.html index e96431dd..806f846f 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.html +++ b/EEDU-Frontend/src/app/settings/settings.component.html @@ -12,7 +12,8 @@

Themes


-

+

+

diff --git a/EEDU-Frontend/src/app/settings/settings.component.ts b/EEDU-Frontend/src/app/settings/settings.component.ts index 750fb52c..36009862 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.ts +++ b/EEDU-Frontend/src/app/settings/settings.component.ts @@ -109,7 +109,7 @@ export class SettingsComponent implements OnInit { * * @param parsedUserData Parsed object representation of the userData JSON retrieved from local storage */ - public processSettings(parsedUserData: any) { + public processSettings(parsedUserData: any): void { const theme$: Observable = this.setTheme(this.selectedTheme); console.log(theme$); const observables: Observable[] = [theme$]; // add further observables along the way @@ -139,6 +139,27 @@ export class SettingsComponent implements OnInit { })); } + /** + * Creates a cookie containing the received theme data. + * + * @param themeId - The theme to receive + */ + public setThemeLocally(): void + { + this.getTheme(this.selectedTheme).subscribe((themeModel: ThemeModel): void => { + let themeModelJson: string = JSON.stringify(themeModel); + document.cookie = `theme=${themeModelJson}; max-age=${60 * 60 * 24 * 365}; path=/;`; + }); + } + + public getTheme(themeId: bigint): Observable { + const url: string = `${environment.backendUrl}/user/theme/get/${themeId}`; + + return this.http.get(url, { + withCredentials: true + }); + } + /** * Fetches all themes as a SimpleThemeEntity array observable. Typically used for * a theme selection dropdown. From 9f024a14c7a552690ffb7ccba5f4b6f38f02bc56 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Tue, 25 Mar 2025 17:48:57 +0100 Subject: [PATCH 22/28] Made Frequent Appointments creatable again --- .../gaz/eedu/user/verification/TokenData.java | 35 +++++++++++++++++-- .../verification/VerificationService.java | 6 +--- .../src/app/timetable/timetable.component.ts | 15 ++++---- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/TokenData.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/TokenData.java index ae1161f5..3541f1c4 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/TokenData.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/TokenData.java @@ -2,6 +2,7 @@ import de.gaz.eedu.user.verification.authority.InvalidTokenException; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.jetbrains.annotations.Contract; @@ -37,7 +38,8 @@ public TokenData(long userId, boolean advanced, @NotNull Set restrictedC userId, advanced, restrictedClaims, - Stream.of(claimHolder).collect(Collectors.toMap(ClaimHolder::key, ClaimHolder::content))); + Stream.of(claimHolder).collect(Collectors.toMap(ClaimHolder::key, ClaimHolder::content)) + ); } private static void validate(@NotNull Set keys, Set elements) throws InvalidTokenException @@ -59,7 +61,7 @@ private static void validate(@NotNull Set keys, Set elements) th // get rid of this, will be generated with next thing additionalClaims.remove("sub"); additionalClaims.remove("iat"); - additionalClaims.remove("exo"); + additionalClaims.remove("exp"); validate(additionalClaims.keySet(), restricted); @@ -68,6 +70,33 @@ private static void validate(@NotNull Set keys, Set elements) th return new TokenData(claims, userId, advanced, restricted, additionalClaims); } + @Contract("_, _ -> new") + public static @NotNull TokenData purgeClaims(@NotNull TokenData tokenData, String @NotNull ... keys) throws InvalidTokenException + { + for (String key : keys) + { + tokenData.deleteRestrictedClaim(key); + } + + return new TokenData( + tokenData.getParent().map((claims) -> removeClaims(claims, keys)).orElse(tokenData.parent()), + tokenData.userId(), + tokenData.advanced(), + tokenData.restrictedClaims(), + tokenData.additionalClaims() + ); + } + + private static Claims removeClaims(@NotNull Claims parent, @NotNull String @NotNull ... keys) + { + ClaimsBuilder claimsBuilder = Jwts.claims().add(parent); + for (String key : keys) + { + claimsBuilder.delete(key); + } + return claimsBuilder.build(); + } + @Contract("_, _ -> new") public static @NotNull TokenData deserialize(@NotNull String key, @NotNull String token) throws InvalidTokenException { @@ -101,7 +130,7 @@ public boolean addRestrictedClaim(boolean override, @NotNull String key, @NotNul public boolean deleteRestrictedClaim(@NotNull String key) { - return restrictedClaims().remove(key) && removeClaim(key); + return restrictedClaims().remove(key) || removeClaim(key); } public boolean unrestrictedClaim(@NotNull String key) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/VerificationService.java b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/VerificationService.java index f4c088f2..52e979f5 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/VerificationService.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/user/verification/VerificationService.java @@ -212,11 +212,7 @@ public boolean hasToken(@NotNull Authentication authentication, @NotNull JwtToke JwtTokenType type = tokenData.advanced() ? JwtTokenType.ADVANCED_AUTHORIZATION : JwtTokenType.AUTHORIZED; Instant expiry = Instant.ofEpochMilli(tokenData.get("expiry", Long.class)); - tokenData.deleteRestrictedClaim("expiry"); - tokenData.deleteRestrictedClaim("available"); - tokenData.deleteRestrictedClaim("temporary"); - - return generateKey(type, expiry, tokenData); + return generateKey(type, expiry, TokenData.purgeClaims(tokenData, "expiry", "available", "temporary")); } /** diff --git a/EEDU-Frontend/src/app/timetable/timetable.component.ts b/EEDU-Frontend/src/app/timetable/timetable.component.ts index edfa8697..15185003 100644 --- a/EEDU-Frontend/src/app/timetable/timetable.component.ts +++ b/EEDU-Frontend/src/app/timetable/timetable.component.ts @@ -71,6 +71,11 @@ export class TimetableComponent implements OnInit, OnDestroy { } private set selectedEvent(value: CalendarEvent | undefined) { + if(value?.meta.eventData instanceof FrequentAppointmentModel) + { + this.createEvent(value?.start, value?.meta.eventData as FrequentAppointmentModel); + } + this._selectedEvent = value; } @@ -150,14 +155,10 @@ export class TimetableComponent implements OnInit, OnDestroy { this.document.body.classList.remove(this.CALENDAR_THEME_CLASS) } - protected createEvent(): void { - if (!this.selectedEvent) { - return; - } + protected createEvent(start: Date, eventData: FrequentAppointmentModel): void { - const frequentData: FrequentAppointmentModel = this.selectedEvent.meta.eventData as FrequentAppointmentModel; - this._appointmentService.createAppointment(frequentData.course.id, [AppointmentCreateModel.fromObject({ - start: this.selectedEvent.start, room: frequentData.room, duration: frequentData.duration, + this._appointmentService.createAppointment(eventData.course.id, [AppointmentCreateModel.fromObject({ + start: start, room: eventData.room, duration: eventData.duration, })]).subscribe((createdEvent: AppointmentEntryModel[]): void => { this.selectedEvent = this.events.find((current: CalendarEvent): boolean => { return typeof current.id === 'number' && BigInt(current.id) === createdEvent[0].id; From 959eb439ce74d74b45f18cc0a65ada2b8b89efd9 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Tue, 25 Mar 2025 19:56:05 +0100 Subject: [PATCH 23/28] Fixed themes --- .../chat-creation.component.scss | 10 ++- .../entity-list/entity-list.component.html | 2 +- .../entity-list/entity-list.component.scss | 3 + .../file-upload-button.component.html | 2 +- .../file-upload-button.component.scss | 12 +-- .../article-creation.component.html | 8 +- .../article-creation.component.scss | 4 + .../article-creation.component.ts | 4 +- .../src/app/settings/settings.component.html | 2 +- .../src/app/settings/settings.component.scss | 2 +- EEDU-Frontend/src/styles.scss | 74 +++++++++++++++++++ 11 files changed, 104 insertions(+), 19 deletions(-) diff --git a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.scss b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.scss index a310ead4..67adc40a 100644 --- a/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.scss +++ b/EEDU-Frontend/src/app/chat/chat-creation/chat-creation.component.scss @@ -1,5 +1,5 @@ .dialog-container { - background-color: white; + background-color: var(--widget-color); font-family: 'Arial', sans-serif; text-align: center; padding: 20px; @@ -9,6 +9,8 @@ flex-direction: column; align-items: center; overflow: hidden; + color: var(--text-color); +} .user-list { width: 100%; @@ -28,13 +30,13 @@ padding: 12px 16px; text-align: left; font-size: 14px; - background-color: #ffffff; + background-color: var(--widget-color); border-radius: 6px; - color: #333; + color: var(--text-color); transition: all 0.3s; box-sizing: border-box; } .mat-divider { margin: 0; -}} +} diff --git a/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.html b/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.html index 4ed1a6fe..ea428498 100644 --- a/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.html +++ b/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.html @@ -4,7 +4,7 @@
folder_open - +

Select image

- +
  • {{ file.name }}
diff --git a/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.scss b/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.scss index f200152b..f50d0fdf 100644 --- a/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.scss +++ b/EEDU-Frontend/src/app/file/file-upload-button/file-upload-button.component.scss @@ -1,9 +1,9 @@ .upload-container { text-align: center; padding: 20px; - border: 1px solid #cccccc; + border: 1px dashed var(--text-color); border-radius: 8px; - background-color: #f8f8f8; + background-color: var(--background-color); max-width: 400px; margin-top: 10px; margin-bottom: 10px; @@ -11,14 +11,14 @@ .upload-container p { font-size: 16px; - color: #333; + color: var(--text-color); } -button { +.button { padding: 10px 20px; font-size: 14px; - background-color: #00796b; - color: white; + background-color: var(--widget-color); + color: var(--text-color); border: none; border-radius: 5px; cursor: pointer; diff --git a/EEDU-Frontend/src/app/news/article-creation/article-creation.component.html b/EEDU-Frontend/src/app/news/article-creation/article-creation.component.html index 66575312..d1c92a6f 100644 --- a/EEDU-Frontend/src/app/news/article-creation/article-creation.component.html +++ b/EEDU-Frontend/src/app/news/article-creation/article-creation.component.html @@ -10,7 +10,7 @@

Create new article

Title - + Specify an author @@ -18,7 +18,7 @@

Create new article

Author - +
Write the article body @@ -27,12 +27,12 @@

Create new article

Hint: ElementEDU uses markdown to style your articles. (max. 65000 characters) - +
Upload article thumbnail - + diff --git a/EEDU-Frontend/src/app/news/article-creation/article-creation.component.scss b/EEDU-Frontend/src/app/news/article-creation/article-creation.component.scss index 679c5ab7..b629cfbf 100644 --- a/EEDU-Frontend/src/app/news/article-creation/article-creation.component.scss +++ b/EEDU-Frontend/src/app/news/article-creation/article-creation.component.scss @@ -8,6 +8,10 @@ margin-bottom: -40px; } +.step-buttons { + background-color: var(--widget-color); +} + .stepper { overflow: auto; diff --git a/EEDU-Frontend/src/app/news/article-creation/article-creation.component.ts b/EEDU-Frontend/src/app/news/article-creation/article-creation.component.ts index 0667cb04..17ecbf65 100644 --- a/EEDU-Frontend/src/app/news/article-creation/article-creation.component.ts +++ b/EEDU-Frontend/src/app/news/article-creation/article-creation.component.ts @@ -12,6 +12,7 @@ import {MatIcon} from "@angular/material/icon"; import {NgForOf} from "@angular/common"; import {HttpClient} from "@angular/common/http"; import {ArticleCreationService} from "./article-creation.service"; +import {FileUploadComponent} from "../../common/file-upload/file-upload.component"; @Injectable({ providedIn: 'root' @@ -36,7 +37,8 @@ import {ArticleCreationService} from "./article-creation.service"; MatChipGrid, MatChipInput, NgForOf, - MatStepperNext + MatStepperNext, + FileUploadComponent ], templateUrl: './article-creation.component.html', styleUrl: './article-creation.component.scss' diff --git a/EEDU-Frontend/src/app/settings/settings.component.html b/EEDU-Frontend/src/app/settings/settings.component.html index 806f846f..2df26298 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.html +++ b/EEDU-Frontend/src/app/settings/settings.component.html @@ -30,4 +30,4 @@

Feedback

Feedback
-


+


diff --git a/EEDU-Frontend/src/app/settings/settings.component.scss b/EEDU-Frontend/src/app/settings/settings.component.scss index 968c5aff..dc3a106b 100644 --- a/EEDU-Frontend/src/app/settings/settings.component.scss +++ b/EEDU-Frontend/src/app/settings/settings.component.scss @@ -9,6 +9,6 @@ } .feedback-button { - background-color: transparent; + background-color: var(--widget-color); color: var(--text-color); } diff --git a/EEDU-Frontend/src/styles.scss b/EEDU-Frontend/src/styles.scss index 55d8056c..c75fed33 100644 --- a/EEDU-Frontend/src/styles.scss +++ b/EEDU-Frontend/src/styles.scss @@ -104,6 +104,80 @@ mat-mdc-paginator { background-color: rgba(255, 255, 255, .1); } +.mat-calendar-table { + background-color: var(--widget-color) !important; +} + +.mat-calendar-content { + background-color: var(--widget-color) !important; +} + +.mat-calendar-header { + background-color: var(--widget-color) !important; +} + +.mat-calendar { + background-color: var(--widget-color) !important; +} + +.mat-calendar-controls { + color: var(--text-color) !important; +} + +.stepper-header { + background-color: var(--widget-color) !important; + color: var(--text-color) !important; +} + +.mat-step-header { + background-color: var(--widget-color) !important; + color: var(--text-color) !important; +} + +.ng-star-inserted { + color: var(--text-color) !important; +} + +.mat-mdc-card { + background-color: var(--widget-color) !important; +} +.mat-mdc-card-content { + background-color: var(--widget-color) !important; +} + +.mat-mdc-card-header { + background-color: var(--widget-color) !important; +} + +.mat-mdc-card-actions { + background-color: var(--widget-color) !important; + color: var(--text-color) !important; +} + +.mat-mdc-tab { + color: var(--text-color) !important; +} + +.mdc-tab__text-label { + color: var(--text-color) !important; +} + +// WHY TWO UNDERSCORES +.mdc-button__label { + color: var(--text-color); +} + +.mat-icon { + -webkit-text-fill-color: var(--text-color); +} + +.mat-vertical-content { + background-color: var(--widget-color) !important; +} + +.mat-step { + background-color: var(--widget-color) !important; +} textarea::placeholder { color: var(--text-color); } From 2fa2ba3f7c4452a928089354031e7590017aed2f Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Tue, 25 Mar 2025 20:07:14 +0100 Subject: [PATCH 24/28] Moved from post to put --- .../gaz/eedu/course/appointment/AppointmentController.java | 5 ++--- .../src/app/user/courses/appointment/appointment.service.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java index 60bba574..5deaea05 100644 --- a/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java +++ b/EEDU-Backend/src/main/java/de/gaz/eedu/course/appointment/AppointmentController.java @@ -66,10 +66,9 @@ public class AppointmentController extends EntityController setAppointment(@PathVariable long course, @RequestBody @NotNull AppointmentEntryCreateModel... createModel) + @PutMapping("/{course}/schedule/standalone") @PreAuthorize("hasRole('teacher') or hasRole('administrator')") + public @NotNull ResponseEntity scheduleAppointment(@PathVariable long course, @RequestBody @NotNull AppointmentEntryCreateModel... createModel) { - List createdEntities = getService().createAppointment(course, Set.of(createModel)); return ResponseEntity.ok(createdEntities.toArray(AppointmentEntryModel[]::new)); } diff --git a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts index 6186e812..4f6a4c13 100644 --- a/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts +++ b/EEDU-Frontend/src/app/user/courses/appointment/appointment.service.ts @@ -54,7 +54,7 @@ export class AppointmentService { */ public createAppointment(course: bigint, createModel: AppointmentCreateModel[]): Observable { const url = `${this.BACKEND_URL}/${course}/schedule/standalone` - return this.http.post(url, createModel.map((current: AppointmentCreateModel): { + return this.http.put(url, createModel.map((current: AppointmentCreateModel): { start: number, duration: number, description?: string, assignment?: AppointmentCreateModel } => current.toPacket), {withCredentials: true}).pipe(map((response: any[]): AppointmentEntryModel[] => response.map((item: any): AppointmentEntryModel => AppointmentEntryModel.fromObject(item))), tap({next: (response: AppointmentEntryModel[]): void => this.pushAppointment(response)})); } From 70fffbaadc4fc97eed1d61519e1d3f35fe71a8a5 Mon Sep 17 00:00:00 2001 From: SortyFix Date: Tue, 25 Mar 2025 20:28:24 +0100 Subject: [PATCH 25/28] Fixed login page --- .../user/authentication/authentication.component.html | 2 +- .../user/authentication/authentication.component.scss | 10 ++++++++++ EEDU-Frontend/src/styles.scss | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/EEDU-Frontend/src/app/user/authentication/authentication.component.html b/EEDU-Frontend/src/app/user/authentication/authentication.component.html index 88a4f65a..ef6eae46 100644 --- a/EEDU-Frontend/src/app/user/authentication/authentication.component.html +++ b/EEDU-Frontend/src/app/user/authentication/authentication.component.html @@ -1,5 +1,5 @@
- +
-
- {{ insight.submitted }} -
-
@@ -23,3 +19,5 @@
+ + diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts index 5a67ea7f..d5b0603b 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-student-view/assignment-student-view.component.ts @@ -9,6 +9,7 @@ import {MatIcon} from "@angular/material/icon"; import {AssignmentService} from "../../../../user/courses/appointment/entry/assignment/assignment.service"; import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; import {AssignmentModel} from "../../../../user/courses/appointment/entry/assignment/assignment-model"; +import {InsightListComponent} from "../insight-list/insight-list.component"; @Component({ selector: 'app-assignment-student-view', @@ -22,6 +23,7 @@ import {AssignmentModel} from "../../../../user/courses/appointment/entry/assign MatListItem, NgIf, MatIcon, + InsightListComponent, ], templateUrl: './assignment-student-view.component.html', styleUrl: './assignment-student-view.component.scss' @@ -34,8 +36,8 @@ export class AssignmentStudentViewComponent implements AfterViewInit { private _insight?: AssignmentInsightModel; - protected get insight(): AssignmentInsightModel | undefined { - return this._insight; + protected get insight(): AssignmentInsightModel | null { + return this._insight || null; } public constructor(private readonly _assignmentService: AssignmentService, form: FormBuilder) { diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html index e28fde36..888d2b46 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.html @@ -24,11 +24,8 @@
-
    - @for (file of currentInsight.files; track file) { -
  • {{ file }}
  • - } -
+ +
@@ -41,11 +38,6 @@
- -
-

{{ currentInsight.assessment.feedback }}

-
-
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts index 008f5b48..9f5cd2f3 100644 --- a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/assignment-teacher-view/assignment-teacher-view.component.ts @@ -17,11 +17,12 @@ import { AssessmentCreateModel } from "../../../../user/courses/appointment/entry/assignment/assessment/assessment-create-model"; import {GeneralErrorBoxComponent} from "../../../../common/general-error-box/general-error-box.component"; +import {InsightListComponent} from "../insight-list/insight-list.component"; @Component({ selector: 'app-assignment-teacher-view', standalone: true, - imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule, GeneralErrorBoxComponent], + imports: [NgIf, MatLabel, MatFormField, MatSelect, MatOption, MatIcon, MatButton, MatInput, ReactiveFormsModule, GeneralErrorBoxComponent, InsightListComponent], templateUrl: './assignment-teacher-view.component.html', styleUrl: './assignment-teacher-view.component.scss' }) diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.html b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.html new file mode 100644 index 00000000..afd7c9ee --- /dev/null +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.html @@ -0,0 +1,11 @@ +
+
    + @for (file of insight()!.files || []; track file) { +
  • {{ file }}
  • + } +
+ + +

{{ insight()!.assessment?.feedback }}

+
+
diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.scss b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.spec.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.spec.ts new file mode 100644 index 00000000..ec373c46 --- /dev/null +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InsightListComponent } from './insight-list.component'; + +describe('InsightListComponent', () => { + let component: InsightListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [InsightListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(InsightListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.ts b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.ts new file mode 100644 index 00000000..a7bd7eee --- /dev/null +++ b/EEDU-Frontend/src/app/timetable/event-data/assignment-tab/insight-list/insight-list.component.ts @@ -0,0 +1,18 @@ +import {Component, input, InputSignal} from '@angular/core'; +import {AssignmentInsightModel} from "../../../../user/courses/appointment/entry/assignment/assignment-insight-model"; +import {NgIf} from "@angular/common"; + +@Component({ + selector: 'app-insight-list', + imports: [ + NgIf + ], + templateUrl: './insight-list.component.html', + styleUrl: './insight-list.component.scss' +}) +export class InsightListComponent { + + public readonly insight: InputSignal = input(null); + + +} From c7b2e3dc0908eb04d281d0d22500bf077b9051e1 Mon Sep 17 00:00:00 2001 From: Ivo Quiring Date: Fri, 28 Mar 2025 09:25:43 +0100 Subject: [PATCH 28/28] Fixed some styles --- .../src/app/entity/entity-list/entity-list.component.scss | 1 - EEDU-Frontend/src/assets/styles/angular-calendar.scss | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.scss b/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.scss index e0c35129..3a2bfbab 100644 --- a/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.scss +++ b/EEDU-Frontend/src/app/entity/entity-list/entity-list.component.scss @@ -21,7 +21,6 @@ .mat-icon { transform: scale(4); padding: 30px; - -webkit-text-fill-color: var(--widget-color); } button { diff --git a/EEDU-Frontend/src/assets/styles/angular-calendar.scss b/EEDU-Frontend/src/assets/styles/angular-calendar.scss index 9df2e9e1..fa020d42 100644 --- a/EEDU-Frontend/src/assets/styles/angular-calendar.scss +++ b/EEDU-Frontend/src/assets/styles/angular-calendar.scss @@ -1,3 +1,4 @@ +@use '@angular/material' as mat; @import "angular-calendar/scss/angular-calendar"; .calendar-theme { @@ -60,4 +61,9 @@ } } +@include mat.card-overrides(( + subtitle-text-color: color-mix(in srgb, var(--text-color) 50%, transparent), + elevated-container-color: var(--background-color) +)); +