remove auth boilerplate
This commit is contained in:
@@ -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 ?: "")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
27
app/src/main/java/com/android/trisolarispms/auth/Role.kt
Normal file
27
app/src/main/java/com/android/trisolarispms/auth/Role.kt
Normal 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 }
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
response.isSuccessful && (status == "OK" || status == "SUPER_ADMIN" || status == "NO_PROPERTIES") ->
|
||||
setVerifiedState(
|
||||
userId = userId,
|
||||
apiVerified = true,
|
||||
needsName = userName.isNullOrBlank(),
|
||||
nameInput = userName ?: "",
|
||||
userName = userName,
|
||||
isSuperAdmin = isSuperAdmin,
|
||||
noProperties = body?.status == "NO_PROPERTIES",
|
||||
unauthorized = false,
|
||||
propertyRoles = propertyRoles,
|
||||
error = null
|
||||
noProperties = status == "NO_PROPERTIES"
|
||||
)
|
||||
}
|
||||
}
|
||||
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 -> {
|
||||
_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(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user