Skip to content

Commit

Permalink
Merge pull request #68 from KjellBerlin/Introduce-slack-message-sending
Browse files Browse the repository at this point in the history
Introduce Slack message sending
  • Loading branch information
KjellBerlin authored Aug 30, 2024
2 parents eab5d3e + 1d41f1f commit b61ba58
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 11 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ dependencies {
// Mollie
implementation("be.woutschoovaerts:mollie:4.3.0")

// Slack
implementation("com.slack.api:slack-api-model-kotlin-extension:1.42.0")
implementation("com.slack.api:slack-api-client-kotlin-extension:1.42.0")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.mockk:mockk:1.13.11")
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/com/carbonara/core/address/Address.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,13 @@ data class Address(
fun addressPropertiesComplete(): Boolean {
return street != null && streetNumber != null && postCode != null && city != null && country != null
}

override fun toString(): String {
return "$street $streetNumber, $postCode $city"
}

fun createGoogleMapsLink(): String {
return "https://www.google.com/maps/place/?q=place_id:$googlePlaceId"
}
}

12 changes: 10 additions & 2 deletions src/main/kotlin/com/carbonara/core/order/OrderService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.carbonara.core.payment.MolliePaymentService
import com.carbonara.core.payment.PaymentException
import com.carbonara.core.product.ProductDao
import com.carbonara.core.product.ProductService
import com.carbonara.core.slack.SlackMessageService
import kotlinx.coroutines.reactor.awaitSingleOrNull
import mu.KotlinLogging
import org.bson.types.ObjectId
Expand All @@ -16,7 +17,8 @@ import java.time.OffsetDateTime
class OrderService(
private val orderRepository: OrderRepository,
private val productService: ProductService,
private val molliePaymentService: MolliePaymentService
private val molliePaymentService: MolliePaymentService,
private val slackMessageService: SlackMessageService
) {

suspend fun createOrder(
Expand Down Expand Up @@ -96,7 +98,13 @@ class OrderService(
if (order.paymentDetails.internalPaymentStatus != InternalPaymentStatus.PAID) {
updateOrderToPaid(order, paymentStatus)

// TODO: trigger delivery
slackMessageService.sendNewOrderMessage(
customerName = order.userName,
orderId = order.orderId.toString(),
address = order.deliveryAddress.toString(),
googleMapsLink = order.deliveryAddress.createGoogleMapsLink(),
productNames = order.products.map { it.productName }
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.carbonara.core.payment

import com.carbonara.core.order.OrderService
import mu.KotlinLogging
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController

Expand All @@ -12,10 +13,11 @@ class MolliePaymentWebhookController(

// Potential dos attack endpoint, introduce rate limiting

@PostMapping("/mollie-payment-status", "application/x-www-form-urlencoded")
suspend fun handleMollieWebhook(requestBody: MollieWebhookRequestBody) {
@PostMapping("/mollie-payment-status", consumes = ["application/x-www-form-urlencoded"])
suspend fun handleMollieWebhook(requestBody: MollieWebhookRequestBody): ResponseEntity<Void> {
log.info("Webhook received for paymentId: {}", requestBody.id)
orderService.handleOrderPayment(requestBody.id)
return ResponseEntity.ok().build()
}

companion object {
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/com/carbonara/core/slack/SlackException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.carbonara.core.slack

class SlackException(message: String) : RuntimeException(message)
76 changes: 76 additions & 0 deletions src/main/kotlin/com/carbonara/core/slack/SlackMessageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.carbonara.core.slack

import com.slack.api.Slack
import com.slack.api.methods.kotlin_extension.request.chat.blocks
import mu.KotlinLogging
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service

@Service
class SlackMessageService {

@Value("\${slack.token}")
lateinit var slackToken: String

@Value("\${slack.channel}")
lateinit var slackChannel: String

fun sendNewOrderMessage(
customerName: String,
orderId: String,
address: String,
googleMapsLink: String,
productNames: List<String>
) {
val slack = Slack.getInstance()
val response = slack.methods(slackToken).chatPostMessage { req -> req
.channel(slackChannel)
.blocks {
section {
fields {
markdownText("*Customer Name:*\n$customerName")
markdownText("*OrderId:*\n$orderId")
}
}
section {
fields {
markdownText("*Address:*\n$address\n$googleMapsLink")
markdownText("*Products:*\n${productNames.joinToString(", ")}")
}
}
actions {
button {
text("ACCEPT", emoji = true)
style("primary")
value("processing_order")
}
button {
text("DELIVERY_IN_PROGRESS", emoji = true)
style("primary")
value("delivery_in_progress")
}
button {
text("DELIVERED", emoji = true)
style("primary")
value("delivered")
}
button {
text("CANCELLED", emoji = true)
style("danger")
value("cancelled")
}
}
divider()
}
}

if (!response.isOk) {
log.error("Slack API error: ${response.error}")
throw SlackException("Failed to send slack message for orderId: $orderId. Error: ${response.error}")
}
}

companion object {
private val log = KotlinLogging.logger {}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.carbonara.core.delivery
package com.carbonara.core.slack

import com.carbonara.core.order.OrderService
import mu.KotlinLogging
Expand All @@ -12,6 +12,8 @@ class SlackDeliveryWebhookController(

// Potential dos attack endpoint, introduce rate limiting

// TODO: Handle webhook

@PostMapping("/slack-delivery-status", "application/x-www-form-urlencoded")
suspend fun handleSlackWebhook(requestBody: SlackWebhookRequestBody) {
log.info("--Start Slack--")
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-staging.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ mollie.redirectUrl=carbonara://order-status
mollie.paymentWebhookUrl=https://carbonara-core-mkvkriomda-ew.a.run.app/mollie-payment-status

google.apiKey=${sm://projects/897213585789/secrets/google-apiKey}

slack.token=${sm://projects/897213585789/secrets/slack-token}
slack.channel=C07KMKK3ZDX
27 changes: 22 additions & 5 deletions src/test/kotlin/com/carbonara/core/address/AddressTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,36 @@ package com.carbonara.core.address

import com.carbonara.core.serviceAvailability.ServiceAvailabilityServiceTest.Companion.GOOGLE_PLACE_ID
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class AddressTest {

@Nested
inner class AddressPropertiesCompleteTests {

@Test
fun `Happy case - address complete`() {
assertEquals(true, ADDRESS.addressPropertiesComplete())
}

@Test
fun `Happy case - address incomplete`() {
val address = ADDRESS.copy(streetNumber = null)
assertEquals(false, address.addressPropertiesComplete())
}
}

@Test
fun `Happy case - address complete`() {
assertEquals(true, ADDRESS.addressPropertiesComplete())
fun `Happy case - address to string`() {
val result = ADDRESS.toString()
assertEquals("Baker Street 221b, 12345 London", result)
}

@Test
fun `Happy case - address incomplete`() {
val address = ADDRESS.copy(streetNumber = null)
assertEquals(false, address.addressPropertiesComplete())
fun `Happy case - googleMapsLink`() {
val result = ADDRESS.createGoogleMapsLink()
assertEquals("https://www.google.com/maps/place/?q=place_id:$GOOGLE_PLACE_ID", result)
}

companion object {
Expand Down
6 changes: 5 additions & 1 deletion src/test/kotlin/com/carbonara/core/order/OrderServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.carbonara.core.payment.MolliePaymentService
import com.carbonara.core.payment.PaymentException
import com.carbonara.core.product.ProductDao
import com.carbonara.core.product.ProductService
import com.carbonara.core.slack.SlackMessageService
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
Expand All @@ -31,16 +32,19 @@ class OrderServiceTest {
private lateinit var orderRepository: OrderRepository
private lateinit var productService: ProductService
private lateinit var molliePaymentService: MolliePaymentService
private lateinit var slackMessageService: SlackMessageService

@BeforeEach
fun init() {
orderRepository = mockk()
productService = mockk()
molliePaymentService = mockk()
orderService = OrderService(orderRepository, productService, molliePaymentService)
slackMessageService = mockk()
orderService = OrderService(orderRepository, productService, molliePaymentService, slackMessageService)

mockkStatic(OffsetDateTime::class)
every { OffsetDateTime.now() } returns TIME
every { slackMessageService.sendNewOrderMessage(any(), any(), any(), any(), any()) } returns Unit
}

@Nested
Expand Down

0 comments on commit b61ba58

Please sign in to comment.