remove auth boilerplate

This commit is contained in:
androidlover5842
2026-02-02 05:39:20 +05:30
parent 342ff6a237
commit 99ce18a435
6 changed files with 179 additions and 122 deletions

View File

@@ -11,6 +11,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.trisolarispms.auth.AuthzPolicy
import com.android.trisolarispms.auth.Role
import com.android.trisolarispms.auth.toRoleNameList
import com.android.trisolarispms.auth.toRoleSet
import com.android.trisolarispms.ui.AppRoute
import com.android.trisolarispms.ui.auth.AuthScreen
import com.android.trisolarispms.ui.auth.AuthViewModel
@@ -82,58 +86,16 @@ class MainActivity : ComponentActivity() {
val roomFormKey = remember { mutableStateOf(0) }
val amenitiesReturnRoute = remember { mutableStateOf<AppRoute>(AppRoute.Home) }
val currentRoute = route.value
val authz = remember(state.isSuperAdmin, state.propertyRoles) {
AuthzPolicy(
isSuperAdmin = state.isSuperAdmin,
propertyRoles = state.propertyRoles
)
}
val singlePropertyId = state.propertyRoles.keys.firstOrNull()
val singlePropertyIsAdmin = singlePropertyId?.let {
state.propertyRoles[it]?.contains("ADMIN") == true
} ?: false
val canManageProperty: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || (state.propertyRoles[propertyId]?.contains("ADMIN") == true)
}
val canIssueTemporaryCard: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
it == "ADMIN" || it == "MANAGER"
} == true
}
val canViewCardInfo: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
it == "ADMIN" || it == "MANAGER" || it == "STAFF"
} == true
}
val canManageRazorpaySettings: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
}
val canDeleteCashPayment: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
}
val canAddBookingPayment: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
it == "ADMIN" || it == "MANAGER" || it == "STAFF"
} == true
}
val canRefundBookingPayment: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
it == "ADMIN" || it == "MANAGER"
} == true
}
val canManagePropertyUsers: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
}
val canCreateBookingFor: (String) -> Boolean = { propertyId ->
state.isSuperAdmin || state.propertyRoles[propertyId]?.any {
it == "ADMIN" || it == "MANAGER" || it == "STAFF"
} == true
}
val singlePropertyIsAdmin = singlePropertyId?.let(authz::isPropertyAdmin) ?: false
val allowedAccessCodeRoles: (String) -> List<String> = { propertyId ->
if (state.isSuperAdmin) {
listOf("MANAGER", "STAFF", "AGENT")
} else {
val roles = state.propertyRoles[propertyId].orEmpty()
when {
roles.contains("ADMIN") -> listOf("MANAGER", "STAFF", "AGENT")
roles.contains("MANAGER") -> listOf("STAFF", "AGENT")
else -> emptyList()
}
}
authz.allowedAccessCodeRoles(propertyId).toRoleNameList()
}
BackHandler(enabled = currentRoute != AppRoute.Home) {
@@ -142,8 +104,10 @@ class MainActivity : ComponentActivity() {
AppRoute.AddProperty -> route.value = AppRoute.Home
AppRoute.SuperAdminUsers -> route.value = AppRoute.Home
is AppRoute.ActiveRoomStays -> {
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1
val blockBack = authz.shouldBlockBackToHome(
propertyId = currentRoute.propertyId,
propertyCount = state.propertyRoles.size
)
if (!blockBack) {
route.value = AppRoute.Home
}
@@ -280,7 +244,7 @@ class MainActivity : ComponentActivity() {
onJoinPropertySuccess = { joinedPropertyId, joinedRoles ->
refreshKey.value++
authViewModel.refreshMe()
val isAdmin = joinedRoles.contains("ADMIN")
val isAdmin = joinedRoles.toRoleSet().contains(Role.ADMIN)
val shouldAutoOpen = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size <= 1
if (shouldAutoOpen) {
route.value = AppRoute.ActiveRoomStays(
@@ -361,23 +325,24 @@ class MainActivity : ComponentActivity() {
propertyId = currentRoute.propertyId,
propertyName = currentRoute.propertyName,
onBack = {
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1
val blockBack = authz.shouldBlockBackToHome(
propertyId = currentRoute.propertyId,
propertyCount = state.propertyRoles.size
)
if (!blockBack) {
route.value = AppRoute.Home
}
},
showBack = run {
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1
!blockBack
},
showBack = !authz.shouldBlockBackToHome(
propertyId = currentRoute.propertyId,
propertyCount = state.propertyRoles.size
),
onViewRooms = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
onCreateBooking = { route.value = AppRoute.CreateBooking(currentRoute.propertyId) },
canCreateBooking = canCreateBookingFor(currentRoute.propertyId),
showRazorpaySettings = canManageRazorpaySettings(currentRoute.propertyId),
canCreateBooking = authz.canCreateBookingFor(currentRoute.propertyId),
showRazorpaySettings = authz.canManageRazorpaySettings(currentRoute.propertyId),
onRazorpaySettings = { route.value = AppRoute.RazorpaySettings(currentRoute.propertyId) },
showUserAdmin = canManagePropertyUsers(currentRoute.propertyId),
showUserAdmin = authz.canManagePropertyUsers(currentRoute.propertyId),
onUserAdmin = { route.value = AppRoute.PropertyUsers(currentRoute.propertyId) },
onLogout = authViewModel::signOut,
onManageRoomStay = { booking ->
@@ -584,14 +549,14 @@ class MainActivity : ComponentActivity() {
bookingId = currentRoute.bookingId
)
},
canManageDocuments = canManageRazorpaySettings(currentRoute.propertyId)
canManageDocuments = authz.canManageRazorpaySettings(currentRoute.propertyId)
)
is AppRoute.BookingPayments -> BookingPaymentsScreen(
propertyId = currentRoute.propertyId,
bookingId = currentRoute.bookingId,
canAddCash = canAddBookingPayment(currentRoute.propertyId),
canDeleteCash = canDeleteCashPayment(currentRoute.propertyId),
canRefund = canRefundBookingPayment(currentRoute.propertyId),
canAddCash = authz.canAddBookingPayment(currentRoute.propertyId),
canDeleteCash = authz.canDeleteCashPayment(currentRoute.propertyId),
canRefund = authz.canRefundBookingPayment(currentRoute.propertyId),
onBack = {
route.value = AppRoute.BookingDetailsTabs(
currentRoute.propertyId,
@@ -605,17 +570,11 @@ class MainActivity : ComponentActivity() {
)
is AppRoute.PropertyUsers -> PropertyUsersScreen(
propertyId = currentRoute.propertyId,
allowedRoleAssignments = when {
state.isSuperAdmin -> listOf("ADMIN", "MANAGER", "STAFF", "AGENT")
state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true ->
listOf("ADMIN", "MANAGER", "STAFF", "AGENT")
state.propertyRoles[currentRoute.propertyId]?.contains("MANAGER") == true ->
listOf("STAFF", "AGENT")
else -> emptyList()
},
canDisableAdmin = state.isSuperAdmin ||
state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true,
canDisableManager = state.propertyRoles[currentRoute.propertyId]?.contains("MANAGER") == true,
allowedRoleAssignments = authz
.allowedRoleAssignments(currentRoute.propertyId)
.toRoleNameList(),
canDisableAdmin = authz.canDisableAdmin(currentRoute.propertyId),
canDisableManager = authz.canDisableManager(currentRoute.propertyId),
onBack = {
route.value = AppRoute.ActiveRoomStays(
currentRoute.propertyId,
@@ -645,16 +604,16 @@ class MainActivity : ComponentActivity() {
},
onViewRoomTypes = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
onViewCardInfo = { route.value = AppRoute.CardInfo(currentRoute.propertyId) },
canManageRooms = canManageProperty(currentRoute.propertyId),
canViewCardInfo = canViewCardInfo(currentRoute.propertyId),
canIssueTemporaryCard = canIssueTemporaryCard(currentRoute.propertyId),
canManageRooms = authz.canManageProperty(currentRoute.propertyId),
canViewCardInfo = authz.canViewCardInfo(currentRoute.propertyId),
canIssueTemporaryCard = authz.canIssueTemporaryCard(currentRoute.propertyId),
onEditRoom = {
selectedRoom.value = it
roomFormKey.value++
route.value = AppRoute.EditRoom(currentRoute.propertyId, it.id ?: "")
},
onIssueTemporaryCard = {
if (it.id != null && canIssueTemporaryCard(currentRoute.propertyId)) {
if (it.id != null && authz.canIssueTemporaryCard(currentRoute.propertyId)) {
selectedRoom.value = it
route.value = AppRoute.IssueTemporaryCard(currentRoute.propertyId, it.id)
}
@@ -664,7 +623,7 @@ class MainActivity : ComponentActivity() {
propertyId = currentRoute.propertyId,
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
onAdd = { route.value = AppRoute.AddRoomType(currentRoute.propertyId) },
canManageRoomTypes = canManageProperty(currentRoute.propertyId),
canManageRoomTypes = authz.canManageProperty(currentRoute.propertyId),
onEdit = {
selectedRoomType.value = it
route.value = AppRoute.EditRoomType(currentRoute.propertyId, it.id ?: "")

View File

@@ -0,0 +1,59 @@
package com.android.trisolarispms.auth
class AuthzPolicy(
private val isSuperAdmin: Boolean,
propertyRoles: Map<String, List<Role>>
) {
private val rolesByProperty: Map<String, Set<Role>> = propertyRoles.mapValues { it.value.toSet() }
fun isPropertyAdmin(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
fun canManageProperty(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
fun canIssueTemporaryCard(propertyId: String): Boolean =
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER)
fun canViewCardInfo(propertyId: String): Boolean =
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER, Role.STAFF)
fun canManageRazorpaySettings(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
fun canDeleteCashPayment(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
fun canAddBookingPayment(propertyId: String): Boolean =
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER, Role.STAFF)
fun canRefundBookingPayment(propertyId: String): Boolean =
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER)
fun canManagePropertyUsers(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
fun canCreateBookingFor(propertyId: String): Boolean =
hasAnyRole(propertyId, Role.ADMIN, Role.MANAGER, Role.STAFF)
fun canDisableAdmin(propertyId: String): Boolean = hasRole(propertyId, Role.ADMIN)
fun canDisableManager(propertyId: String): Boolean = hasRole(propertyId, Role.MANAGER)
fun allowedAccessCodeRoles(propertyId: String): List<Role> = when {
isSuperAdmin || hasRole(propertyId, Role.ADMIN) -> listOf(Role.MANAGER, Role.STAFF, Role.AGENT)
hasRole(propertyId, Role.MANAGER) -> listOf(Role.STAFF, Role.AGENT)
else -> emptyList()
}
fun allowedRoleAssignments(propertyId: String): List<Role> = when {
isSuperAdmin || hasRole(propertyId, Role.ADMIN) ->
listOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.AGENT)
hasRole(propertyId, Role.MANAGER) -> listOf(Role.STAFF, Role.AGENT)
else -> emptyList()
}
fun shouldBlockBackToHome(propertyId: String, propertyCount: Int): Boolean =
!isSuperAdmin && !isPropertyAdmin(propertyId) && propertyCount == 1
private fun hasRole(propertyId: String, requiredRole: Role): Boolean =
isSuperAdmin || (rolesByProperty[propertyId]?.contains(requiredRole) == true)
private fun hasAnyRole(propertyId: String, vararg requiredRoles: Role): Boolean =
isSuperAdmin || (rolesByProperty[propertyId]?.any { role -> role in requiredRoles } == true)
}

View File

@@ -0,0 +1,27 @@
package com.android.trisolarispms.auth
import java.util.Locale
enum class Role {
ADMIN,
MANAGER,
STAFF,
HOUSEKEEPING,
FINANCE,
GUIDE,
SUPERVISOR,
AGENT;
companion object {
fun from(value: String?): Role? {
val normalized = value?.trim()?.uppercase(Locale.US) ?: return null
return entries.firstOrNull { it.name == normalized }
}
}
}
fun Collection<String>?.toRoles(): List<Role> = this.orEmpty().mapNotNull(Role::from)
fun Collection<String>?.toRoleSet(): Set<Role> = toRoles().toSet()
fun Collection<Role>.toRoleNameList(): List<String> = map { it.name }

View File

@@ -1,5 +1,7 @@
package com.android.trisolarispms.ui.auth
import com.android.trisolarispms.auth.Role
data class AuthUiState(
val countryCode: String = "+91",
val phoneCountryCode: String = "IN",
@@ -19,5 +21,5 @@ data class AuthUiState(
val nameInput: String = "",
val needsName: Boolean = false,
val unauthorized: Boolean = false,
val propertyRoles: Map<String, List<String>> = emptyMap()
val propertyRoles: Map<String, List<Role>> = emptyMap()
)

View File

@@ -3,6 +3,8 @@ package com.android.trisolarispms.ui.auth
import androidx.activity.ComponentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.trisolarispms.auth.Role
import com.android.trisolarispms.auth.toRoles
import com.android.trisolarispms.data.api.ApiClient
import com.google.firebase.FirebaseException
import com.google.firebase.auth.FirebaseAuth
@@ -208,50 +210,25 @@ class AuthViewModel(
response: retrofit2.Response<com.android.trisolarispms.data.api.model.AuthVerifyResponse>
) {
val body = response.body()
val status = body?.status
val userName = body?.user?.name
val isSuperAdmin = body?.user?.superAdmin == true
val propertyRoles = body?.properties
.orEmpty()
.mapNotNull { entry ->
val id = entry.propertyId
id?.let { it to entry.roles.orEmpty() }
id?.let { it to entry.roles.toRoles() }
}
.toMap()
when {
response.isSuccessful && (body?.status == "OK" || body?.status == "SUPER_ADMIN") -> {
_state.update {
it.copy(
isLoading = false,
userId = userId,
apiVerified = true,
needsName = userName.isNullOrBlank(),
nameInput = userName ?: "",
userName = userName,
isSuperAdmin = isSuperAdmin,
noProperties = body?.status == "NO_PROPERTIES",
unauthorized = false,
propertyRoles = propertyRoles,
error = null
)
}
}
response.isSuccessful && body?.status == "NO_PROPERTIES" -> {
_state.update {
it.copy(
isLoading = false,
userId = userId,
apiVerified = true,
needsName = userName.isNullOrBlank(),
nameInput = userName ?: "",
userName = userName,
isSuperAdmin = isSuperAdmin,
noProperties = true,
unauthorized = false,
propertyRoles = propertyRoles,
error = null
)
}
}
response.isSuccessful && (status == "OK" || status == "SUPER_ADMIN" || status == "NO_PROPERTIES") ->
setVerifiedState(
userId = userId,
userName = userName,
isSuperAdmin = isSuperAdmin,
propertyRoles = propertyRoles,
noProperties = status == "NO_PROPERTIES"
)
response.code() == 401 -> {
_state.update {
it.copy(
@@ -281,6 +258,30 @@ class AuthViewModel(
}
}
private fun setVerifiedState(
userId: String?,
userName: String?,
isSuperAdmin: Boolean,
propertyRoles: Map<String, List<Role>>,
noProperties: Boolean
) {
_state.update {
it.copy(
isLoading = false,
userId = userId,
apiVerified = true,
needsName = userName.isNullOrBlank(),
nameInput = userName ?: "",
userName = userName,
isSuperAdmin = isSuperAdmin,
noProperties = noProperties,
unauthorized = false,
propertyRoles = propertyRoles,
error = null
)
}
}
fun signOut() {
auth.signOut()
_state.update { AuthUiState() }
@@ -335,5 +336,4 @@ class AuthViewModel(
}
}
}
}

View File

@@ -14,6 +14,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.trisolarispms.auth.Role
import com.android.trisolarispms.auth.toRoles
@Composable
fun PropertyUserCard(
@@ -73,6 +75,14 @@ fun canDisableUser(
if (user.userId.isNullOrBlank()) return false
if (canDisableAdmin) return true
if (!canDisableManager) return false
val allowed = setOf("STAFF", "AGENT", "HOUSEKEEPING", "FINANCE", "GUIDE", "SUPERVISOR")
return user.roles.all { allowed.contains(it) }
val allowed = setOf(
Role.STAFF,
Role.AGENT,
Role.HOUSEKEEPING,
Role.FINANCE,
Role.GUIDE,
Role.SUPERVISOR
)
val parsedRoles = user.roles.toRoles()
return parsedRoles.size == user.roles.size && parsedRoles.all { it in allowed }
}