diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml index 1db1bbb..1dc98ae 100644 --- a/.github/workflows/dockerimage.yml +++ b/.github/workflows/dockerimage.yml @@ -19,8 +19,8 @@ jobs: password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: build image run: gradle bootBuildImage - - name: tag to latest - run: docker tag folivonet/matrix-sms-bridge:${GITHUB_REF:11} folivonet/matrix-sms-bridge:latest + - name: tag to version + run: docker tag folivonet/matrix-sms-bridge:latest folivonet/matrix-sms-bridge:${GITHUB_REF:11} - name: Publish version tagged image to DockerHub run: docker push folivonet/matrix-sms-bridge:${GITHUB_REF:11} - name: Publish latest image to DockerHub diff --git a/build.gradle.kts b/build.gradle.kts index 643ee91..165af4d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ repositories { } group = "net.folivo" -version = "0.5.3" +version = "0.5.4" java.sourceCompatibility = JavaVersion.VERSION_11 tasks.withType() { @@ -90,7 +90,7 @@ tasks.withType { } tasks.getByName("bootBuildImage") { - imageName = "folivonet/matrix-sms-bridge:${project.version}" + imageName = "folivonet/matrix-sms-bridge:latest" } tasks.register("docker-gammu") { diff --git a/examples/android/docker-compose.yml b/examples/android/docker-compose.yml index dd5c61f..19ef924 100644 --- a/examples/android/docker-compose.yml +++ b/examples/android/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.3' services: matrix-sms-bridge: - image: folivonet/matrix-sms-bridge:0.5.3 + image: folivonet/matrix-sms-bridge:latest volumes: - type: bind source: ./config diff --git a/src/main/kotlin/net/folivo/matrix/bridge/sms/SmsBridgeProperties.kt b/src/main/kotlin/net/folivo/matrix/bridge/sms/SmsBridgeProperties.kt index 0393911..5f166d5 100644 --- a/src/main/kotlin/net/folivo/matrix/bridge/sms/SmsBridgeProperties.kt +++ b/src/main/kotlin/net/folivo/matrix/bridge/sms/SmsBridgeProperties.kt @@ -41,6 +41,8 @@ data class SmsBridgeProperties( val botSmsSendError: String = "There was an error while sending message to the telephone number(s) {receiverNumbers}. Reason: {error}", val botSmsInviteSuccess: String = "{sender} was invited to {roomAlias}.", val botSmsInviteError: String = "There was an error while invite {sender} to {roomAlias}. Reason: {error}", + val botSmsAbortSuccess: String = "The deferred sending of messages in this room were aborted.", + val botSmsAbortError: String = "There was an error running this command. Reason: {error}", val providerSendError: String = "Could not send sms to {receiver} with your provider. We will try to resend it and will notify you as soon as it was successful. Reason: {error}", val providerResendSuccess: String = "The resend was successful for all messages.", val providerReceiveError: String = "Could not receive messages through your configured provider. If this message does not appear again in the next few minutes, then retrying the receiving was successful. Reason: {error}" diff --git a/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandler.kt b/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandler.kt index 16137e2..ed222f8 100644 --- a/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandler.kt +++ b/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandler.kt @@ -18,6 +18,7 @@ import org.springframework.stereotype.Component class MessageToBotHandler( private val smsSendCommandHandler: SmsSendCommandHandler, private val smsInviteCommandHandler: SmsInviteCommandHandler, + private val smsAbortCommandHandler: SmsAbortCommandHandler, private val phoneNumberService: PhoneNumberService, private val smsBridgeProperties: SmsBridgeProperties, private val userService: MatrixUserService, @@ -39,8 +40,9 @@ class MessageToBotHandler( LOG.debug("ignore message from managed user") false } else if (body.startsWith("sms")) { - if (membershipSize > 2) { - LOG.debug("to many members in room form sms command") + // TODO is there a less hacky way for "sms abort"? Maybe completely switch to non-console? + if (membershipSize > 2 && !body.startsWith("sms abort")) { + LOG.debug("to many members in room for sms command") context.answer(smsBridgeProperties.templates.botTooManyMembers) true } else { @@ -63,6 +65,10 @@ class MessageToBotHandler( SmsInviteCommand( senderId, smsInviteCommandHandler + ), + SmsAbortCommand( + roomId, + smsAbortCommandHandler ) ) .parse(args) diff --git a/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommand.kt b/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommand.kt new file mode 100644 index 0000000..5d0823a --- /dev/null +++ b/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommand.kt @@ -0,0 +1,15 @@ +package net.folivo.matrix.bridge.sms.handler + +import com.github.ajalt.clikt.core.CliktCommand +import kotlinx.coroutines.runBlocking +import net.folivo.matrix.core.model.MatrixId.RoomId + +class SmsAbortCommand( + private val roomId: RoomId, + private val handler: SmsAbortCommandHandler +) : CliktCommand(name = "abort") { + + override fun run() { + echo(runBlocking { handler.handleCommand(roomId) }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandHandler.kt b/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandHandler.kt new file mode 100644 index 0000000..4a00ab1 --- /dev/null +++ b/src/main/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandHandler.kt @@ -0,0 +1,36 @@ +package net.folivo.matrix.bridge.sms.handler + +import net.folivo.matrix.bridge.sms.SmsBridgeProperties +import net.folivo.matrix.bridge.sms.SmsBridgeProperties.SmsBridgeTemplateProperties +import net.folivo.matrix.bridge.sms.message.MatrixMessageService +import net.folivo.matrix.core.model.MatrixId.RoomId +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + + +@Component +class SmsAbortCommandHandler( + private val messageService: MatrixMessageService, + smsBridgeProperties: SmsBridgeProperties, +) { + + private val templates: SmsBridgeTemplateProperties = smsBridgeProperties.templates + + companion object { + private val LOG = LoggerFactory.getLogger(this::class.java) + } + + suspend fun handleCommand( + roomId: RoomId + ): String { + return try { + messageService.deleteByRoomId(roomId) + templates.botSmsAbortSuccess + } catch (ex: Throwable) { + LOG.debug("got exception") + templates.botSmsAbortError + .replace("{error}", ex.message ?: "unknown") + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepository.kt b/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepository.kt index 41a3134..8ff2bfb 100644 --- a/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepository.kt +++ b/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepository.kt @@ -1,8 +1,10 @@ package net.folivo.matrix.bridge.sms.message +import net.folivo.matrix.core.model.MatrixId.RoomId import org.springframework.data.repository.kotlin.CoroutineCrudRepository import org.springframework.stereotype.Repository @Repository interface MatrixMessageRepository : CoroutineCrudRepository { + suspend fun deleteByRoomId(roomId: RoomId) } \ No newline at end of file diff --git a/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageService.kt b/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageService.kt index 82266c9..072e100 100644 --- a/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageService.kt +++ b/src/main/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageService.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toSet import net.folivo.matrix.bot.membership.MatrixMembershipService +import net.folivo.matrix.core.model.MatrixId.RoomId import net.folivo.matrix.core.model.MatrixId.UserId import net.folivo.matrix.core.model.events.m.room.message.NoticeMessageEventContent import net.folivo.matrix.core.model.events.m.room.message.TextMessageEventContent @@ -74,7 +75,7 @@ class MatrixMessageService( } } - internal suspend fun saveMessageAndReceivers(message: MatrixMessage, requiredMembers: Set) { + suspend fun saveMessageAndReceivers(message: MatrixMessage, requiredMembers: Set) { val savedMessage = messageRepository.save(message) requiredMembers.forEach { if (savedMessage.id != null) @@ -82,10 +83,14 @@ class MatrixMessageService( } } - internal suspend fun deleteMessage(message: MatrixMessage) { + suspend fun deleteMessage(message: MatrixMessage) { if (message.id != null) messageRepository.delete(message) } + suspend fun deleteByRoomId(roomId: RoomId) { + messageRepository.deleteByRoomId(roomId) + } + suspend fun processMessageQueue() { messageRepository.findAll() .collect { message -> diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index 7fa597c..4c23346 100644 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -13,3 +13,4 @@ \ \ __< \ \ __< \ \ \ \ \ \/\ \ \ \ \__ \ \ \ __\ \ \_____\ \ \_\ \_\ \ \_\ \ \____- \ \_____\ \ \_____\ \/_____/ \/_/ /_/ \/_/ \/____/ \/_____/ \/_____/ + diff --git a/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandlerTest.kt b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandlerTest.kt index 5849e1b..e22a0b3 100644 --- a/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandlerTest.kt +++ b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/MessageToBotHandlerTest.kt @@ -18,6 +18,7 @@ private fun testBody(): DescribeSpec.() -> Unit { return { val smsSendCommandHandlerMock: SmsSendCommandHandler = mockk() val smsInviteCommandHandlerMock: SmsInviteCommandHandler = mockk() + val smsAbortCommandHandlerMock: SmsAbortCommandHandler = mockk() val phoneNumberServiceMock: PhoneNumberService = mockk() val smsBridgePropertiesMock: SmsBridgeProperties = mockk { every { templates.botHelp }.returns("help") @@ -30,6 +31,7 @@ private fun testBody(): DescribeSpec.() -> Unit { val cut = MessageToBotHandler( smsSendCommandHandlerMock, smsInviteCommandHandlerMock, + smsAbortCommandHandlerMock, phoneNumberServiceMock, smsBridgePropertiesMock, userServiceMock, @@ -57,9 +59,11 @@ private fun testBody(): DescribeSpec.() -> Unit { } } describe("to many members in room") { - it("should warn user and return true") { - coEvery { userServiceMock.getOrCreateUser(senderId) }.returns(MatrixUser(senderId)) + beforeTest { coEvery { membershipServiceMock.getMembershipsSizeByRoomId(roomId) }.returns(3L) + coEvery { userServiceMock.getOrCreateUser(senderId) }.returns(MatrixUser(senderId)) + } + it("should warn user and return true") { cut.handleMessage(roomId, "sms", senderId, contextMock).shouldBeTrue() coVerifyAll { smsSendCommandHandlerMock wasNot Called @@ -67,6 +71,20 @@ private fun testBody(): DescribeSpec.() -> Unit { contextMock.answer("toMany") } } + it("should accept sms abort command") { + coEvery { smsAbortCommandHandlerMock.handleCommand(any()) } + .returns("aborted") + cut.handleMessage( + roomId, + "sms abort", + senderId, + contextMock + ).shouldBeTrue() + + coVerify(exactly = 1) { + contextMock.answer("aborted") + } + } } describe("valid sms command") { beforeTest { diff --git a/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandHandlerTest.kt b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandHandlerTest.kt new file mode 100644 index 0000000..4b4ed30 --- /dev/null +++ b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandHandlerTest.kt @@ -0,0 +1,37 @@ +package net.folivo.matrix.bridge.sms.handler + +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe +import io.mockk.* +import net.folivo.matrix.bridge.sms.SmsBridgeProperties +import net.folivo.matrix.bridge.sms.message.MatrixMessageService +import net.folivo.matrix.core.model.MatrixId.RoomId + +class SmsAbortCommandHandlerTest : DescribeSpec(testBody()) + +private fun testBody(): DescribeSpec.() -> Unit { + return { + val messageServiceMock: MatrixMessageService = mockk() + val smsBridgePropertiesMock: SmsBridgeProperties = mockk { + every { templates.botSmsAbortSuccess }.returns("success") + every { templates.botSmsAbortError }.returns("error:{error}") + } + val cut = SmsAbortCommandHandler(messageServiceMock, smsBridgePropertiesMock) + + val roomId = RoomId("room", "server") + + describe(SmsAbortCommandHandler::handleCommand.name) { + it("should delete messages") { + coEvery { messageServiceMock.deleteByRoomId(roomId) } just Runs + cut.handleCommand(roomId).shouldBe("success") + coVerify { messageServiceMock.deleteByRoomId(roomId) } + } + it("should catch error") { + coEvery { messageServiceMock.deleteByRoomId(roomId) }.throws(RuntimeException("meteor")) + cut.handleCommand(roomId).shouldBe("error:meteor") + } + } + + afterTest { clearMocks(messageServiceMock) } + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandTest.kt b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandTest.kt new file mode 100644 index 0000000..969b107 --- /dev/null +++ b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsAbortCommandTest.kt @@ -0,0 +1,33 @@ +package net.folivo.matrix.bridge.sms.handler + +import com.github.ajalt.clikt.core.context +import com.github.ajalt.clikt.output.CliktConsole +import io.kotest.core.spec.style.DescribeSpec +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import net.folivo.matrix.core.model.MatrixId.RoomId + +class SmsAbortCommandTest : DescribeSpec(testBody()) + +private fun testBody(): DescribeSpec.() -> Unit { + return { + val roomId = RoomId("room", "server") + val handlerMock: SmsAbortCommandHandler = mockk() + val consoleMock: CliktConsole = mockk(relaxed = true) + val cut = SmsAbortCommand(roomId, handlerMock) + cut.context { console = consoleMock } + + describe("run command") { + it("should run command") { + coEvery { handlerMock.handleCommand(roomId) }.returns("answer") + cut.parse(listOf()) + coVerify { handlerMock.handleCommand(roomId) } + coVerify { consoleMock.print("answer", false) } + } + } + + afterTest { clearMocks(consoleMock) } + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsInviteCommandTest.kt b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsInviteCommandTest.kt index 6fb9d7a..64582b7 100644 --- a/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsInviteCommandTest.kt +++ b/src/test/kotlin/net/folivo/matrix/bridge/sms/handler/SmsInviteCommandTest.kt @@ -25,19 +25,25 @@ private fun testBody(): DescribeSpec.() -> Unit { cut.context { console = consoleMock } describe("alias was given") { - coEvery { handlerMock.handleCommand(sender, roomAliasId) }.returns("answer") - cut.parse(listOf("#alias:server")) - coVerify { handlerMock.handleCommand(sender, roomAliasId) } - coVerify { consoleMock.print("answer", false) } + it("should run") { + coEvery { handlerMock.handleCommand(sender, roomAliasId) }.returns("answer") + cut.parse(listOf("#alias:server")) + coVerify { handlerMock.handleCommand(sender, roomAliasId) } + coVerify { consoleMock.print("answer", false) } + } } describe("alias was not given") { - shouldThrow { - cut.parse(listOf()) + it("should throw missing argument") { + shouldThrow { + cut.parse(listOf()) + } } } describe("alias was no alias") { - shouldThrow { - cut.parse(listOf("!noAlias:server")) + it("should throw bad parameter") { + shouldThrow { + cut.parse(listOf("!noAlias:server")) + } } } diff --git a/src/test/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepositoryTest.kt b/src/test/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepositoryTest.kt new file mode 100644 index 0000000..d3c550a --- /dev/null +++ b/src/test/kotlin/net/folivo/matrix/bridge/sms/message/MatrixMessageRepositoryTest.kt @@ -0,0 +1,52 @@ +package net.folivo.matrix.bridge.sms.message + +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.reactive.awaitFirst +import net.folivo.matrix.bot.config.MatrixBotDatabaseAutoconfiguration +import net.folivo.matrix.bot.room.MatrixRoom +import net.folivo.matrix.bridge.sms.SmsBridgeDatabaseConfiguration +import net.folivo.matrix.core.model.MatrixId.RoomId +import org.springframework.boot.autoconfigure.ImportAutoConfiguration +import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate +import org.springframework.data.r2dbc.core.delete + +@DataR2dbcTest +@ImportAutoConfiguration(value = [MatrixBotDatabaseAutoconfiguration::class, SmsBridgeDatabaseConfiguration::class]) +class MatrixMessageRepositoryTest( + cut: MatrixMessageRepository, + db: R2dbcEntityTemplate +) : DescribeSpec(testBody(cut, db)) + +private fun testBody(cut: MatrixMessageRepository, db: R2dbcEntityTemplate): DescribeSpec.() -> Unit { + return { + val room1 = RoomId("room1", "server") + val room2 = RoomId("room2", "server") + + val message1 = MatrixMessage(room1, "some body 1") + val message2 = MatrixMessage(room1, "some body 2") + val message3 = MatrixMessage(room2, "some body 3") + + beforeSpec { + db.insert(MatrixRoom(room1)).awaitFirst() + db.insert(MatrixRoom(room2)).awaitFirst() + db.insert(message1).awaitFirst() + db.insert(message2).awaitFirst() + db.insert(message3).awaitFirst() + } + + describe(MatrixMessageRepository::deleteByRoomId.name) { + it("should delete all matching rooms") { + cut.count().shouldBe(3) + cut.deleteByRoomId(room1) + cut.count().shouldBe(1) + } + } + + afterSpec { + db.delete().all().awaitFirst() + db.delete().all().awaitFirst() + } + } +} \ No newline at end of file