From 1400451bfe54714d204e53633e90e35202202db9 Mon Sep 17 00:00:00 2001 From: androidlover5842 Date: Mon, 26 Jan 2026 22:33:59 +0530 Subject: [PATCH] Remove org model; make AppUser global with super admin --- AGENTS.md | 25 ++--- .../component/PropertyAccess.kt | 8 +- .../controller/BookingFlow.kt | 8 +- .../controller/GuestDocuments.kt | 4 +- .../controller/GuestRatings.kt | 10 +- .../trisolarisserver/controller/Guests.kt | 14 ++- .../trisolarisserver/controller/Orgs.kt | 95 ------------------- .../trisolarisserver/controller/Properties.kt | 86 +++++------------ .../controller/TransportModes.kt | 8 +- .../controller/dto/GuestRatingDtos.kt | 1 - .../controller/dto/OrgPropertyDtos.kt | 19 +--- .../trisolarisserver/db/repo/GuestRepo.kt | 2 +- .../trisolarisserver/models/booking/Guest.kt | 8 +- .../models/booking/GuestRating.kt | 5 - .../models/booking/GuestVehicle.kt | 8 +- .../models/property/AppUser.kt | 14 ++- .../models/property/Organization.kt | 37 -------- .../models/property/PendingUser.kt | 33 ------- .../models/property/Property.kt | 19 ++-- .../trisolarisserver/repo/AppUserRepo.kt | 1 - .../trisolarisserver/repo/GuestVehicleRepo.kt | 4 +- .../trisolarisserver/repo/OrganizationRepo.kt | 11 --- .../trisolarisserver/repo/PendingUserRepo.kt | 9 -- .../trisolarisserver/repo/PropertyRepo.kt | 4 +- .../trisolarisserver/repo/PropertyUserRepo.kt | 22 ----- .../service/EmailIngestionService.kt | 8 +- 26 files changed, 101 insertions(+), 362 deletions(-) delete mode 100644 src/main/kotlin/com/android/trisolarisserver/controller/Orgs.kt delete mode 100644 src/main/kotlin/com/android/trisolarisserver/models/property/Organization.kt delete mode 100644 src/main/kotlin/com/android/trisolarisserver/models/property/PendingUser.kt delete mode 100644 src/main/kotlin/com/android/trisolarisserver/repo/OrganizationRepo.kt delete mode 100644 src/main/kotlin/com/android/trisolarisserver/repo/PendingUserRepo.kt diff --git a/AGENTS.md b/AGENTS.md index baae99f..62e3e2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ Core principles - Room availability by room number; toAt=null means occupied. - Room change = close old RoomStay + open new one. - Multi-property: every domain object scoped to property_id. -- Users belong to org; access granted per property. +- AppUser is global; access granted per property. Immutable rules - Use Kotlin only; no microservices. @@ -46,19 +46,18 @@ Security/Auth - /auth/verify and /auth/me. Domain entities -- Organization: name, emailAliases, allowedTransportModes. - Property: code, name, addressText, emailAddresses, otaAliases, allowedTransportModes. -- AppUser, PropertyUser (roles per property). +- AppUser (global, superAdmin), PropertyUser (roles per property). - RoomType: code/name/occupancy + otaAliases. - Room: roomNumber, floor, hasNfc, active, maintenance, notes. - Booking: status, expected check-in/out, emailAuditPdfUrl, transportMode, transportVehicleNumber. -- Guest (org-scoped). +- Guest (property-scoped). - RoomStay. - RoomStayChange (idempotent room move). - IssuedCard (cardId, cardIndex, issuedAt, expiresAt, issuedBy, revokedAt). - PropertyCardCounter (per-property cardIndex counter). - GuestDocument (files + AI-extracted json). -- GuestVehicle (org-scoped vehicle numbers). +- GuestVehicle (property-scoped vehicle numbers). - InboundEmail (audit PDF + raw EML, extracted json, status). - RoomImage (original + thumbnail). @@ -68,14 +67,10 @@ Auth - /auth/verify - /auth/me -Organizations / Properties / Users -- POST /orgs -- GET /orgs/{orgId} -- POST /orgs/{orgId}/properties -- GET /orgs/{orgId}/properties +Properties / Users +- POST /properties (creator becomes ADMIN on that property) +- GET /properties (super admin gets all; others get memberships) - PUT /properties/{propertyId} -- GET /orgs/{orgId}/users -- POST /orgs/{orgId}/users (removed; users created by app) - GET /properties/{propertyId}/users - PUT /properties/{propertyId}/users/{userId}/roles - DELETE /properties/{propertyId}/users/{userId} (ADMIN only) @@ -93,9 +88,8 @@ Room types - PUT /properties/{propertyId}/room-types/{roomTypeId} - DELETE /properties/{propertyId}/room-types/{roomTypeId} -Properties / Orgs +Properties - Property create/update accepts addressText, otaAliases, emailAddresses, allowedTransportModes. -- Org create/get returns emailAliases + allowedTransportModes. Booking flow - /properties/{propertyId}/bookings/{bookingId}/check-in (creates RoomStay rows) @@ -126,7 +120,7 @@ Room images - Thumbnails generated (320px). Transport modes -- /properties/{propertyId}/transport-modes -> returns enabled list (property > org > default all). +- /properties/{propertyId}/transport-modes -> returns enabled list (property or default all). Inbound email ingestion - IMAP poller (1 min) with enable flag. @@ -153,5 +147,6 @@ Config Notes / constraints - Users are created by app; API only manages roles. +- Super admin can create properties and assign users to properties. - Admin can assign ADMIN/MANAGER/STAFF/AGENT; Manager can assign STAFF/AGENT. - Agents can only see free rooms. diff --git a/src/main/kotlin/com/android/trisolarisserver/component/PropertyAccess.kt b/src/main/kotlin/com/android/trisolarisserver/component/PropertyAccess.kt index 611dd75..fbf5b3e 100644 --- a/src/main/kotlin/com/android/trisolarisserver/component/PropertyAccess.kt +++ b/src/main/kotlin/com/android/trisolarisserver/component/PropertyAccess.kt @@ -1,5 +1,6 @@ package com.android.trisolarisserver.component +import com.android.trisolarisserver.repo.AppUserRepo import com.android.trisolarisserver.repo.PropertyUserRepo import com.android.trisolarisserver.models.property.Role import org.springframework.security.access.AccessDeniedException @@ -8,14 +9,19 @@ import java.util.UUID @Component class PropertyAccess( - private val repo: PropertyUserRepo + private val repo: PropertyUserRepo, + private val appUserRepo: AppUserRepo ) { fun requireMember(propertyId: UUID, userId: UUID) { + val user = appUserRepo.findById(userId).orElse(null) + if (user?.superAdmin == true) return if (!repo.existsByIdPropertyIdAndIdUserId(propertyId, userId)) throw AccessDeniedException("No access to property") } fun requireAnyRole(propertyId: UUID, userId: UUID, vararg roles: Role) { + val user = appUserRepo.findById(userId).orElse(null) + if (user?.superAdmin == true) return if (!repo.hasAnyRole(propertyId, userId, roles.toSet())) throw AccessDeniedException("Missing role") } diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/BookingFlow.kt b/src/main/kotlin/com/android/trisolarisserver/controller/BookingFlow.kt index 7a54173..afdaf69 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/BookingFlow.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/BookingFlow.kt @@ -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) } diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/GuestDocuments.kt b/src/main/kotlin/com/android/trisolarisserver/controller/GuestDocuments.kt index 7774f3f..b4cbad7 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/GuestDocuments.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/GuestDocuments.kt @@ -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") diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/GuestRatings.kt b/src/main/kotlin/com/android/trisolarisserver/controller/GuestRatings.kt index 4987817..2b3c364 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/GuestRatings.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/GuestRatings.kt @@ -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!!, diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/Guests.kt b/src/main/kotlin/com/android/trisolarisserver/controller/Guests.kt index 098590d..1ca3dcc 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/Guests.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/Guests.kt @@ -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() 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.toResponse( return this.map { guest -> GuestResponse( id = guest.id!!, - orgId = guest.org.id!!, name = guest.name, phoneE164 = guest.phoneE164, nationality = guest.nationality, diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/Orgs.kt b/src/main/kotlin/com/android/trisolarisserver/controller/Orgs.kt deleted file mode 100644 index 65da76c..0000000 --- a/src/main/kotlin/com/android/trisolarisserver/controller/Orgs.kt +++ /dev/null @@ -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): MutableSet { - return try { - modes.map { TransportMode.valueOf(it) }.toMutableSet() - } catch (_: IllegalArgumentException) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown transport mode") - } - } -} diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/Properties.kt b/src/main/kotlin/com/android/trisolarisserver/controller/Properties.kt index b8cd276..313b397 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/Properties.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/Properties.kt @@ -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 { 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 { - 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): MutableSet { 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 - ) -} diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/TransportModes.kt b/src/main/kotlin/com/android/trisolarisserver/controller/TransportModes.kt index 59cca37..3137322 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/TransportModes.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/TransportModes.kt @@ -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( diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/dto/GuestRatingDtos.kt b/src/main/kotlin/com/android/trisolarisserver/controller/dto/GuestRatingDtos.kt index 5b03ca6..3d210fb 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/dto/GuestRatingDtos.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/dto/GuestRatingDtos.kt @@ -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, diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/dto/OrgPropertyDtos.kt b/src/main/kotlin/com/android/trisolarisserver/controller/dto/OrgPropertyDtos.kt index 0854db8..6323caf 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/dto/OrgPropertyDtos.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/dto/OrgPropertyDtos.kt @@ -2,19 +2,6 @@ package com.android.trisolarisserver.controller.dto import java.util.UUID -data class OrgCreateRequest( - val name: String, - val emailAliases: Set? = null, - val allowedTransportModes: Set? = null -) - -data class OrgResponse( - val id: UUID, - val name: String, - val emailAliases: Set, - val allowedTransportModes: Set -) - 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( diff --git a/src/main/kotlin/com/android/trisolarisserver/db/repo/GuestRepo.kt b/src/main/kotlin/com/android/trisolarisserver/db/repo/GuestRepo.kt index 2b68827..7138505 100644 --- a/src/main/kotlin/com/android/trisolarisserver/db/repo/GuestRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/db/repo/GuestRepo.kt @@ -5,5 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository import java.util.UUID interface GuestRepo : JpaRepository { - fun findByOrgIdAndPhoneE164(orgId: UUID, phoneE164: String): Guest? + fun findByPropertyIdAndPhoneE164(propertyId: UUID, phoneE164: String): Guest? } diff --git a/src/main/kotlin/com/android/trisolarisserver/models/booking/Guest.kt b/src/main/kotlin/com/android/trisolarisserver/models/booking/Guest.kt index bfb8e5b..8773414 100644 --- a/src/main/kotlin/com/android/trisolarisserver/models/booking/Guest.kt +++ b/src/main/kotlin/com/android/trisolarisserver/models/booking/Guest.kt @@ -1,6 +1,6 @@ package com.android.trisolarisserver.models.booking -import com.android.trisolarisserver.models.property.Organization +import com.android.trisolarisserver.models.property.Property import jakarta.persistence.* import java.time.OffsetDateTime import java.util.UUID @@ -8,7 +8,7 @@ import java.util.UUID @Entity @Table( name = "guest", - uniqueConstraints = [UniqueConstraint(columnNames = ["org_id", "phone_e164"])] + uniqueConstraints = [UniqueConstraint(columnNames = ["property_id", "phone_e164"])] ) class Guest( @Id @@ -17,8 +17,8 @@ class Guest( val id: UUID? = null, @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "org_id", nullable = false) - var org: Organization, + @JoinColumn(name = "property_id", nullable = false) + var property: Property, @Column(name = "phone_e164") var phoneE164: String? = null, diff --git a/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestRating.kt b/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestRating.kt index 626ae70..4c321fb 100644 --- a/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestRating.kt +++ b/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestRating.kt @@ -1,7 +1,6 @@ package com.android.trisolarisserver.models.booking import com.android.trisolarisserver.models.property.AppUser -import com.android.trisolarisserver.models.property.Organization import com.android.trisolarisserver.models.property.Property import jakarta.persistence.Column import jakarta.persistence.Entity @@ -30,10 +29,6 @@ class GuestRating( @Column(columnDefinition = "uuid") val id: UUID? = null, - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "org_id", nullable = false) - var org: Organization, - @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "property_id", nullable = false) var property: Property, diff --git a/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestVehicle.kt b/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestVehicle.kt index d3f9c4b..83e0884 100644 --- a/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestVehicle.kt +++ b/src/main/kotlin/com/android/trisolarisserver/models/booking/GuestVehicle.kt @@ -1,6 +1,6 @@ package com.android.trisolarisserver.models.booking -import com.android.trisolarisserver.models.property.Organization +import com.android.trisolarisserver.models.property.Property import jakarta.persistence.* import java.time.OffsetDateTime import java.util.UUID @@ -8,7 +8,7 @@ import java.util.UUID @Entity @Table( name = "guest_vehicle", - uniqueConstraints = [UniqueConstraint(columnNames = ["org_id", "vehicle_number"])] + uniqueConstraints = [UniqueConstraint(columnNames = ["property_id", "vehicle_number"])] ) class GuestVehicle( @Id @@ -17,8 +17,8 @@ class GuestVehicle( val id: UUID? = null, @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "org_id", nullable = false) - var org: Organization, + @JoinColumn(name = "property_id", nullable = false) + var property: Property, @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "guest_id", nullable = false) diff --git a/src/main/kotlin/com/android/trisolarisserver/models/property/AppUser.kt b/src/main/kotlin/com/android/trisolarisserver/models/property/AppUser.kt index 703758b..b4745e2 100644 --- a/src/main/kotlin/com/android/trisolarisserver/models/property/AppUser.kt +++ b/src/main/kotlin/com/android/trisolarisserver/models/property/AppUser.kt @@ -1,6 +1,11 @@ package com.android.trisolarisserver.models.property -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint import java.time.OffsetDateTime import java.util.UUID @@ -15,10 +20,6 @@ class AppUser( @Column(columnDefinition = "uuid") val id: UUID? = null, - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "org_id", nullable = false) - var org: Organization, - @Column(name = "firebase_uid") var firebaseUid: String? = null, // optional if using firebase @@ -27,6 +28,9 @@ class AppUser( var name: String? = null, + @Column(name = "is_super_admin", nullable = false) + var superAdmin: Boolean = false, + @Column(name = "is_disabled", nullable = false) var disabled: Boolean = false, diff --git a/src/main/kotlin/com/android/trisolarisserver/models/property/Organization.kt b/src/main/kotlin/com/android/trisolarisserver/models/property/Organization.kt deleted file mode 100644 index 1109924..0000000 --- a/src/main/kotlin/com/android/trisolarisserver/models/property/Organization.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.android.trisolarisserver.models.property - -import jakarta.persistence.* -import java.time.OffsetDateTime -import java.util.* - -@Entity -@Table(name = "organization") -class Organization { - @Id - @GeneratedValue - @Column(columnDefinition = "uuid") - val id: UUID? = null - @Column(nullable = false) - var name: String? = null - - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable( - name = "org_email_alias", - joinColumns = [JoinColumn(name = "org_id")] - ) - @Column(name = "email", nullable = false) - var emailAliases: MutableSet = mutableSetOf() - - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable( - name = "org_transport_mode", - joinColumns = [JoinColumn(name = "org_id")] - ) - @Column(name = "mode", nullable = false) - @Enumerated(EnumType.STRING) - var allowedTransportModes: MutableSet = - mutableSetOf() - - @Column(name = "created_at", nullable = false, columnDefinition = "timestamptz") - val createdAt: OffsetDateTime = OffsetDateTime.now() -} diff --git a/src/main/kotlin/com/android/trisolarisserver/models/property/PendingUser.kt b/src/main/kotlin/com/android/trisolarisserver/models/property/PendingUser.kt deleted file mode 100644 index 3b24472..0000000 --- a/src/main/kotlin/com/android/trisolarisserver/models/property/PendingUser.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.android.trisolarisserver.models.property - -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.GeneratedValue -import jakarta.persistence.Id -import jakarta.persistence.Table -import jakarta.persistence.UniqueConstraint -import java.time.OffsetDateTime -import java.util.UUID - -@Entity -@Table( - name = "pending_user", - uniqueConstraints = [UniqueConstraint(columnNames = ["firebase_uid"])] -) -class PendingUser( - @Id - @GeneratedValue - @Column(columnDefinition = "uuid") - val id: UUID? = null, - - @Column(name = "firebase_uid", nullable = false) - var firebaseUid: String, - - @Column(name = "phone_e164") - var phoneE164: String? = null, - - var name: String? = null, - - @Column(name = "created_at", nullable = false, columnDefinition = "timestamptz") - val createdAt: OffsetDateTime = OffsetDateTime.now() -) diff --git a/src/main/kotlin/com/android/trisolarisserver/models/property/Property.kt b/src/main/kotlin/com/android/trisolarisserver/models/property/Property.kt index fb27450..344bd68 100644 --- a/src/main/kotlin/com/android/trisolarisserver/models/property/Property.kt +++ b/src/main/kotlin/com/android/trisolarisserver/models/property/Property.kt @@ -1,13 +1,24 @@ package com.android.trisolarisserver.models.property -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.ElementCollection +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.CollectionTable +import jakarta.persistence.Table +import jakarta.persistence.UniqueConstraint import java.time.OffsetDateTime import java.util.UUID @Entity @Table( name = "property", - uniqueConstraints = [UniqueConstraint(columnNames = ["org_id", "code"])] + uniqueConstraints = [UniqueConstraint(columnNames = ["code"])] ) class Property( @Id @@ -15,10 +26,6 @@ class Property( @Column(columnDefinition = "uuid") val id: UUID? = null, - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "org_id", nullable = false) - var org: Organization, - @Column(nullable = false) var code: String, // "TRI-VNS" diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/AppUserRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/AppUserRepo.kt index fd915b9..7e7cc6d 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/AppUserRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/AppUserRepo.kt @@ -7,5 +7,4 @@ import java.util.UUID interface AppUserRepo : JpaRepository { fun findByFirebaseUid(firebaseUid: String): AppUser? fun existsByFirebaseUid(firebaseUid: String): Boolean - fun findByOrgId(orgId: UUID): List } diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/GuestVehicleRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/GuestVehicleRepo.kt index 9521e78..d3a3b1f 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/GuestVehicleRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/GuestVehicleRepo.kt @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository import java.util.UUID interface GuestVehicleRepo : JpaRepository { - fun findByOrgIdAndVehicleNumberIgnoreCase(orgId: UUID, vehicleNumber: String): GuestVehicle? + fun findByPropertyIdAndVehicleNumberIgnoreCase(propertyId: UUID, vehicleNumber: String): GuestVehicle? fun findByGuestIdIn(guestIds: List): List - fun existsByOrgIdAndVehicleNumberIgnoreCase(orgId: UUID, vehicleNumber: String): Boolean + fun existsByPropertyIdAndVehicleNumberIgnoreCase(propertyId: UUID, vehicleNumber: String): Boolean } diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/OrganizationRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/OrganizationRepo.kt deleted file mode 100644 index 3e6318c..0000000 --- a/src/main/kotlin/com/android/trisolarisserver/repo/OrganizationRepo.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.android.trisolarisserver.repo - -import com.android.trisolarisserver.models.property.Organization -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface OrganizationRepo : JpaRepository { - fun findByName(name: String): Organization? - fun findByNameIgnoreCase(name: String): Organization? - fun existsByNameIgnoreCase(name: String): Boolean -} diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/PendingUserRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/PendingUserRepo.kt deleted file mode 100644 index f160b95..0000000 --- a/src/main/kotlin/com/android/trisolarisserver/repo/PendingUserRepo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.android.trisolarisserver.repo - -import com.android.trisolarisserver.models.property.PendingUser -import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID - -interface PendingUserRepo : JpaRepository { - fun findByFirebaseUid(firebaseUid: String): PendingUser? -} diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/PropertyRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/PropertyRepo.kt index d4b5ad4..0330b38 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/PropertyRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/PropertyRepo.kt @@ -5,6 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository import java.util.UUID interface PropertyRepo : JpaRepository { - fun existsByOrgIdAndCode(orgId: UUID, code: String): Boolean - fun existsByOrgIdAndCodeAndIdNot(orgId: UUID, code: String, id: UUID): Boolean + fun existsByCode(code: String): Boolean + fun existsByCodeAndIdNot(code: String, id: UUID): Boolean } diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/PropertyUserRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/PropertyUserRepo.kt index 6dbafb5..36334a0 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/PropertyUserRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/PropertyUserRepo.kt @@ -25,16 +25,6 @@ interface PropertyUserRepo : JpaRepository { @Param("userId") userId: UUID ): Set - @Query(""" - select pu.property.id - from PropertyUser pu - where pu.user.id = :userId - and pu.property.org.id = :orgId - """) - fun findPropertyIdsByOrgAndUser( - @Param("orgId") orgId: UUID, - @Param("userId") userId: UUID - ): List @Query(""" select case when count(pu) > 0 then true else false end @@ -49,16 +39,4 @@ interface PropertyUserRepo : JpaRepository { @Param("roles") roles: Set ): Boolean - @Query(""" - select case when count(pu) > 0 then true else false end - from PropertyUser pu join pu.roles r - where pu.user.id = :userId - and pu.property.org.id = :orgId - and r in :roles - """) - fun hasAnyRoleInOrg( - @Param("orgId") orgId: UUID, - @Param("userId") userId: UUID, - @Param("roles") roles: Set - ): Boolean } diff --git a/src/main/kotlin/com/android/trisolarisserver/service/EmailIngestionService.kt b/src/main/kotlin/com/android/trisolarisserver/service/EmailIngestionService.kt index 0782db7..95d214d 100644 --- a/src/main/kotlin/com/android/trisolarisserver/service/EmailIngestionService.kt +++ b/src/main/kotlin/com/android/trisolarisserver/service/EmailIngestionService.kt @@ -157,11 +157,11 @@ class EmailIngestionService( private fun resolveGuest(property: Property, extracted: Map): Guest { val phone = extracted["guestPhone"]?.takeIf { !it.contains("NONE", true) }?.trim() if (!phone.isNullOrBlank()) { - val existing = guestRepo.findByOrgIdAndPhoneE164(property.org.id!!, phone) + val existing = guestRepo.findByPropertyIdAndPhoneE164(property.id!!, phone) if (existing != null) return existing } val guest = Guest( - org = property.org, + property = property, phoneE164 = phone, name = extracted["guestName"]?.takeIf { !it.contains("NONE", true) } ) @@ -281,10 +281,6 @@ class EmailIngestionService( if (propertyEmails.isNotEmpty() && recipients.any { it.lowercase() in propertyEmails }) { return@filter true } - val orgEmails = property.org.emailAliases.map { it.lowercase() }.toSet() - if (orgEmails.isNotEmpty() && recipients.any { it.lowercase() in orgEmails }) { - return@filter true - } } val aliases = mutableSetOf() aliases.add(property.name)