added init logic, updated userDTO, added change rights logic in admin panel, some fixes

This commit is contained in:
Izlydov 2025-02-20 12:22:54 +03:00
parent 952a1f6714
commit 6a78060f25
14 changed files with 212 additions and 67 deletions

View File

@ -4,6 +4,7 @@ import android.app.Application
import com.displaynone.acss.components.acs.models.gate.GateServiceST import com.displaynone.acss.components.acs.models.gate.GateServiceST
import com.displaynone.acss.components.acs.models.visit.VisitServiceST import com.displaynone.acss.components.acs.models.visit.VisitServiceST
import com.displaynone.acss.components.auth.models.user.UserServiceST import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.init.models.InitServiceST
class ACSSApplication : Application() { class ACSSApplication : Application() {
override fun onCreate() { override fun onCreate() {
@ -11,5 +12,6 @@ class ACSSApplication : Application() {
UserServiceST.createInstance(this) UserServiceST.createInstance(this)
GateServiceST.createInstance() GateServiceST.createInstance()
VisitServiceST.createInstance() VisitServiceST.createInstance()
InitServiceST.createInstance()
} }
} }

View File

@ -5,6 +5,7 @@ import android.view.ViewGroup
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.displaynone.acss.R
import com.displaynone.acss.components.acs.models.visit.repository.dto.VisitDto import com.displaynone.acss.components.acs.models.visit.repository.dto.VisitDto
import com.displaynone.acss.databinding.ItemScannerViewBinding import com.displaynone.acss.databinding.ItemScannerViewBinding
@ -37,7 +38,7 @@ class VisitAdapter: PagingDataAdapter<VisitDto, VisitAdapter.ViewHolder>(VisitDi
): RecyclerView.ViewHolder(binding.root) { ): RecyclerView.ViewHolder(binding.root) {
fun bind(item: VisitDto){ fun bind(item: VisitDto){
binding.scanTime.text = item.createdAt binding.scanTime.text = item.createdAt
binding.scannerId.text = item.gateId.toString() binding.scannerId.text = "Id " + item.gateId.toString()
} }
} }

View File

@ -23,6 +23,7 @@ class UserMapper {
photo = userEntity.photo, photo = userEntity.photo,
position = userEntity.position, position = userEntity.position,
roles = emptyList(), roles = emptyList(),
isACSBlocked = false
// lastVisit = userEntity.lastVisit, // lastVisit = userEntity.lastVisit,
) )
return userDto return userDto

View File

@ -70,6 +70,9 @@ class UserServiceST(
fun logout(){ fun logout(){
tokenManager.clear() tokenManager.clear()
} }
suspend fun changeRights(login: String, isACSBlocked: Boolean): Result<Unit> {
return userRepository.changeRightsByLogin(login, tokenManager.authTokenPair!!.accessToken, isACSBlocked)
}
suspend fun getInfo(): Result<UserDTO>{ suspend fun getInfo(): Result<UserDTO>{
if (!tokenManager.hasTokens()) { if (!tokenManager.hasTokens()) {
throw RuntimeException("access token is null") throw RuntimeException("access token is null")

View File

@ -5,11 +5,13 @@ import com.displaynone.acss.components.auth.models.AuthTokenPair
import com.displaynone.acss.config.Constants.serverUrl import com.displaynone.acss.config.Constants.serverUrl
import com.displaynone.acss.config.Network import com.displaynone.acss.config.Network
import com.displaynone.acss.components.auth.models.user.repository.dto.RegisterUserDto import com.displaynone.acss.components.auth.models.user.repository.dto.RegisterUserDto
import com.displaynone.acss.components.auth.models.user.repository.dto.UpdateUserDTO
import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO
import com.displaynone.acss.components.auth.models.user.repository.dto.UserLoginDto import com.displaynone.acss.components.auth.models.user.repository.dto.UserLoginDto
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.headers import io.ktor.client.request.headers
import io.ktor.client.request.patch
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
@ -101,25 +103,28 @@ class UserRepository(
result.body() result.body()
} }
} }
suspend fun changeRightsByLogin(login: String, token: String, isACSBlocked: Boolean): Result<Unit> = withContext(Dispatchers.IO) {
suspend fun register(login: String, password: String): Result<Unit> = runCatching {
withContext(Dispatchers.IO) { val encodedLogin = login.encodeURLPath()
runCatching { val result = Network.client.patch("$serverUrl/api/users/login/$encodedLogin") {
val result = Network.client.post("$serverUrl/api/person/register") { headers{
contentType(ContentType.Application.Json) append(HttpHeaders.Authorization, "Bearer $token")
setBody(
RegisterUserDto(
login = login,
password = password,
)
)
} }
if (result.status != HttpStatusCode.Created) { contentType(ContentType.Application.Json)
Log.w("UserRepository", "Status: ${result.status}, Body: ${result.body<String>()}") setBody(UpdateUserDTO(
error("Status ${result.status}: ${result.body<String>()}") password = null,
} name = null,
Unit photo = null,
position = null,
isACSBlocked = isACSBlocked
))
} }
if (result.status != HttpStatusCode.OK) {
Log.w("UserRepository", "Status: ${result.status}, Body: ${result.body<String>()}")
error("Status ${result.status}: ${result.body<String>()}")
}
Unit
} }
}
} }

View File

@ -0,0 +1,18 @@
package com.displaynone.acss.components.auth.models.user.repository.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class UpdateUserDTO (
@SerialName("password")
val password: String?,
@SerialName("name")
val name: String?,
@SerialName("photo")
val photo: String?,
@SerialName("position")
val position: String?,
@SerialName("isACSBlocked")
val isACSBlocked: Boolean,
)

View File

@ -22,7 +22,8 @@ data class UserDTO (
val position: String, val position: String,
@SerialName("roles") @SerialName("roles")
val roles: List<AuthorityDTO>, val roles: List<AuthorityDTO>,
@SerialName("acsblocked")
val isACSBlocked: Boolean
// @SerialName("lastVisit") // @SerialName("lastVisit")
// val lastVisit: String, // val lastVisit: String,
) : java.io.Serializable ) : java.io.Serializable

View File

@ -0,0 +1,25 @@
package com.displaynone.acss.components.init.models
import com.displaynone.acss.components.acs.models.visit.VisitServiceST
import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.init.models.repository.InitRepository
class InitServiceST {
private val initRepository = InitRepository()
companion object {
private var instance: InitServiceST? = null
fun createInstance() {
if (instance == null) {
instance = InitServiceST()
}
}
fun getInstance(): InitServiceST {
return instance ?: throw RuntimeException("null instance")
}
}
suspend fun ping(): Result<Boolean> {
return initRepository.ping(UserServiceST.getInstance().getTokenPair().accessToken)
}
}

View File

@ -0,0 +1,31 @@
package com.displaynone.acss.components.init.models.repository
import android.util.Log
import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO
import com.displaynone.acss.config.Constants.serverUrl
import com.displaynone.acss.config.Network
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.encodeURLPath
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class InitRepository {
suspend fun ping(token: String): Result<Boolean> = withContext(
Dispatchers.IO){
runCatching {
val result = Network.client.get("$serverUrl/api/utils/ping") {
headers {
append(HttpHeaders.Authorization, "Bearer $token")
}
}
result.status == HttpStatusCode.OK
}
}
}

View File

@ -1,22 +0,0 @@
package com.displaynone.acss.components.main.utils
import com.displaynone.acss.config.Constants.serverUrl
import com.displaynone.acss.config.Network
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun pingServer(token: String): Result<Boolean> = withContext(Dispatchers.IO) {
runCatching {
val result = Network.client.get("$serverUrl/api/ping") {
headers {
append(HttpHeaders.Authorization, "Bearer $token")
}
}
result.status == HttpStatusCode.OK
}
}

View File

@ -7,18 +7,23 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.displaynone.acss.R import com.displaynone.acss.R
import com.displaynone.acss.components.auth.models.user.UserServiceST import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO
import com.displaynone.acss.databinding.FragmentInitBinding import com.displaynone.acss.databinding.FragmentInitBinding
import com.displaynone.acss.util.collectWithLifecycle
import com.displaynone.acss.util.navigateTo import com.displaynone.acss.util.navigateTo
class InitFragment : Fragment(R.layout.fragment_init) { class InitFragment : Fragment(R.layout.fragment_init) {
private var _binding: FragmentInitBinding? = null private var _binding: FragmentInitBinding? = null
private val binding: FragmentInitBinding get() = _binding!! private val binding: FragmentInitBinding get() = _binding!!
private val viewModel: InitFragmentViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentInitBinding.bind(view) _binding = FragmentInitBinding.bind(view)
@ -28,33 +33,43 @@ class InitFragment : Fragment(R.layout.fragment_init) {
return return
} }
if (!pingServer()) { pingServer()
handleError(R.string.serverIsUnabailable)
return
}
val navController: NavController = findNavController()
if (!isUserAuthenticated()) {
navigateTo(navController, R.id.action_nav_init_to_nav_auth)
return
}
val user: UserDTO? = updateUser()
if (user == null) {
navigateTo(navController, R.id.action_nav_init_to_nav_auth)
return
}
navigateTo(navController, R.id.action_nav_init_to_nav_profile)
} }
private fun pingServer(): Boolean { private fun pingServer() {
return true viewModel.pingServer()
viewModel.state.collectWithLifecycle(this) { state ->
if (state is InitFragmentViewModel.State.Show){
if(!state.item){
handleError(R.string.serverIsUnabailable)
} else{
val navController: NavController = findNavController()
if (!isUserAuthenticated()) {
navigateTo(navController, R.id.action_nav_init_to_nav_auth)
} else {
updateUser(navController)
}
}
}
}
} }
private fun updateUser(): UserDTO? { private fun updateUser(navController: NavController) {
return null viewModel.updateUserInfo()
viewModel.state.collectWithLifecycle(this){ state ->
if (state is InitFragmentViewModel.State.Update) {
if (state.item == null) {
navigateTo(navController, R.id.action_nav_init_to_nav_auth)
} else{
navigateTo(navController, R.id.action_nav_init_to_nav_profile)
}
}
if (state is InitFragmentViewModel.State.Error){
navigateTo(navController, R.id.action_nav_init_to_nav_auth)
}
}
} }
private fun checkInternetConnection(): Boolean { private fun checkInternetConnection(): Boolean {
@ -91,4 +106,7 @@ class InitFragment : Fragment(R.layout.fragment_init) {
private fun handleError(string: Int) { private fun handleError(string: Int) {
binding.error.text = requireContext().getString(string) binding.error.text = requireContext().getString(string)
} }
private fun handleError(errorMessage: String) {
binding.error.text = errorMessage
}
} }

View File

@ -1,14 +1,53 @@
package com.displaynone.acss.ui.init package com.displaynone.acss.ui.init
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO
import com.displaynone.acss.components.init.models.InitServiceST
import com.displaynone.acss.ui.profile.ProfileViewModel.State
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class InitFragmentViewModel: ViewModel() { class InitFragmentViewModel: ViewModel() {
private fun pingServer(): Boolean { val _state = MutableStateFlow<State>(State.Loading)
val state = _state.asStateFlow()
fun pingServer() {
viewModelScope.launch {
InitServiceST.getInstance().ping().fold(
onSuccess = { _state.emit(State.Show(true)) },
onFailure = {
_state.emit(State.Show(false))
Log.d("InitFragmentViewModel", "failed to ping") }
)
}
} }
private fun updateUser(): UserDTO? { fun updateUserInfo() {
viewModelScope.launch {
UserServiceST.getInstance().getInfo().fold(
onSuccess = { data ->
_state.emit(State.Update(data))
},
onFailure = { error ->
_state.emit(State.Error(error.message.toString()))
Log.d("InitFragmentViewModel", "failed to update user info ${error.message}")
}
)
}
}
sealed interface State {
data object Loading : State
data class Show(
val item: Boolean
) : State
data class Update(
val item: UserDTO
) : State
data class Error(
val errorMessage: String
) : State
} }
} }

View File

@ -57,6 +57,7 @@ class ProfileFragment: Fragment(R.layout.fragment_profile) {
waitForQRScanResult() waitForQRScanResult()
} else{ } else{
showData(getUserDto()!!) showData(getUserDto()!!)
Log.d("ProfileFragment", "set login") Log.d("ProfileFragment", "set login")
viewModel.visitListStateFromLogin.collectWithLifecycle(this) { data -> viewModel.visitListStateFromLogin.collectWithLifecycle(this) { data ->
adapter.submitData(data) adapter.submitData(data)
@ -92,6 +93,7 @@ class ProfileFragment: Fragment(R.layout.fragment_profile) {
binding.logout.visibility = View.GONE binding.logout.visibility = View.GONE
binding.scan.visibility = View.GONE binding.scan.visibility = View.GONE
binding.buttonSearch.visibility = View.GONE binding.buttonSearch.visibility = View.GONE
binding.changeRights.visibility = View.VISIBLE
} }
fun showMyData(userDTO: UserDTO){ fun showMyData(userDTO: UserDTO){
binding.fio.text = userDTO.name binding.fio.text = userDTO.name
@ -101,7 +103,11 @@ class ProfileFragment: Fragment(R.layout.fragment_profile) {
fun showData(userDTO: UserDTO){ fun showData(userDTO: UserDTO){
binding.fio.text = userDTO.name binding.fio.text = userDTO.name
binding.position.text = userDTO.position binding.position.text = userDTO.position
binding.changeRights.text = if(userDTO.isACSBlocked) "Разблокировать пользователя" else "Заблокировать пользователя"
viewModel.setLogin(login1 = userDTO.login) viewModel.setLogin(login1 = userDTO.login)
binding.changeRights.setOnClickListener {
viewModel.changeRights(userDTO.login, !userDTO.isACSBlocked)
}
Log.d("ProfileFragment", userDTO.login) Log.d("ProfileFragment", userDTO.login)
// binding.lastEntry.text = userDTO.lastVisit // binding.lastEntry.text = userDTO.lastVisit

View File

@ -10,6 +10,7 @@ import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.acs.models.visit.VisitListPagingSource import com.displaynone.acss.components.acs.models.visit.VisitListPagingSource
import com.displaynone.acss.components.acs.models.visit.VisitServiceST import com.displaynone.acss.components.acs.models.visit.VisitServiceST
import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO
import io.ktor.util.reflect.instanceOf
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -66,6 +67,19 @@ class ProfileViewModel(): ViewModel() {
) )
} }
} }
fun changeRights(login: String, isACSBlocked: Boolean){
viewModelScope.launch {
UserServiceST.getInstance().changeRights(login, isACSBlocked).fold(
onSuccess = {
Log.d("ProfileViewModel", "changed rights")
// _state.emit(State.Change(isACSBlocked))
},
onFailure = { error ->
Log.d("ProfileViewModel", "failed to change rights ${error.message}")
}
)
}
}
fun openAuth(){ fun openAuth(){
viewModelScope.launch { viewModelScope.launch {
_action.send(Action.GoToAuth) _action.send(Action.GoToAuth)
@ -82,6 +96,9 @@ class ProfileViewModel(): ViewModel() {
data class Show( data class Show(
val item: UserDTO val item: UserDTO
) : State ) : State
data class Change(
val item: Boolean
)
} }
sealed interface Action { sealed interface Action {
data object GoToAuth: Action data object GoToAuth: Action