From cd5e970d5026214a45575616c86851a4dbb64952 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 30 Apr 2026 14:31:11 +0200 Subject: [PATCH 1/2] add image assessment upload --- .../backend/game/tasks/api/TaskController.kt | 13 +++++++++++++ .../game/tasks/application/TaskService.kt | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/api/TaskController.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/api/TaskController.kt index c00ebbd0..2d35dcf3 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/api/TaskController.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/api/TaskController.kt @@ -4,6 +4,7 @@ import edu.ntnu.idi.idatt.backend.game.tasks.application.TaskService import edu.ntnu.idi.idatt.backend.iam.security.CurrentUser import jakarta.validation.Valid import org.springframework.http.HttpStatus +import org.springframework.http.MediaType import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping @@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile /** * REST API for listing, authoring, publishing, and deleting classroom tasks. @@ -71,6 +73,17 @@ class TaskController( @AuthenticationPrincipal currentUser: CurrentUser, ): TaskResponse = taskService.createTask(request.toCommand(classroomId, currentUser)).toResponse() + /** + * Uploads an image for teacher-authored task content inside one classroom. + */ + @PostMapping("/upload", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) + @ResponseStatus(HttpStatus.CREATED) + fun uploadTaskMedia( + @PathVariable classroomId: Long, + @RequestParam("file") file: MultipartFile, + @AuthenticationPrincipal currentUser: CurrentUser, + ): Map = mapOf("url" to taskService.uploadTaskMedia(currentUser, classroomId, file)) + /** * Replaces the editable contents of an existing task. */ diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt index db16535b..4a3a508c 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt @@ -19,6 +19,7 @@ import edu.ntnu.idi.idatt.backend.game.tasks.domain.TaskPassingRule import edu.ntnu.idi.idatt.backend.game.tasks.domain.TaskTranslation import edu.ntnu.idi.idatt.backend.game.tasks.domain.TaskTranslationId import edu.ntnu.idi.idatt.backend.game.tasks.infrastructure.TaskRepository +import edu.ntnu.idi.idatt.backend.upload.application.FileStorageService import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.domain.User import edu.ntnu.idi.idatt.backend.iam.domain.UserRole @@ -27,6 +28,7 @@ import org.springframework.dao.DataIntegrityViolationException import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile import org.springframework.web.server.ResponseStatusException /** @@ -39,6 +41,7 @@ class TaskService( private val currentUserResolver: CurrentUserResolver, private val pupilStopProgressService: PupilStopProgressService, private val gameAccessContextResolver: GameAccessContextResolver, + private val fileStorageService: FileStorageService, ) { /** * Lists tasks for a classroom, optionally filtered by stop. @@ -236,6 +239,21 @@ class TaskService( taskRepository.delete(task) } + /** + * Uploads one image for task authoring inside the given classroom. + * + * The returned URL is intended to be written into a task item's mediaUrl. + */ + @Transactional(readOnly = true) + fun uploadTaskMedia( + currentUser: CurrentUser, + classroomId: Long, + file: MultipartFile, + ): String { + requireOwnedClassroom(currentUser.subject, classroomId) + return fileStorageService.store(file, "tasks/classroom-$classroomId") + } + /** * Submits answers for all published tasks at a stop. * From 6cd410600ec9a8378ab83df0494c021a409e9a05 Mon Sep 17 00:00:00 2001 From: eilifhl Date: Thu, 30 Apr 2026 14:38:51 +0200 Subject: [PATCH 2/2] format --- .../idi/idatt/backend/game/tasks/application/TaskService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt index 4a3a508c..7b0b69ae 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/game/tasks/application/TaskService.kt @@ -19,11 +19,11 @@ import edu.ntnu.idi.idatt.backend.game.tasks.domain.TaskPassingRule import edu.ntnu.idi.idatt.backend.game.tasks.domain.TaskTranslation import edu.ntnu.idi.idatt.backend.game.tasks.domain.TaskTranslationId import edu.ntnu.idi.idatt.backend.game.tasks.infrastructure.TaskRepository -import edu.ntnu.idi.idatt.backend.upload.application.FileStorageService import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.domain.User import edu.ntnu.idi.idatt.backend.iam.domain.UserRole import edu.ntnu.idi.idatt.backend.iam.security.CurrentUser +import edu.ntnu.idi.idatt.backend.upload.application.FileStorageService import org.springframework.dao.DataIntegrityViolationException import org.springframework.http.HttpStatus import org.springframework.stereotype.Service