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.ForbiddenException import net.halfbinary.scavengerhuntapi.error.exception.NotFoundException import net.halfbinary.scavengerhuntapi.model.HuntId 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.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.response.PhotoResponse import net.halfbinary.scavengerhuntapi.repository.PhotoRepository import org.springframework.http.MediaType import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.time.LocalDateTime @Service class PhotoService( private val photoRepository: PhotoRepository, private val hunterService: HunterService, private val teamService: TeamService, private val s3StorageService: S3StorageService, private val fileProbeService: FileProbeService ) { fun submitPhoto(huntId: HuntId, itemId: ItemId, email: String, file: MultipartFile) { val originalBytes = file.bytes val fileType = fileProbeService.getFileType(originalBytes) if(!fileProbeService.isImageType(fileType)) throw BadFileException("Not an image") val originalAsJpeg = try { toJpeg(file.bytes) } catch (_: UnsupportedFormatException) { throw BadFileException("Image type is not supported") } val hunter = hunterService.getHunterByEmail(email) val now = LocalDateTime.now() val photo = Photo( itemId = itemId, huntId = huntId, hunterId = hunter.id, foundDateTime = now, status = PhotoStatus.SUBMITTED, statusChangeDateTime = now ) val savedRecord = photoRepository.save(photo.toRecord()) val baseName = savedRecord.id.toString() s3StorageService.upload("$baseName${fileProbeService.getFileExtension(fileType)}", originalBytes, fileType) s3StorageService.upload("${baseName}_large.jpg", originalAsJpeg, MediaType.IMAGE_JPEG_VALUE) s3StorageService.upload("${baseName}_medium.jpg", resize(originalBytes, 800), 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 { val output = ByteArrayOutputStream() Thumbnails.of(ByteArrayInputStream(bytes)) .scale(1.0) .outputFormat("jpg") .toOutputStream(output) return output.toByteArray() } private fun resize(bytes: ByteArray, width: Int): ByteArray { val output = ByteArrayOutputStream() Thumbnails.of(ByteArrayInputStream(bytes)) .width(width) .outputFormat("jpg") .toOutputStream(output) return output.toByteArray() } }