253 lines
10 KiB
Kotlin
253 lines
10 KiB
Kotlin
package com.android.trisolarisserver.controller
|
|
|
|
import com.android.trisolarisserver.component.PropertyAccess
|
|
import com.android.trisolarisserver.controller.dto.PropertyCreateRequest
|
|
import com.android.trisolarisserver.controller.dto.PropertyResponse
|
|
import com.android.trisolarisserver.controller.dto.PropertyUpdateRequest
|
|
import com.android.trisolarisserver.controller.dto.PropertyUserResponse
|
|
import com.android.trisolarisserver.controller.dto.PropertyUserRoleRequest
|
|
import com.android.trisolarisserver.repo.AppUserRepo
|
|
import com.android.trisolarisserver.repo.PropertyRepo
|
|
import com.android.trisolarisserver.repo.PropertyUserRepo
|
|
import com.android.trisolarisserver.models.property.Property
|
|
import com.android.trisolarisserver.models.property.PropertyUser
|
|
import com.android.trisolarisserver.models.property.PropertyUserId
|
|
import com.android.trisolarisserver.models.property.Role
|
|
import com.android.trisolarisserver.security.MyPrincipal
|
|
import com.android.trisolarisserver.models.booking.TransportMode
|
|
import org.springframework.http.HttpStatus
|
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
|
import org.springframework.web.bind.annotation.DeleteMapping
|
|
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.PutMapping
|
|
import org.springframework.web.bind.annotation.RequestBody
|
|
import org.springframework.web.bind.annotation.ResponseStatus
|
|
import org.springframework.web.bind.annotation.RestController
|
|
import org.springframework.web.server.ResponseStatusException
|
|
import java.util.UUID
|
|
|
|
@RestController
|
|
class Properties(
|
|
private val propertyAccess: PropertyAccess,
|
|
private val propertyRepo: PropertyRepo,
|
|
private val propertyUserRepo: PropertyUserRepo,
|
|
private val appUserRepo: AppUserRepo
|
|
) {
|
|
|
|
@PostMapping("/properties")
|
|
@ResponseStatus(HttpStatus.CREATED)
|
|
fun createProperty(
|
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
|
@RequestBody request: PropertyCreateRequest
|
|
): PropertyResponse {
|
|
val user = requireUser(principal)
|
|
if (propertyRepo.existsByCode(request.code)) {
|
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
|
|
}
|
|
|
|
val property = Property(
|
|
code = request.code,
|
|
name = request.name,
|
|
addressText = request.addressText,
|
|
timezone = request.timezone ?: "Asia/Kolkata",
|
|
currency = request.currency ?: "INR",
|
|
active = request.active ?: true,
|
|
otaAliases = request.otaAliases?.toMutableSet() ?: mutableSetOf(),
|
|
emailAddresses = request.emailAddresses?.toMutableSet() ?: mutableSetOf(),
|
|
allowedTransportModes = request.allowedTransportModes?.let { parseTransportModes(it) } ?: mutableSetOf()
|
|
)
|
|
val saved = propertyRepo.save(property)
|
|
|
|
val creatorId = user.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing")
|
|
val propertyUserId = PropertyUserId(propertyId = saved.id!!, userId = creatorId)
|
|
if (!propertyUserRepo.existsById(propertyUserId)) {
|
|
propertyUserRepo.save(
|
|
PropertyUser(
|
|
id = propertyUserId,
|
|
property = saved,
|
|
user = user,
|
|
roles = mutableSetOf(Role.ADMIN)
|
|
)
|
|
)
|
|
}
|
|
|
|
return saved.toResponse()
|
|
}
|
|
|
|
@GetMapping("/properties")
|
|
fun listProperties(
|
|
@AuthenticationPrincipal principal: MyPrincipal?
|
|
): List<PropertyResponse> {
|
|
val user = requireUser(principal)
|
|
return if (user.superAdmin) {
|
|
propertyRepo.findAll().map { it.toResponse() }
|
|
} else {
|
|
val propertyIds = propertyUserRepo.findByIdUserId(user.id!!).map { it.id.propertyId!! }
|
|
propertyRepo.findAllById(propertyIds).map { it.toResponse() }
|
|
}
|
|
}
|
|
|
|
@GetMapping("/properties/{propertyId}/users")
|
|
fun listPropertyUsers(
|
|
@PathVariable propertyId: UUID,
|
|
@AuthenticationPrincipal principal: MyPrincipal?
|
|
): List<PropertyUserResponse> {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN, Role.MANAGER)
|
|
val users = propertyUserRepo.findByIdPropertyId(propertyId)
|
|
return users.map {
|
|
PropertyUserResponse(
|
|
userId = it.id.userId!!,
|
|
propertyId = it.id.propertyId!!,
|
|
roles = it.roles.map { role -> role.name }.toSet()
|
|
)
|
|
}
|
|
}
|
|
|
|
@PutMapping("/properties/{propertyId}/users/{userId}/roles")
|
|
fun upsertPropertyUserRoles(
|
|
@PathVariable propertyId: UUID,
|
|
@PathVariable userId: UUID,
|
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
|
@RequestBody request: PropertyUserRoleRequest
|
|
): PropertyUserResponse {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
|
|
val actorUser = appUserRepo.findById(principal.userId).orElse(null)
|
|
val actorRoles = propertyUserRepo.findRolesByPropertyAndUser(propertyId, principal.userId)
|
|
val allowedRoles = when {
|
|
actorUser?.superAdmin == true -> setOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.AGENT)
|
|
actorRoles.contains(Role.ADMIN) -> setOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.AGENT)
|
|
actorRoles.contains(Role.MANAGER) -> setOf(Role.STAFF, Role.AGENT)
|
|
else -> emptySet()
|
|
}
|
|
if (allowedRoles.isEmpty()) {
|
|
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Missing role")
|
|
}
|
|
val requestedRoles = try {
|
|
request.roles.map { Role.valueOf(it) }.toSet()
|
|
} catch (ex: IllegalArgumentException) {
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown role")
|
|
}
|
|
if (!allowedRoles.containsAll(requestedRoles)) {
|
|
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Role not allowed")
|
|
}
|
|
|
|
val property = propertyRepo.findById(propertyId).orElseThrow {
|
|
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
|
}
|
|
val targetUser = appUserRepo.findById(userId).orElseThrow {
|
|
ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
|
|
}
|
|
|
|
val propertyUser = PropertyUser(
|
|
id = PropertyUserId(propertyId = propertyId, userId = userId),
|
|
property = property,
|
|
user = targetUser,
|
|
roles = requestedRoles.toMutableSet()
|
|
)
|
|
val saved = propertyUserRepo.save(propertyUser)
|
|
return PropertyUserResponse(
|
|
userId = saved.id.userId!!,
|
|
propertyId = saved.id.propertyId!!,
|
|
roles = saved.roles.map { it.name }.toSet()
|
|
)
|
|
}
|
|
|
|
@DeleteMapping("/properties/{propertyId}/users/{userId}")
|
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
fun deletePropertyUser(
|
|
@PathVariable propertyId: UUID,
|
|
@PathVariable userId: UUID,
|
|
@AuthenticationPrincipal principal: MyPrincipal?
|
|
) {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN)
|
|
|
|
val id = PropertyUserId(propertyId = propertyId, userId = userId)
|
|
if (propertyUserRepo.existsById(id)) {
|
|
propertyUserRepo.deleteById(id)
|
|
}
|
|
}
|
|
|
|
@PutMapping("/properties/{propertyId}")
|
|
fun updateProperty(
|
|
@PathVariable propertyId: UUID,
|
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
|
@RequestBody request: PropertyUpdateRequest
|
|
): PropertyResponse {
|
|
requirePrincipal(principal)
|
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
|
propertyAccess.requireAnyRole(propertyId, principal.userId, Role.ADMIN)
|
|
|
|
val property = propertyRepo.findById(propertyId).orElseThrow {
|
|
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
|
}
|
|
if (propertyRepo.existsByCodeAndIdNot(request.code, propertyId)) {
|
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
|
|
}
|
|
|
|
property.code = request.code
|
|
property.name = request.name
|
|
property.addressText = request.addressText ?: property.addressText
|
|
property.timezone = request.timezone ?: property.timezone
|
|
property.currency = request.currency ?: property.currency
|
|
property.active = request.active ?: property.active
|
|
if (request.otaAliases != null) {
|
|
property.otaAliases = request.otaAliases.toMutableSet()
|
|
}
|
|
if (request.emailAddresses != null) {
|
|
property.emailAddresses = request.emailAddresses.toMutableSet()
|
|
}
|
|
if (request.allowedTransportModes != null) {
|
|
property.allowedTransportModes = parseTransportModes(request.allowedTransportModes)
|
|
}
|
|
|
|
return propertyRepo.save(property).toResponse()
|
|
}
|
|
|
|
private fun requirePrincipal(principal: MyPrincipal?) {
|
|
if (principal == null) {
|
|
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
|
|
}
|
|
}
|
|
|
|
private fun requireUser(principal: MyPrincipal?): com.android.trisolarisserver.models.property.AppUser {
|
|
if (principal == null) {
|
|
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
|
|
}
|
|
return appUserRepo.findById(principal.userId).orElseThrow {
|
|
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
|
|
}
|
|
}
|
|
|
|
private fun parseTransportModes(modes: Set<String>): MutableSet<TransportMode> {
|
|
return try {
|
|
modes.map { TransportMode.valueOf(it) }.toMutableSet()
|
|
} catch (_: IllegalArgumentException) {
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown transport mode")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun Property.toResponse(): PropertyResponse {
|
|
val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Property id missing")
|
|
return PropertyResponse(
|
|
id = id,
|
|
code = code,
|
|
name = name,
|
|
addressText = addressText,
|
|
timezone = timezone,
|
|
currency = currency,
|
|
active = active,
|
|
otaAliases = otaAliases.toSet(),
|
|
emailAddresses = emailAddresses.toSet(),
|
|
allowedTransportModes = allowedTransportModes.map { it.name }.toSet()
|
|
)
|
|
}
|