added auth,property db and room db,org db
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
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.UserCreateRequest
|
||||
import com.android.trisolarisserver.controller.dto.UserResponse
|
||||
import com.android.trisolarisserver.db.repo.AppUserRepo
|
||||
import com.android.trisolarisserver.db.repo.OrganizationRepo
|
||||
import com.android.trisolarisserver.db.repo.PropertyRepo
|
||||
import com.android.trisolarisserver.db.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 org.springframework.http.HttpStatus
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
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,
|
||||
timezone = request.timezone ?: "Asia/Kolkata",
|
||||
currency = request.currency ?: "INR",
|
||||
active = request.active ?: true
|
||||
)
|
||||
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() }
|
||||
}
|
||||
|
||||
@PostMapping("/orgs/{orgId}/users")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun createUser(
|
||||
@PathVariable orgId: UUID,
|
||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||
@RequestBody request: UserCreateRequest
|
||||
): UserResponse {
|
||||
val user = requireUser(principal)
|
||||
if (user.org.id != orgId) {
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "No access to org")
|
||||
}
|
||||
requireOrgRole(orgId, user.id!!, Role.ADMIN)
|
||||
|
||||
if (appUserRepo.existsByFirebaseUid(request.firebaseUid)) {
|
||||
throw ResponseStatusException(HttpStatus.CONFLICT, "User already exists")
|
||||
}
|
||||
val org = orgRepo.findById(orgId).orElseThrow {
|
||||
ResponseStatusException(HttpStatus.NOT_FOUND, "Org not found")
|
||||
}
|
||||
val newUser = com.android.trisolarisserver.models.property.AppUser(
|
||||
org = org,
|
||||
firebaseUid = request.firebaseUid,
|
||||
phoneE164 = request.phoneE164,
|
||||
name = request.name,
|
||||
disabled = request.disabled ?: false
|
||||
)
|
||||
val saved = appUserRepo.save(newUser)
|
||||
return saved.toUserResponse()
|
||||
}
|
||||
|
||||
@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) -> Role.entries.toSet()
|
||||
actorRoles.contains(Role.MANAGER) -> setOf(Role.STAFF, Role.AGENT)
|
||||
else -> emptySet()
|
||||
}
|
||||
if (allowedRoles.isEmpty()) {
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Missing role")
|
||||
}
|
||||
val requestedRoles = request.roles.map { Role.valueOf(it) }.toSet()
|
||||
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.timezone = request.timezone ?: property.timezone
|
||||
property.currency = request.currency ?: property.currency
|
||||
property.active = request.active ?: property.active
|
||||
|
||||
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 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,
|
||||
timezone = timezone,
|
||||
currency = currency,
|
||||
active = active
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user