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.visit.VisitServiceST
import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.init.models.InitServiceST
class ACSSApplication : Application() {
override fun onCreate() {
@ -11,5 +12,6 @@ class ACSSApplication : Application() {
UserServiceST.createInstance(this)
GateServiceST.createInstance()
VisitServiceST.createInstance()
InitServiceST.createInstance()
}
}

View File

@ -5,6 +5,7 @@ import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
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.databinding.ItemScannerViewBinding
@ -37,7 +38,7 @@ class VisitAdapter: PagingDataAdapter<VisitDto, VisitAdapter.ViewHolder>(VisitDi
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: VisitDto){
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,
position = userEntity.position,
roles = emptyList(),
isACSBlocked = false
// lastVisit = userEntity.lastVisit,
)
return userDto

View File

@ -70,6 +70,9 @@ class UserServiceST(
fun logout(){
tokenManager.clear()
}
suspend fun changeRights(login: String, isACSBlocked: Boolean): Result<Unit> {
return userRepository.changeRightsByLogin(login, tokenManager.authTokenPair!!.accessToken, isACSBlocked)
}
suspend fun getInfo(): Result<UserDTO>{
if (!tokenManager.hasTokens()) {
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.Network
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.UserLoginDto
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.client.request.patch
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
@ -101,25 +103,28 @@ class UserRepository(
result.body()
}
}
suspend fun register(login: String, password: String): Result<Unit> =
withContext(Dispatchers.IO) {
runCatching {
val result = Network.client.post("$serverUrl/api/person/register") {
contentType(ContentType.Application.Json)
setBody(
RegisterUserDto(
login = login,
password = password,
)
)
suspend fun changeRightsByLogin(login: String, token: String, isACSBlocked: Boolean): Result<Unit> = withContext(Dispatchers.IO) {
runCatching {
val encodedLogin = login.encodeURLPath()
val result = Network.client.patch("$serverUrl/api/users/login/$encodedLogin") {
headers{
append(HttpHeaders.Authorization, "Bearer $token")
}
if (result.status != HttpStatusCode.Created) {
Log.w("UserRepository", "Status: ${result.status}, Body: ${result.body<String>()}")
error("Status ${result.status}: ${result.body<String>()}")
}
Unit
contentType(ContentType.Application.Json)
setBody(UpdateUserDTO(
password = null,
name = null,
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,
@SerialName("roles")
val roles: List<AuthorityDTO>,
@SerialName("acsblocked")
val isACSBlocked: Boolean
// @SerialName("lastVisit")
// val lastVisit: String,
) : 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.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import com.displaynone.acss.R
import com.displaynone.acss.components.auth.models.user.UserServiceST
import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO
import com.displaynone.acss.databinding.FragmentInitBinding
import com.displaynone.acss.util.collectWithLifecycle
import com.displaynone.acss.util.navigateTo
class InitFragment : Fragment(R.layout.fragment_init) {
private var _binding: FragmentInitBinding? = null
private val binding: FragmentInitBinding get() = _binding!!
private val viewModel: InitFragmentViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentInitBinding.bind(view)
@ -28,33 +33,43 @@ class InitFragment : Fragment(R.layout.fragment_init) {
return
}
if (!pingServer()) {
handleError(R.string.serverIsUnabailable)
return
}
pingServer()
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 {
return true
private fun pingServer() {
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? {
return null
private fun updateUser(navController: NavController) {
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 {
@ -91,4 +106,7 @@ class InitFragment : Fragment(R.layout.fragment_init) {
private fun handleError(string: Int) {
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
import android.util.Log
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.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() {
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()
} else{
showData(getUserDto()!!)
Log.d("ProfileFragment", "set login")
viewModel.visitListStateFromLogin.collectWithLifecycle(this) { data ->
adapter.submitData(data)
@ -92,6 +93,7 @@ class ProfileFragment: Fragment(R.layout.fragment_profile) {
binding.logout.visibility = View.GONE
binding.scan.visibility = View.GONE
binding.buttonSearch.visibility = View.GONE
binding.changeRights.visibility = View.VISIBLE
}
fun showMyData(userDTO: UserDTO){
binding.fio.text = userDTO.name
@ -101,7 +103,11 @@ class ProfileFragment: Fragment(R.layout.fragment_profile) {
fun showData(userDTO: UserDTO){
binding.fio.text = userDTO.name
binding.position.text = userDTO.position
binding.changeRights.text = if(userDTO.isACSBlocked) "Разблокировать пользователя" else "Заблокировать пользователя"
viewModel.setLogin(login1 = userDTO.login)
binding.changeRights.setOnClickListener {
viewModel.changeRights(userDTO.login, !userDTO.isACSBlocked)
}
Log.d("ProfileFragment", userDTO.login)
// 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.VisitServiceST
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.Channel
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(){
viewModelScope.launch {
_action.send(Action.GoToAuth)
@ -82,6 +96,9 @@ class ProfileViewModel(): ViewModel() {
data class Show(
val item: UserDTO
) : State
data class Change(
val item: Boolean
)
}
sealed interface Action {
data object GoToAuth: Action