Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class NotificationEventListener(
bodyArgs = arrayOf(event.senderNickname),
deepLinkParams = mapOf(
"goalId" to event.goalId.toString(),
"date" to LocalDate.now().toString(),
"date" to event.verificationDate.toString(),
),
)
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.yapp.love.application.notification.event

import java.time.LocalDate

data class PartnerConnectedEvent(
val targetUserId: Long,
val senderUserId: Long,
Expand All @@ -10,6 +12,7 @@ data class PokedEvent(
val senderNickname: String,
val goalId: Long,
val goalName: String,
val verificationDate: LocalDate,
)

data class PhotologCreatedEvent(
Expand All @@ -28,7 +31,7 @@ data class ReactionCreatedEvent(
val reactorUserId: Long,
val photologOwnerId: Long,
val goalId: Long,
val verificationDate: java.time.LocalDate,
val verificationDate: LocalDate,
)

data class DailyGoalAchievedEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

private val logger = KotlinLogging.logger {}
import java.time.LocalDate

@Service
class PokeService(
Expand All @@ -28,6 +27,7 @@ class PokeService(
fun poke(
senderId: Long,
goalId: Long,
verificationDate: LocalDate,
) {
// 1. 커플 정보 확인
val coupleInfo =
Expand Down Expand Up @@ -71,6 +71,7 @@ class PokeService(
senderNickname = senderNickname,
goalId = goalId,
goalName = goal.name,
verificationDate = verificationDate,
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class NotificationEventListenerTest : DescribeSpec({
senderNickname = "철수",
goalId = 10L,
goalName = "운동하기",
verificationDate = LocalDate.of(2026, 5, 18),
)

listener.handlePoked(event)
Expand All @@ -94,13 +95,19 @@ class NotificationEventListenerTest : DescribeSpec({
type = NotificationType.POKE,
titleArgs = arrayOf("철수", "운동하기"),
bodyArgs = arrayOf("철수"),
deepLinkParams = mapOf("goalId" to "10", "date" to LocalDate.now().toString()),
deepLinkParams = mapOf("goalId" to "10", "date" to "2026-05-18"),
)
}
}

it("예외 발생 시 메서드가 정상 종료된다") {
val event = PokedEvent(targetUserId = 2L, senderNickname = "철수", goalId = 10L, goalName = "운동하기")
val event = PokedEvent(
targetUserId = 2L,
senderNickname = "철수",
goalId = 10L,
goalName = "운동하기",
verificationDate = LocalDate.of(2026, 5, 18),
)
every { notificationService.sendNotification(any(), any(), any(), any(), any()) } throws RuntimeException("DB 오류")

listener.handlePoked(event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class PokeServiceTest : DescribeSpec({
val receiverId = 2L
val coupleId = 100L
val goalId = 10L
val verificationDate = LocalDate.of(2026, 5, 18)

val coupleInfo = CoupleInfo(
id = coupleId,
Expand Down Expand Up @@ -78,7 +79,7 @@ class PokeServiceTest : DescribeSpec({
}

it("찌르기가 저장된다") {
pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)

verify {
pokeRepository.save(match<Poke> {
Expand All @@ -90,7 +91,7 @@ class PokeServiceTest : DescribeSpec({
}

it("상대방에게 PokedEvent가 발행된다") {
pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)

verify {
eventPublisher.publishEvent(
Expand All @@ -99,6 +100,7 @@ class PokeServiceTest : DescribeSpec({
senderNickname = "철수",
goalId = goalId,
goalName = "운동하기",
verificationDate = verificationDate,
)
)
}
Expand All @@ -107,7 +109,7 @@ class PokeServiceTest : DescribeSpec({
it("닉네임이 없으면 '상대방'으로 이벤트가 발행된다") {
every { userAdditionInfoRepository.findByUserId(senderId) } returns null

pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)

verify {
eventPublisher.publishEvent(
Expand All @@ -116,6 +118,7 @@ class PokeServiceTest : DescribeSpec({
senderNickname = "상대방",
goalId = goalId,
goalName = "운동하기",
verificationDate = verificationDate,
)
)
}
Expand All @@ -127,7 +130,7 @@ class PokeServiceTest : DescribeSpec({
every { coupleInfoRepository.findByUserId(senderId) } returns null

shouldThrow<GlobalException> {
pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)
}
}
}
Expand All @@ -138,7 +141,7 @@ class PokeServiceTest : DescribeSpec({
every { goalRepository.findById(goalId) } returns null

shouldThrow<GlobalException> {
pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)
}
}
}
Expand All @@ -161,7 +164,7 @@ class PokeServiceTest : DescribeSpec({
every { goalRepository.findById(goalId) } returns otherGoal

shouldThrow<GlobalException> {
pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)
}
}
}
Expand All @@ -184,7 +187,7 @@ class PokeServiceTest : DescribeSpec({
every { goalRepository.findById(goalId) } returns completedGoal

shouldThrow<GlobalException> {
pokeService.poke(senderId, goalId)
pokeService.poke(senderId, goalId, verificationDate)
}
}
}
Expand Down
35 changes: 34 additions & 1 deletion love/web/src/main/kotlin/com/yapp/love/web/poke/PokeApiSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,43 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Operation(
summary = "찌르기",
description = "상대방을 찔러서 목표 인증을 독려합니다.",
description = "상대방을 찔러서 특정 날짜의 목표 인증을 독려합니다. " +
"date 필드로 어떤 날짜의 인증을 독려할지 명시하며, 알림 딥링크에 그대로 전달됩니다. " +
"body를 생략하거나 date가 null이면 오늘 날짜로 처리됩니다(구버전 클라이언트 호환).",
requestBody = RequestBody(
required = false,
content = [
Content(
mediaType = "application/json",
schema = Schema(implementation = PokeRequest::class),
examples = [
ExampleObject(
name = "오늘 인증 독려",
summary = "오늘 날짜의 목표 인증을 찌릅니다.",
value = """{"date": "2026-05-18"}""",
),
ExampleObject(
name = "과거 날짜 인증 독려",
summary = "캘린더에서 과거 미달성 날짜를 보고 찌릅니다.",
value = """{"date": "2026-05-15"}""",
),
ExampleObject(
name = "구버전 클라이언트 (body 생략)",
summary = "body 없이 호출하면 서버가 오늘 날짜로 처리합니다.",
value = "",
),
],
),
],
),
)
@ApiResponses(
value = [
Expand All @@ -32,6 +61,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses
name = "진행중이지 않은 목표",
value = """{"status": 400, "code": "G4000", "message": "진행중이지 않은 목표는 찌를 수 없습니다."}""",
),
ExampleObject(
name = "JSON 형식 오류",
value = """{"status": 400, "code": "G4002", "message": "JSON 형식이 올바르지 않습니다."}""",
),
],
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package com.yapp.love.web.poke
import com.yapp.love.application.poke.PokeService
import com.yapp.love.web.auth.AuthUser
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDate

@Tag(name = "Poke", description = "찌르기 API")
@RestController
Expand All @@ -20,12 +23,18 @@ class PokeController(
fun poke(
@AuthUser userId: Long,
@PathVariable goalId: Long,
@RequestBody(required = false) request: PokeRequest?,
): ResponseEntity<PokeResponse> {
pokeService.poke(userId, goalId)
pokeService.poke(userId, goalId, request?.date ?: LocalDate.now())
return ResponseEntity.ok(PokeResponse(message = "찌르기를 보냈습니다."))
}
}

data class PokeRequest(
@field:DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
val date: LocalDate? = null,
)

data class PokeResponse(
val message: String,
)
Loading