Remove org model; make AppUser global with super admin
All checks were successful
build-and-deploy / build-deploy (push) Successful in 27s

This commit is contained in:
androidlover5842
2026-01-26 22:33:59 +05:30
parent bf87d329d4
commit 1400451bfe
26 changed files with 101 additions and 362 deletions

View File

@@ -261,10 +261,10 @@ class BookingFlow(
property: com.android.trisolarisserver.models.property.Property,
mode: TransportMode
): Boolean {
val allowed = when {
property.allowedTransportModes.isNotEmpty() -> property.allowedTransportModes
property.org.allowedTransportModes.isNotEmpty() -> property.org.allowedTransportModes
else -> TransportMode.entries.toSet()
val allowed = if (property.allowedTransportModes.isNotEmpty()) {
property.allowedTransportModes
} else {
TransportMode.entries.toSet()
}
return allowed.contains(mode)
}

View File

@@ -71,8 +71,8 @@ class GuestDocuments(
val guest = guestRepo.findById(guestId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Guest not found")
}
if (guest.org.id != property.org.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property org")
if (guest.property.id != property.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property")
}
val booking = bookingRepo.findById(bookingId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found")

View File

@@ -51,8 +51,8 @@ class GuestRatings(
val guest = guestRepo.findById(guestId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Guest not found")
}
if (guest.org.id != property.org.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property org")
if (guest.property.id != property.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property")
}
val booking = bookingRepo.findById(request.bookingId).orElseThrow {
@@ -70,7 +70,6 @@ class GuestRatings(
val score = parseScore(request.score)
val rating = GuestRating(
org = property.org,
property = property,
guest = guest,
booking = booking,
@@ -97,8 +96,8 @@ class GuestRatings(
val guest = guestRepo.findById(guestId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Guest not found")
}
if (guest.org.id != property.org.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property org")
if (guest.property.id != property.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property")
}
return guestRatingRepo.findByGuestIdOrderByCreatedAtDesc(guestId).map { it.toResponse() }
@@ -117,7 +116,6 @@ class GuestRatings(
private fun GuestRating.toResponse(): GuestRatingResponse {
return GuestRatingResponse(
id = id!!,
orgId = org.id!!,
propertyId = property.id!!,
guestId = guest.id!!,
bookingId = booking.id!!,

View File

@@ -43,15 +43,14 @@ class Guests(
val property = propertyRepo.findById(propertyId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
}
val orgId = property.org.id ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Org missing")
val guests = mutableSetOf<Guest>()
if (!phone.isNullOrBlank()) {
val guest = guestRepo.findByOrgIdAndPhoneE164(orgId, phone)
val guest = guestRepo.findByPropertyIdAndPhoneE164(propertyId, phone)
if (guest != null) guests.add(guest)
}
if (!vehicleNumber.isNullOrBlank()) {
val vehicle = guestVehicleRepo.findByOrgIdAndVehicleNumberIgnoreCase(orgId, vehicleNumber)
val vehicle = guestVehicleRepo.findByPropertyIdAndVehicleNumberIgnoreCase(propertyId, vehicleNumber)
if (vehicle != null) guests.add(vehicle.guest)
}
return guests.toResponse(guestVehicleRepo, guestRatingRepo)
@@ -74,15 +73,15 @@ class Guests(
val guest = guestRepo.findById(guestId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Guest not found")
}
if (guest.org.id != property.org.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property org")
if (guest.property.id != property.id) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Guest not in property")
}
if (guestVehicleRepo.existsByOrgIdAndVehicleNumberIgnoreCase(property.org.id!!, request.vehicleNumber)) {
if (guestVehicleRepo.existsByPropertyIdAndVehicleNumberIgnoreCase(property.id!!, request.vehicleNumber)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Vehicle number already exists")
}
val vehicle = GuestVehicle(
org = property.org,
property = property,
guest = guest,
vehicleNumber = request.vehicleNumber.trim()
)
@@ -116,7 +115,6 @@ private fun Set<Guest>.toResponse(
return this.map { guest ->
GuestResponse(
id = guest.id!!,
orgId = guest.org.id!!,
name = guest.name,
phoneE164 = guest.phoneE164,
nationality = guest.nationality,

View File

@@ -1,95 +0,0 @@
package com.android.trisolarisserver.controller
import com.android.trisolarisserver.controller.dto.OrgCreateRequest
import com.android.trisolarisserver.controller.dto.OrgResponse
import com.android.trisolarisserver.repo.AppUserRepo
import com.android.trisolarisserver.repo.OrganizationRepo
import com.android.trisolarisserver.repo.PropertyUserRepo
import com.android.trisolarisserver.models.booking.TransportMode
import com.android.trisolarisserver.models.property.Organization
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.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
@RequestMapping("/orgs")
class Orgs(
private val orgRepo: OrganizationRepo,
private val appUserRepo: AppUserRepo,
private val propertyUserRepo: PropertyUserRepo
) {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createOrg(
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: OrgCreateRequest
): OrgResponse {
val user = requireUser(principal)
val orgId = user.org.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Org missing")
if (!propertyUserRepo.hasAnyRoleInOrg(orgId, user.id!!, setOf(Role.ADMIN))) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Missing role")
}
val org = Organization().apply {
name = request.name
emailAliases = request.emailAliases?.toMutableSet() ?: mutableSetOf()
if (request.allowedTransportModes != null) {
allowedTransportModes = parseTransportModes(request.allowedTransportModes)
}
}
val saved = orgRepo.save(org)
return OrgResponse(
id = saved.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Org id missing"),
name = saved.name ?: "",
emailAliases = saved.emailAliases.toSet(),
allowedTransportModes = saved.allowedTransportModes.map { it.name }.toSet()
)
}
@GetMapping("/{orgId}")
fun getOrg(
@PathVariable orgId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?
): OrgResponse {
val user = requireUser(principal)
if (user.org.id != orgId) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "No access to org")
}
val org = orgRepo.findById(orgId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Org not found")
}
return OrgResponse(
id = org.id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Org id missing"),
name = org.name ?: "",
emailAliases = org.emailAliases.toSet(),
allowedTransportModes = org.allowedTransportModes.map { it.name }.toSet()
)
}
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")
}
}
}

View File

@@ -6,9 +6,7 @@ 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
@@ -34,33 +32,22 @@ import java.util.UUID
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")
@PostMapping("/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")
if (propertyRepo.existsByCode(request.code)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
}
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,
@@ -72,33 +59,34 @@ class Properties(
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("/orgs/{orgId}/properties")
@GetMapping("/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")
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() }
}
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")
@@ -153,9 +141,6 @@ class Properties(
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),
@@ -201,8 +186,8 @@ class Properties(
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")
if (propertyRepo.existsByCodeAndIdNot(request.code, propertyId)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
}
property.code = request.code
@@ -239,12 +224,6 @@ class Properties(
}
}
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()
@@ -256,10 +235,8 @@ class Properties(
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,
@@ -271,16 +248,3 @@ private fun Property.toResponse(): PropertyResponse {
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
)
}

View File

@@ -31,10 +31,10 @@ class TransportModes(
val property = propertyRepo.findById(propertyId).orElseThrow {
ResponseStatusException(org.springframework.http.HttpStatus.NOT_FOUND, "Property not found")
}
val allowed = when {
property.allowedTransportModes.isNotEmpty() -> property.allowedTransportModes
property.org.allowedTransportModes.isNotEmpty() -> property.org.allowedTransportModes
else -> TransportMode.entries.toSet()
val allowed = if (property.allowedTransportModes.isNotEmpty()) {
property.allowedTransportModes
} else {
TransportMode.entries.toSet()
}
return TransportMode.entries.map { mode ->
TransportModeStatusResponse(

View File

@@ -10,7 +10,6 @@ data class GuestRatingCreateRequest(
data class GuestRatingResponse(
val id: UUID,
val orgId: UUID,
val propertyId: UUID,
val guestId: UUID,
val bookingId: UUID,

View File

@@ -2,19 +2,6 @@ package com.android.trisolarisserver.controller.dto
import java.util.UUID
data class OrgCreateRequest(
val name: String,
val emailAliases: Set<String>? = null,
val allowedTransportModes: Set<String>? = null
)
data class OrgResponse(
val id: UUID,
val name: String,
val emailAliases: Set<String>,
val allowedTransportModes: Set<String>
)
data class PropertyCreateRequest(
val code: String,
val name: String,
@@ -41,7 +28,6 @@ data class PropertyUpdateRequest(
data class PropertyResponse(
val id: UUID,
val orgId: UUID,
val code: String,
val name: String,
val addressText: String?,
@@ -55,7 +41,6 @@ data class PropertyResponse(
data class GuestResponse(
val id: UUID,
val orgId: UUID,
val name: String?,
val phoneE164: String?,
val nationality: String?,
@@ -75,11 +60,11 @@ data class TransportModeStatusResponse(
data class UserResponse(
val id: UUID,
val orgId: UUID,
val firebaseUid: String?,
val phoneE164: String?,
val name: String?,
val disabled: Boolean
val disabled: Boolean,
val superAdmin: Boolean
)
data class PropertyUserRoleRequest(