From dbd988a573dd4b507590bf8bafd2f91e87a12f96 Mon Sep 17 00:00:00 2001 From: aarbit Date: Thu, 14 May 2026 23:35:53 -0500 Subject: [PATCH] Implements team item status endpoint --- .../controller/TeamController.kt | 9 +++--- .../scavengerhuntapi/model/FoundStatus.kt | 3 +- .../scavengerhuntapi/model/domain/Photo.kt | 2 +- .../model/response/TeamItemResponse.kt | 5 +--- .../repository/HunterTeamRepository.kt | 2 ++ .../repository/PhotoRepository.kt | 1 + .../scavengerhuntapi/service/PhotoService.kt | 29 +++++++++++++++++-- .../scavengerhuntapi/service/TeamService.kt | 5 ++++ 8 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/controller/TeamController.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/controller/TeamController.kt index 6cd9a8e..75eaef7 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/controller/TeamController.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/controller/TeamController.kt @@ -47,12 +47,13 @@ class TeamController(private val teamService: TeamService, private val photoServ } @GetMapping("/{teamId}/item/{itemId}") - @Operation(summary = "Get found/not found status and photo information about the Item for the specified Team, Hunt, and Item") + @Operation(summary = "Get found/not found status about the Item for the specified Team, Hunt, and Item") fun getItemForTeam(@PathVariable huntId: HuntId, @PathVariable teamId: TeamId, - @PathVariable itemId: ItemId): ResponseEntity { - TODO("Get found/not found status about the Item for the specified Team, Hunt, and Item") - + @PathVariable itemId: ItemId, + authentication: Authentication): ResponseEntity { + val foundStatus = photoService.getItemFoundStatus(huntId, teamId, itemId, authentication.name) + return ResponseEntity.ok(TeamItemResponse(id = itemId, itemFoundStatus = foundStatus)) } @GetMapping("/{teamId}/item/{itemId}/photo") diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/FoundStatus.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/FoundStatus.kt index 7e80c2c..02aafe7 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/FoundStatus.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/FoundStatus.kt @@ -4,6 +4,5 @@ enum class FoundStatus { NOT_FOUND, SUBMITTED, APPROVED, - REJECTED, - REMOVED + REJECTED } \ No newline at end of file diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/domain/Photo.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/domain/Photo.kt index 9a2d80d..0700dd4 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/domain/Photo.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/domain/Photo.kt @@ -6,7 +6,7 @@ import net.halfbinary.scavengerhuntapi.model.ItemId import net.halfbinary.scavengerhuntapi.model.PhotoId import net.halfbinary.scavengerhuntapi.model.PhotoStatus import java.time.LocalDateTime -import java.util.UUID +import java.util.* data class Photo( val id: PhotoId = UUID.randomUUID(), diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/response/TeamItemResponse.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/response/TeamItemResponse.kt index 5b3bb67..dc03711 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/response/TeamItemResponse.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/model/response/TeamItemResponse.kt @@ -2,11 +2,8 @@ package net.halfbinary.scavengerhuntapi.model.response import net.halfbinary.scavengerhuntapi.model.FoundStatus import net.halfbinary.scavengerhuntapi.model.ItemId -import java.time.LocalDateTime data class TeamItemResponse( val id: ItemId, - val hunterName: String?, - val itemFoundStatus: FoundStatus, - val itemStatusChangeDateTime: LocalDateTime, + val itemFoundStatus: FoundStatus ) diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/HunterTeamRepository.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/HunterTeamRepository.kt index 74dfec8..873328c 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/HunterTeamRepository.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/HunterTeamRepository.kt @@ -1,6 +1,7 @@ package net.halfbinary.scavengerhuntapi.repository import net.halfbinary.scavengerhuntapi.model.HunterId +import net.halfbinary.scavengerhuntapi.model.TeamId import net.halfbinary.scavengerhuntapi.model.record.HunterTeamRecord import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -9,4 +10,5 @@ import java.util.* @Repository interface HunterTeamRepository : JpaRepository { fun findByHunterId(hunterId: HunterId): List + fun findByTeamId(teamId: TeamId): List } diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/PhotoRepository.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/PhotoRepository.kt index 80148e4..20384c5 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/PhotoRepository.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/repository/PhotoRepository.kt @@ -9,5 +9,6 @@ import org.springframework.stereotype.Repository @Repository interface PhotoRepository : JpaRepository { fun findByItemId(itemId: ItemId): List + fun findByHuntIdAndItemId(huntId: HuntId, itemId: ItemId): List fun findByIdAndItemIdAndHuntId(id: PhotoId, itemId: ItemId, huntId: HuntId): PhotoRecord? } \ No newline at end of file diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/PhotoService.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/PhotoService.kt index f9ffb7c..7195f27 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/PhotoService.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/PhotoService.kt @@ -5,17 +5,18 @@ import net.coobird.thumbnailator.tasks.UnsupportedFormatException import net.halfbinary.scavengerhuntapi.error.exception.BadFileException import net.halfbinary.scavengerhuntapi.error.exception.ForbiddenException import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException +import net.halfbinary.scavengerhuntapi.model.FoundStatus import net.halfbinary.scavengerhuntapi.model.HuntId -import net.halfbinary.scavengerhuntapi.model.ItemId import net.halfbinary.scavengerhuntapi.model.ImageVersion +import net.halfbinary.scavengerhuntapi.model.ItemId import net.halfbinary.scavengerhuntapi.model.PhotoId import net.halfbinary.scavengerhuntapi.model.PhotoStatus import net.halfbinary.scavengerhuntapi.model.TeamId -import net.halfbinary.scavengerhuntapi.model.domain.PhotoFile import net.halfbinary.scavengerhuntapi.model.converter.toDomain import net.halfbinary.scavengerhuntapi.model.converter.toRecord import net.halfbinary.scavengerhuntapi.model.converter.toResponse import net.halfbinary.scavengerhuntapi.model.domain.Photo +import net.halfbinary.scavengerhuntapi.model.domain.PhotoFile import net.halfbinary.scavengerhuntapi.model.response.PhotoResponse import net.halfbinary.scavengerhuntapi.repository.PhotoRepository import org.springframework.core.io.InputStreamResource @@ -115,6 +116,30 @@ class PhotoService( return PhotoFile(InputStreamResource(stream), MediaType.parseMediaType(contentType)) } + fun getItemFoundStatus(huntId: HuntId, teamId: TeamId, itemId: ItemId, email: String): FoundStatus { + val requestingHunter = hunterService.getHunterByEmail(email) + + if (!requestingHunter.isAdmin) { + val team = try { + teamService.getTeamForHunterInHunt(huntId, email) + } catch (_: NotFoundException) { + throw ForbiddenException() + } + if (team.id != teamId) throw ForbiddenException() + } + + val teamHunterIds = teamService.getHunterIdsForTeam(teamId) + val photos = photoRepository.findByHuntIdAndItemId(huntId, itemId) + .filter { it.hunterId in teamHunterIds && it.status != PhotoStatus.REMOVED } + + return when { + photos.any { it.status == PhotoStatus.APPROVED } -> FoundStatus.APPROVED + photos.any { it.status == PhotoStatus.REJECTED } -> FoundStatus.REJECTED + photos.any { it.status == PhotoStatus.SUBMITTED } -> FoundStatus.SUBMITTED + else -> FoundStatus.NOT_FOUND + } + } + fun updatePhotoStatus(photoId: PhotoId, status: PhotoStatus) { val record = photoRepository.findByIdOrNull(photoId) ?: throw NotFoundException(PHOTO_NOT_FOUND) diff --git a/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/TeamService.kt b/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/TeamService.kt index e872060..baefbf2 100644 --- a/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/TeamService.kt +++ b/src/main/kotlin/net/halfbinary/scavengerhuntapi/service/TeamService.kt @@ -2,6 +2,7 @@ package net.halfbinary.scavengerhuntapi.service import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException import net.halfbinary.scavengerhuntapi.model.HuntId +import net.halfbinary.scavengerhuntapi.model.HunterId import net.halfbinary.scavengerhuntapi.model.TeamId import net.halfbinary.scavengerhuntapi.model.converter.toDomain import net.halfbinary.scavengerhuntapi.model.converter.toRecord @@ -49,6 +50,10 @@ class TeamService( ?: throw NotFoundException("No team found for hunter $email in hunt $huntId") } + fun getHunterIdsForTeam(teamId: TeamId): Set { + return hunterTeamRepository.findByTeamId(teamId).map { it.hunterId }.toSet() + } + fun joinTeam(teamId: TeamId, email: String) { val hunter = hunterRepository.findByEmail(email) ?: throw NotFoundException("No hunter with email $email found") hunterTeamRepository.save(HunterTeamRecord(UUID.randomUUID(), hunter.id, teamId))