2 Commits

7 changed files with 53 additions and 11 deletions

View File

@@ -69,8 +69,9 @@ class TeamController(private val teamService: TeamService, private val photoServ
fun getPhotoInfo(@PathVariable huntId: HuntId, fun getPhotoInfo(@PathVariable huntId: HuntId,
@PathVariable teamId: TeamId, @PathVariable teamId: TeamId,
@PathVariable itemId: ItemId, @PathVariable itemId: ItemId,
@PathVariable photoId: PhotoId): ResponseEntity<PhotoResponse> { @PathVariable photoId: PhotoId,
TODO("Get photo information for the specified Team, Hunt, Item, and Photo. Join on the Hunter table to get the Hunter name. Also verify that the requesting user is either an admin or is on the same Hunt and Team as the Hunter who submitted the Photo") authentication: Authentication): ResponseEntity<PhotoResponse> {
return ResponseEntity.ok(photoService.getPhotoInfo(huntId, teamId, itemId, photoId, authentication.name))
} }
@GetMapping("/{teamId}/item/{itemId}/photo/{photoId}/file") @GetMapping("/{teamId}/item/{itemId}/photo/{photoId}/file")

View File

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

View File

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

View File

@@ -15,6 +15,16 @@ fun Photo.toRecord() = PhotoRecord(
statusChangeDateTime = statusChangeDateTime statusChangeDateTime = statusChangeDateTime
) )
fun PhotoRecord.toDomain() = Photo(
id = id,
itemId = itemId,
huntId = huntId,
hunterId = hunterId,
foundDateTime = foundDateTime,
status = status,
statusChangeDateTime = statusChangeDateTime
)
fun Photo.toResponse(hunter: Hunter) = PhotoResponse( fun Photo.toResponse(hunter: Hunter) = PhotoResponse(
id = id, id = id,
hunterName = hunter.name, hunterName = hunter.name,

View File

@@ -1,19 +1,13 @@
package net.halfbinary.scavengerhuntapi.repository package net.halfbinary.scavengerhuntapi.repository
import net.halfbinary.scavengerhuntapi.model.HuntId
import net.halfbinary.scavengerhuntapi.model.ItemId import net.halfbinary.scavengerhuntapi.model.ItemId
import net.halfbinary.scavengerhuntapi.model.PhotoId import net.halfbinary.scavengerhuntapi.model.PhotoId
import net.halfbinary.scavengerhuntapi.model.record.PhotoRecord import net.halfbinary.scavengerhuntapi.model.record.PhotoRecord
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
interface PhotoRepository : JpaRepository<PhotoRecord, PhotoId> { interface PhotoRepository : JpaRepository<PhotoRecord, PhotoId> {
@Query(""" fun findByItemId(itemId: ItemId): List<PhotoRecord>
SELECT * fun findByIdAndItemIdAndHuntId(id: PhotoId, itemId: ItemId, huntId: HuntId): PhotoRecord?
FROM photo p
WHERE
p.
""", nativeQuery = true)
fun findPhotosByItemId(itemId: ItemId): List<PhotoRecord>
} }

View File

@@ -1,6 +1,7 @@
package net.halfbinary.scavengerhuntapi.service package net.halfbinary.scavengerhuntapi.service
import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException
import net.halfbinary.scavengerhuntapi.model.HunterId
import net.halfbinary.scavengerhuntapi.model.converter.toDomain import net.halfbinary.scavengerhuntapi.model.converter.toDomain
import net.halfbinary.scavengerhuntapi.model.domain.Hunter import net.halfbinary.scavengerhuntapi.model.domain.Hunter
import net.halfbinary.scavengerhuntapi.repository.HunterRepository import net.halfbinary.scavengerhuntapi.repository.HunterRepository
@@ -12,4 +13,8 @@ class HunterService(private val hunterRepository: HunterRepository) {
return hunterRepository.findByEmail(email)?.toDomain() return hunterRepository.findByEmail(email)?.toDomain()
?: throw NotFoundException("No hunter with email $email found") ?: throw NotFoundException("No hunter with email $email found")
} }
fun getHunterById(hunterId: HunterId): Hunter {
return hunterRepository.findById(hunterId).orElseThrow { NotFoundException("No hunter with id $hunterId found") }.toDomain()
}
} }

View File

@@ -3,11 +3,18 @@ package net.halfbinary.scavengerhuntapi.service
import net.coobird.thumbnailator.Thumbnails import net.coobird.thumbnailator.Thumbnails
import net.coobird.thumbnailator.tasks.UnsupportedFormatException import net.coobird.thumbnailator.tasks.UnsupportedFormatException
import net.halfbinary.scavengerhuntapi.error.exception.BadFileException 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.HuntId import net.halfbinary.scavengerhuntapi.model.HuntId
import net.halfbinary.scavengerhuntapi.model.ItemId import net.halfbinary.scavengerhuntapi.model.ItemId
import net.halfbinary.scavengerhuntapi.model.PhotoId
import net.halfbinary.scavengerhuntapi.model.PhotoStatus import net.halfbinary.scavengerhuntapi.model.PhotoStatus
import net.halfbinary.scavengerhuntapi.model.TeamId
import net.halfbinary.scavengerhuntapi.model.converter.toDomain
import net.halfbinary.scavengerhuntapi.model.converter.toRecord 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.Photo
import net.halfbinary.scavengerhuntapi.model.response.PhotoResponse
import net.halfbinary.scavengerhuntapi.repository.PhotoRepository import net.halfbinary.scavengerhuntapi.repository.PhotoRepository
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -20,6 +27,7 @@ import java.time.LocalDateTime
class PhotoService( class PhotoService(
private val photoRepository: PhotoRepository, private val photoRepository: PhotoRepository,
private val hunterService: HunterService, private val hunterService: HunterService,
private val teamService: TeamService,
private val s3StorageService: S3StorageService, private val s3StorageService: S3StorageService,
private val fileProbeService: FileProbeService private val fileProbeService: FileProbeService
) { ) {
@@ -54,6 +62,20 @@ class PhotoService(
s3StorageService.upload("${baseName}_small.jpg", resize(originalBytes, 200), MediaType.IMAGE_JPEG_VALUE) s3StorageService.upload("${baseName}_small.jpg", resize(originalBytes, 200), MediaType.IMAGE_JPEG_VALUE)
} }
fun getPhotoInfo(huntId: HuntId, teamId: TeamId, itemId: ItemId, photoId: PhotoId, email: String): PhotoResponse {
val requestingHunter = hunterService.getHunterByEmail(email)
val photoRecord = photoRepository.findByIdAndItemIdAndHuntId(photoId, itemId, huntId)
?: throw NotFoundException("Photo not found")
if (!requestingHunter.isAdmin) {
val team = teamService.getTeamForHunterInHunt(huntId, email)
if (team.id != teamId) throw ForbiddenException("Access denied")
}
val submitter = hunterService.getHunterById(photoRecord.hunterId)
return photoRecord.toDomain().toResponse(submitter)
}
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))