Implements remove photo endpoint

This commit is contained in:
2026-05-14 23:55:05 -05:00
parent dbd988a573
commit ac6f3a7014
5 changed files with 40 additions and 5 deletions

View File

@@ -10,9 +10,6 @@ REST API to support a community scavenger hunt app.
## TODO:
### User Endpoints
* upload photo for hunt item POST `/hunt/{huntId}/team/{teamId}/item/{itemId}/photo` - body: image binary
* delete photo for hunt item DELETE `/hunt/{huntId}/team/{teamId}/item/{itemId}/photo/{photoId}`
* list hunt teams with scores for hunt `GET /lead/hunt/{huntId}/team`
* list hunters with scores for hunt GET `/lead/hunt/{huntId}/hunter`
### Admin Endpoints
* approve photo for hunt item POST `/admin/hunt/{huntId}/team/{teamId}/item/{itemId}/photo/{photoId}` - body: approval status
* list hunt teams with scores for hunt GET `/lead/hunt/{huntId}/team`
* list hunters with scores for hunt GET `/lead/hunt/{huntId}/hunter`

View File

@@ -16,6 +16,7 @@ import net.halfbinary.scavengerhuntapi.service.TeamService
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
@@ -64,6 +65,16 @@ class TeamController(private val teamService: TeamService, private val photoServ
TODO("Get list of photo information for the specified Team, Hunt, and Item")
}
@PatchMapping("/{teamId}/item/{itemId}/photo/{photoId}")
@Operation(summary = "Mark the specified Photo as removed")
fun removePhoto(@PathVariable huntId: HuntId,
@PathVariable teamId: TeamId,
@PathVariable itemId: ItemId,
@PathVariable photoId: PhotoId,
authentication: Authentication) {
photoService.removePhoto(huntId, teamId, itemId, photoId, authentication.name)
}
@GetMapping("/{teamId}/item/{itemId}/photo/{photoId}")
@Operation(summary = "Get photo information for the specified Team, Hunt, Item, and Photo")
fun getPhotoInfo(@PathVariable huntId: HuntId,

View File

@@ -1,6 +1,7 @@
package net.halfbinary.scavengerhuntapi.error
import net.halfbinary.scavengerhuntapi.error.exception.BadFileException
import net.halfbinary.scavengerhuntapi.error.exception.ConflictException
import net.halfbinary.scavengerhuntapi.error.exception.ForbiddenException
import net.halfbinary.scavengerhuntapi.error.exception.InvalidEmailException
import net.halfbinary.scavengerhuntapi.error.exception.LoginFailedException
@@ -54,6 +55,12 @@ class ExceptionHandler {
return e.message
}
@ExceptionHandler(ConflictException::class)
@ResponseStatus(HttpStatus.CONFLICT)
fun conflictException(e: ConflictException): String? {
return e.message
}
@ExceptionHandler(HttpMessageNotReadableException::class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
fun httpMessageNotReadableException(e: HttpMessageNotReadableException): Map<String, String?> {

View File

@@ -0,0 +1,3 @@
package net.halfbinary.scavengerhuntapi.error.exception
class ConflictException(override val message: String) : RuntimeException(message)

View File

@@ -3,6 +3,7 @@ package net.halfbinary.scavengerhuntapi.service
import net.coobird.thumbnailator.Thumbnails
import net.coobird.thumbnailator.tasks.UnsupportedFormatException
import net.halfbinary.scavengerhuntapi.error.exception.BadFileException
import net.halfbinary.scavengerhuntapi.error.exception.ConflictException
import net.halfbinary.scavengerhuntapi.error.exception.ForbiddenException
import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException
import net.halfbinary.scavengerhuntapi.model.FoundStatus
@@ -140,6 +141,22 @@ class PhotoService(
}
}
fun removePhoto(huntId: HuntId, teamId: TeamId, itemId: ItemId, photoId: PhotoId, email: String) {
val photoRecord = photoRepository.findByIdAndItemIdAndHuntId(photoId, itemId, huntId)
?: throw NotFoundException(PHOTO_NOT_FOUND)
val team = try {
teamService.getTeamForHunterInHunt(huntId, email)
} catch (_: NotFoundException) {
throw ForbiddenException()
}
if (team.id != teamId) throw ForbiddenException()
if (photoRecord.status == PhotoStatus.APPROVED) throw ConflictException("Cannot remove an approved photo")
photoRepository.save(photoRecord.copy(status = PhotoStatus.REMOVED, statusChangeDateTime = LocalDateTime.now()))
}
fun updatePhotoStatus(photoId: PhotoId, status: PhotoStatus) {
val record = photoRepository.findByIdOrNull(photoId)
?: throw NotFoundException(PHOTO_NOT_FOUND)