Files
TrisolarisServer/src/main/kotlin/com/android/trisolarisserver/controller/Properties.kt
androidlover5842 72d9f5bb12 modes of transport
2026-01-24 22:39:45 +05:30

287 lines
12 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.controller.dto.UserResponse
import com.android.trisolarisserver.repo.AppUserRepo
import com.android.trisolarisserver.repo.OrganizationRepo
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 orgRepo: OrganizationRepo,
private val propertyUserRepo: PropertyUserRepo,
private val appUserRepo: AppUserRepo
) {
@PostMapping("/orgs/{orgId}/properties")
@ResponseStatus(HttpStatus.CREATED)
fun createProperty(
@PathVariable orgId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: PropertyCreateRequest
): PropertyResponse {
val user = requireUser(principal)
if (user.org.id != orgId) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "No access to org")
}
requireOrgRole(orgId, user.id!!, Role.ADMIN)
if (propertyRepo.existsByOrgIdAndCode(orgId, request.code)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists for org")
}
val org = orgRepo.findById(orgId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Org not found")
}
val property = Property(
org = org,
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)
return saved.toResponse()
}
@GetMapping("/orgs/{orgId}/properties")
fun listProperties(
@PathVariable orgId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?
): List<PropertyResponse> {
val user = requireUser(principal)
if (user.org.id != orgId) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "No access to org")
}
val propertyIds = propertyUserRepo.findPropertyIdsByOrgAndUser(orgId, user.id!!)
return propertyRepo.findAllById(propertyIds).map { it.toResponse() }
}
@GetMapping("/orgs/{orgId}/users")
fun listUsers(
@PathVariable orgId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?
): List<UserResponse> {
val user = requireUser(principal)
if (user.org.id != orgId) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "No access to org")
}
requireOrgRole(orgId, user.id!!, Role.ADMIN, Role.MANAGER)
return appUserRepo.findByOrgId(orgId).map { it.toUserResponse() }
}
@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 actorRoles = propertyUserRepo.findRolesByPropertyAndUser(propertyId, principal.userId)
val allowedRoles = when {
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")
}
if (targetUser.org.id != property.org.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "User not in property org")
}
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.existsByOrgIdAndCodeAndIdNot(property.org.id!!, request.code, propertyId)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists for org")
}
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 requireOrgRole(orgId: UUID, userId: UUID, vararg roles: Role) {
if (!propertyUserRepo.hasAnyRoleInOrg(orgId, userId, roles.toSet())) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Missing role")
}
}
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")
val orgId = org.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Org id missing")
return PropertyResponse(
id = id,
orgId = orgId,
code = code,
name = name,
addressText = addressText,
timezone = timezone,
currency = currency,
active = active,
otaAliases = otaAliases.toSet(),
emailAddresses = emailAddresses.toSet(),
allowedTransportModes = allowedTransportModes.map { it.name }.toSet()
)
}
private fun com.android.trisolarisserver.models.property.AppUser.toUserResponse(): UserResponse {
val id = this.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "User id missing")
val orgId = this.org.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Org id missing")
return UserResponse(
id = id,
orgId = orgId,
firebaseUid = firebaseUid,
phoneE164 = phoneE164,
name = name,
disabled = disabled
)
}