151 lines
6.4 KiB
Kotlin
151 lines
6.4 KiB
Kotlin
package com.android.trisolarisserver.controller
|
|
|
|
import com.android.trisolarisserver.component.PropertyAccess
|
|
import com.android.trisolarisserver.component.RoomImageStorage
|
|
import com.android.trisolarisserver.controller.dto.RoomImageResponse
|
|
import com.android.trisolarisserver.models.room.RoomImage
|
|
import com.android.trisolarisserver.models.property.Role
|
|
import com.android.trisolarisserver.repo.RoomImageRepo
|
|
import com.android.trisolarisserver.repo.RoomRepo
|
|
import com.android.trisolarisserver.security.MyPrincipal
|
|
import org.springframework.core.io.FileSystemResource
|
|
import org.springframework.http.HttpHeaders
|
|
import org.springframework.http.HttpStatus
|
|
import org.springframework.http.MediaType
|
|
import org.springframework.http.ResponseEntity
|
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
|
import org.springframework.web.bind.annotation.GetMapping
|
|
import org.springframework.web.bind.annotation.PathVariable
|
|
import org.springframework.web.bind.annotation.PostMapping
|
|
import org.springframework.web.bind.annotation.RequestMapping
|
|
import org.springframework.web.bind.annotation.RequestParam
|
|
import org.springframework.web.bind.annotation.ResponseStatus
|
|
import org.springframework.web.bind.annotation.RestController
|
|
import org.springframework.web.multipart.MultipartFile
|
|
import org.springframework.web.server.ResponseStatusException
|
|
import java.nio.file.Files
|
|
import java.nio.file.Paths
|
|
import java.util.UUID
|
|
|
|
@RestController
|
|
@RequestMapping("/properties/{propertyId}/rooms/{roomId}/images")
|
|
class RoomImages(
|
|
private val propertyAccess: PropertyAccess,
|
|
private val roomRepo: RoomRepo,
|
|
private val roomImageRepo: RoomImageRepo,
|
|
private val storage: RoomImageStorage,
|
|
@org.springframework.beans.factory.annotation.Value("\${storage.rooms.publicBaseUrl}")
|
|
private val publicBaseUrl: String
|
|
) {
|
|
|
|
@GetMapping
|
|
fun list(
|
|
@PathVariable propertyId: UUID,
|
|
@PathVariable roomId: UUID,
|
|
@AuthenticationPrincipal principal: MyPrincipal?
|
|
): List<RoomImageResponse> {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
ensureRoom(propertyId, roomId)
|
|
return roomImageRepo.findByRoomIdOrdered(roomId)
|
|
.map { it.toResponse(publicBaseUrl) }
|
|
}
|
|
|
|
@PostMapping
|
|
@ResponseStatus(HttpStatus.CREATED)
|
|
fun upload(
|
|
@PathVariable propertyId: UUID,
|
|
@PathVariable roomId: UUID,
|
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
|
@RequestParam("file") file: MultipartFile,
|
|
@RequestParam(required = false) tags: List<String>?
|
|
): RoomImageResponse {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN, Role.MANAGER)
|
|
val room = ensureRoom(propertyId, roomId)
|
|
|
|
if (file.isEmpty) {
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "File is empty")
|
|
}
|
|
val stored = try {
|
|
storage.store(propertyId, roomId, file)
|
|
} catch (ex: IllegalArgumentException) {
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message ?: "Invalid image")
|
|
}
|
|
|
|
val nextRoomSortOrder = roomImageRepo.findMaxRoomSortOrder(roomId) + 1
|
|
val nextRoomTypeSortOrder = roomImageRepo.findMaxRoomTypeSortOrder(room.roomType.code) + 1
|
|
val image = RoomImage(
|
|
property = room.property,
|
|
room = room,
|
|
originalPath = stored.originalPath,
|
|
thumbnailPath = stored.thumbnailPath,
|
|
contentType = stored.contentType,
|
|
sizeBytes = stored.sizeBytes,
|
|
roomTypeCode = room.roomType.code,
|
|
tags = tags?.toMutableSet() ?: mutableSetOf(),
|
|
roomSortOrder = nextRoomSortOrder,
|
|
roomTypeSortOrder = nextRoomTypeSortOrder
|
|
)
|
|
return roomImageRepo.save(image).toResponse(publicBaseUrl)
|
|
}
|
|
|
|
@GetMapping("/{imageId}/file")
|
|
fun file(
|
|
@PathVariable propertyId: UUID,
|
|
@PathVariable roomId: UUID,
|
|
@PathVariable imageId: UUID,
|
|
@RequestParam(required = false, defaultValue = "full") size: String,
|
|
@AuthenticationPrincipal principal: MyPrincipal?
|
|
): ResponseEntity<FileSystemResource> {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
ensureRoom(propertyId, roomId)
|
|
|
|
val image = roomImageRepo.findByIdAndRoomIdAndPropertyId(imageId, roomId, propertyId)
|
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Image not found")
|
|
val path = if (size.equals("thumb", true)) image.thumbnailPath else image.originalPath
|
|
val file = Paths.get(path)
|
|
if (!Files.exists(file)) {
|
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "File missing")
|
|
}
|
|
val resource = FileSystemResource(file)
|
|
val type = image.contentType
|
|
return ResponseEntity.ok()
|
|
.contentType(MediaType.parseMediaType(type))
|
|
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"room-${imageId}.${file.fileName}\"")
|
|
.contentLength(resource.contentLength())
|
|
.body(resource)
|
|
}
|
|
|
|
private fun ensureRoom(propertyId: UUID, roomId: UUID): com.android.trisolarisserver.models.room.Room {
|
|
return roomRepo.findByIdAndPropertyId(roomId, propertyId)
|
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Room not found")
|
|
}
|
|
|
|
private fun requirePrincipal(principal: MyPrincipal?) {
|
|
if (principal == null) {
|
|
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun RoomImage.toResponse(baseUrl: String): RoomImageResponse {
|
|
val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Image id missing")
|
|
return RoomImageResponse(
|
|
id = id,
|
|
propertyId = property.id!!,
|
|
roomId = room.id!!,
|
|
roomTypeCode = roomTypeCode,
|
|
url = "$baseUrl/properties/${property.id}/rooms/${room.id}/images/$id/file",
|
|
thumbnailUrl = "$baseUrl/properties/${property.id}/rooms/${room.id}/images/$id/file?size=thumb",
|
|
contentType = contentType,
|
|
sizeBytes = sizeBytes,
|
|
tags = tags.toSet(),
|
|
roomSortOrder = roomSortOrder,
|
|
roomTypeSortOrder = roomTypeSortOrder,
|
|
createdAt = createdAt.toString()
|
|
)
|
|
}
|