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 { 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 { 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): MutableSet { 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() ) }