Skip to content

Commit 340a207

Browse files
authored
release: 0.7.7 (#83)
2 parents 0dca61b + 7da2e58 commit 340a207

File tree

11 files changed

+128
-8
lines changed

11 files changed

+128
-8
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ jobs:
7373
"INTERNAL_SECRET=${{ secrets.INTERNAL_SECRET }}"
7474
"SLACK_TOKEN=${{ secrets.SLACK_TOKEN }}"
7575
"JWT_KEY=${{ secrets.JWT_KEY }}"
76+
"TEST_SECRET=${{ secrets.TEST_SECRET }}"
7677
7778
deploy:
7879
needs: build

Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ARG GH_OAUTH_SECRET
1111
ARG INTERNAL_SECRET
1212
ARG SLACK_TOKEN
1313
ARG JWT_KEY
14+
ARG TEST_SECRET
1415

1516
ARG JAR_FILE=./build/libs/*.jar
1617
COPY ${JAR_FILE} gitanimals-api.jar
@@ -25,7 +26,8 @@ ENV db_url=${DB_URL} \
2526
oauth_client_secret_github=${GH_OAUTH_SECRET} \
2627
internal_secret=${INTERNAL_SECRET} \
2728
slack_token=${SLACK_TOKEN} \
28-
jwt_key=${JWT_KEY}
29+
jwt_key=${JWT_KEY} \
30+
test_secret=${TEST_SECRET}
2931

3032
ENTRYPOINT java -Djava.net.preferIPv4Stack=true -jar gitanimals-api.jar \
3133
--spring.datasource.url=${db_url} \
@@ -38,4 +40,5 @@ ENTRYPOINT java -Djava.net.preferIPv4Stack=true -jar gitanimals-api.jar \
3840
--oauth.client.secret.github=${oauth_client_secret_github} \
3941
--internal.secret=${internal_secret} \
4042
--slack.token=${slack_token} \
41-
--jwt.key=${jwt_key}
43+
--jwt.key=${jwt_key} \
44+
--test.secret=${test_secret}

src/main/kotlin/org/gitanimals/auction/app/BuyProductFacade.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.gitanimals.auction.app
22

3+
import org.gitanimals.auction.app.event.InboxInputEvent
34
import org.gitanimals.auction.domain.Product
45
import org.gitanimals.auction.domain.ProductService
56
import org.rooftop.netx.api.Orchestrator
67
import org.rooftop.netx.api.OrchestratorFactory
8+
import org.rooftop.netx.api.SagaManager
79
import org.springframework.stereotype.Service
810
import java.util.*
911

@@ -12,19 +14,30 @@ class BuyProductFacade(
1214
private val renderApi: RenderApi,
1315
private val identityApi: IdentityApi,
1416
private val productService: ProductService,
17+
private val sagaManager: SagaManager,
1518
orchestratorFactory: OrchestratorFactory,
1619
) {
1720

1821
private lateinit var orchestrator: Orchestrator<Long, Product>
1922

2023
fun buyProduct(token: String, productId: Long): Product {
21-
return orchestrator.sagaSync(
24+
val result = orchestrator.sagaSync(
2225
productId,
2326
mapOf(
2427
"token" to token,
2528
"idempotencyKey" to UUID.randomUUID().toString()
2629
)
2730
).decodeResultOrThrow(Product::class)
31+
32+
publishSoldOutEvent(result)
33+
34+
return result
35+
}
36+
37+
private fun publishSoldOutEvent(product: Product) {
38+
val user = identityApi.getUserById(product.sellerId)
39+
40+
sagaManager.startSync(InboxInputEvent.createSoldOutInbox(user.username, product))
2841
}
2942

3043
init {

src/main/kotlin/org/gitanimals/auction/app/IdentityApi.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ interface IdentityApi {
44

55
fun getUserByToken(token: String): UserResponse
66

7+
fun getUserById(userId: Long): UserResponse
8+
79
fun decreasePoint(token: String, idempotencyKey: String, point: String)
810

911
fun increasePoint(token: String, idempotencyKey: String, point: String)
@@ -12,6 +14,7 @@ interface IdentityApi {
1214

1315
fun decreasePointById(userId: Long, idempotencyKey: String, point: String)
1416

17+
1518
data class UserResponse(
1619
val id: String,
1720
val username: String,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.gitanimals.auction.app.event
2+
3+
import org.gitanimals.auction.domain.Product
4+
import java.time.Instant
5+
6+
data class InboxInputEvent(
7+
val inboxData: InboxData,
8+
val publisher: Publisher,
9+
) {
10+
11+
data class Publisher(
12+
val publisher: String,
13+
val publishedAt: Instant,
14+
)
15+
16+
data class InboxData(
17+
val userId: Long,
18+
val type: String = "INBOX",
19+
val title: String,
20+
val body: String,
21+
val image: String,
22+
val redirectTo: String,
23+
)
24+
25+
companion object {
26+
fun createSoldOutInbox(sellerName: String, product: Product): InboxInputEvent {
27+
return InboxInputEvent(
28+
inboxData = InboxData(
29+
userId = product.sellerId,
30+
title = "펫이 판매되었어요",
31+
body = "$sellerName 님의 펫이 ${product.getPrice()}원에 판매되었습니다.",
32+
image = "https://avatars.githubusercontent.com/u/171903401?s=200&v=4",
33+
redirectTo = "NO_REDIRECT",
34+
),
35+
publisher = Publisher(
36+
publisher = "AUCTION",
37+
publishedAt = Instant.now(),
38+
)
39+
)
40+
}
41+
}
42+
}

src/main/kotlin/org/gitanimals/auction/infra/RestIdentityApi.kt

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,28 @@ class RestIdentityApi(
3131
}
3232
}
3333

34+
override fun getUserById(userId: Long): IdentityApi.UserResponse {
35+
return restClient.get()
36+
.uri("/users/$userId")
37+
.header(INTERNAL_SECRET_KEY, internalSecret)
38+
.exchange { _, response ->
39+
runCatching {
40+
response.bodyTo(IdentityApi.UserResponse::class.java)
41+
}.getOrElse {
42+
if (response.statusCode.is4xxClientError) {
43+
throw IllegalArgumentException("Cannot find user by id \"$userId\"")
44+
}
45+
46+
throw IllegalStateException(it)
47+
}
48+
}
49+
}
50+
3451
override fun decreasePoint(token: String, idempotencyKey: String, point: String) {
3552
return restClient.post()
3653
.uri("/internals/users/points/decreases?point=$point&idempotency-key=$idempotencyKey")
3754
.header(HttpHeaders.AUTHORIZATION, token)
38-
.header("Internal-Secret", internalSecret)
55+
.header(INTERNAL_SECRET_KEY, internalSecret)
3956
.exchange { _, response ->
4057
if (response.statusCode.is2xxSuccessful) {
4158
return@exchange
@@ -50,7 +67,7 @@ class RestIdentityApi(
5067
return restClient.post()
5168
.uri("/internals/users/points/increases?point=$point&idempotency-key=$idempotencyKey")
5269
.header(HttpHeaders.AUTHORIZATION, token)
53-
.header("Internal-Secret", internalSecret)
70+
.header(INTERNAL_SECRET_KEY, internalSecret)
5471
.exchange { _, response ->
5572
if (response.statusCode.is2xxSuccessful) {
5673
return@exchange
@@ -64,7 +81,7 @@ class RestIdentityApi(
6481
override fun decreasePointById(userId: Long, idempotencyKey: String, point: String) {
6582
return restClient.post()
6683
.uri("/internals/users/points/decreases/$userId?point=$point&idempotency-key=$idempotencyKey")
67-
.header("Internal-Secret", internalSecret)
84+
.header(INTERNAL_SECRET_KEY, internalSecret)
6885
.exchange { _, response ->
6986
if (response.statusCode.is2xxSuccessful) {
7087
return@exchange
@@ -78,7 +95,7 @@ class RestIdentityApi(
7895
override fun increasePointById(userId: Long, idempotencyKey: String, point: String) {
7996
return restClient.post()
8097
.uri("/internals/users/points/increases/$userId?point=$point&idempotency-key=$idempotencyKey")
81-
.header("Internal-Secret", internalSecret)
98+
.header(INTERNAL_SECRET_KEY, internalSecret)
8299
.exchange { _, response ->
83100
if (response.statusCode.is2xxSuccessful) {
84101
return@exchange
@@ -88,4 +105,8 @@ class RestIdentityApi(
88105
)
89106
}
90107
}
108+
109+
private companion object {
110+
private const val INTERNAL_SECRET_KEY = "Internal-Secret"
111+
}
91112
}

src/main/kotlin/org/gitanimals/identity/controller/UserController.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class UserController(
1414
private val userService: UserService,
1515
) {
1616

17+
@ResponseStatus(HttpStatus.OK)
1718
@GetMapping("/users")
1819
fun getUserByToken(
1920
@RequestHeader(HttpHeaders.AUTHORIZATION) token: String,
@@ -22,6 +23,15 @@ class UserController(
2223
return UserResponse.from(user)
2324
}
2425

26+
@ResponseStatus(HttpStatus.OK)
27+
@GetMapping("/internals/users/{user-id}")
28+
fun getUserById(
29+
@PathVariable("user-id") userId: Long,
30+
): UserResponse {
31+
val user = userService.getUserById(userId)
32+
return UserResponse.from(user)
33+
}
34+
2535
@ResponseStatus(HttpStatus.OK)
2636
@PostMapping("/internals/users/points/decreases")
2737
fun decreaseUserPoints(
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.gitanimals.inbox.controller
2+
3+
import org.gitanimals.inbox.infra.event.InboxInputEvent
4+
import org.rooftop.netx.api.SagaManager
5+
import org.springframework.beans.factory.annotation.Value
6+
import org.springframework.http.HttpStatus
7+
import org.springframework.web.bind.annotation.*
8+
9+
@RestController
10+
class InboxTestController(
11+
private val sagaManager: SagaManager,
12+
@Value("\$test.secret") private val testSecret: String,
13+
) {
14+
15+
@PostMapping("test/input-inbox")
16+
@ResponseStatus(HttpStatus.OK)
17+
fun inputInbox(
18+
@RequestHeader("Test-Secret") testSecret: String,
19+
@RequestBody inboxInputEvent: InboxInputEvent,
20+
) {
21+
require(testSecret == this.testSecret) { "Invalid testSecret" }
22+
sagaManager.startSync(inboxInputEvent)
23+
}
24+
}

src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ sentry.dsn=https://d4fd0e848b11f200766d8cd577159b04@o4505051656486912.ingest.us.
3232
sentry.traces-sample-rate=1.0
3333

3434
internal.secret=
35+
test.secret=
3536

3637
spring.application.name=api.gitanimals
3738
management.endpoints.web.exposure.include=prometheus

src/test/kotlin/org/gitanimals/auction/app/BuyProductFacadeTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ internal class BuyProductFacadeTest(
5858
mockUserServer.enqueue200()
5959
mockUserServer.enqueue200()
6060
mockRenderServer.enqueue200()
61+
mockUserServer.enqueue200(userResponse)
6162
it("product 구매에 성공한다.") {
6263
val response = buyProductFacade.buyProduct(VALID_TOKEN, product.id)
6364

6465
eventually(5.seconds) {
65-
sagaCapture.startCountShouldBe(1)
66+
sagaCapture.startCountShouldBe(2)
6667
sagaCapture.joinCountShouldBe(3)
6768
sagaCapture.commitCountShouldBe(1)
6869
sagaCapture.rollbackCountShouldBe(0)

0 commit comments

Comments
 (0)