Skip to content

Commit 8b202f4

Browse files
authored
�feat: 알림기능을 개발합니다. (#123)
* [BOOK-313] feat(apis): FCM 토큰 및 알림 설정 API 추가 (#122) * [BOOK-313] feat(domain): 유저 마지막 활동 시간, 알림 설정, FCM 토큰 필드 추가 (#122) * [BOOK-313] feat(user): 유저 활동내역 업데이트 기능 추가 (#122) * [BOOK-313] feat(batch): 비활성 유저 알림 기능 추가 (#122) * [BOOK-313] fix: batch: delete test code (#122) * [BOOK-313] refactor: apis - 컨벤션에 맞게 리팩토링 (#122) * [BOOK-313] feat: batch - 알림 title을 수정합니다 (#122)
1 parent 7dad91f commit 8b202f4

File tree

39 files changed

+1019
-23
lines changed

39 files changed

+1019
-23
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,6 @@ node_modules/
175175
# secret
176176
application-*-secret.properties
177177
secret/
178+
**/reed-firebase-adminsdk.json
178179

179180
# End of https://www.toptal.com/developers/gitignore/api/intellij,kotlin,gradle

apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@ import org.yapp.apis.book.exception.UserBookException
1212
import org.yapp.domain.userbook.BookStatus
1313
import org.yapp.domain.userbook.UserBookDomainService
1414
import org.yapp.domain.userbook.UserBookSortType
15+
import org.yapp.domain.user.UserDomainService
1516
import org.yapp.globalutils.annotation.ApplicationService
1617
import java.util.*
1718

1819
@ApplicationService
1920
class UserBookService(
20-
private val userBookDomainService: UserBookDomainService
21+
private val userBookDomainService: UserBookDomainService,
22+
private val userDomainService: UserDomainService
2123
) {
2224
fun upsertUserBook(@Valid upsertUserBookRequest: UpsertUserBookRequest): UserBookResponse {
25+
val userId = upsertUserBookRequest.validUserId()
2326
val userBookInfoVO = userBookDomainService.upsertUserBook(
24-
upsertUserBookRequest.validUserId(),
27+
userId,
2528
upsertUserBookRequest.validBookId(),
2629
upsertUserBookRequest.validBookIsbn13(),
2730
upsertUserBookRequest.validBookTitle(),
@@ -30,6 +33,10 @@ class UserBookService(
3033
upsertUserBookRequest.validBookCoverImageUrl(),
3134
upsertUserBookRequest.validStatus()
3235
)
36+
37+
// Update user's lastActivity when a book is registered
38+
userDomainService.updateLastActivity(userId)
39+
3340
return UserBookResponse.from(userBookInfoVO)
3441
}
3542

apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import org.yapp.apis.readingrecord.dto.request.UpdateReadingRecordRequest
77
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
88
import org.yapp.domain.readingrecord.ReadingRecordDomainService
99
import org.yapp.domain.readingrecord.ReadingRecordSortType
10+
import org.yapp.domain.user.UserDomainService
1011
import org.yapp.globalutils.annotation.ApplicationService
1112
import java.util.*
1213

1314
@ApplicationService
1415
class ReadingRecordService(
1516
private val readingRecordDomainService: ReadingRecordDomainService,
17+
private val userDomainService: UserDomainService
1618
) {
1719
fun createReadingRecord(
1820
userId: UUID,
@@ -27,6 +29,9 @@ class ReadingRecordService(
2729
emotionTags = request.validEmotionTags()
2830
)
2931

32+
// Update user's lastActivity when a reading record is created
33+
userDomainService.updateLastActivity(userId)
34+
3035
return ReadingRecordResponse.from(readingRecordInfoVO)
3136
}
3237

@@ -51,6 +56,7 @@ class ReadingRecordService(
5156
readingRecordDomainService.deleteAllByUserBookId(userBookId)
5257
}
5358
fun updateReadingRecord(
59+
userId: UUID,
5460
readingRecordId: UUID,
5561
request: UpdateReadingRecordRequest
5662
): ReadingRecordResponse {
@@ -61,6 +67,10 @@ class ReadingRecordService(
6167
review = request.validReview(),
6268
emotionTags = request.validEmotionTags()
6369
)
70+
71+
// Update user's lastActivity when a reading record is updated
72+
userDomainService.updateLastActivity(userId)
73+
6474
return ReadingRecordResponse.from(readingRecordInfoVO)
6575
}
6676

apis/src/main/kotlin/org/yapp/apis/readingrecord/usecase/ReadingRecordUseCase.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class ReadingRecordUseCase(
8686
userService.validateUserExists(userId)
8787

8888
return readingRecordService.updateReadingRecord(
89+
userId = userId,
8990
readingRecordId = readingRecordId,
9091
request = request
9192
)

apis/src/main/kotlin/org/yapp/apis/user/controller/UserController.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import jakarta.validation.Valid
44
import org.springframework.http.ResponseEntity
55
import org.springframework.security.core.annotation.AuthenticationPrincipal
66
import org.springframework.web.bind.annotation.*
7+
import org.yapp.apis.user.dto.request.FcmTokenRequest
8+
import org.yapp.apis.user.dto.request.NotificationSettingsRequest
79
import org.yapp.apis.user.dto.request.TermsAgreementRequest
810
import org.yapp.apis.user.dto.response.UserProfileResponse
911
import org.yapp.apis.user.usecase.UserUseCase
@@ -30,4 +32,22 @@ class UserController(
3032
val userProfile = userUseCase.updateTermsAgreement(userId, request)
3133
return ResponseEntity.ok(userProfile)
3234
}
35+
36+
@PutMapping("/me/notification-settings")
37+
override fun updateNotificationSettings(
38+
@AuthenticationPrincipal userId: UUID,
39+
@Valid @RequestBody request: NotificationSettingsRequest
40+
): ResponseEntity<UserProfileResponse> {
41+
val userProfile = userUseCase.updateNotificationSettings(userId, request)
42+
return ResponseEntity.ok(userProfile)
43+
}
44+
45+
@PutMapping("/me/fcm-token")
46+
override fun updateFcmToken(
47+
@AuthenticationPrincipal userId: UUID,
48+
@Valid @RequestBody request: FcmTokenRequest
49+
): ResponseEntity<UserProfileResponse> {
50+
val userProfile = userUseCase.updateFcmToken(userId, request)
51+
return ResponseEntity.ok(userProfile)
52+
}
3353
}

apis/src/main/kotlin/org/yapp/apis/user/controller/UserControllerApi.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import org.springframework.web.bind.annotation.GetMapping
1414
import org.springframework.web.bind.annotation.PutMapping
1515
import org.springframework.web.bind.annotation.RequestBody
1616
import org.springframework.web.bind.annotation.RequestMapping
17+
import org.yapp.apis.user.dto.request.FcmTokenRequest
18+
import org.yapp.apis.user.dto.request.NotificationSettingsRequest
1719
import org.yapp.apis.user.dto.request.TermsAgreementRequest
1820
import org.yapp.apis.user.dto.response.UserProfileResponse
1921
import org.yapp.globalutils.exception.ErrorResponse
@@ -84,4 +86,72 @@ interface UserControllerApi {
8486
@Parameter(hidden = true) @AuthenticationPrincipal userId: UUID,
8587
@Valid @RequestBody @Parameter(description = "약관 동의 요청 객체") request: TermsAgreementRequest
8688
): ResponseEntity<UserProfileResponse>
89+
90+
@Operation(
91+
summary = "알림 설정 업데이트",
92+
description = "사용자의 알림 설정을 업데이트합니다."
93+
)
94+
@ApiResponses(
95+
value = [
96+
ApiResponse(
97+
responseCode = "200",
98+
description = "알림 설정 업데이트 성공",
99+
content = [Content(schema = Schema(implementation = UserProfileResponse::class))]
100+
),
101+
ApiResponse(
102+
responseCode = "400",
103+
description = "잘못된 요청 파라미터",
104+
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
105+
),
106+
ApiResponse(
107+
responseCode = "401",
108+
description = "인증되지 않은 사용자",
109+
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
110+
),
111+
ApiResponse(
112+
responseCode = "404",
113+
description = "사용자를 찾을 수 없습니다.",
114+
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
115+
)
116+
]
117+
)
118+
@PutMapping("/me/notification-settings")
119+
fun updateNotificationSettings(
120+
@Parameter(hidden = true) @AuthenticationPrincipal userId: UUID,
121+
@Valid @RequestBody @Parameter(description = "알림 설정 요청 객체") request: NotificationSettingsRequest
122+
): ResponseEntity<UserProfileResponse>
123+
124+
@Operation(
125+
summary = "FCM 토큰 등록",
126+
description = "사용자의 FCM 토큰을 등록합니다."
127+
)
128+
@ApiResponses(
129+
value = [
130+
ApiResponse(
131+
responseCode = "200",
132+
description = "FCM 토큰 등록 성공",
133+
content = [Content(schema = Schema(implementation = UserProfileResponse::class))]
134+
),
135+
ApiResponse(
136+
responseCode = "400",
137+
description = "잘못된 요청 파라미터",
138+
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
139+
),
140+
ApiResponse(
141+
responseCode = "401",
142+
description = "인증되지 않은 사용자",
143+
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
144+
),
145+
ApiResponse(
146+
responseCode = "404",
147+
description = "사용자를 찾을 수 없습니다.",
148+
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
149+
)
150+
]
151+
)
152+
@PutMapping("/me/fcm-token")
153+
fun updateFcmToken(
154+
@Parameter(hidden = true) @AuthenticationPrincipal userId: UUID,
155+
@Valid @RequestBody @Parameter(description = "FCM 토큰 요청 객체") request: FcmTokenRequest
156+
): ResponseEntity<UserProfileResponse>
87157
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.yapp.apis.user.dto.request
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotBlank
5+
6+
@Schema(
7+
name = "FcmTokenRequest",
8+
description = "DTO for FCM token update requests"
9+
)
10+
data class FcmTokenRequest private constructor(
11+
@field:Schema(
12+
description = "FCM 토큰",
13+
example = "epGzIKlHScicTBrbt26pFG:APA91bG-ZPD-KMJyS-JOiflEPUIVvrp8l9DFBN2dlNG8IHw8mFlkAPok7dVPFJR4phc9061KPztkAIjBJaryZLnv6vIJXNGQsykzDcok3wFC9LrsC-F_aKY",
14+
required = true
15+
)
16+
@field:NotBlank(message = "FCM 토큰은 필수입니다.")
17+
val fcmToken: String? = null
18+
) {
19+
fun validFcmToken(): String = fcmToken!!.trim()
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.yapp.apis.user.dto.request
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotNull
5+
6+
@Schema(
7+
name = "NotificationSettingsRequest",
8+
description = "DTO for notification settings update requests"
9+
)
10+
data class NotificationSettingsRequest private constructor(
11+
@field:Schema(
12+
description = "알림 설정 여부",
13+
example = "true",
14+
required = true
15+
)
16+
@field:NotNull(message = "알림 설정 여부는 필수입니다.")
17+
val notificationEnabled: Boolean? = null
18+
) {
19+
fun validNotificationEnabled(): Boolean = notificationEnabled!!
20+
}

apis/src/main/kotlin/org/yapp/apis/user/dto/response/UserProfileResponse.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ data class UserProfileResponse(
3939
description = "Whether the user has agreed to the terms of service",
4040
example = "false"
4141
)
42-
val termsAgreed: Boolean
42+
val termsAgreed: Boolean,
43+
44+
@field:Schema(
45+
description = "Whether notifications are enabled for the user",
46+
example = "true"
47+
)
48+
val notificationEnabled: Boolean
4349
) {
4450
companion object {
4551
fun from(userProfileVO: UserProfileVO): UserProfileResponse {
@@ -48,7 +54,8 @@ data class UserProfileResponse(
4854
email = userProfileVO.email.value,
4955
nickname = userProfileVO.nickname,
5056
provider = userProfileVO.provider,
51-
termsAgreed = userProfileVO.termsAgreed
57+
termsAgreed = userProfileVO.termsAgreed,
58+
notificationEnabled = userProfileVO.notificationEnabled
5259
)
5360
}
5461
}

apis/src/main/kotlin/org/yapp/apis/user/service/UserService.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package org.yapp.apis.user.service
33
import jakarta.validation.Valid
44
import org.yapp.apis.auth.exception.AuthErrorCode
55
import org.yapp.apis.auth.exception.AuthException
6+
import org.yapp.apis.user.dto.request.FcmTokenRequest
67
import org.yapp.apis.user.dto.request.FindUserIdentityRequest
8+
import org.yapp.apis.user.dto.request.NotificationSettingsRequest
79
import org.yapp.apis.user.dto.request.TermsAgreementRequest
810
import org.yapp.apis.user.dto.response.UserAuthInfoResponse
911
import org.yapp.apis.user.dto.response.UserProfileResponse
@@ -36,4 +38,16 @@ class UserService(
3638
val userIdentity = userDomainService.findUserIdentityById(findUserIdentityRequest.validUserId())
3739
return UserAuthInfoResponse.from(userIdentity)
3840
}
41+
42+
fun updateNotificationSettings(userId: UUID, @Valid request: NotificationSettingsRequest): UserProfileResponse {
43+
validateUserExists(userId)
44+
val updatedUserProfile = userDomainService.updateNotificationSettings(userId, request.validNotificationEnabled())
45+
return UserProfileResponse.from(updatedUserProfile)
46+
}
47+
48+
fun updateFcmToken(userId: UUID, @Valid request: FcmTokenRequest): UserProfileResponse {
49+
validateUserExists(userId)
50+
val updatedUserProfile = userDomainService.updateFcmToken(userId, request.validFcmToken())
51+
return UserProfileResponse.from(updatedUserProfile)
52+
}
3953
}

0 commit comments

Comments
 (0)