Skip to content

Commit 97b0ed1

Browse files
authored
feat: PreRegister Coolsms SMS 인증 기능 구현 및 통합 테스트 작성
1 parent 7d7b783 commit 97b0ed1

34 files changed

Lines changed: 1255 additions & 362 deletions

backend/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,16 @@ db_dev.trace.db
4343

4444
### Application Configuration ###
4545
!src/main/resources/application-example.yml
46+
src/main/resources/application.yml
47+
src/main/resources/application-*.yml
4648
src/main/resources/application-secret.yml
4749
src/main/resources/application-local.yml
4850
src/main/resources/application-test.yml
4951
src/main/resources/application-perf.yml
52+
!src/main/resources/application-example.yml
5053

54+
### macOS ###
55+
.DS_Store
56+
.AppleDouble
57+
.LSOverride
58+
._*

backend/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ dependencies {
6666
// actuator, micrometer
6767
implementation("org.springframework.boot:spring-boot-starter-actuator")
6868
implementation("io.micrometer:micrometer-registry-prometheus")
69+
70+
//coolsms
71+
implementation("net.nurigo:sdk:4.3.0")
6972
}
7073

7174
tasks.withType<Test> {

backend/src/main/java/com/back/api/event/controller/EventApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ ApiResponse<Page<EventListResponse>> getEvents(
8484
@Parameter(description = "이벤트 카테고리 필터 (미입력 시 전체 조회)", example = "CONCERT")
8585
@RequestParam(required = false) EventCategory category,
8686

87-
@Parameter(description = "페이징 정보")
87+
@Parameter(description = "페이징 정보(기본값: page=0, size=10, sort=\"createAt\" 으로 설정해주세요.)")
8888
@PageableDefault(size = 10, sort = "createAt", direction = Sort.Direction.DESC)
8989
Pageable pageable);
9090
}

backend/src/main/java/com/back/api/event/dto/request/EventCreateRequest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
@Schema(description = "이벤트 생성 요청 DTO")
1515
public record EventCreateRequest(
16-
@Schema(description = "이벤트 제목", example = "2025 서울 뮤직 페스티벌")
16+
@Schema(description = "이벤트 제목", example = "2026 서울 뮤직 페스티벌")
1717
@NotBlank(message = "이벤트 제목은 필수입니다.")
1818
String title,
1919

@@ -41,19 +41,19 @@ public record EventCreateRequest(
4141
@Min(value = 0, message = "최대 가격은 0 이상이어야 합니다.")
4242
Integer maxPrice,
4343

44-
@Schema(description = "사전등록 시작일시", example = "2025-01-01T10:00:00")
44+
@Schema(description = "사전등록 시작일시", example = "2026-01-11T10:00:00")
4545
@NotNull(message = "사전등록 시작일은 필수입니다.")
4646
LocalDateTime preOpenAt,
4747

48-
@Schema(description = "사전등록 종료일시", example = "2025-01-10T23:59:59")
48+
@Schema(description = "사전등록 종료일시", example = "2026-01-15T23:59:59")
4949
@NotNull(message = "사전등록 종료일은 필수입니다.")
5050
LocalDateTime preCloseAt,
5151

52-
@Schema(description = "티켓팅 시작일시", example = "2025-01-15T10:00:00")
52+
@Schema(description = "티켓팅 시작일시", example = "2026-01-25T10:00:00")
5353
@NotNull(message = "티켓팅 시작일은 필수입니다.")
5454
LocalDateTime ticketOpenAt,
5555

56-
@Schema(description = "티켓팅 종료일시", example = "2025-01-20T23:59:59")
56+
@Schema(description = "티켓팅 종료일시", example = "2026-01-30T23:59:59")
5757
@NotNull(message = "티켓팅 종료일은 필수입니다.")
5858
LocalDateTime ticketCloseAt,
5959

backend/src/main/java/com/back/api/event/dto/request/EventUpdateRequest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
@Schema(description = "이벤트 수정 요청 DTO")
1414
public record EventUpdateRequest(
15-
@Schema(description = "이벤트 제목", example = "2025 서울 뮤직 페스티벌 (수정)")
15+
@Schema(description = "이벤트 제목", example = "2026 서울 뮤직 페스티벌 (수정)")
1616
@NotBlank(message = "이벤트 제목은 필수입니다.")
1717
String title,
1818

@@ -40,19 +40,19 @@ public record EventUpdateRequest(
4040
@Min(value = 0, message = "최대 가격은 0 이상이어야 합니다.")
4141
Integer maxPrice,
4242

43-
@Schema(description = "사전등록 시작일시", example = "2025-01-01T10:00:00")
43+
@Schema(description = "사전등록 시작일시", example = "2026-01-11T10:00:00")
4444
@NotNull(message = "사전등록 시작일은 필수입니다.")
4545
LocalDateTime preOpenAt,
4646

47-
@Schema(description = "사전등록 종료일시", example = "2025-01-10T23:59:59")
47+
@Schema(description = "사전등록 종료일시", example = "2026-01-20T23:59:59")
4848
@NotNull(message = "사전등록 종료일은 필수입니다.")
4949
LocalDateTime preCloseAt,
5050

51-
@Schema(description = "티켓팅 시작일시", example = "2025-01-15T10:00:00")
51+
@Schema(description = "티켓팅 시작일시", example = "2026-01-25T10:00:00")
5252
@NotNull(message = "티켓팅 시작일은 필수입니다.")
5353
LocalDateTime ticketOpenAt,
5454

55-
@Schema(description = "티켓팅 종료일시", example = "2025-01-20T23:59:59")
55+
@Schema(description = "티켓팅 종료일시", example = "2026-01-30T23:59:59")
5656
@NotNull(message = "티켓팅 종료일은 필수입니다.")
5757
LocalDateTime ticketCloseAt,
5858

backend/src/main/java/com/back/api/event/dto/response/EventListResponse.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public record EventListResponse(
1313
@Schema(description = "이벤트 ID", example = "1")
1414
Long id,
1515

16-
@Schema(description = "이벤트 제목", example = "2025 서울 뮤직 페스티벌")
16+
@Schema(description = "이벤트 제목", example = "2026 서울 뮤직 페스티벌")
1717
String title,
1818

1919
@Schema(description = "이벤트 카테고리", example = "CONCERT")
@@ -31,20 +31,20 @@ public record EventListResponse(
3131
@Schema(description = "최대 티켓 가격 (원)", example = "150000")
3232
Integer maxPrice,
3333

34-
@Schema(description = "사전등록 시작일시", example = "2025-01-01T10:00:00")
34+
@Schema(description = "사전등록 시작일시", example = "2026-01-11T10:00:00")
3535
LocalDateTime preOpenAt,
3636

37-
@Schema(description = "사전등록 종료일시", example = "2025-01-10T23:59:59")
37+
@Schema(description = "사전등록 종료일시", example = "2026-01-20T23:59:59")
3838
LocalDateTime preCloseAt,
3939

40-
@Schema(description = "티켓팅 시작일시", example = "2025-01-15T10:00:00")
40+
@Schema(description = "티켓팅 시작일시", example = "2026-01-25T10:00:00")
4141
LocalDateTime ticketOpenAt,
4242

4343
@Schema(description = "이벤트 상태 (READY: 준비중, PRE_OPEN: 사전등록중, QUEUE_READY: 대기열 준비, OPEN: 티켓팅 진행중, CLOSED: 마감)",
4444
example = "PRE_OPEN")
4545
EventStatus status,
4646

47-
@Schema(description = "생성일시", example = "2024-12-01T10:00:00")
47+
@Schema(description = "생성일시", example = "2025-12-01T10:00:00")
4848
LocalDateTime createdAt
4949
) {
5050
public static EventListResponse from(Event event) {

backend/src/main/java/com/back/api/event/dto/response/EventResponse.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public record EventResponse(
1313
@Schema(description = "이벤트 ID", example = "1")
1414
Long id,
1515

16-
@Schema(description = "이벤트 제목", example = "2025 서울 뮤직 페스티벌")
16+
@Schema(description = "이벤트 제목", example = "2026 서울 뮤직 페스티벌")
1717
String title,
1818

1919
@Schema(description = "이벤트 카테고리", example = "CONCERT")
@@ -34,16 +34,16 @@ public record EventResponse(
3434
@Schema(description = "최대 티켓 가격 (원)", example = "150000")
3535
Integer maxPrice,
3636

37-
@Schema(description = "사전등록 시작일시", example = "2025-01-01T10:00:00")
37+
@Schema(description = "사전등록 시작일시", example = "2026-01-11T10:00:00")
3838
LocalDateTime preOpenAt,
3939

40-
@Schema(description = "사전등록 종료일시", example = "2025-01-10T23:59:59")
40+
@Schema(description = "사전등록 종료일시", example = "2026-01-20T23:59:59")
4141
LocalDateTime preCloseAt,
4242

43-
@Schema(description = "티켓팅 시작일시", example = "2025-01-15T10:00:00")
43+
@Schema(description = "티켓팅 시작일시", example = "2026-01-25T10:00:00")
4444
LocalDateTime ticketOpenAt,
4545

46-
@Schema(description = "티켓팅 종료일시", example = "2025-01-20T23:59:59")
46+
@Schema(description = "티켓팅 종료일시", example = "2026-01-30T23:59:59")
4747
LocalDateTime ticketCloseAt,
4848

4949
@Schema(description = "최대 티켓 수량", example = "5000")

backend/src/main/java/com/back/api/preregister/controller/PreRegisterApi.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import org.springframework.web.bind.annotation.PathVariable;
44
import org.springframework.web.bind.annotation.RequestBody;
5-
import org.springframework.web.bind.annotation.RequestParam;
65

76
import com.back.api.preregister.dto.request.PreRegisterCreateRequest;
87
import com.back.api.preregister.dto.response.PreRegisterResponse;
@@ -11,15 +10,17 @@
1110

1211
import io.swagger.v3.oas.annotations.Operation;
1312
import io.swagger.v3.oas.annotations.Parameter;
13+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
1414
import io.swagger.v3.oas.annotations.tags.Tag;
1515
import jakarta.validation.Valid;
1616

1717
@Tag(name = "PreRegister API", description = "사전등록 API")
1818
public interface PreRegisterApi {
1919

2020
@Operation(
21-
summary = "사전등록 (V1 본인 인증)",
22-
description = "이벤트에 사전등록합니다. 이름, 비밀번호, 생년월일을 통한 본인 인증이 필요하며, 약관 동의가 필수입니다."
21+
summary = "사전등록",
22+
description = "이벤트에 사전등록합니다. 휴대폰 번호, 생년월일을 통한 본인 인증이 필요하며, 약관 동의가 필수입니다. 이름은 JWT 토큰으로 인증된 사용자 정보에서 자동으로 가져옵니다.",
23+
security = @SecurityRequirement(name = "bearerAuth")
2324
)
2425
@ApiErrorCode({
2526
"NOT_FOUND_EVENT",
@@ -28,41 +29,42 @@ public interface PreRegisterApi {
2829
"INVALID_PRE_REGISTRATION_PERIOD",
2930
"INVALID_USER_INFO",
3031
"TERMS_NOT_AGREED",
31-
"PRIVACY_NOT_AGREED"
32+
"PRIVACY_NOT_AGREED",
33+
"UNAUTHORIZED"
3234
})
3335
ApiResponse<PreRegisterResponse> register(
3436
@Parameter(description = "이벤트 ID", example = "1")
3537
@PathVariable Long eventId,
36-
@Parameter(description = "사용자 ID", example = "1")
37-
@RequestParam Long userId,
3838
@Valid @RequestBody PreRegisterCreateRequest request
3939
);
4040

4141
@Operation(
4242
summary = "사전등록 취소",
43-
description = "사전등록을 취소합니다. 이미 취소된 경우 오류가 발생합니다."
43+
description = "사전등록을 취소합니다. 이미 취소된 경우 오류가 발생합니다. JWT 토큰을 통해 사용자를 인증합니다.",
44+
security = @SecurityRequirement(name = "bearerAuth")
4445
)
4546
@ApiErrorCode({
4647
"NOT_FOUND_PRE_REGISTER",
47-
"ALREADY_CANCELED"
48+
"ALREADY_CANCELED",
49+
"UNAUTHORIZED"
4850
})
4951
ApiResponse<Void> cancel(
5052
@Parameter(description = "이벤트 ID", example = "1")
51-
@PathVariable Long eventId,
52-
@Parameter(description = "사용자 ID", example = "1")
53-
@RequestParam Long userId
53+
@PathVariable Long eventId
5454
);
5555

5656
@Operation(
5757
summary = "내 사전등록 조회",
58-
description = "특정 이벤트에 대한 내 사전등록 정보를 조회합니다."
58+
description = "특정 이벤트에 대한 내 사전등록 정보를 조회합니다. JWT 토큰을 통해 사용자를 인증합니다.",
59+
security = @SecurityRequirement(name = "bearerAuth")
5960
)
60-
@ApiErrorCode("NOT_FOUND_PRE_REGISTER")
61+
@ApiErrorCode({
62+
"NOT_FOUND_PRE_REGISTER",
63+
"UNAUTHORIZED"
64+
})
6165
ApiResponse<PreRegisterResponse> getMyPreRegister(
6266
@Parameter(description = "이벤트 ID", example = "1")
63-
@PathVariable Long eventId,
64-
@Parameter(description = "사용자 ID", example = "1")
65-
@RequestParam Long userId
67+
@PathVariable Long eventId
6668
);
6769

6870
@Operation(
@@ -77,12 +79,12 @@ ApiResponse<Long> getRegistrationCount(
7779

7880
@Operation(
7981
summary = "사전등록 여부 확인",
80-
description = "특정 이벤트에 사전등록했는지 여부를 확인합니다."
82+
description = "특정 이벤트에 사전등록했는지 여부를 확인합니다. JWT 토큰을 통해 사용자를 인증합니다.",
83+
security = @SecurityRequirement(name = "bearerAuth")
8184
)
85+
@ApiErrorCode("UNAUTHORIZED")
8286
ApiResponse<Boolean> isRegistered(
8387
@Parameter(description = "이벤트 ID", example = "1")
84-
@PathVariable Long eventId,
85-
@Parameter(description = "사용자 ID", example = "1")
86-
@RequestParam Long userId
88+
@PathVariable Long eventId
8789
);
8890
}

backend/src/main/java/com/back/api/preregister/controller/PreRegisterController.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import org.springframework.web.bind.annotation.PostMapping;
77
import org.springframework.web.bind.annotation.RequestBody;
88
import org.springframework.web.bind.annotation.RequestMapping;
9-
import org.springframework.web.bind.annotation.RequestParam;
109
import org.springframework.web.bind.annotation.RestController;
1110

1211
import com.back.api.preregister.dto.request.PreRegisterCreateRequest;
1312
import com.back.api.preregister.dto.response.PreRegisterResponse;
1413
import com.back.api.preregister.service.PreRegisterService;
14+
import com.back.global.http.HttpRequestContext;
1515
import com.back.global.response.ApiResponse;
1616

1717
import jakarta.validation.Valid;
@@ -23,31 +23,32 @@
2323
public class PreRegisterController implements PreRegisterApi {
2424

2525
private final PreRegisterService preRegisterService;
26+
private final HttpRequestContext httpRequestContext;
2627

2728
@Override
2829
@PostMapping
2930
public ApiResponse<PreRegisterResponse> register(
3031
@PathVariable Long eventId,
31-
@RequestParam Long userId,
3232
@Valid @RequestBody PreRegisterCreateRequest request) {
33+
Long userId = httpRequestContext.getUserId();
3334
PreRegisterResponse response = preRegisterService.register(eventId, userId, request);
3435
return ApiResponse.created("사전등록이 완료되었습니다.", response);
3536
}
3637

3738
@Override
3839
@DeleteMapping
3940
public ApiResponse<Void> cancel(
40-
@PathVariable Long eventId,
41-
@RequestParam Long userId) {
41+
@PathVariable Long eventId) {
42+
Long userId = httpRequestContext.getUserId();
4243
preRegisterService.cancel(eventId, userId);
4344
return ApiResponse.noContent("사전등록이 취소되었습니다.");
4445
}
4546

4647
@Override
4748
@GetMapping("/me")
4849
public ApiResponse<PreRegisterResponse> getMyPreRegister(
49-
@PathVariable Long eventId,
50-
@RequestParam Long userId) {
50+
@PathVariable Long eventId) {
51+
Long userId = httpRequestContext.getUserId();
5152
PreRegisterResponse response = preRegisterService.getMyPreRegister(eventId, userId);
5253
return ApiResponse.ok("사전등록 정보를 조회했습니다.", response);
5354
}
@@ -63,8 +64,8 @@ public ApiResponse<Long> getRegistrationCount(
6364
@Override
6465
@GetMapping("/status")
6566
public ApiResponse<Boolean> isRegistered(
66-
@PathVariable Long eventId,
67-
@RequestParam Long userId) {
67+
@PathVariable Long eventId) {
68+
Long userId = httpRequestContext.getUserId();
6869
Boolean isRegistered = preRegisterService.isRegistered(eventId, userId);
6970
return ApiResponse.ok("사전등록 여부를 확인했습니다.", isRegistered);
7071
}

backend/src/main/java/com/back/api/preregister/dto/request/PreRegisterCreateRequest.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,16 @@
66
import jakarta.validation.constraints.NotBlank;
77
import jakarta.validation.constraints.NotNull;
88
import jakarta.validation.constraints.Past;
9+
import jakarta.validation.constraints.Pattern;
910
import jakarta.validation.constraints.Size;
1011

11-
@Schema(description = "사전등록 생성 요청 (V1 본인 인증)")
12+
@Schema(description = "사전등록 생성 요청 (SMS 본인 인증)")
1213
public record PreRegisterCreateRequest(
1314

14-
@Schema(description = "이름", example = "홍길동")
15-
@NotBlank(message = "이름은 필수입니다.")
16-
@Size(max = 50, message = "이름은 50자 이하여야 합니다.")
17-
String nickname,
18-
19-
@Schema(description = "비밀번호 (6자 이상, 인증용)", example = "password123")
20-
@NotBlank(message = "비밀번호는 필수입니다.")
21-
@Size(min = 6, message = "비밀번호는 6자 이상이어야 합니다.")
22-
String password,
15+
@Schema(description = "휴대폰 번호 (하이픈 제거)", example = "01012345678")
16+
@NotBlank(message = "휴대폰 번호는 필수입니다.")
17+
@Pattern(regexp = "^01[0-9]{8,9}$", message = "올바른 휴대폰 번호 형식이 아닙니다.")
18+
String phoneNumber,
2319

2420
@Schema(description = "생년월일 (YYYY-MM-DD)", example = "1990-01-01")
2521
@NotNull(message = "생년월일은 필수입니다.")

0 commit comments

Comments
 (0)