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..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 @@ -23,10 +23,12 @@ 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 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. *