Make amenities global and super-admin managed
All checks were successful
build-and-deploy / build-deploy (push) Successful in 27s

This commit is contained in:
androidlover5842
2026-01-27 04:33:51 +05:30
parent c3ec6e8d4a
commit eb0b99f55a
5 changed files with 34 additions and 55 deletions

View File

@@ -1,11 +1,9 @@
package com.android.trisolarisserver.controller package com.android.trisolarisserver.controller
import com.android.trisolarisserver.component.PropertyAccess
import com.android.trisolarisserver.controller.dto.AmenityResponse import com.android.trisolarisserver.controller.dto.AmenityResponse
import com.android.trisolarisserver.controller.dto.AmenityUpsertRequest import com.android.trisolarisserver.controller.dto.AmenityUpsertRequest
import com.android.trisolarisserver.models.property.Role
import com.android.trisolarisserver.models.room.RoomAmenity import com.android.trisolarisserver.models.room.RoomAmenity
import com.android.trisolarisserver.repo.PropertyRepo import com.android.trisolarisserver.repo.AppUserRepo
import com.android.trisolarisserver.repo.RoomAmenityRepo import com.android.trisolarisserver.repo.RoomAmenityRepo
import com.android.trisolarisserver.security.MyPrincipal import com.android.trisolarisserver.security.MyPrincipal
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
@@ -23,43 +21,32 @@ import org.springframework.web.server.ResponseStatusException
import java.util.UUID import java.util.UUID
@RestController @RestController
@RequestMapping("/properties/{propertyId}/amenities") @RequestMapping("/amenities")
class RoomAmenities( class RoomAmenities(
private val propertyAccess: PropertyAccess,
private val roomAmenityRepo: RoomAmenityRepo, private val roomAmenityRepo: RoomAmenityRepo,
private val propertyRepo: PropertyRepo private val appUserRepo: AppUserRepo
) { ) {
@GetMapping @GetMapping
fun listAmenities( fun listAmenities(
@PathVariable propertyId: UUID,
@AuthenticationPrincipal principal: MyPrincipal? @AuthenticationPrincipal principal: MyPrincipal?
): List<AmenityResponse> { ): List<AmenityResponse> {
requirePrincipal(principal) requirePrincipal(principal)
propertyAccess.requireMember(propertyId, principal!!.userId) return roomAmenityRepo.findAllByOrderByName().map { it.toResponse() }
return roomAmenityRepo.findByPropertyIdOrderByName(propertyId).map { it.toResponse() }
} }
@PostMapping @PostMapping
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
fun createAmenity( fun createAmenity(
@PathVariable propertyId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?, @AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: AmenityUpsertRequest @RequestBody request: AmenityUpsertRequest
): AmenityResponse { ): AmenityResponse {
requirePrincipal(principal) requireSuperAdmin(principal)
propertyAccess.requireMember(propertyId, principal!!.userId)
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN, Role.MANAGER)
if (roomAmenityRepo.existsByPropertyIdAndName(propertyId, request.name)) { if (roomAmenityRepo.existsByName(request.name)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists for property") throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists")
}
val property = propertyRepo.findById(propertyId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
} }
val amenity = RoomAmenity( val amenity = RoomAmenity(
property = property,
name = request.name, name = request.name,
category = request.category, category = request.category,
iconKey = request.iconKey, iconKey = request.iconKey,
@@ -70,20 +57,17 @@ class RoomAmenities(
@PutMapping("/{amenityId}") @PutMapping("/{amenityId}")
fun updateAmenity( fun updateAmenity(
@PathVariable propertyId: UUID,
@PathVariable amenityId: UUID, @PathVariable amenityId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?, @AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: AmenityUpsertRequest @RequestBody request: AmenityUpsertRequest
): AmenityResponse { ): AmenityResponse {
requirePrincipal(principal) requireSuperAdmin(principal)
propertyAccess.requireMember(propertyId, principal!!.userId)
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN, Role.MANAGER)
val amenity = roomAmenityRepo.findByIdAndPropertyId(amenityId, propertyId) val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found") ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
if (roomAmenityRepo.existsByPropertyIdAndNameAndIdNot(propertyId, request.name, amenityId)) { if (roomAmenityRepo.existsByNameAndIdNot(request.name, amenityId)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists for property") throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists")
} }
amenity.name = request.name amenity.name = request.name
@@ -96,15 +80,12 @@ class RoomAmenities(
@DeleteMapping("/{amenityId}") @DeleteMapping("/{amenityId}")
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteAmenity( fun deleteAmenity(
@PathVariable propertyId: UUID,
@PathVariable amenityId: UUID, @PathVariable amenityId: UUID,
@AuthenticationPrincipal principal: MyPrincipal? @AuthenticationPrincipal principal: MyPrincipal?
) { ) {
requirePrincipal(principal) requireSuperAdmin(principal)
propertyAccess.requireMember(propertyId, principal!!.userId)
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN, Role.MANAGER)
val amenity = roomAmenityRepo.findByIdAndPropertyId(amenityId, propertyId) val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found") ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
roomAmenityRepo.delete(amenity) roomAmenityRepo.delete(amenity)
@@ -115,14 +96,24 @@ class RoomAmenities(
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal") throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
} }
} }
private fun requireSuperAdmin(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
val user = appUserRepo.findById(principal.userId).orElseThrow {
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
}
if (!user.superAdmin) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Super admin only")
}
}
} }
private fun RoomAmenity.toResponse(): AmenityResponse { private fun RoomAmenity.toResponse(): AmenityResponse {
val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Amenity id missing") val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Amenity id missing")
val propertyId = property.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Property id missing")
return AmenityResponse( return AmenityResponse(
id = id, id = id,
propertyId = propertyId,
name = name, name = name,
category = category, category = category,
iconKey = iconKey, iconKey = iconKey,

View File

@@ -74,16 +74,16 @@ class RoomTypes(
otaAliases = request.otaAliases?.toMutableSet() ?: mutableSetOf() otaAliases = request.otaAliases?.toMutableSet() ?: mutableSetOf()
) )
if (request.amenityIds != null) { if (request.amenityIds != null) {
roomType.amenities = resolveAmenities(propertyId, request.amenityIds) roomType.amenities = resolveAmenities(request.amenityIds)
} }
return roomTypeRepo.save(roomType).toResponse() return roomTypeRepo.save(roomType).toResponse()
} }
private fun resolveAmenities(propertyId: UUID, ids: Set<UUID>): MutableSet<RoomAmenity> { private fun resolveAmenities(ids: Set<UUID>): MutableSet<RoomAmenity> {
if (ids.isEmpty()) { if (ids.isEmpty()) {
return mutableSetOf() return mutableSetOf()
} }
val amenities = roomAmenityRepo.findByPropertyIdAndIdIn(propertyId, ids) val amenities = roomAmenityRepo.findByIdIn(ids)
if (amenities.size != ids.size) { if (amenities.size != ids.size) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found") throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
} }
@@ -118,7 +118,7 @@ class RoomTypes(
roomType.otaAliases = request.otaAliases.toMutableSet() roomType.otaAliases = request.otaAliases.toMutableSet()
} }
if (request.amenityIds != null) { if (request.amenityIds != null) {
roomType.amenities = resolveAmenities(propertyId, request.amenityIds) roomType.amenities = resolveAmenities(request.amenityIds)
} }
return roomTypeRepo.save(roomType).toResponse() return roomTypeRepo.save(roomType).toResponse()
} }
@@ -169,10 +169,8 @@ private fun RoomType.toResponse(): RoomTypeResponse {
private fun RoomAmenity.toResponse(): com.android.trisolarisserver.controller.dto.AmenityResponse { private fun RoomAmenity.toResponse(): com.android.trisolarisserver.controller.dto.AmenityResponse {
val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Amenity id missing") val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Amenity id missing")
val propertyId = property.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Property id missing")
return com.android.trisolarisserver.controller.dto.AmenityResponse( return com.android.trisolarisserver.controller.dto.AmenityResponse(
id = id, id = id,
propertyId = propertyId,
name = name, name = name,
category = category, category = category,
iconKey = iconKey, iconKey = iconKey,

View File

@@ -35,7 +35,6 @@ data class AmenityUpsertRequest(
data class AmenityResponse( data class AmenityResponse(
val id: UUID, val id: UUID,
val propertyId: UUID,
val name: String, val name: String,
val category: String?, val category: String?,
val iconKey: String?, val iconKey: String?,

View File

@@ -1,13 +1,9 @@
package com.android.trisolarisserver.models.room package com.android.trisolarisserver.models.room
import com.android.trisolarisserver.models.property.Property
import jakarta.persistence.Column import jakarta.persistence.Column
import jakarta.persistence.Entity import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.Table import jakarta.persistence.Table
import jakarta.persistence.UniqueConstraint import jakarta.persistence.UniqueConstraint
import java.time.OffsetDateTime import java.time.OffsetDateTime
@@ -16,7 +12,7 @@ import java.util.UUID
@Entity @Entity
@Table( @Table(
name = "room_amenity", name = "room_amenity",
uniqueConstraints = [UniqueConstraint(columnNames = ["property_id", "name"])] uniqueConstraints = [UniqueConstraint(columnNames = ["name"])]
) )
class RoomAmenity( class RoomAmenity(
@Id @Id
@@ -24,10 +20,6 @@ class RoomAmenity(
@Column(columnDefinition = "uuid") @Column(columnDefinition = "uuid")
val id: UUID? = null, val id: UUID? = null,
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "property_id", nullable = false)
var property: Property,
@Column(nullable = false) @Column(nullable = false)
var name: String, var name: String,

View File

@@ -5,9 +5,8 @@ import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID import java.util.UUID
interface RoomAmenityRepo : JpaRepository<RoomAmenity, UUID> { interface RoomAmenityRepo : JpaRepository<RoomAmenity, UUID> {
fun findByIdAndPropertyId(id: UUID, propertyId: UUID): RoomAmenity? fun findAllByOrderByName(): List<RoomAmenity>
fun findByPropertyIdOrderByName(propertyId: UUID): List<RoomAmenity> fun findByIdIn(ids: Set<UUID>): List<RoomAmenity>
fun findByPropertyIdAndIdIn(propertyId: UUID, ids: Set<UUID>): List<RoomAmenity> fun existsByName(name: String): Boolean
fun existsByPropertyIdAndName(propertyId: UUID, name: String): Boolean fun existsByNameAndIdNot(name: String, id: UUID): Boolean
fun existsByPropertyIdAndNameAndIdNot(propertyId: UUID, name: String, id: UUID): Boolean
} }