fix: instance unban (#887)

This commit is contained in:
Diego Beraldin 2024-05-26 11:09:50 +02:00 committed by GitHub
parent 30379ef681
commit af239aafb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 160 additions and 94 deletions

View File

@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class BlockSiteForm( data class BlockInstanceForm(
@SerialName("instance_id") val instanceId: InstanceId, @SerialName("instance_id") val instanceId: InstanceId,
@SerialName("block") val block: Boolean, @SerialName("block") val block: Boolean,
) )

View File

@ -0,0 +1,9 @@
package com.github.diegoberaldin.raccoonforlemmy.core.api.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class BlockInstanceResponse(
@SerialName("blocked") val blocked: Boolean,
)

View File

@ -1,6 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.core.api.service package com.github.diegoberaldin.raccoonforlemmy.core.api.service
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.BlockSiteForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.BlockInstanceForm
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.BlockInstanceResponse
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.GetSiteResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.GetSiteResponse
import de.jensklingenberg.ktorfit.Response import de.jensklingenberg.ktorfit.Response
import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.Body
@ -21,6 +22,6 @@ interface SiteService {
@Headers("Content-Type: application/json") @Headers("Content-Type: application/json")
suspend fun block( suspend fun block(
@Header("Authorization") authHeader: String? = null, @Header("Authorization") authHeader: String? = null,
@Body form: BlockSiteForm, @Body form: BlockInstanceForm,
): Response<GetSiteResponse> ): Response<BlockInstanceResponse>
} }

View File

@ -423,24 +423,24 @@ class DefaultCommunityRepositoryTest {
mockk { mockk {
every { isSuccessful } returns true every { isSuccessful } returns true
every { body() } returns mockk() every { body() } returns mockk()
val token = "fake-token"
sut.block(
auth = token,
id = communityId,
blocked = true,
)
coVerify {
communityService.block(
authHeader = token.toAuthHeader(),
withArg { data ->
assertEquals(communityId, data.communityId)
assertTrue(data.block)
},
)
}
} }
val token = "fake-token"
sut.block(
auth = token,
id = communityId,
blocked = true,
)
coVerify {
communityService.block(
authHeader = token.toAuthHeader(),
withArg { data ->
assertEquals(communityId, data.communityId)
assertTrue(data.block)
},
)
}
} }
@Test @Test

View File

@ -64,7 +64,7 @@ interface CommunityRepository {
id: Long, id: Long,
blocked: Boolean, blocked: Boolean,
auth: String?, auth: String?,
): Result<Unit> )
suspend fun banUser( suspend fun banUser(
auth: String?, auth: String?,

View File

@ -217,21 +217,18 @@ internal class DefaultCommunityRepository(
id: Long, id: Long,
blocked: Boolean, blocked: Boolean,
auth: String?, auth: String?,
): Result<Unit> = ): Unit =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { val data =
val data = BlockCommunityForm(
BlockCommunityForm( communityId = id,
communityId = id, block = blocked,
block = blocked, auth = auth.orEmpty(),
auth = auth.orEmpty(),
)
services.community.block(
authHeader = auth.toAuthHeader(),
form = data,
) )
Unit services.community.block(
} authHeader = auth.toAuthHeader(),
form = data,
)
} }
override suspend fun banUser( override suspend fun banUser(

View File

@ -1,6 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.BlockSiteForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.BlockInstanceForm
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.AccountBansModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.AccountBansModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.AccountSettingsModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.AccountSettingsModel
@ -61,20 +61,18 @@ internal class DefaultSiteRepository(
id: Long, id: Long,
blocked: Boolean, blocked: Boolean,
auth: String?, auth: String?,
): Result<Unit> = ): Unit =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { val data =
val data = BlockInstanceForm(
BlockSiteForm( instanceId = id,
instanceId = id, block = blocked,
block = blocked,
)
services.site.block(
authHeader = auth.toAuthHeader(),
form = data,
) )
Unit services.site.block(
} authHeader = auth.toAuthHeader(),
form = data,
)
Unit
} }
override suspend fun getMetadata(url: String): MetadataModel? = override suspend fun getMetadata(url: String): MetadataModel? =

View File

@ -285,21 +285,19 @@ internal class DefaultUserRepository(
id: Long, id: Long,
blocked: Boolean, blocked: Boolean,
auth: String?, auth: String?,
): Result<Unit> = ): Unit =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
runCatching { val data =
val data = BlockPersonForm(
BlockPersonForm( personId = id,
personId = id, block = blocked,
block = blocked, auth = auth.orEmpty(),
auth = auth.orEmpty(),
)
services.user.block(
authHeader = auth.toAuthHeader(),
form = data,
) )
Unit services.user.block(
} authHeader = auth.toAuthHeader(),
form = data,
)
Unit
} }
override suspend fun getModeratedCommunities( override suspend fun getModeratedCommunities(

View File

@ -18,7 +18,7 @@ interface SiteRepository {
id: Long, id: Long,
blocked: Boolean, blocked: Boolean,
auth: String? = null, auth: String? = null,
): Result<Unit> )
suspend fun getMetadata(url: String): MetadataModel? suspend fun getMetadata(url: String): MetadataModel?

View File

@ -90,7 +90,7 @@ interface UserRepository {
id: Long, id: Long,
blocked: Boolean, blocked: Boolean,
auth: String? = null, auth: String? = null,
): Result<Unit> )
suspend fun getModeratedCommunities( suspend fun getModeratedCommunities(
auth: String? = null, auth: String? = null,

View File

@ -394,7 +394,7 @@ class CommunityDetailScreen(
this += this +=
Option( Option(
OptionId.Block, OptionId.Block,
LocalXmlStrings.current.communityDetailBlock, LocalXmlStrings.current.blockActionCommunity,
) )
this += this +=
Option( Option(

View File

@ -595,7 +595,11 @@ class CommunityDetailViewModel(
updateState { it.copy(asyncInProgress = true) } updateState { it.copy(asyncInProgress = true) }
try { try {
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
communityRepository.block(communityId, true, auth).getOrThrow() communityRepository.block(
id = communityId,
blocked = true,
auth = auth,
)
emitEffect(CommunityDetailMviModel.Effect.Success) emitEffect(CommunityDetailMviModel.Effect.Success)
} catch (e: Throwable) { } catch (e: Throwable) {
emitEffect(CommunityDetailMviModel.Effect.Error(e.message)) emitEffect(CommunityDetailMviModel.Effect.Error(e.message))
@ -612,7 +616,7 @@ class CommunityDetailViewModel(
val community = uiState.value.community val community = uiState.value.community
val instanceId = community.instanceId val instanceId = community.instanceId
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
siteRepository.block(instanceId, true, auth).getOrThrow() siteRepository.block(instanceId, true, auth)
emitEffect(CommunityDetailMviModel.Effect.Success) emitEffect(CommunityDetailMviModel.Effect.Success)
} catch (e: Throwable) { } catch (e: Throwable) {
emitEffect(CommunityDetailMviModel.Effect.Error(e.message)) emitEffect(CommunityDetailMviModel.Effect.Error(e.message))

View File

@ -40,5 +40,9 @@ interface ManageBanMviModel :
val bannedInstances: List<InstanceModel> = emptyList(), val bannedInstances: List<InstanceModel> = emptyList(),
) )
sealed interface Effect sealed interface Effect {
data object Success : Effect
data class Failure(val message: String?) : Effect
}
} }

View File

@ -14,6 +14,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -27,9 +28,11 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -50,6 +53,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.unit.manageban.components.InstanceItem import com.github.diegoberaldin.raccoonforlemmy.unit.manageban.components.InstanceItem
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class ManageBanScreen : Screen { class ManageBanScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@ -64,6 +69,22 @@ class ManageBanScreen : Screen {
val settingsRepository = remember { getSettingsRepository() } val settingsRepository = remember { getSettingsRepository() }
val settings by settingsRepository.currentSettings.collectAsState() val settings by settingsRepository.currentSettings.collectAsState()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val successMessage = LocalXmlStrings.current.messageOperationSuccessful
val errorMessage = LocalXmlStrings.current.messageGenericError
LaunchedEffect(model) {
model.effects.onEach { evt ->
when (evt) {
is ManageBanMviModel.Effect.Failure -> {
snackbarHostState.showSnackbar(evt.message ?: errorMessage)
}
ManageBanMviModel.Effect.Success -> {
snackbarHostState.showSnackbar(successMessage)
}
}
}.launchIn(this)
}
Scaffold( Scaffold(
modifier = Modifier.background(MaterialTheme.colorScheme.background), modifier = Modifier.background(MaterialTheme.colorScheme.background),
@ -200,13 +221,11 @@ class ManageBanScreen : Screen {
) )
}, },
onOptionSelected = onOptionSelected =
rememberCallbackArgs(model) { optionId -> rememberCallbackArgs(user) { optionId ->
when (optionId) { when (optionId) {
OptionId.Unban -> { OptionId.Unban -> {
model.reduce( model.reduce(
ManageBanMviModel.Intent.UnblockUser( ManageBanMviModel.Intent.UnblockUser(user.id),
user.id,
),
) )
} }
@ -250,13 +269,11 @@ class ManageBanScreen : Screen {
) )
}, },
onOptionSelected = onOptionSelected =
rememberCallbackArgs(model) { optionId -> rememberCallbackArgs(community) { optionId ->
when (optionId) { when (optionId) {
OptionId.Unban -> { OptionId.Unban -> {
model.reduce( model.reduce(
ManageBanMviModel.Intent.UnblockCommunity( ManageBanMviModel.Intent.UnblockCommunity(community.id),
community.id,
),
) )
} }
@ -298,13 +315,11 @@ class ManageBanScreen : Screen {
) )
}, },
onOptionSelected = onOptionSelected =
rememberCallbackArgs(model) { optionId -> rememberCallbackArgs(instance) { optionId ->
when (optionId) { when (optionId) {
OptionId.Unban -> { OptionId.Unban -> {
model.reduce( model.reduce(
ManageBanMviModel.Intent.UnblockInstance( ManageBanMviModel.Intent.UnblockInstance(instance.id),
instance.id,
),
) )
} }
@ -317,6 +332,14 @@ class ManageBanScreen : Screen {
} }
} }
} }
PullRefreshIndicator(
refreshing = uiState.refreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
)
} }
} }
} }

View File

@ -74,7 +74,7 @@ class ManageBanViewModel(
private fun unbanUser(id: Long) { private fun unbanUser(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
runCatching { try {
userRepository.block( userRepository.block(
id = id, id = id,
blocked = false, blocked = false,
@ -83,6 +83,9 @@ class ManageBanViewModel(
updateState { updateState {
it.copy(bannedUsers = it.bannedUsers.filter { e -> e.id != id }) it.copy(bannedUsers = it.bannedUsers.filter { e -> e.id != id })
} }
emitEffect(ManageBanMviModel.Effect.Success)
} catch (e: Throwable) {
emitEffect(ManageBanMviModel.Effect.Failure(e.message))
} }
} }
} }
@ -90,7 +93,7 @@ class ManageBanViewModel(
private fun unbanCommunity(id: Long) { private fun unbanCommunity(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
runCatching { try {
communityRepository.block( communityRepository.block(
id = id, id = id,
blocked = false, blocked = false,
@ -99,6 +102,9 @@ class ManageBanViewModel(
updateState { updateState {
it.copy(bannedCommunities = it.bannedCommunities.filter { e -> e.id != id }) it.copy(bannedCommunities = it.bannedCommunities.filter { e -> e.id != id })
} }
emitEffect(ManageBanMviModel.Effect.Success)
} catch (e: Throwable) {
emitEffect(ManageBanMviModel.Effect.Failure(e.message))
} }
} }
} }
@ -106,7 +112,7 @@ class ManageBanViewModel(
private fun unbanInstance(id: Long) { private fun unbanInstance(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
runCatching { try {
siteRepository.block( siteRepository.block(
id = id, id = id,
blocked = false, blocked = false,
@ -115,6 +121,9 @@ class ManageBanViewModel(
updateState { updateState {
it.copy(bannedInstances = it.bannedInstances.filter { e -> e.id != id }) it.copy(bannedInstances = it.bannedInstances.filter { e -> e.id != id })
} }
emitEffect(ManageBanMviModel.Effect.Success)
} catch (e: Throwable) {
emitEffect(ManageBanMviModel.Effect.Failure(e.message))
} }
} }
} }

View File

@ -63,8 +63,9 @@ fun InstanceItem(
) )
Text( Text(
modifier = Modifier.weight(1f),
text = name, text = name,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodyLarge,
color = fullColor, color = fullColor,
) )

View File

@ -517,25 +517,39 @@ class PostListViewModel(
private fun blockUser(userId: Long) { private fun blockUser(userId: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value runCatching {
userRepository.block(userId, true, auth) val auth = identityRepository.authToken.value
userRepository.block(
id = userId,
blocked = true,
auth = auth,
)
}
} }
} }
private fun blockCommunity(communityId: Long) { private fun blockCommunity(communityId: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value runCatching {
communityRepository.block(communityId, true, auth) val auth = identityRepository.authToken.value
communityRepository.block(
id = communityId,
blocked = true,
auth = auth,
)
}
} }
} }
private fun blockInstance(instanceId: Long) { private fun blockInstance(instanceId: Long) {
screenModelScope.launch { screenModelScope.launch {
try { runCatching {
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
siteRepository.block(instanceId, true, auth) siteRepository.block(
} catch (e: Throwable) { id = instanceId,
e.printStackTrace() blocked = true,
auth = auth,
)
} }
} }
} }

View File

@ -278,7 +278,7 @@ class UserDetailScreen(
this += this +=
Option( Option(
OptionId.Block, OptionId.Block,
LocalXmlStrings.current.communityDetailBlock, LocalXmlStrings.current.blockActionUser,
) )
this += this +=
Option( Option(

View File

@ -536,7 +536,11 @@ class UserDetailViewModel(
updateState { it.copy(asyncInProgress = true) } updateState { it.copy(asyncInProgress = true) }
try { try {
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
userRepository.block(userId, true, auth).getOrThrow() userRepository.block(
id = userId,
blocked = true,
auth = auth,
)
emitEffect(UserDetailMviModel.Effect.Success) emitEffect(UserDetailMviModel.Effect.Success)
} catch (e: Throwable) { } catch (e: Throwable) {
emitEffect(UserDetailMviModel.Effect.Error(e.message)) emitEffect(UserDetailMviModel.Effect.Error(e.message))
@ -553,7 +557,11 @@ class UserDetailViewModel(
val user = uiState.value.user val user = uiState.value.user
val instanceId = user.instanceId val instanceId = user.instanceId
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
siteRepository.block(instanceId, true, auth).getOrThrow() siteRepository.block(
id = instanceId,
blocked = true,
auth = auth,
)
emitEffect(UserDetailMviModel.Effect.Success) emitEffect(UserDetailMviModel.Effect.Success)
} catch (e: Throwable) { } catch (e: Throwable) {
emitEffect(UserDetailMviModel.Effect.Error(e.message)) emitEffect(UserDetailMviModel.Effect.Error(e.message))