From b88a242f6be85c13f0b9ff6c9169a11d6831ca14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojtek=20Kalici=C5=84ski?= <146713236+wkal-pubnub@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:28:39 +0100 Subject: [PATCH] Lock moderated messages from editing (#122) Additionally clean up empty actions maps when removing actions --- .gitignore | 3 + pubnub-chat-api/pubnub_chat_api.podspec | 56 ---------------- pubnub-chat-impl/pubnub_chat_impl.podspec | 56 ---------------- .../com/pubnub/chat/internal/Constants.kt | 3 + .../com/pubnub/chat/internal/UserImpl.kt | 1 + .../chat/internal/error/PubNubErrorMessage.kt | 1 + .../chat/internal/message/BaseMessage.kt | 36 ++++++---- .../kotlin/com/pubnub/kmp/BaseMessageTest.kt | 67 +++++++++++++++++++ pubnub_chat.podspec | 56 ---------------- 9 files changed, 97 insertions(+), 182 deletions(-) delete mode 100644 pubnub-chat-api/pubnub_chat_api.podspec delete mode 100644 pubnub-chat-impl/pubnub_chat_impl.podspec create mode 100644 pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/BaseMessageTest.kt delete mode 100644 pubnub_chat.podspec diff --git a/.gitignore b/.gitignore index bbd70385..ac2b0bbd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +*.podspec +node_modules +js-chat/dist ### IntelliJ IDEA ### .idea/modules.xml diff --git a/pubnub-chat-api/pubnub_chat_api.podspec b/pubnub-chat-api/pubnub_chat_api.podspec deleted file mode 100644 index f8416ed3..00000000 --- a/pubnub-chat-api/pubnub_chat_api.podspec +++ /dev/null @@ -1,56 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'pubnub_chat_api' - spec.version = '0.9.0' - spec.homepage = '' - spec.source = { :http=> ''} - spec.authors = '' - spec.license = '' - spec.summary = '' - spec.vendored_frameworks = 'build/cocoapods/framework/pubnub_chat_api.framework' - spec.libraries = 'c++' - spec.ios.deployment_target = '14.0' - spec.osx.deployment_target = '11.0' - spec.tvos.deployment_target = '14.0' - spec.dependency 'PubNubSwift', '8.0.1' - - if !Dir.exist?('build/cocoapods/framework/pubnub_chat_api.framework') || Dir.empty?('build/cocoapods/framework/pubnub_chat_api.framework') - raise " - - Kotlin framework 'pubnub_chat_api' doesn't exist yet, so a proper Xcode project can't be generated. - 'pod install' should be executed after running ':generateDummyFramework' Gradle task: - - ./gradlew :pubnub-chat-api:generateDummyFramework - - Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" - end - - spec.xcconfig = { - 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', - } - - spec.pod_target_xcconfig = { - 'KOTLIN_PROJECT_PATH' => ':pubnub-chat-api', - 'PRODUCT_MODULE_NAME' => 'pubnub_chat_api', - } - - spec.script_phases = [ - { - :name => 'Build pubnub_chat_api', - :execution_position => :before_compile, - :shell_path => '/bin/sh', - :script => <<-SCRIPT - if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then - echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" - exit 0 - fi - set -ev - REPO_ROOT="$PODS_TARGET_SRCROOT" - "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ - -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ - -Pkotlin.native.cocoapods.archs="$ARCHS" \ - -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" - SCRIPT - } - ] - -end \ No newline at end of file diff --git a/pubnub-chat-impl/pubnub_chat_impl.podspec b/pubnub-chat-impl/pubnub_chat_impl.podspec deleted file mode 100644 index 0768794d..00000000 --- a/pubnub-chat-impl/pubnub_chat_impl.podspec +++ /dev/null @@ -1,56 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'pubnub_chat_impl' - spec.version = '0.9.0' - spec.homepage = 'Link to a Kotlin/Native module homepage' - spec.source = { :http=> ''} - spec.authors = '' - spec.license = '' - spec.summary = 'Some description for a Kotlin/Native module' - spec.vendored_frameworks = 'build/cocoapods/framework/pubnub_chat_impl.framework' - spec.libraries = 'c++' - spec.ios.deployment_target = '14.0' - spec.osx.deployment_target = '11.0' - spec.tvos.deployment_target = '14.0' - spec.dependency 'PubNubSwift', '8.0.1' - - if !Dir.exist?('build/cocoapods/framework/pubnub_chat_impl.framework') || Dir.empty?('build/cocoapods/framework/pubnub_chat_impl.framework') - raise " - - Kotlin framework 'pubnub_chat_impl' doesn't exist yet, so a proper Xcode project can't be generated. - 'pod install' should be executed after running ':generateDummyFramework' Gradle task: - - ./gradlew :pubnub-chat-impl:generateDummyFramework - - Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" - end - - spec.xcconfig = { - 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', - } - - spec.pod_target_xcconfig = { - 'KOTLIN_PROJECT_PATH' => ':pubnub-chat-impl', - 'PRODUCT_MODULE_NAME' => 'pubnub_chat_impl', - } - - spec.script_phases = [ - { - :name => 'Build pubnub_chat_impl', - :execution_position => :before_compile, - :shell_path => '/bin/sh', - :script => <<-SCRIPT - if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then - echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" - exit 0 - fi - set -ev - REPO_ROOT="$PODS_TARGET_SRCROOT" - "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ - -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ - -Pkotlin.native.cocoapods.archs="$ARCHS" \ - -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" - SCRIPT - } - ] - -end \ No newline at end of file diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/Constants.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/Constants.kt index 798492e5..6927d31f 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/Constants.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/Constants.kt @@ -16,6 +16,9 @@ internal const val ORIGINAL_PUBLISHER = "originalPublisher" internal const val ORIGINAL_CHANNEL_ID = "originalChannelId" internal const val HTTP_ERROR_404 = 404 internal const val INTERNAL_MODERATION_PREFIX = "PUBNUB_INTERNAL_MODERATION_" +internal const val PUBNUB_INTERNAL_AUTOMODERATED = "PUBNUB_INTERNAL_AUTOMODERATED" +internal const val INTERNAL_MODERATOR_DATA_ID = "PUBNUB_INTERNAL_MODERATOR" +internal const val INTERNAL_MODERATOR_DATA_TYPE = "internal" internal const val MESSAGE_THREAD_ID_PREFIX = "PUBNUB_INTERNAL_THREAD" internal val MINIMAL_TYPING_INDICATOR_TIMEOUT: Duration = 1.seconds internal const val THREAD_ROOT_ID = "threadRootId" diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt index 6213fc35..97f090ca 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt @@ -298,3 +298,4 @@ data class UserImpl( } internal val User.uuidFilterString get() = "uuid.id == '${this.id}'" +internal val User.isInternalModerator get() = this.id == INTERNAL_MODERATOR_DATA_ID && this.type == INTERNAL_MODERATOR_DATA_TYPE diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt index 0a17ef2e..1c98cb58 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/error/PubNubErrorMessage.kt @@ -59,4 +59,5 @@ internal object PubNubErrorMessage { internal const val CANNOT_QUOTE_MESSAGE_FROM_OTHER_CHANNELS = "You cannot quote messages from other channels" internal const val MENTION_SUGGESTION_INVALID = "This mention suggestion is no longer valid - the message draft text has been changed." internal const val MENTION_CANNOT_INTERSECT = "Cannot intersect with existing mention:" + internal const val AUTOMODERATED_MESSAGE_CANNOT_BE_EDITED = "The automoderated message can no longer be edited" } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt index 25bc5ecc..8b055db3 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/message/BaseMessage.kt @@ -21,13 +21,17 @@ import com.pubnub.chat.internal.METADATA_MENTIONED_USERS import com.pubnub.chat.internal.METADATA_QUOTED_MESSAGE import com.pubnub.chat.internal.METADATA_REFERENCED_CHANNELS import com.pubnub.chat.internal.METADATA_TEXT_LINKS +import com.pubnub.chat.internal.PUBNUB_INTERNAL_AUTOMODERATED import com.pubnub.chat.internal.THREAD_ROOT_ID import com.pubnub.chat.internal.channel.ChannelImpl import com.pubnub.chat.internal.error.PubNubErrorMessage +import com.pubnub.chat.internal.error.PubNubErrorMessage.AUTOMODERATED_MESSAGE_CANNOT_BE_EDITED import com.pubnub.chat.internal.error.PubNubErrorMessage.CANNOT_STREAM_MESSAGE_UPDATES_ON_EMPTY_LIST import com.pubnub.chat.internal.error.PubNubErrorMessage.KEY_IS_NOT_VALID_INTEGER import com.pubnub.chat.internal.error.PubNubErrorMessage.THIS_MESSAGE_HAS_NOT_BEEN_DELETED +import com.pubnub.chat.internal.isInternalModerator import com.pubnub.chat.internal.serialization.PNDataEncoder +import com.pubnub.chat.internal.util.logErrorAndReturnException import com.pubnub.chat.internal.util.pnError import com.pubnub.chat.types.EventContent import com.pubnub.chat.types.File @@ -115,6 +119,10 @@ abstract class BaseMessage( override fun editText(newText: String): PNFuture { val type = chat.editMessageActionName + if (this.meta?.containsKey(PUBNUB_INTERNAL_AUTOMODERATED) == true && !this.chat.currentUser.isInternalModerator) { + return log.logErrorAndReturnException(AUTOMODERATED_MESSAGE_CANNOT_BE_EDITED).asFuture() + } + return chat.pubNub.addMessageAction( channelId, PNMessageAction( @@ -375,23 +383,23 @@ abstract class BaseMessage( internal fun filterAction(actions: Actions?, action: PNMessageAction): Actions { return buildMap { actions?.entries?.forEach { entry -> - put( - entry.key, - buildMap { - entry.value.forEach { innerEntry -> - if (entry.key == action.type && innerEntry.key == action.value) { - put( - innerEntry.key, - innerEntry.value.filter { - it.actionTimetoken != action.actionTimetoken || it.uuid != action.uuid - } - ) - } else { - put(innerEntry.key, innerEntry.value) + val actionMap = buildMap { + entry.value.forEach { innerEntry -> + if (entry.key == action.type && innerEntry.key == action.value) { + val actionList = innerEntry.value.filter { + it.actionTimetoken != action.actionTimetoken || it.uuid != action.uuid + } + if (actionList.isNotEmpty()) { + put(innerEntry.key, actionList) } + } else { + put(innerEntry.key, innerEntry.value) } } - ) + } + if (actionMap.isNotEmpty()) { + put(entry.key, actionMap) + } } } } diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/BaseMessageTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/BaseMessageTest.kt new file mode 100644 index 00000000..63806f12 --- /dev/null +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/BaseMessageTest.kt @@ -0,0 +1,67 @@ +package com.pubnub.kmp + +import com.pubnub.api.createJsonElement +import com.pubnub.chat.internal.ChatInternal +import com.pubnub.chat.internal.INTERNAL_MODERATOR_DATA_ID +import com.pubnub.chat.internal.INTERNAL_MODERATOR_DATA_TYPE +import com.pubnub.chat.internal.PUBNUB_INTERNAL_AUTOMODERATED +import com.pubnub.chat.internal.UserImpl +import com.pubnub.chat.internal.message.MessageImpl +import com.pubnub.chat.types.EventContent +import com.pubnub.kmp.utils.BaseTest +import com.pubnub.test.await +import dev.mokkery.answering.returns +import dev.mokkery.every +import dev.mokkery.mock +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertNotEquals + +class BaseMessageTest : BaseTest() { + val chat: ChatInternal = mock() + + @Test + fun shouldThrowExceptionOnEditTextWhenMessageWasModerated() = runTest { + every { chat.currentUser } returns UserImpl(chat, "") + every { chat.editMessageActionName } returns "edit" + val message = + MessageImpl( + chat, + 1L, + EventContent.TextMessageContent(""), + "", + "", + metaInternal = createJsonElement(mapOf(PUBNUB_INTERNAL_AUTOMODERATED to true)) + ) + val exception = assertFails { message.editText("aaa").await() } + assertEquals("The automoderated message can no longer be edited", exception.message) + } + + @Test + fun shouldNotThrowExceptionOnEditTextWhenMessageWasNotModerated() = runTest { + every { chat.currentUser } returns UserImpl(chat, "") + every { chat.editMessageActionName } returns "edit" + val message = MessageImpl(chat, 1L, EventContent.TextMessageContent(""), "", "") + val exception = assertFails { message.editText("aaa").await() } + assertNotEquals("The automoderated message can no longer be edited", exception.message) + } + + @Test + fun shouldNotThrowExceptionOnEditTextWhenUserIsModerator() = runTest { + every { chat.currentUser } returns UserImpl(chat, INTERNAL_MODERATOR_DATA_ID, type = INTERNAL_MODERATOR_DATA_TYPE) + every { chat.editMessageActionName } returns "edit" + val message = + MessageImpl( + chat, + 1L, + EventContent.TextMessageContent(""), + "", + "", + metaInternal = createJsonElement(mapOf(PUBNUB_INTERNAL_AUTOMODERATED to true)) + ) + val exception = assertFails { message.editText("aaa").await() } + assertNotEquals("The automoderated message can no longer be edited", exception.message) + } +} diff --git a/pubnub_chat.podspec b/pubnub_chat.podspec deleted file mode 100644 index 527c987e..00000000 --- a/pubnub_chat.podspec +++ /dev/null @@ -1,56 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'pubnub_chat' - spec.version = '0.9.0' - spec.homepage = 'Link to a Kotlin/Native module homepage' - spec.source = { :http=> ''} - spec.authors = '' - spec.license = '' - spec.summary = 'Some description for a Kotlin/Native module' - spec.vendored_frameworks = 'build/cocoapods/framework/PubNubChat.framework' - spec.libraries = 'c++' - spec.ios.deployment_target = '14.0' - spec.osx.deployment_target = '11.0' - spec.tvos.deployment_target = '14.0' - spec.dependency 'PubNubSwift', '8.0.1' - - if !Dir.exist?('build/cocoapods/framework/PubNubChat.framework') || Dir.empty?('build/cocoapods/framework/PubNubChat.framework') - raise " - - Kotlin framework 'PubNubChat' doesn't exist yet, so a proper Xcode project can't be generated. - 'pod install' should be executed after running ':generateDummyFramework' Gradle task: - - ./gradlew :generateDummyFramework - - Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" - end - - spec.xcconfig = { - 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', - } - - spec.pod_target_xcconfig = { - 'KOTLIN_PROJECT_PATH' => '', - 'PRODUCT_MODULE_NAME' => 'PubNubChat', - } - - spec.script_phases = [ - { - :name => 'Build pubnub_chat', - :execution_position => :before_compile, - :shell_path => '/bin/sh', - :script => <<-SCRIPT - if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then - echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" - exit 0 - fi - set -ev - REPO_ROOT="$PODS_TARGET_SRCROOT" - "$REPO_ROOT/gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ - -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ - -Pkotlin.native.cocoapods.archs="$ARCHS" \ - -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" - SCRIPT - } - ] - -end \ No newline at end of file