Implements photo review endpoint

This commit is contained in:
2026-05-14 23:09:08 -05:00
parent bc1bcf6e8d
commit 67fb801812
3 changed files with 28 additions and 7 deletions

View File

@@ -2,21 +2,25 @@ package net.halfbinary.scavengerhuntapi.controller
import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
import net.halfbinary.scavengerhuntapi.model.PhotoId import net.halfbinary.scavengerhuntapi.model.PhotoId
import net.halfbinary.scavengerhuntapi.model.request.ReviewPhotoRequest
import net.halfbinary.scavengerhuntapi.service.PhotoService
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@RequestMapping("admin") @RequestMapping("admin")
class AdminController { class AdminController(private val photoService: PhotoService) {
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@Tag(name = "Admin") @Tag(name = "Admin")
@PostMapping("/admin/photo/{photoId}") @PatchMapping("/photo/{photoId}")
@Operation(summary = "Sets a review status for the specified photo") @Operation(summary = "Sets a review status for the specified photo")
fun reviewPhoto(@PathVariable photoId: PhotoId) { fun reviewPhoto(@PathVariable photoId: PhotoId, @Valid @RequestBody request: ReviewPhotoRequest) {
TODO("Set a review status for the specified photo, and update the photo record's status change timestamp") photoService.updatePhotoStatus(photoId, request.status)
} }
} }

View File

@@ -0,0 +1,9 @@
package net.halfbinary.scavengerhuntapi.model.request
import jakarta.validation.constraints.NotBlank
import net.halfbinary.scavengerhuntapi.model.PhotoStatus
data class ReviewPhotoRequest(
@field:NotBlank(message = "Status must not be blank")
val status: PhotoStatus
)

View File

@@ -27,6 +27,8 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.time.LocalDateTime import java.time.LocalDateTime
private const val PHOTO_NOT_FOUND = "Photo not found"
@Service @Service
class PhotoService( class PhotoService(
private val photoRepository: PhotoRepository, private val photoRepository: PhotoRepository,
@@ -69,7 +71,7 @@ class PhotoService(
fun getPhotoInfo(huntId: HuntId, teamId: TeamId, itemId: ItemId, photoId: PhotoId, email: String): PhotoResponse { fun getPhotoInfo(huntId: HuntId, teamId: TeamId, itemId: ItemId, photoId: PhotoId, email: String): PhotoResponse {
val requestingHunter = hunterService.getHunterByEmail(email) val requestingHunter = hunterService.getHunterByEmail(email)
val photoRecord = photoRepository.findByIdAndItemIdAndHuntId(photoId, itemId, huntId) val photoRecord = photoRepository.findByIdAndItemIdAndHuntId(photoId, itemId, huntId)
?: throw NotFoundException("Photo not found") ?: throw NotFoundException(PHOTO_NOT_FOUND)
if (!requestingHunter.isAdmin) { if (!requestingHunter.isAdmin) {
val team = try { val team = try {
@@ -87,7 +89,7 @@ class PhotoService(
fun getPhotoFile(photoId: PhotoId, email: String, version: ImageVersion = ImageVersion.LARGE): PhotoFile { fun getPhotoFile(photoId: PhotoId, email: String, version: ImageVersion = ImageVersion.LARGE): PhotoFile {
val requestingHunter = hunterService.getHunterByEmail(email) val requestingHunter = hunterService.getHunterByEmail(email)
val photoRecord = photoRepository.findByIdOrNull(photoId) val photoRecord = photoRepository.findByIdOrNull(photoId)
?: throw NotFoundException("Photo not found") ?: throw NotFoundException(PHOTO_NOT_FOUND)
if (!requestingHunter.isAdmin) { if (!requestingHunter.isAdmin) {
val submitter = hunterService.getHunterById(photoRecord.hunterId) val submitter = hunterService.getHunterById(photoRecord.hunterId)
@@ -113,6 +115,12 @@ class PhotoService(
return PhotoFile(InputStreamResource(stream), MediaType.parseMediaType(contentType)) return PhotoFile(InputStreamResource(stream), MediaType.parseMediaType(contentType))
} }
fun updatePhotoStatus(photoId: PhotoId, status: PhotoStatus) {
val record = photoRepository.findByIdOrNull(photoId)
?: throw NotFoundException(PHOTO_NOT_FOUND)
photoRepository.save(record.copy(status = status, statusChangeDateTime = LocalDateTime.now()))
}
private fun toJpeg(bytes: ByteArray): ByteArray { private fun toJpeg(bytes: ByteArray): ByteArray {
val output = ByteArrayOutputStream() val output = ByteArrayOutputStream()
Thumbnails.of(ByteArrayInputStream(bytes)) Thumbnails.of(ByteArrayInputStream(bytes))