diff --git a/.editorconfig b/.editorconfig
index 01b5b05b..ce472594 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -16,8 +16,7 @@ ij_java_continuation_indent_size = 4
ij_java_class_count_to_use_import_on_demand = 999
ij_java_names_count_to_use_import_on_demand = 999
# Import order to match Checkstyle groups: java, javax, org, com
-ij_java_imports_layout = import java.*; import javax.*; import org.*; import com.*; blank_line; import all other imports; blank_line; import static all other static imports
+ij_java_imports_layout = $*,java.**,javax.**,org.**,com.**,*
[*.{yml,yaml}]
indent_size = 2
-
diff --git a/.github/workflows/deploy-application.yaml b/.github/workflows/deploy-application.yaml
new file mode 100644
index 00000000..fe65cddc
--- /dev/null
+++ b/.github/workflows/deploy-application.yaml
@@ -0,0 +1,30 @@
+name: deploy-to-k8s
+
+on:
+ push:
+ branches:
+ - "**"
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v5
+
+ - name: Build & Publish Docker image
+ id: build
+ uses: Agile-Software-Engineering-25/build-and-publish-image@v1.0.0
+ with:
+ # optional: set extra tags if needed
+ extra_tags: ${{ github.sha }}
+
+ - name: Deploy to Namespace
+ uses: Agile-Software-Engineering-25/deploy-to-k8s@v1.0.1
+ with:
+ kubeconfig: ${{ secrets.KUBECONFIG }}
+ namespace: ${{ vars.K8S_NAMESPACE }}
+ deployment: grading-service
+ image: ghcr.io/agile-software-engineering-25/team-12-backend-exagrad-lecturer-service
+ tag: ${{ github.sha }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..98af8c8a
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,65 @@
+name: CI - Test
+
+on:
+ push:
+ branches:
+ - '**'
+ pull_request:
+ branches:
+ - '**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-test-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ name: Test (Java ${{ matrix.java }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [ '21', '22' ]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Temurin JDK ${{ matrix.java }}
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+ cache: maven
+
+ - name: Show Java and Maven versions
+ run: |
+ java -version
+ mvn -v
+
+ - name: Build and run tests
+ run: mvn -B -ntp clean verify
+
+ - name: Upload test reports (Surefire)
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: surefire-reports-java-${{ matrix.java }}
+ path: |
+ **/target/surefire-reports/**
+ if-no-files-found: ignore
+ retention-days: 14
+
+ - name: Upload integration test reports (Failsafe)
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: failsafe-reports-java-${{ matrix.java }}
+ path: |
+ **/target/failsafe-reports/**
+ if-no-files-found: ignore
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index 7a6a576f..28564dcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,7 +31,7 @@ build/
### VS Code ###
.vscode/
-
+/data/
### MacOs ###
-*/.DS_STORE
\ No newline at end of file
+*/.DS_STORE
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..78dd770e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,22 @@
+# Stage 1: build the jar
+FROM maven:3.9.9-eclipse-temurin-21-alpine AS builder
+WORKDIR /app
+
+COPY pom.xml .
+RUN mvn dependency:go-offline -B
+
+COPY src src
+RUN mvn clean package -DskipTests -B
+
+# Stage 2: run the jar
+FROM eclipse-temurin:21-jre-alpine
+WORKDIR /app
+
+# Copy the fat jar (Spring Boot creates a runnable jar under target/)
+COPY --from=builder /app/target/*.jar app.jar
+RUN chown -R 1000:1000 /app
+
+# Run as non-root
+USER 1000:1000
+EXPOSE 8080
+ENTRYPOINT ["java","-jar","/app/app.jar"]
diff --git a/checkstyle.xml b/checkstyle.xml
index 0d7ee386..89298d65 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -73,7 +73,7 @@
-
+
@@ -143,9 +143,9 @@
-
+
-
+
diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml
new file mode 100644
index 00000000..9fe698cb
--- /dev/null
+++ b/k8s/deployment.yaml
@@ -0,0 +1,95 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: grading-service
+ namespace: ase-12
+ labels:
+ app.kubernetes.io/name: grading-service
+ app.kubernetes.io/instance: grading-service
+spec:
+ replicas: 2
+ revisionHistoryLimit: 2
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 0
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: grading-service
+ app.kubernetes.io/instance: grading-service
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: grading-service
+ app.kubernetes.io/instance: grading-service
+ spec:
+ serviceAccountName: default
+ terminationGracePeriodSeconds: 30
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 1000
+ runAsGroup: 1000
+ fsGroup: 1000
+ containers:
+ - name: app
+ image: ghcr.io/agile-software-engineering-25/team-12-backend-exagrad-lecturer-service:latest
+ imagePullPolicy: IfNotPresent
+ ports:
+ - name: http
+ containerPort: 8080
+ env:
+ # Enable graceful shutdown at app level (align with terminationGracePeriodSeconds)
+ - name: SERVER_SERVLET_CONTEXT_PATH
+ value: "/exa-grad/grading-service"
+ - name: SERVER_SHUTDOWN
+ value: "graceful"
+
+ readinessProbe:
+ httpGet:
+ path: /exa-grad/grading-service/actuator/health/readiness
+ port: 8080
+ initialDelaySeconds: 10
+ periodSeconds: 5
+ failureThreshold: 6
+ timeoutSeconds: 1
+ livenessProbe:
+ httpGet:
+ path: /exa-grad/grading-service/actuator/health/liveness
+ port: 8080
+ initialDelaySeconds: 20
+ periodSeconds: 10
+ failureThreshold: 3
+ timeoutSeconds: 1
+ startupProbe:
+ httpGet:
+ path: /exa-grad/grading-service/actuator/health
+ port: 8080
+ periodSeconds: 5
+ failureThreshold: 30
+
+ resources:
+ requests:
+ cpu: "250m"
+ memory: "512Mi"
+ limits:
+ cpu: "1"
+ memory: "1024Mi"
+
+ securityContext:
+ allowPrivilegeEscalation: false
+ readOnlyRootFilesystem: true # Immutable root FS → mount /tmp as writable
+ capabilities:
+ drop: [ "ALL" ]
+
+ volumeMounts:
+ - name: data
+ mountPath: /app/data
+ - name: tmp
+ mountPath: /tmp
+
+ volumes:
+ - name: tmp
+ emptyDir: { }
+ - name: data
+ emptyDir: { }
diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml
new file mode 100644
index 00000000..1dfa9064
--- /dev/null
+++ b/k8s/ingress.yaml
@@ -0,0 +1,23 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: grading-service
+ namespace: ase-12
+ annotations:
+ traefik.ingress.kubernetes.io/router.entrypoints: websecure # send traffic over HTTPS
+spec:
+ ingressClassName: traefik
+ tls:
+ - hosts: ["sau-portal.de"]
+ secretName: tls-to-come
+ rules:
+ - host: sau-portal.de
+ http:
+ paths:
+ - path: /exa-grad/grading-service # Public URL prefix for this service
+ pathType: Prefix
+ backend:
+ service:
+ name: grading-service
+ port:
+ number: 80
diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml
new file mode 100644
index 00000000..a67b402e
--- /dev/null
+++ b/k8s/kustomization.yaml
@@ -0,0 +1,7 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: ase-12 #e.g ase-1
+resources:
+ - deployment.yaml
+ - service.yaml
+ - ingress.yaml
diff --git a/k8s/service.yaml b/k8s/service.yaml
new file mode 100644
index 00000000..45b819cf
--- /dev/null
+++ b/k8s/service.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: grading-service
+ namespace: ase-12
+spec:
+ selector:
+ app.kubernetes.io/name: grading-service # Must match Deployment template labels
+ ports:
+ - name: http
+ port: 80
+ targetPort: 8080
diff --git a/pom.xml b/pom.xml
index e26b2f8a..e1073c4c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,16 +31,13 @@
- ch.bildspur
- artnet4j
- 0.6.2
+ org.springframework.boot
+ spring-boot-starter-actuator
-
org.springframework.boot
spring-boot-starter-web
-
org.projectlombok
lombok
@@ -65,4 +62,22 @@
runtime
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.ase.lecturerservice.Application
+
+
+
+
+ repackage
+
+
+
+
+
+
diff --git a/src/main/java/com/ase/userservice/Application.java b/src/main/java/com/ase/lecturerservice/Application.java
similarity index 91%
rename from src/main/java/com/ase/userservice/Application.java
rename to src/main/java/com/ase/lecturerservice/Application.java
index fecb641b..2bfb7667 100644
--- a/src/main/java/com/ase/userservice/Application.java
+++ b/src/main/java/com/ase/lecturerservice/Application.java
@@ -1,4 +1,4 @@
-package com.ase.userservice;
+package com.ase.lecturerservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
diff --git a/src/main/java/com/ase/userservice/components/.gitkeep b/src/main/java/com/ase/lecturerservice/components/.gitkeep
similarity index 100%
rename from src/main/java/com/ase/userservice/components/.gitkeep
rename to src/main/java/com/ase/lecturerservice/components/.gitkeep
diff --git a/src/main/java/com/ase/lecturerservice/config/CorsConfig.java b/src/main/java/com/ase/lecturerservice/config/CorsConfig.java
new file mode 100644
index 00000000..9bc320e6
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/config/CorsConfig.java
@@ -0,0 +1,47 @@
+package com.ase.lecturerservice.config;
+
+import java.util.Arrays;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Configuration
+@RequiredArgsConstructor
+@EnableConfigurationProperties(CorsConfig.CorsConfigurationProperties.class)
+public class CorsConfig {
+
+ private final CorsConfigurationProperties corsConfigurationProperties;
+
+ @Bean
+ CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration corsConfiguration = new CorsConfiguration();
+ corsConfiguration.addAllowedHeader("*");
+ corsConfiguration.addAllowedMethod("*");
+ corsConfiguration.setAllowCredentials(true);
+ Arrays.stream(corsConfigurationProperties.allowedOrigins)
+ .forEach(corsConfiguration::addAllowedOrigin);
+ log.info("Allowed origins:");
+ Arrays.stream(corsConfigurationProperties.allowedOrigins).forEach(System.out::println);
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", corsConfiguration);
+ return source;
+ }
+
+ @Bean
+ CorsFilter corsFilter() {
+ return new CorsFilter(corsConfigurationSource());
+ }
+
+ @ConfigurationProperties(prefix = "app.cors")
+ record CorsConfigurationProperties(String[] allowedOrigins) {
+
+ }
+}
diff --git a/src/main/java/com/ase/lecturerservice/controllers/BaseController.java b/src/main/java/com/ase/lecturerservice/controllers/BaseController.java
new file mode 100644
index 00000000..ca0e250e
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/controllers/BaseController.java
@@ -0,0 +1,5 @@
+package com.ase.lecturerservice.controllers;
+
+public interface BaseController {
+ String BASE_PATH = "/api/v1";
+}
diff --git a/src/main/java/com/ase/lecturerservice/controllers/ExamController.java b/src/main/java/com/ase/lecturerservice/controllers/ExamController.java
new file mode 100644
index 00000000..e16cc6f2
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/controllers/ExamController.java
@@ -0,0 +1,37 @@
+package com.ase.lecturerservice.controllers;
+
+import static com.ase.lecturerservice.controllers.BaseController.BASE_PATH;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.ase.lecturerservice.dtos.ExamDto;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.services.ExamService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RestController
+@RequestMapping(BASE_PATH + "/exams")
+@RequiredArgsConstructor
+public class ExamController {
+ private final ExamService examService;
+ private final ObjectMapper objectMapper;
+
+ @GetMapping
+ public ResponseEntity> getExams(@RequestParam String lecturerUuid)
+ throws IllegalArgumentException {
+ List exams = examService.getExamsByLecturer(lecturerUuid);
+
+ List examDtoList = exams.stream()
+ .map(exam -> objectMapper.convertValue(exam, ExamDto.class))
+ .collect(Collectors.toList());
+
+ return ResponseEntity.ok(examDtoList);
+ }
+}
diff --git a/src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java b/src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java
new file mode 100644
index 00000000..1e74eec2
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/controllers/FeedbackController.java
@@ -0,0 +1,32 @@
+package com.ase.lecturerservice.controllers;
+
+import static com.ase.lecturerservice.controllers.BaseController.BASE_PATH;
+import java.util.List;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.ase.lecturerservice.entities.Feedback;
+import com.ase.lecturerservice.services.FeedbackService;
+import lombok.RequiredArgsConstructor;
+
+@RestController
+@RequestMapping(BASE_PATH + "/feedback")
+@RequiredArgsConstructor
+public class FeedbackController {
+ private final FeedbackService feedbackService;
+
+ @GetMapping
+ public ResponseEntity getFeedbackFromExam(
+ @RequestParam String examUuid, @RequestParam String studentUuid) {
+ return ResponseEntity.ok(feedbackService.getFeedbackExam(studentUuid, examUuid));
+ }
+
+ @GetMapping("/for-lecturer/{lecturerUuid}")
+ public ResponseEntity> getFeedbacksForLecturer(@PathVariable String lecturerUuid) {
+ return ResponseEntity.ok(feedbackService.getFeedbackForLecturer(lecturerUuid));
+ }
+
+}
diff --git a/src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java b/src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java
new file mode 100644
index 00000000..df688e95
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/controllers/SubmissionController.java
@@ -0,0 +1,29 @@
+package com.ase.lecturerservice.controllers;
+
+import static com.ase.lecturerservice.controllers.BaseController.BASE_PATH;
+import java.util.List;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ase.lecturerservice.entities.Submission;
+import com.ase.lecturerservice.services.SubmissionService;
+import lombok.RequiredArgsConstructor;
+
+@RestController
+@RequestMapping(BASE_PATH + "/submissions")
+@RequiredArgsConstructor
+public class SubmissionController {
+
+ private final SubmissionService submissionService;
+
+ @GetMapping("/for-lecturer/{lecturerUuid}")
+ public ResponseEntity> getRelevantSubmissions(
+ @PathVariable String lecturerUuid
+ ) {
+ return ResponseEntity.ok(
+ submissionService.getAllAccessibleSubmissionsForLecturer(lecturerUuid)
+ );
+ }
+}
diff --git a/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java
new file mode 100644
index 00000000..b0db3be9
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/dtos/ExamDto.java
@@ -0,0 +1,37 @@
+package com.ase.lecturerservice.dtos;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.antlr.v4.runtime.misc.NotNull;
+import com.ase.lecturerservice.entities.ExamType;
+import com.ase.lecturerservice.entities.user.Student;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ExamDto {
+ @NotNull
+ private String uuid;
+
+ @NotNull
+ private String name;
+
+ @NotNull
+ private LocalDate date;
+
+ @NotNull
+ private String module;
+
+ @NotNull
+ private ExamType examType;
+
+ @NotNull
+ private List assignedStudents;
+
+ @NotNull
+ private int time;
+
+ @NotNull
+ private int totalPoints;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/Exam.java b/src/main/java/com/ase/lecturerservice/entities/Exam.java
new file mode 100644
index 00000000..55ebf302
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/Exam.java
@@ -0,0 +1,43 @@
+package com.ase.lecturerservice.entities;
+
+import java.time.LocalDate;
+import java.util.List;
+import com.ase.lecturerservice.entities.user.Student;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Setter
+@Getter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Exam {
+ private String uuid;
+
+ private String name;
+
+ private int totalPoints;
+
+ private ExamType examType;
+
+ private LocalDate date;
+
+ private int time;
+
+ private String allowedResources;
+
+ private int attempt;
+
+ private int etcs;
+
+ private String room;
+
+ private String module;
+
+ private String lecturerUuid;
+
+ private List assignedStudents;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/ExamType.java b/src/main/java/com/ase/lecturerservice/entities/ExamType.java
new file mode 100644
index 00000000..2f441523
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/ExamType.java
@@ -0,0 +1,9 @@
+package com.ase.lecturerservice.entities;
+
+public enum ExamType {
+ EXAM,
+ PRESENTATION,
+ ORAL,
+ PROJECT,
+ OTHERS
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/Feedback.java b/src/main/java/com/ase/lecturerservice/entities/Feedback.java
new file mode 100644
index 00000000..c3d710a8
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/Feedback.java
@@ -0,0 +1,69 @@
+package com.ase.lecturerservice.entities;
+
+import java.time.LocalDate;
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.persistence.CollectionTable;
+import jakarta.persistence.Column;
+import jakarta.persistence.ElementCollection;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Builder
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+public class Feedback {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String uuid;
+
+ @Column(name = "graded_at")
+ private LocalDate gradedAt;
+
+ @Column(name = "graded_exam")
+ @JsonProperty("examUuid")
+ private String examUuid;
+
+ @Column(name = "graded_by")
+ @JsonProperty("lecturerUuid")
+ private String lecturerUuid;
+
+ @Column(name = "grade_for")
+ @JsonProperty("studentUuid")
+ private String studentUuid;
+
+ @Column(name = "graded_submission")
+ @JsonProperty("submissionUuid")
+ private String submissionUuid;
+
+ @Column(name = "comment")
+ @JsonProperty("comment")
+ private String comment;
+
+ @ElementCollection
+ @JsonProperty("fileReference")
+ @CollectionTable(
+ name = "grade_file_references",
+ joinColumns = @JoinColumn(name = "grade_id")
+ )
+ private List fileReference;
+
+ @Column(name = "points")
+ @JsonProperty("points")
+ private int points;
+
+ @Column(name = "grade")
+ @JsonProperty("grade")
+ private float grade;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/FileReference.java b/src/main/java/com/ase/lecturerservice/entities/FileReference.java
new file mode 100644
index 00000000..db1544c3
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/FileReference.java
@@ -0,0 +1,21 @@
+package com.ase.lecturerservice.entities;
+
+import jakarta.persistence.Embeddable;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@SuperBuilder
+@Embeddable
+public class FileReference {
+ private String fileUuid;
+
+ private String filename;
+
+ private String downloadLink;
+
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/Submission.java b/src/main/java/com/ase/lecturerservice/entities/Submission.java
new file mode 100644
index 00000000..ecb9cd1c
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/Submission.java
@@ -0,0 +1,14 @@
+package com.ase.lecturerservice.entities;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class Submission {
+ private String id;
+ private String examId;
+ private String studentId;
+ private String submissionDate;
+ private FileReference fileUpload;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java b/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java
new file mode 100644
index 00000000..df34aeea
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/user/Lecturer.java
@@ -0,0 +1,15 @@
+package com.ase.lecturerservice.entities.user;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@SuperBuilder
+public class Lecturer extends UserEntity {
+
+ private final UserType type = UserType.LECTURER;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/user/Student.java b/src/main/java/com/ase/lecturerservice/entities/user/Student.java
new file mode 100644
index 00000000..844f5728
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/user/Student.java
@@ -0,0 +1,19 @@
+package com.ase.lecturerservice.entities.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class Student extends UserEntity {
+
+ private String matriculationNumber;
+
+ private UserType type = UserType.STUDENT;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java b/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java
new file mode 100644
index 00000000..3a4e05b4
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/user/UserEntity.java
@@ -0,0 +1,30 @@
+package com.ase.lecturerservice.entities.user;
+
+import org.hibernate.annotations.UuidGenerator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public abstract class UserEntity {
+ protected String email;
+
+ @UuidGenerator
+ private String uuid;
+
+ @JsonProperty("first_name")
+ private String firstName;
+
+ @JsonProperty("last_name")
+ private String lastName;
+
+ @JsonProperty("type")
+ private UserType type;
+}
diff --git a/src/main/java/com/ase/lecturerservice/entities/user/UserType.java b/src/main/java/com/ase/lecturerservice/entities/user/UserType.java
new file mode 100644
index 00000000..ead31a66
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/entities/user/UserType.java
@@ -0,0 +1,6 @@
+package com.ase.lecturerservice.entities.user;
+
+public enum UserType {
+ LECTURER,
+ STUDENT
+}
diff --git a/src/main/java/com/ase/userservice/config/.gitkeep b/src/main/java/com/ase/lecturerservice/exception/.gitkeep
similarity index 100%
rename from src/main/java/com/ase/userservice/config/.gitkeep
rename to src/main/java/com/ase/lecturerservice/exception/.gitkeep
diff --git a/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java b/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java
new file mode 100644
index 00000000..bd82db89
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/exception/CustomExceptionHandler.java
@@ -0,0 +1,31 @@
+package com.ase.lecturerservice.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.resource.NoResourceFoundException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ControllerAdvice
+public class CustomExceptionHandler {
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity handleIllegalArgumentException(
+ IllegalArgumentException illegalArgumentException) {
+ return ResponseEntity.badRequest().body(illegalArgumentException.getMessage());
+ }
+
+ @ExceptionHandler(NoResourceFoundException.class)
+ public ResponseEntity handleNoResourceFoundException(
+ NoResourceFoundException noResourceFoundException
+ ) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("This page could not be found");
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity internalErrorHandler(Exception exception) {
+ log.error(exception.getMessage(), exception);
+ return ResponseEntity.internalServerError().body("Internal Server Error");
+ }
+}
diff --git a/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java b/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java
new file mode 100644
index 00000000..5bd35854
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/mockvalues/MockValues.java
@@ -0,0 +1,76 @@
+package com.ase.lecturerservice.mockvalues;
+
+import lombok.Getter;
+
+public class MockValues {
+
+ @Getter
+ public enum UuidMocks {
+ EXAM_UUID("550e8400-e29b-41d4-a716-446655440000"),
+ EXAM_UUID2("550e8400-e29b-41d4-a716-446655440001"),
+ EXAM_UUID3("550e8400-e29b-41d4-a716-446655440002"),
+ EXAM_UUID4("550e8400-e29b-41d4-a716-446655440003"),
+ EXAM_UUID5("550e8400-e29b-41d4-a716-446655440004"),
+ EXAM_UUID6("550e8400-e29b-41d4-a716-446655440005"),
+
+ STUDENT_UUID("d1c27c4f-e7d7-45b8-bc4e-6f634e7c5e8f"),
+ STUDENT_UUID2("f2a26e3f-3b50-44ac-a7f9-02fe3b41cf6a"),
+ STUDENT_UUID3("7283a092-2b64-4bfa-bf92-4242448b740a"),
+ STUDENT_UUID4("a9f5d8b5-2632-42b5-8520-1db4010fc80d"),
+ STUDENT_UUID5("be7f4234-cd28-4b29-9b09-5d1a38d3c67a"),
+
+ GRADE_UUID("ea3f2b67-5ed0-4d89-bc2c-28533a210ae2"),
+ GRADE_UUID2("27d211f8-e45e-4f5a-b264-e7b4f51e8f95"),
+ GRADE_UUID3("cc28b1f6-3b5b-44e1-963f-0793b742a6d4"),
+ GRADE_UUID4("23428b1f6-3b5b-44e1-963f-0793b742a53"),
+ GRADE_UUID5("5318b1fs6-3b5b-46e1-963f-079dw742a53"),
+ GRADE_UUID6("5318b1fs6-a6ds-46e1-963f-079dw742a53"),
+ GRADE_UUID7("ac28b1f6-3b5b-44e1-963f-9033b742a6d4"),
+
+ LECTURER_UUID("12345678-62hj-jhj2-h23j-901234567890"),
+ LECTURER_UUID2("3f8a9c12-7b4e-4d21-9c8a-2e6b7d9f1a23"),
+ LECTURER_UUID3("c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"),
+ LECTURER_UUID4("a0b1c2d3-e4f5-4a67-8b9c-d0e1f2a3b4c5"),
+ LECTURER_UUID5("9d8c7b6a-5e4f-4a3b-9c8d-7e6f5a4b3c2d"),
+ LECTURER_UUID6("f0e1d2c3-b4a5-49c6-8d7e-6f5a4b3c2d1e");
+
+
+ private final String value;
+
+ UuidMocks(String value) {
+ this.value = value;
+ }
+ }
+
+ @Getter
+ public enum FloatMocks {
+ GRADE(1.3f),
+ AVERAGE_GRADE(2.0f);
+
+ private final float value;
+
+ FloatMocks(float value) {
+ this.value = value;
+ }
+ }
+
+ @Getter
+ public enum IntMocks {
+ SUBMISSIONS(0),
+ TOTAL_POINTS(100),
+ ACHIEVED_POINTS(95),
+ TIME_SECONDS(5400),
+ TIME_MIN(90),
+ ATTEMPT(1),
+ ETCS(5),
+ DATE_YEAR(2015),
+ DATE_MONTH(10),
+ DATE_DAY(25);
+
+ private final int value;
+
+ IntMocks(int value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/src/main/java/com/ase/userservice/entities/.gitkeep b/src/main/java/com/ase/lecturerservice/repositories/.gitkeep
similarity index 100%
rename from src/main/java/com/ase/userservice/entities/.gitkeep
rename to src/main/java/com/ase/lecturerservice/repositories/.gitkeep
diff --git a/src/main/java/com/ase/lecturerservice/services/DummyData.java b/src/main/java/com/ase/lecturerservice/services/DummyData.java
new file mode 100644
index 00000000..2820173c
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/services/DummyData.java
@@ -0,0 +1,262 @@
+package com.ase.lecturerservice.services;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.UUID;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.entities.ExamType;
+import com.ase.lecturerservice.entities.Feedback;
+import com.ase.lecturerservice.entities.FileReference;
+import com.ase.lecturerservice.entities.Submission;
+import com.ase.lecturerservice.entities.user.Student;
+import com.ase.lecturerservice.mockvalues.MockValues;
+
+public class DummyData {
+ public static List SUBMISSIONS = List.of(
+ Submission.builder()
+ .studentId(MockValues.UuidMocks.STUDENT_UUID.getValue())
+ .examId(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .submissionDate("2025-09-01T10:30:00Z")
+ .fileUpload(FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("submission1.pdf")
+ .downloadLink("https://example.com/submission1.pdf")
+ .build())
+ .build(),
+ Submission.builder()
+ .studentId(MockValues.UuidMocks.STUDENT_UUID2.getValue())
+ .examId(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .submissionDate("2025-09-01T11:10:00Z")
+ .fileUpload(FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("submission2.pdf")
+ .downloadLink("https://example.com/submission2.pdf")
+ .build())
+ .build(),
+ Submission.builder()
+ .studentId(MockValues.UuidMocks.STUDENT_UUID3.getValue())
+ .examId(MockValues.UuidMocks.EXAM_UUID2.getValue())
+ .submissionDate("2025-09-02T09:15:00Z")
+ .fileUpload(FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("physics_midterm_attempt1.zip")
+ .downloadLink("https://example.com/physics_midterm_attempt1.zip")
+ .build())
+ .build(),
+ Submission.builder()
+ .studentId(MockValues.UuidMocks.STUDENT_UUID.getValue())
+ .examId(MockValues.UuidMocks.EXAM_UUID6.getValue())
+ .submissionDate("2025-09-03T14:45:00Z")
+ .fileUpload(FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("software_project_demo.mp4")
+ .downloadLink("https://example.com/software_project_demo.mp4")
+ .build())
+ .build(),
+ Submission.builder()
+ .studentId(MockValues.UuidMocks.STUDENT_UUID2.getValue())
+ .examId(MockValues.UuidMocks.EXAM_UUID4.getValue())
+ .submissionDate("2025-09-04T08:05:00Z")
+ .fileUpload(FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("chemistry_lab_report.docx")
+ .downloadLink("https://example.com/chemistry_lab_report.docx")
+ .build())
+ .build()
+ );
+ static LocalDate date = LocalDate.of(
+ MockValues.IntMocks.DATE_YEAR.getValue(),
+ MockValues.IntMocks.DATE_MONTH.getValue(),
+ MockValues.IntMocks.DATE_DAY.getValue());
+ static List studentList = List.of(
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID.getValue()).matriculationNumber("D725").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID2.getValue()).matriculationNumber("D755").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID3.getValue()).matriculationNumber("D735").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID4.getValue()).matriculationNumber("D729").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID5.getValue()).matriculationNumber("D726").build()
+ );
+ static List studentList2 = List.of(
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID.getValue()).matriculationNumber("D725").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID2.getValue()).matriculationNumber("D755").build()
+ );
+ public static List EXAMS = List.of(
+ Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .name("Mathematics Final Exam")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.PRESENTATION)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Calculator, Formula Sheet")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Room A101")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue())
+ .module("Mathe")
+ .assignedStudents(studentList)
+ .build(),
+ Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID2.getValue())
+ .name("Physics Midterm")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.EXAM)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("None")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Room B202")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue())
+ .module("Physics")
+ .assignedStudents(studentList2)
+ .build(),
+ Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID3.getValue())
+ .name("Computer Science Project")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.OTHERS)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Laptop, IDE")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Online Submission")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID2.getValue())
+ .module("CS I")
+ .assignedStudents(studentList)
+ .build(),
+ Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID4.getValue())
+ .name("Chemistry Lab Exam")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.ORAL)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Lab Equipment, Safety Manual")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Online Submission")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID2.getValue())
+ .module("Chemistry")
+ .assignedStudents(studentList2)
+ .build(),
+ Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID6.getValue())
+ .name("Software Development Project")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.PRESENTATION)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Notes, Textbook")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Room C303")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID2.getValue())
+ .assignedStudents(studentList2)
+ .module("History I")
+ .build()
+ );
+ static List fileReferencesList = List.of(
+ FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("dummy_file")
+ .build(),
+ FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename("dummy_file2")
+ .build()
+ );
+ public static List Feedbacks = List.of(
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .comment("Excellent work on the assignment.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build(),
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID2.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID2.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .comment("Great effort! Check feedback in files.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build(),
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID3.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID3.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .comment("Incomplete submission. Please review guidelines.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build(),
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID4.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID6.getValue())
+ .comment("Great effort! Check feedback in files.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build(),
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID5.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID2.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID6.getValue())
+ .comment("Great effort! Check feedback in files.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build(),
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID6.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID2.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID2.getValue())
+ .comment("Great effort! Check feedback in files.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build(),
+ Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID7.getValue())
+ .gradedAt(date)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID4.getValue())
+ .comment("Great effort! Check feedback in files.")
+ .fileReference(fileReferencesList)
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build()
+ );
+}
diff --git a/src/main/java/com/ase/lecturerservice/services/ExamService.java b/src/main/java/com/ase/lecturerservice/services/ExamService.java
new file mode 100644
index 00000000..e371f398
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/services/ExamService.java
@@ -0,0 +1,28 @@
+package com.ase.lecturerservice.services;
+
+import java.util.List;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+import com.ase.lecturerservice.entities.Exam;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ExamService {
+
+ public List getExamsByLecturer(String lecturerUuid) {
+ if (lecturerUuid == null || lecturerUuid.isBlank()) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
+ "Lecturer name is required");
+ }
+
+ // TODO: change this to a webclient call, when the API is ready
+ log.info("The Exams from {} has been requested", lecturerUuid);
+ return DummyData.EXAMS.stream()
+ .filter(exam -> exam.getLecturerUuid().equals(lecturerUuid))
+ .toList();
+ }
+}
diff --git a/src/main/java/com/ase/lecturerservice/services/FeedbackService.java b/src/main/java/com/ase/lecturerservice/services/FeedbackService.java
new file mode 100644
index 00000000..fc6efdbe
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/services/FeedbackService.java
@@ -0,0 +1,38 @@
+package com.ase.lecturerservice.services;
+
+import java.util.List;
+import java.util.Optional;
+import org.springframework.stereotype.Service;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.entities.Feedback;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FeedbackService {
+
+ // TODO: Adjust this when saving data is implemented
+ public Feedback getFeedbackExam(String studentUuid, String examUuid) {
+ return DummyData.Feedbacks.stream()
+ .filter(feedback -> feedback.getStudentUuid().equals(studentUuid)
+ && feedback.getExamUuid().equals(examUuid))
+ .findFirst()
+ .orElse(null);
+ }
+
+ public List getFeedbackForLecturer(String lecturerUuid) {
+ return DummyData.Feedbacks.stream().filter(feedback ->
+ Optional.ofNullable(getExam(feedback.getExamUuid()))
+ .map(exam -> exam.getLecturerUuid().equals(lecturerUuid))
+ .orElse(false)
+ ).toList();
+ }
+
+ public Exam getExam(String uuid) {
+ return DummyData.EXAMS.stream()
+ .filter(exam -> exam.getUuid().equals(uuid))
+ .findFirst().orElse(null);
+ }
+}
diff --git a/src/main/java/com/ase/lecturerservice/services/SubmissionService.java b/src/main/java/com/ase/lecturerservice/services/SubmissionService.java
new file mode 100644
index 00000000..68985294
--- /dev/null
+++ b/src/main/java/com/ase/lecturerservice/services/SubmissionService.java
@@ -0,0 +1,40 @@
+package com.ase.lecturerservice.services;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.springframework.stereotype.Service;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.entities.Submission;
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class SubmissionService {
+
+ private final ExamService examService;
+
+ public List getSubmissionsForExam(String examId) {
+ return DummyData.SUBMISSIONS.stream()
+ .filter(submission -> submission.getExamId().equals(examId))
+ .toList();
+ }
+
+ public List getSubmissionsForStudent(String studentId) {
+ return DummyData.SUBMISSIONS.stream()
+ .filter(submission -> submission.getStudentId().equals(studentId))
+ .toList();
+ }
+
+ public List getAllAccessibleSubmissionsForLecturer(String lecturerUuid) {
+ Set examsOfLecturer = examService.getExamsByLecturer(lecturerUuid)
+ .stream()
+ .map(Exam::getUuid)
+ .collect(Collectors.toSet());
+
+ return DummyData.SUBMISSIONS.stream()
+ .filter(submission -> examsOfLecturer.contains(submission.getExamId()))
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/com/ase/userservice/controllers/RootController.java b/src/main/java/com/ase/userservice/controllers/RootController.java
deleted file mode 100644
index 34e21cb6..00000000
--- a/src/main/java/com/ase/userservice/controllers/RootController.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.ase.userservice.controllers;
-
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-public class RootController {
-
- @GetMapping("/")
- public ResponseEntity root() {
- return ResponseEntity.ok("API Root: /api/v1/");
- }
-
-}
diff --git a/src/main/java/com/ase/userservice/services/.gitkeep b/src/main/java/com/ase/userservice/services/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 4b5711b9..3965cd82 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -17,3 +17,18 @@ spring:
server:
error:
include-message: always
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health,readiness,liveness,prometheus
+ base-path: /actuator
+ endpoint:
+ health:
+ probes:
+ enabled: true
+app:
+ cors:
+ allowed-origins:
+ - http://localhost:5173
+ - https://sau-portal.de
diff --git a/src/test/java/com/ase/userservice/ApplicationTests.java b/src/test/java/com/ase/lecturerservice/ApplicationTests.java
similarity index 67%
rename from src/test/java/com/ase/userservice/ApplicationTests.java
rename to src/test/java/com/ase/lecturerservice/ApplicationTests.java
index be5b4249..688544d4 100644
--- a/src/test/java/com/ase/userservice/ApplicationTests.java
+++ b/src/test/java/com/ase/lecturerservice/ApplicationTests.java
@@ -1,4 +1,4 @@
-package com.ase.userservice;
+package com.ase.lecturerservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@@ -6,8 +6,8 @@
@SpringBootTest
class ApplicationTests {
- @Test
- void contextLoads() {
- }
+ @Test
+ void contextLoads() {
+ }
}
diff --git a/src/test/java/com/ase/lecturerservice/MockValues.java b/src/test/java/com/ase/lecturerservice/MockValues.java
new file mode 100644
index 00000000..aeb7364e
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/MockValues.java
@@ -0,0 +1,71 @@
+package com.ase.lecturerservice;
+
+import lombok.Getter;
+
+public class MockValues {
+
+ @Getter
+ public enum UuidMocks {
+ EXAM_UUID("550e8400-e29b-41d4-a716-446655440000"),
+ EXAM_UUID2("550e8400-e29b-41d4-a716-446655440001"),
+ EXAM_UUID3("550e8400-e29b-41d4-a716-446655440002"),
+ EXAM_UUID4("550e8400-e29b-41d4-a716-446655440003"),
+ EXAM_UUID5("550e8400-e29b-41d4-a716-446655440004"),
+
+ STUDENT_UUID("d1c27c4f-e7d7-45b8-bc4e-6f634e7c5e8f"),
+ STUDENT_UUID2("f2a26e3f-3b50-44ac-a7f9-02fe3b41cf6a"),
+ STUDENT_UUID3("7283a092-2b64-4bfa-bf92-4242448b740a"),
+ STUDENT_UUID4("a9f5d8b5-2632-42b5-8520-1db4010fc80d"),
+ STUDENT_UUID5("be7f4234-cd28-4b29-9b09-5d1a38d3c67a"),
+
+ GRADE_UUID("ea3f2b67-5ed0-4d89-bc2c-28533a210ae2"),
+ GRADE_UUID2("27d211f8-e45e-4f5a-b264-e7b4f51e8f95"),
+ GRADE_UUID3("cc28b1f6-3b5b-44e1-963f-0793b742a6d4"),
+
+ LECTURER_UUID("12345678-62hj-jhj2-h23j-901234567890"),
+ LECTURER_UUID2("3f8a9c12-7b4e-4d21-9c8a-2e6b7d9f1a23"),
+ LECTURER_UUID3("c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"),
+ LECTURER_UUID4("a0b1c2d3-e4f5-4a67-8b9c-d0e1f2a3b4c5"),
+ LECTURER_UUID5("9d8c7b6a-5e4f-4a3b-9c8d-7e6f5a4b3c2d"),
+ LECTURER_UUID6("f0e1d2c3-b4a5-49c6-8d7e-6f5a4b3c2d1e");
+
+
+ private final String value;
+
+ UuidMocks(String value) {
+ this.value = value;
+ }
+ }
+
+ @Getter
+ public enum FloatMocks {
+ GRADE(1.3f),
+ AVERAGE_GRADE(2.0f);
+
+ private final float value;
+
+ FloatMocks(float value) {
+ this.value = value;
+ }
+ }
+
+ @Getter
+ public enum IntMocks {
+ SUBMISSIONS(0),
+ TOTAL_POINTS(100),
+ ACHIEVED_POINTS(95),
+ TIME_SECONDS(5400),
+ TIME_MIN(90),
+ ATTEMPT(1),
+ ETCS(5),
+ DATE_YEAR(2015),
+ DATE_MONTH(10),
+ DATE_DAY(25);
+
+ private final int value;
+
+ IntMocks(int value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java
new file mode 100644
index 00000000..7a7dfbc7
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/controllers/ExamControllerTest.java
@@ -0,0 +1,100 @@
+package com.ase.lecturerservice.controllers;
+
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.time.LocalDate;
+import java.util.List;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import com.ase.lecturerservice.MockValues;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.entities.ExamType;
+import com.ase.lecturerservice.entities.user.Student;
+import com.ase.lecturerservice.services.ExamService;
+
+@ExtendWith(SpringExtension.class)
+@WebMvcTest(ExamController.class)
+public class ExamControllerTest {
+ private static final List STUDENT_LIST = List.of(
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID.getValue()).matriculationNumber("D725").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID2.getValue()).matriculationNumber("D755").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID3.getValue()).matriculationNumber("D735").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID4.getValue()).matriculationNumber("D729").build(),
+ Student.builder().uuid(
+ MockValues.UuidMocks.STUDENT_UUID5.getValue()).matriculationNumber("D726").build()
+ );
+ private static Exam exam;
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockitoBean
+ private ExamService examService;
+
+ @BeforeAll
+ public static void setup() {
+ LocalDate date = LocalDate.of(
+ MockValues.IntMocks.DATE_YEAR.getValue(),
+ MockValues.IntMocks.DATE_MONTH.getValue(),
+ MockValues.IntMocks.DATE_DAY.getValue());
+
+ exam = Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .name("Test")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.EXAM)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Calculator, Formula Sheet")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Room A101")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue())
+ .module("Test")
+ .assignedStudents(STUDENT_LIST)
+ .build();
+ }
+
+ @Test
+ void fetchExamsShouldReturnExamDtos() throws Exception {
+ when(examService.getExamsByLecturer("Tom")).thenReturn(List.of(exam));
+
+ mockMvc.perform(get("/api/v1/exams?lecturerUuid={lecturerUuid}", "Tom")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[0].uuid")
+ .value(MockValues.UuidMocks.EXAM_UUID.getValue()))
+ .andExpect(jsonPath("$[0].name").value("Test"))
+ .andExpect(jsonPath("$[0].date").value("2015-10-25"))
+ .andExpect(jsonPath("$[0].module").value("Test"))
+ .andExpect(jsonPath("$[0].time").value(MockValues.IntMocks.TIME_SECONDS.getValue()))
+ .andExpect(jsonPath("$[0].examType").value(ExamType.EXAM.toString()))
+ .andExpect(jsonPath("$[0].assignedStudents").isNotEmpty());
+ }
+
+ @Test
+ void fetchExamsShouldThrowException() throws Exception {
+ when(examService.getExamsByLecturer(" "))
+ .thenThrow(new IllegalArgumentException("Lecturer cannot be empty"));
+
+ mockMvc.perform(get("/api/v1/exams?lecturerUuid={lecturerUuid}", " ")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isBadRequest())
+ .andExpect(content().string("Lecturer cannot be empty"));
+ }
+}
diff --git a/src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java
new file mode 100644
index 00000000..c2863771
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/controllers/FeedbackControllerTest.java
@@ -0,0 +1,71 @@
+package com.ase.lecturerservice.controllers;
+
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.time.LocalDate;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import com.ase.lecturerservice.MockValues;
+import com.ase.lecturerservice.entities.Feedback;
+import com.ase.lecturerservice.services.FeedbackService;
+
+@ExtendWith(SpringExtension.class)
+@WebMvcTest(FeedbackController.class)
+public class FeedbackControllerTest {
+ private static final LocalDate DATE = LocalDate.of(
+ MockValues.IntMocks.DATE_YEAR.getValue(),
+ MockValues.IntMocks.DATE_MONTH.getValue(),
+ MockValues.IntMocks.DATE_DAY.getValue());
+ private static Feedback feedback;
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockitoBean
+ private FeedbackService feedbackService;
+
+ @BeforeAll
+ public static void setup() {
+ feedback = Feedback.builder()
+ .uuid(MockValues.UuidMocks.GRADE_UUID.getValue())
+ .gradedAt(DATE)
+ .lecturerUuid(UUID.randomUUID().toString())
+ .studentUuid(MockValues.UuidMocks.STUDENT_UUID.getValue())
+ .submissionUuid(UUID.randomUUID().toString())
+ .examUuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .comment("Excellent work on the assignment.")
+ .points(MockValues.IntMocks.ACHIEVED_POINTS.getValue())
+ .grade(MockValues.FloatMocks.GRADE.getValue())
+ .build();
+ }
+
+ @Test
+ void getGradedExamShouldReturnGradeWhenValidUuids() throws Exception {
+ String studentUuid = MockValues.UuidMocks.STUDENT_UUID.getValue();
+ String examUuid = MockValues.UuidMocks.EXAM_UUID.getValue();
+
+ when(feedbackService.getFeedbackExam(studentUuid, examUuid))
+ .thenReturn(feedback);
+
+ mockMvc.perform(get("/api/v1/feedback")
+ .param("studentUuid", studentUuid)
+ .param("examUuid", examUuid)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.gradedAt").value(DATE.toString()))
+ .andExpect(jsonPath("$.studentUuid").value(studentUuid))
+ .andExpect(jsonPath("$.examUuid").value(examUuid))
+ .andExpect(jsonPath("$.comment").value("Excellent work on the assignment."))
+ .andExpect(jsonPath("$.grade").value(feedback.getGrade()))
+ .andExpect(jsonPath("$.points").value(feedback.getPoints()));
+ }
+}
diff --git a/src/main/java/com/ase/userservice/repositories/.gitkeep b/src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java
similarity index 100%
rename from src/main/java/com/ase/userservice/repositories/.gitkeep
rename to src/test/java/com/ase/lecturerservice/controllers/LecturerControllerTest.java
diff --git a/src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java b/src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java
new file mode 100644
index 00000000..08fc8114
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/controllers/SubmissionControllerTest.java
@@ -0,0 +1,105 @@
+package com.ase.lecturerservice.controllers;
+
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.util.List;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import com.ase.lecturerservice.MockValues;
+import com.ase.lecturerservice.entities.FileReference;
+import com.ase.lecturerservice.entities.Submission;
+import com.ase.lecturerservice.services.SubmissionService;
+
+@ExtendWith(SpringExtension.class)
+@WebMvcTest(SubmissionController.class)
+public class SubmissionControllerTest {
+ private static final String LECTURER_UUID = "lecturer-uuid-123";
+ private static final String STUDENT_UUID = MockValues.UuidMocks.STUDENT_UUID.getValue();
+ private static final String EXAM_UUID = MockValues.UuidMocks.EXAM_UUID.getValue();
+ private static final String SUBMISSION_DATE = "2025-09-01T10:30:00Z";
+ private static final String FILENAME = "submission1.pdf";
+ private static final String DOWNLOAD_LINK = "https://example.com/submission1.pdf";
+
+ private static Submission submission;
+ private static List submissionList;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockitoBean
+ private SubmissionService submissionService;
+
+ @BeforeAll
+ public static void setup() {
+ FileReference fileReference = FileReference.builder()
+ .fileUuid(UUID.randomUUID().toString())
+ .filename(FILENAME)
+ .downloadLink(DOWNLOAD_LINK)
+ .build();
+
+ submission = Submission.builder()
+ .id(UUID.randomUUID().toString())
+ .examId(EXAM_UUID)
+ .studentId(STUDENT_UUID)
+ .submissionDate(SUBMISSION_DATE)
+ .fileUpload(fileReference)
+ .build();
+
+ submissionList = List.of(submission);
+ }
+
+ @Test
+ void getRelevantSubmissionsShouldReturnSubmissionsForLecturer() throws Exception {
+ when(submissionService.getAllAccessibleSubmissionsForLecturer(LECTURER_UUID))
+ .thenReturn(submissionList);
+
+ mockMvc.perform(get("/api/v1/submissions/for-lecturer/{lecturerUuid}", LECTURER_UUID)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$[0].examId").value(EXAM_UUID))
+ .andExpect(jsonPath("$[0].studentId").value(STUDENT_UUID))
+ .andExpect(jsonPath("$[0].submissionDate").value(SUBMISSION_DATE))
+ .andExpect(jsonPath("$[0].fileUpload.filename").value(FILENAME))
+ .andExpect(jsonPath("$[0].fileUpload.downloadLink").value(DOWNLOAD_LINK));
+ }
+
+ @Test
+ void getRelevantSubmissionsShouldReturnEmptyListWhenNoSubmissions() throws Exception {
+ when(submissionService.getAllAccessibleSubmissionsForLecturer(LECTURER_UUID))
+ .thenReturn(List.of());
+
+ mockMvc.perform(get("/api/v1/submissions/for-lecturer/{lecturerUuid}", LECTURER_UUID)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$").isEmpty());
+ }
+
+ @Test
+ void getRelevantSubmissionsShouldHandleInvalidLecturerUuid() throws Exception {
+ String invalidLecturerUuid = "invalid-uuid";
+ when(submissionService.getAllAccessibleSubmissionsForLecturer(invalidLecturerUuid))
+ .thenReturn(List.of());
+
+ mockMvc.perform(get("/api/v1/submissions/for-lecturer/{lecturerUuid}", invalidLecturerUuid)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$").isEmpty());
+ }
+}
diff --git a/src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java b/src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java
new file mode 100644
index 00000000..1487fe64
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/services/ExamServiceTest.java
@@ -0,0 +1,90 @@
+package com.ase.lecturerservice.services;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import com.ase.lecturerservice.MockValues;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.entities.ExamType;
+import com.ase.lecturerservice.entities.user.Lecturer;
+import com.ase.lecturerservice.entities.user.UserType;
+
+@SpringBootTest
+public class ExamServiceTest {
+ @Autowired
+ private ExamService examService;
+
+ private Lecturer lecturer;
+ private LocalDate date;
+
+ @BeforeEach
+ public void setUpLecturer() {
+ lecturer = Lecturer.builder()
+ .uuid(MockValues.UuidMocks.LECTURER_UUID.getValue())
+ .email("lecturer@example.com")
+ .type(UserType.LECTURER)
+ .firstName("John")
+ .lastName("Doe")
+ .build();
+
+ date = LocalDate.of(
+ MockValues.IntMocks.DATE_YEAR.getValue(),
+ MockValues.IntMocks.DATE_MONTH.getValue(),
+ MockValues.IntMocks.DATE_DAY.getValue());
+ }
+
+ @Test
+ void fetchExamsByLecturerShouldGetExams() {
+ DummyData.EXAMS = List.of(Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .name("Mathematics Final Exam")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.PRESENTATION)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Calculator, Formula Sheet")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Room A101")
+ .lecturerUuid(MockValues.UuidMocks.LECTURER_UUID.getValue())
+ .module("Mathe")
+ .build());
+
+ List exams = examService.getExamsByLecturer(lecturer.getUuid());
+ Exam exam = exams.getFirst();
+
+ Assertions.assertThat(exams).isNotEmpty();
+ Assertions.assertThat(exam.getUuid())
+ .isEqualTo(MockValues.UuidMocks.EXAM_UUID.getValue());
+ Assertions.assertThat(exam.getName())
+ .isEqualTo("Mathematics Final Exam");
+ Assertions.assertThat(exam.getTotalPoints())
+ .isEqualTo(MockValues.IntMocks.TOTAL_POINTS.getValue());
+ Assertions.assertThat(exam.getExamType()).isEqualTo(ExamType.PRESENTATION);
+ Assertions.assertThat(exam.getDate()).isEqualTo(date);
+ Assertions.assertThat(exam.getTime())
+ .isEqualTo(MockValues.IntMocks.TIME_SECONDS.getValue());
+ Assertions.assertThat(exam.getAllowedResources())
+ .isEqualTo("Calculator, Formula Sheet");
+ Assertions.assertThat(exam.getAttempt())
+ .isEqualTo(MockValues.IntMocks.ATTEMPT.getValue());
+ Assertions.assertThat(exam.getEtcs())
+ .isEqualTo(MockValues.IntMocks.ETCS.getValue());
+ Assertions.assertThat(exam.getRoom()).isEqualTo("Room A101");
+ Assertions.assertThat(exam.getLecturerUuid()).isEqualTo(lecturer.getUuid());
+ Assertions.assertThat(exam.getModule()).isEqualTo("Mathe");
+ }
+
+ @Test
+ void fetchExamsByLecturerShouldNotGetExams() {
+ DummyData.EXAMS = List.of();
+
+ List exams = examService.getExamsByLecturer("Test");
+
+ Assertions.assertThat(exams).isEmpty();
+ }
+}
diff --git a/src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java b/src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java
new file mode 100644
index 00000000..d19e41c5
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/services/FeedbackServiceTest.java
@@ -0,0 +1,8 @@
+package com.ase.lecturerservice.services;
+
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class FeedbackServiceTest {
+
+}
diff --git a/src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java b/src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java
new file mode 100644
index 00000000..2a7ae0e7
--- /dev/null
+++ b/src/test/java/com/ase/lecturerservice/services/SubmissionServiceTest.java
@@ -0,0 +1,122 @@
+package com.ase.lecturerservice.services;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import com.ase.lecturerservice.MockValues;
+import com.ase.lecturerservice.entities.Exam;
+import com.ase.lecturerservice.entities.ExamType;
+import com.ase.lecturerservice.entities.Submission;
+import com.ase.lecturerservice.entities.user.Lecturer;
+import com.ase.lecturerservice.entities.user.UserType;
+
+@SpringBootTest
+public class SubmissionServiceTest {
+ @Autowired
+ private SubmissionService submissionService;
+
+ private Lecturer lecturer;
+ private LocalDate date;
+
+ @BeforeEach
+ public void setUpLecturer() {
+ lecturer = Lecturer.builder()
+ .uuid(MockValues.UuidMocks.LECTURER_UUID.getValue())
+ .email("lecturer@example.com")
+ .type(UserType.LECTURER)
+ .firstName("John")
+ .lastName("Doe")
+ .build();
+
+ date = LocalDate.of(
+ MockValues.IntMocks.DATE_YEAR.getValue(),
+ MockValues.IntMocks.DATE_MONTH.getValue(),
+ MockValues.IntMocks.DATE_DAY.getValue());
+ }
+
+ @Test
+ void getSubmissionsForExamShouldReturnSubmissionsForSpecificExam() {
+ String examId = MockValues.UuidMocks.EXAM_UUID.getValue();
+
+ List submissions = submissionService.getSubmissionsForExam(examId);
+
+ Assertions.assertThat(submissions).isNotEmpty();
+ Assertions.assertThat(submissions)
+ .allMatch(submission -> submission.getExamId().equals(examId));
+ }
+
+ @Test
+ void getSubmissionsForStudentShouldReturnSubmissionsForSpecificStudent() {
+ String studentId = MockValues.UuidMocks.STUDENT_UUID.getValue();
+
+ List submissions = submissionService.getSubmissionsForStudent(studentId);
+
+ Assertions.assertThat(submissions).isNotEmpty();
+ Assertions.assertThat(submissions)
+ .allMatch(submission -> submission.getStudentId().equals(studentId));
+ }
+
+ @Test
+ void getAllAccessibleSubmissionsForLecturerShouldReturnOnlyLecturerSubmissions() {
+ // Setup exam for lecturer
+ Exam exam = Exam.builder()
+ .uuid(MockValues.UuidMocks.EXAM_UUID.getValue())
+ .name("Test Exam")
+ .totalPoints(MockValues.IntMocks.TOTAL_POINTS.getValue())
+ .examType(ExamType.EXAM)
+ .date(date)
+ .time(MockValues.IntMocks.TIME_SECONDS.getValue())
+ .allowedResources("Calculator")
+ .attempt(MockValues.IntMocks.ATTEMPT.getValue())
+ .etcs(MockValues.IntMocks.ETCS.getValue())
+ .room("Room A101")
+ .lecturerUuid(lecturer.getUuid())
+ .module("Test Module")
+ .build();
+
+ DummyData.EXAMS = List.of(exam);
+
+ List submissions = submissionService
+ .getAllAccessibleSubmissionsForLecturer(lecturer.getUuid());
+
+ Assertions.assertThat(submissions).isNotEmpty();
+ // All submissions should be for exams belonging to this lecturer
+ Assertions.assertThat(submissions).allMatch(submission ->
+ DummyData.EXAMS.stream()
+ .anyMatch(ex -> ex.getUuid().equals(submission.getExamId())
+ && ex.getLecturerUuid().equals(lecturer.getUuid()))
+ );
+ }
+
+ @Test
+ void getAllAccessibleSubmissionsForLecturerShouldReturnEmptyListForUnknownLecturer() {
+ String unknownLecturerUuid = "unknown-lecturer-uuid";
+
+ List submissions = submissionService
+ .getAllAccessibleSubmissionsForLecturer(unknownLecturerUuid);
+
+ Assertions.assertThat(submissions).isEmpty();
+ }
+
+ @Test
+ void getSubmissionsForExamShouldReturnEmptyListForUnknownExam() {
+ String unknownExamId = "unknown-exam-id";
+
+ List submissions = submissionService.getSubmissionsForExam(unknownExamId);
+
+ Assertions.assertThat(submissions).isEmpty();
+ }
+
+ @Test
+ void getSubmissionsForStudentShouldReturnEmptyListForUnknownStudent() {
+ String unknownStudentId = "unknown-student-id";
+
+ List submissions = submissionService.getSubmissionsForStudent(unknownStudentId);
+
+ Assertions.assertThat(submissions).isEmpty();
+ }
+}