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.remember
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.viewmodel.compose.viewModel 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.AppRoute
import com.android.trisolarispms.ui.auth.AuthScreen import com.android.trisolarispms.ui.auth.AuthScreen
import com.android.trisolarispms.ui.auth.AuthViewModel import com.android.trisolarispms.ui.auth.AuthViewModel
@@ -82,58 +86,16 @@ class MainActivity : ComponentActivity() {
val roomFormKey = remember { mutableStateOf(0) } val roomFormKey = remember { mutableStateOf(0) }
val amenitiesReturnRoute = remember { mutableStateOf<AppRoute>(AppRoute.Home) } val amenitiesReturnRoute = remember { mutableStateOf<AppRoute>(AppRoute.Home) }
val currentRoute = route.value 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 singlePropertyId = state.propertyRoles.keys.firstOrNull()
val singlePropertyIsAdmin = singlePropertyId?.let { val singlePropertyIsAdmin = singlePropertyId?.let(authz::isPropertyAdmin) ?: false
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 allowedAccessCodeRoles: (String) -> List<String> = { propertyId -> val allowedAccessCodeRoles: (String) -> List<String> = { propertyId ->
if (state.isSuperAdmin) { authz.allowedAccessCodeRoles(propertyId).toRoleNameList()
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()
}
}
} }
BackHandler(enabled = currentRoute != AppRoute.Home) { BackHandler(enabled = currentRoute != AppRoute.Home) {
@@ -142,8 +104,10 @@ class MainActivity : ComponentActivity() {
AppRoute.AddProperty -> route.value = AppRoute.Home AppRoute.AddProperty -> route.value = AppRoute.Home
AppRoute.SuperAdminUsers -> route.value = AppRoute.Home AppRoute.SuperAdminUsers -> route.value = AppRoute.Home
is AppRoute.ActiveRoomStays -> { is AppRoute.ActiveRoomStays -> {
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true val blockBack = authz.shouldBlockBackToHome(
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1 propertyId = currentRoute.propertyId,
propertyCount = state.propertyRoles.size
)
if (!blockBack) { if (!blockBack) {
route.value = AppRoute.Home route.value = AppRoute.Home
} }
@@ -280,7 +244,7 @@ class MainActivity : ComponentActivity() {
onJoinPropertySuccess = { joinedPropertyId, joinedRoles -> onJoinPropertySuccess = { joinedPropertyId, joinedRoles ->
refreshKey.value++ refreshKey.value++
authViewModel.refreshMe() authViewModel.refreshMe()
val isAdmin = joinedRoles.contains("ADMIN") val isAdmin = joinedRoles.toRoleSet().contains(Role.ADMIN)
val shouldAutoOpen = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size <= 1 val shouldAutoOpen = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size <= 1
if (shouldAutoOpen) { if (shouldAutoOpen) {
route.value = AppRoute.ActiveRoomStays( route.value = AppRoute.ActiveRoomStays(
@@ -361,23 +325,24 @@ class MainActivity : ComponentActivity() {
propertyId = currentRoute.propertyId, propertyId = currentRoute.propertyId,
propertyName = currentRoute.propertyName, propertyName = currentRoute.propertyName,
onBack = { onBack = {
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true val blockBack = authz.shouldBlockBackToHome(
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1 propertyId = currentRoute.propertyId,
propertyCount = state.propertyRoles.size
)
if (!blockBack) { if (!blockBack) {
route.value = AppRoute.Home route.value = AppRoute.Home
} }
}, },
showBack = run { showBack = !authz.shouldBlockBackToHome(
val isAdmin = state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true propertyId = currentRoute.propertyId,
val blockBack = !state.isSuperAdmin && !isAdmin && state.propertyRoles.size == 1 propertyCount = state.propertyRoles.size
!blockBack ),
},
onViewRooms = { route.value = AppRoute.Rooms(currentRoute.propertyId) }, onViewRooms = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
onCreateBooking = { route.value = AppRoute.CreateBooking(currentRoute.propertyId) }, onCreateBooking = { route.value = AppRoute.CreateBooking(currentRoute.propertyId) },
canCreateBooking = canCreateBookingFor(currentRoute.propertyId), canCreateBooking = authz.canCreateBookingFor(currentRoute.propertyId),
showRazorpaySettings = canManageRazorpaySettings(currentRoute.propertyId), showRazorpaySettings = authz.canManageRazorpaySettings(currentRoute.propertyId),
onRazorpaySettings = { route.value = AppRoute.RazorpaySettings(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) }, onUserAdmin = { route.value = AppRoute.PropertyUsers(currentRoute.propertyId) },
onLogout = authViewModel::signOut, onLogout = authViewModel::signOut,
onManageRoomStay = { booking -> onManageRoomStay = { booking ->
@@ -584,14 +549,14 @@ class MainActivity : ComponentActivity() {
bookingId = currentRoute.bookingId bookingId = currentRoute.bookingId
) )
}, },
canManageDocuments = canManageRazorpaySettings(currentRoute.propertyId) canManageDocuments = authz.canManageRazorpaySettings(currentRoute.propertyId)
) )
is AppRoute.BookingPayments -> BookingPaymentsScreen( is AppRoute.BookingPayments -> BookingPaymentsScreen(
propertyId = currentRoute.propertyId, propertyId = currentRoute.propertyId,
bookingId = currentRoute.bookingId, bookingId = currentRoute.bookingId,
canAddCash = canAddBookingPayment(currentRoute.propertyId), canAddCash = authz.canAddBookingPayment(currentRoute.propertyId),
canDeleteCash = canDeleteCashPayment(currentRoute.propertyId), canDeleteCash = authz.canDeleteCashPayment(currentRoute.propertyId),
canRefund = canRefundBookingPayment(currentRoute.propertyId), canRefund = authz.canRefundBookingPayment(currentRoute.propertyId),
onBack = { onBack = {
route.value = AppRoute.BookingDetailsTabs( route.value = AppRoute.BookingDetailsTabs(
currentRoute.propertyId, currentRoute.propertyId,
@@ -605,17 +570,11 @@ class MainActivity : ComponentActivity() {
) )
is AppRoute.PropertyUsers -> PropertyUsersScreen( is AppRoute.PropertyUsers -> PropertyUsersScreen(
propertyId = currentRoute.propertyId, propertyId = currentRoute.propertyId,
allowedRoleAssignments = when { allowedRoleAssignments = authz
state.isSuperAdmin -> listOf("ADMIN", "MANAGER", "STAFF", "AGENT") .allowedRoleAssignments(currentRoute.propertyId)
state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true -> .toRoleNameList(),
listOf("ADMIN", "MANAGER", "STAFF", "AGENT") canDisableAdmin = authz.canDisableAdmin(currentRoute.propertyId),
state.propertyRoles[currentRoute.propertyId]?.contains("MANAGER") == true -> canDisableManager = authz.canDisableManager(currentRoute.propertyId),
listOf("STAFF", "AGENT")
else -> emptyList()
},
canDisableAdmin = state.isSuperAdmin ||
state.propertyRoles[currentRoute.propertyId]?.contains("ADMIN") == true,
canDisableManager = state.propertyRoles[currentRoute.propertyId]?.contains("MANAGER") == true,
onBack = { onBack = {
route.value = AppRoute.ActiveRoomStays( route.value = AppRoute.ActiveRoomStays(
currentRoute.propertyId, currentRoute.propertyId,
@@ -645,16 +604,16 @@ class MainActivity : ComponentActivity() {
}, },
onViewRoomTypes = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) }, onViewRoomTypes = { route.value = AppRoute.RoomTypes(currentRoute.propertyId) },
onViewCardInfo = { route.value = AppRoute.CardInfo(currentRoute.propertyId) }, onViewCardInfo = { route.value = AppRoute.CardInfo(currentRoute.propertyId) },
canManageRooms = canManageProperty(currentRoute.propertyId), canManageRooms = authz.canManageProperty(currentRoute.propertyId),
canViewCardInfo = canViewCardInfo(currentRoute.propertyId), canViewCardInfo = authz.canViewCardInfo(currentRoute.propertyId),
canIssueTemporaryCard = canIssueTemporaryCard(currentRoute.propertyId), canIssueTemporaryCard = authz.canIssueTemporaryCard(currentRoute.propertyId),
onEditRoom = { onEditRoom = {
selectedRoom.value = it selectedRoom.value = it
roomFormKey.value++ roomFormKey.value++
route.value = AppRoute.EditRoom(currentRoute.propertyId, it.id ?: "") route.value = AppRoute.EditRoom(currentRoute.propertyId, it.id ?: "")
}, },
onIssueTemporaryCard = { onIssueTemporaryCard = {
if (it.id != null && canIssueTemporaryCard(currentRoute.propertyId)) { if (it.id != null && authz.canIssueTemporaryCard(currentRoute.propertyId)) {
selectedRoom.value = it selectedRoom.value = it
route.value = AppRoute.IssueTemporaryCard(currentRoute.propertyId, it.id) route.value = AppRoute.IssueTemporaryCard(currentRoute.propertyId, it.id)
} }
@@ -664,7 +623,7 @@ class MainActivity : ComponentActivity() {
propertyId = currentRoute.propertyId, propertyId = currentRoute.propertyId,
onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) }, onBack = { route.value = AppRoute.Rooms(currentRoute.propertyId) },
onAdd = { route.value = AppRoute.AddRoomType(currentRoute.propertyId) }, onAdd = { route.value = AppRoute.AddRoomType(currentRoute.propertyId) },
canManageRoomTypes = canManageProperty(currentRoute.propertyId), canManageRoomTypes = authz.canManageProperty(currentRoute.propertyId),
onEdit = { onEdit = {
selectedRoomType.value = it selectedRoomType.value = it
route.value = AppRoute.EditRoomType(currentRoute.propertyId, it.id ?: "") 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 package com.android.trisolarispms.ui.auth
import com.android.trisolarispms.auth.Role
data class AuthUiState( data class AuthUiState(
val countryCode: String = "+91", val countryCode: String = "+91",
val phoneCountryCode: String = "IN", val phoneCountryCode: String = "IN",
@@ -19,5 +21,5 @@ data class AuthUiState(
val nameInput: String = "", val nameInput: String = "",
val needsName: Boolean = false, val needsName: Boolean = false,
val unauthorized: 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.activity.ComponentActivity
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.android.trisolarispms.data.api.ApiClient
import com.google.firebase.FirebaseException import com.google.firebase.FirebaseException
import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseAuth
@@ -208,50 +210,25 @@ class AuthViewModel(
response: retrofit2.Response<com.android.trisolarispms.data.api.model.AuthVerifyResponse> response: retrofit2.Response<com.android.trisolarispms.data.api.model.AuthVerifyResponse>
) { ) {
val body = response.body() val body = response.body()
val status = body?.status
val userName = body?.user?.name val userName = body?.user?.name
val isSuperAdmin = body?.user?.superAdmin == true val isSuperAdmin = body?.user?.superAdmin == true
val propertyRoles = body?.properties val propertyRoles = body?.properties
.orEmpty() .orEmpty()
.mapNotNull { entry -> .mapNotNull { entry ->
val id = entry.propertyId val id = entry.propertyId
id?.let { it to entry.roles.orEmpty() } id?.let { it to entry.roles.toRoles() }
} }
.toMap() .toMap()
when { when {
response.isSuccessful && (body?.status == "OK" || body?.status == "SUPER_ADMIN") -> { response.isSuccessful && (status == "OK" || status == "SUPER_ADMIN" || status == "NO_PROPERTIES") ->
_state.update { setVerifiedState(
it.copy( userId = userId,
isLoading = false, userName = userName,
userId = userId, isSuperAdmin = isSuperAdmin,
apiVerified = true, propertyRoles = propertyRoles,
needsName = userName.isNullOrBlank(), noProperties = status == "NO_PROPERTIES"
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.code() == 401 -> { response.code() == 401 -> {
_state.update { _state.update {
it.copy( 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() { fun signOut() {
auth.signOut() auth.signOut()
_state.update { AuthUiState() } _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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.android.trisolarispms.auth.Role
import com.android.trisolarispms.auth.toRoles
@Composable @Composable
fun PropertyUserCard( fun PropertyUserCard(
@@ -73,6 +75,14 @@ fun canDisableUser(
if (user.userId.isNullOrBlank()) return false if (user.userId.isNullOrBlank()) return false
if (canDisableAdmin) return true if (canDisableAdmin) return true
if (!canDisableManager) return false if (!canDisableManager) return false
val allowed = setOf("STAFF", "AGENT", "HOUSEKEEPING", "FINANCE", "GUIDE", "SUPERVISOR") val allowed = setOf(
return user.roles.all { allowed.contains(it) } 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 }
} }