Skip to content

Commit 35e975d

Browse files
authored
feat: Implement onboarding status API and related business logic (#66)
* feat: Implement onboarding status API and related business logic * feat: apply codereview bot
1 parent 2381707 commit 35e975d

File tree

10 files changed

+149
-5
lines changed

10 files changed

+149
-5
lines changed

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/OnboardingController.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import noweekend.core.api.controller.v1.request.LeaveInputRequest
55
import noweekend.core.api.controller.v1.request.ProfileRequest
66
import noweekend.core.api.controller.v1.request.TagRequest
77
import noweekend.core.api.controller.v1.response.DefaultTags
8+
import noweekend.core.api.controller.v1.response.OnboardingStatusResponse
89
import noweekend.core.api.security.annotations.CurrentUserId
910
import noweekend.core.domain.user.UserService
1011
import noweekend.core.support.response.ApiResponse
@@ -62,4 +63,13 @@ class OnboardingController(
6263
"연차 정보가 성공적으로 저장되었습니다.",
6364
)
6465
}
66+
67+
@GetMapping("/onboarding/status")
68+
override fun getOnboardingStatus(
69+
@CurrentUserId userId: String,
70+
): ApiResponse<OnboardingStatusResponse> {
71+
return ApiResponse.success(
72+
userService.getOnboardingStatus(userId),
73+
)
74+
}
6575
}

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/docs/OnboardingControllerDocs.kt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import noweekend.core.api.controller.v1.request.LeaveInputRequest
1111
import noweekend.core.api.controller.v1.request.ProfileRequest
1212
import noweekend.core.api.controller.v1.request.TagRequest
1313
import noweekend.core.api.controller.v1.response.DefaultTags
14+
import noweekend.core.api.controller.v1.response.OnboardingStatusResponse
1415
import noweekend.core.api.security.annotations.CurrentUserId
1516
import noweekend.core.support.response.ApiResponse
1617
import io.swagger.v3.oas.annotations.responses.ApiResponse as SwaggerApiResponse
@@ -340,4 +341,72 @@ interface OnboardingControllerDocs {
340341
fun getDefaultTag(
341342
@Parameter(hidden = true) @CurrentUserId userId: String,
342343
): ApiResponse<DefaultTags>
344+
345+
@Operation(
346+
summary = "온보딩: 사용자 진행 상태 조회",
347+
description = """
348+
온보딩 플로우(이름/생년월일, 연차, 일정 태그) 입력 진행 상황을 반환합니다.
349+
350+
status 값 설명:
351+
- NONE: 이름/생년월일 미입력 상태
352+
- NAME_AND_BIRTHDAY: 이름/생년월일 입력만 완료
353+
- ANNUAL_LEAVE: 연차 입력까지 완료
354+
- DONE: 모든 온보딩 절차 완료
355+
""",
356+
responses = [
357+
SwaggerApiResponse(
358+
responseCode = "200",
359+
description = "온보딩 상태 조회 성공",
360+
content = [
361+
Content(
362+
mediaType = "application/json",
363+
schema = Schema(implementation = ApiResponse::class),
364+
examples = [
365+
ExampleObject(
366+
name = "예시 응답",
367+
value = """
368+
{
369+
"result": "SUCCESS",
370+
"data": {
371+
"status": "DONE"
372+
},
373+
"error": null
374+
}
375+
""",
376+
),
377+
],
378+
),
379+
],
380+
),
381+
SwaggerApiResponse(
382+
responseCode = "400",
383+
description = "온보딩 순서에 맞지 않게 데이터가 입력된 경우",
384+
content = [
385+
Content(
386+
mediaType = "application/json",
387+
schema = Schema(implementation = ApiResponse::class),
388+
examples = [
389+
ExampleObject(
390+
name = "온보딩 순서 오류 예시",
391+
value = """
392+
{
393+
"result": "ERROR",
394+
"data": null,
395+
"error": {
396+
"code": "INVALID_ONBOARD_STATUS",
397+
"message": "온보딩 순서에 맞게 요청하지 않은 상태입니다. 사용자 정보조회하여 어떤 값이 입력되지 않았는지 확인해주세요.",
398+
"data": {}
399+
}
400+
}
401+
""",
402+
),
403+
],
404+
),
405+
],
406+
),
407+
],
408+
)
409+
fun getOnboardingStatus(
410+
@Parameter(hidden = true) @CurrentUserId userId: String,
411+
): ApiResponse<OnboardingStatusResponse>
343412
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package noweekend.core.api.controller.v1.response
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
@Schema(description = "온보딩 진행 상태 응답")
6+
data class OnboardingStatusResponse(
7+
@Schema(
8+
description = "온보딩 단계 상태 (NONE: 이름/생년월일 미입력, NAME_AND_BIRTHDAY: 이름/생년월일만 입력, ANNUAL_LEAVE: 연차까지 입력, DONE: 모든 온보딩 완료)",
9+
example = "ANNUAL_LEAVE",
10+
)
11+
val status: OnboardingStatus,
12+
)
13+
14+
@Schema(description = "온보딩 상태 Enum")
15+
enum class OnboardingStatus {
16+
@Schema(description = "이름/생년월일 입력 전 상태")
17+
NONE,
18+
19+
@Schema(description = "이름/생년월일 입력 완료 상태")
20+
NAME_AND_BIRTHDAY,
21+
22+
@Schema(description = "연차 입력까지 완료된 상태")
23+
ANNUAL_LEAVE,
24+
25+
@Schema(description = "모든 온보딩 완료 상태")
26+
DONE,
27+
}

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/response/UserInformationResponse.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ data class UserInformationResponse(
2020
val revocableToken: String?,
2121
val role: Role,
2222
val birthDate: LocalDate,
23-
val remainingAnnualLeave: Double,
23+
val remainingAnnualLeave: Double?,
2424
val createdAt: LocalDateTime,
2525
val updatedAt: LocalDateTime?,
2626
var location: Location?,

noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/user/UserService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import noweekend.core.api.controller.v1.request.LeaveInputRequest
44
import noweekend.core.api.controller.v1.request.LocationRequest
55
import noweekend.core.api.controller.v1.request.ProfileRequest
66
import noweekend.core.api.controller.v1.request.TagUpdateRequest
7+
import noweekend.core.api.controller.v1.response.OnboardingStatusResponse
78
import noweekend.core.api.controller.v1.response.UserInformationResponse
89
import noweekend.core.domain.tag.BasicTag
910
import noweekend.core.domain.tag.UserTags
@@ -17,4 +18,5 @@ interface UserService {
1718
fun getStateTags(userId: String): UserTags
1819
fun updateLocation(request: LocationRequest, userId: String)
1920
fun getUserInformationById(userId: String): UserInformationResponse
21+
fun getOnboardingStatus(userId: String): OnboardingStatusResponse
2022
}

noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/user/UserServiceImpl.kt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import noweekend.core.api.controller.v1.request.LeaveInputRequest
44
import noweekend.core.api.controller.v1.request.LocationRequest
55
import noweekend.core.api.controller.v1.request.ProfileRequest
66
import noweekend.core.api.controller.v1.request.TagUpdateRequest
7+
import noweekend.core.api.controller.v1.response.OnboardingStatus
8+
import noweekend.core.api.controller.v1.response.OnboardingStatusResponse
79
import noweekend.core.api.controller.v1.response.UserInformationResponse
810
import noweekend.core.domain.enumerate.ScheduleCategory
911
import noweekend.core.domain.schedule.ScheduleReader
@@ -66,7 +68,7 @@ class UserServiceImpl(
6668
daysToAdd += 0.5
6769
}
6870
val updatedUser = user.copy(
69-
remainingAnnualLeave = user.remainingAnnualLeave + daysToAdd,
71+
remainingAnnualLeave = (user.remainingAnnualLeave ?: 0.0) + daysToAdd,
7072
)
7173
userWriter.upsert(updatedUser)
7274
}
@@ -119,4 +121,36 @@ class UserServiceImpl(
119121

120122
return UserInformationResponse.of(user, averageTemperature)
121123
}
124+
125+
override fun getOnboardingStatus(userId: String): OnboardingStatusResponse {
126+
val user = userReader.findUserById(userId) ?: throw CoreException(ErrorType.USER_NOT_FOUND_INTERNAL)
127+
128+
val nameAndBirthdayEntered = user.birthDate != null && user.name != null
129+
val annualLeaveEntered = user.remainingAnnualLeave != null
130+
val userTags = tagReader.getUserTags(userId)
131+
val selectedTagsCount = (userTags.selectedBasicTags + userTags.unselectedBasicTags + userTags.selectedCustomTags + userTags.unselectedCustomTags).count { it.selected }
132+
val tagEntered = selectedTagsCount > 0
133+
134+
// 1. 이름/생년월일 안됨 -> NONE
135+
if (!nameAndBirthdayEntered && !annualLeaveEntered && !tagEntered) {
136+
return OnboardingStatusResponse(OnboardingStatus.NONE)
137+
}
138+
139+
// 2. 이름/생년월일만 됨 -> NAME_AND_BIRTHDAY
140+
if (nameAndBirthdayEntered && !annualLeaveEntered && !tagEntered) {
141+
return OnboardingStatusResponse(OnboardingStatus.NAME_AND_BIRTHDAY)
142+
}
143+
144+
// 3. 연차까지 됨(이름/생년월일, 연차 ok, 태그 없음) -> ANNUAL_LEAVE
145+
if (nameAndBirthdayEntered && annualLeaveEntered && !tagEntered) {
146+
return OnboardingStatusResponse(OnboardingStatus.ANNUAL_LEAVE)
147+
}
148+
149+
// 4. 모든게 정상적으로 입력됨 -> DONE
150+
if (nameAndBirthdayEntered && annualLeaveEntered && tagEntered) {
151+
return OnboardingStatusResponse(OnboardingStatus.DONE)
152+
}
153+
154+
throw CoreException(ErrorType.INVALID_ONBOARD_STATUS)
155+
}
122156
}

noweekend-core/core-api/src/main/kotlin/noweekend/core/support/error/ErrorType.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ enum class ErrorType(
2525
MCP_SERVER_SANDWICH_ERROR(HttpStatus.GATEWAY_TIMEOUT, ErrorCode.E504, "MCP 추천 서버의 응답이 없습니다. 잠시 후 다시 시도해주세요.", LogLevel.ERROR),
2626
INVALID_LOCATION(HttpStatus.BAD_REQUEST, ErrorCode.E400, "사용자가 한국 위치가 아니기 때문에 날씨를 추천할 수 없습니다.", LogLevel.WARN),
2727
INVALID_SCHEDULE_TAG(HttpStatus.BAD_REQUEST, ErrorCode.E400, "유효하지 않은 태그가 포함되어 있습니다.", LogLevel.WARN),
28+
INVALID_ONBOARD_STATUS(HttpStatus.BAD_REQUEST, ErrorCode.E400, "온보딩 순서에 맞게 요청하지 않은 상태입니다. 사용자 정보조회하여 어떤 값이 입력되지 않았는지 확인해주세요.", LogLevel.WARN),
2829
}

noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/auth/TestUserService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ data class UserWithToken(
8484
revocableToken = user.revocableToken,
8585
role = user.role,
8686
birthDate = user.birthDate,
87-
remainingAnnualLeave = user.remainingAnnualLeave,
87+
remainingAnnualLeave = 10.0,
8888
createdAt = user.createdAt,
8989
updatedAt = user.updatedAt,
9090
)

noweekend-core/core-domain/src/main/kotlin/noweekend/core/domain/user/User.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ data class User(
1717
val revocableToken: String?,
1818
val role: Role,
1919
val birthDate: LocalDate?,
20-
val remainingAnnualLeave: Double = 0.0,
20+
val remainingAnnualLeave: Double?,
2121
val createdAt: LocalDateTime?,
2222
val updatedAt: LocalDateTime?,
2323
var deleted: Boolean,
@@ -41,6 +41,7 @@ data class User(
4141
revocableToken = revocableToken,
4242
role = role,
4343
birthDate = null,
44+
remainingAnnualLeave = null,
4445
createdAt = null,
4546
updatedAt = null,
4647
deleted = false,

noweekend-storage/db-core/src/main/kotlin/noweekend/storage/db/core/user/UserEntity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class UserEntity(
5656
val birthDate: LocalDate? = null,
5757

5858
@Column(name = "remaining_annual_leave", nullable = true)
59-
val remainingAnnualLeave: Double = 0.0,
59+
val remainingAnnualLeave: Double?,
6060

6161
@Embedded
6262
var location: LocationEmbeddable?,

0 commit comments

Comments
 (0)