-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 내 인증현황 조회 API 구현 #138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9c55ba1
6f6eb7c
f35b7ee
aa798cd
c98a4cc
cfabda9
2b58415
8cadf98
def003a
0f1a6b8
2de67f8
b0b579d
bd41469
4f123b9
835dd97
3932e4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,13 @@ | |
| import com.hrr.backend.domain.user.dto.*; | ||
| import com.hrr.backend.domain.user.entity.User; | ||
| import com.hrr.backend.domain.user.service.UserService; | ||
| import com.hrr.backend.domain.verification.dto.VerificationResponseDto; | ||
| import com.hrr.backend.domain.verification.service.VerificationService; | ||
| import com.hrr.backend.global.config.CustomUserDetails; | ||
| import com.hrr.backend.global.response.ApiResponse; | ||
| import com.hrr.backend.global.response.SliceResponseDto; | ||
| import com.hrr.backend.global.response.SuccessCode; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Hidden; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.media.Content; | ||
|
|
@@ -34,6 +35,7 @@ | |
| public class UserController { | ||
|
|
||
| private final UserService userService; | ||
| private final VerificationService verificationService; | ||
|
|
||
|
|
||
| // 닉네임 유효성 검사 API | ||
|
|
@@ -140,37 +142,96 @@ public ApiResponse<UserResponseDto.MyInfoDto> getMyInfo( | |
| return ApiResponse.onSuccess(SuccessCode.OK, myInfo); | ||
| } | ||
|
|
||
| @GetMapping("/search") | ||
| @Operation(summary = "챌린저 검색", description = "검색 키워드가 닉네임에 포함된 사용자를 조회합니다.") | ||
| public ApiResponse<SliceResponseDto<UserResponseDto.ProfileDto>> searchChallengers( | ||
| @RequestParam(name = "keyword") | ||
| @NotBlank(message = "검색어는 필수입니다.") String keyword, | ||
| @GetMapping("/search") | ||
| @Operation(summary = "챌린저 검색", description = "검색 키워드가 닉네임에 포함된 사용자를 조회합니다.") | ||
| public ApiResponse<SliceResponseDto<UserResponseDto.ProfileDto>> searchChallengers( | ||
| @RequestParam(name = "keyword") | ||
| @NotBlank(message = "검색어는 필수입니다.") String keyword, | ||
|
|
||
| // 페이징 | ||
| @Min(1) | ||
| @RequestParam(name = "page", defaultValue = "1") int page, // 페이지 번호 (0부터 시작) | ||
| @Min(1) | ||
| @RequestParam(name = "size", defaultValue = "10") int size, // 페이지 크기) | ||
| // 페이징 | ||
| @Min(1) | ||
| @RequestParam(name = "page", defaultValue = "1") int page, // 페이지 번호 (1부터 시작) | ||
| @Min(1) | ||
| @RequestParam(name = "size", defaultValue = "10") int size, // 페이지 크기 | ||
|
|
||
| @Parameter(hidden = true) | ||
| @AuthenticationPrincipal CustomUserDetails customUserDetails | ||
| ) | ||
| { | ||
| SliceResponseDto<UserResponseDto.ProfileDto> response = userService.searchChallengers(customUserDetails.getUser(), keyword, page-1, size); | ||
| @Parameter(hidden = true) | ||
| @AuthenticationPrincipal CustomUserDetails customUserDetails | ||
| ) | ||
| { | ||
| SliceResponseDto<UserResponseDto.ProfileDto> response = userService.searchChallengers(customUserDetails.getUser(), keyword, page-1, size); | ||
|
|
||
| return ApiResponse.onSuccess(SuccessCode.OK, response); | ||
| } | ||
| return ApiResponse.onSuccess(SuccessCode.OK, response); | ||
| } | ||
|
|
||
| @PatchMapping("/me") | ||
| @GetMapping("/me/verifications/history") | ||
| @Operation( | ||
| summary = "내 기본 정보 수정", | ||
| description = "사용자가 자신의 기본 정보(닉네임, 프로필 이미지, 프로필 공개여부)를 수정합니다" | ||
| summary = "내 챌린지 인증 기록 조회", | ||
| description = "현재 로그인한 사용자가 참여한 모든 챌린지의 인증 기록을 최신순으로 조회합니다." | ||
| ) | ||
| public ApiResponse<UpdateUserInfoResponseDto> updateUserInfo( | ||
| @AuthenticationPrincipal CustomUserDetails userDetails, | ||
| @Valid @RequestBody UpdateUserInfoRequestDto requestDto | ||
| @ApiResponses({ | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse( | ||
| responseCode = "200", | ||
| description = "성공", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| examples = @ExampleObject( | ||
| value = """ | ||
| { | ||
| "resultType": "SUCCESS", | ||
| "error": null, | ||
| "success": { | ||
| "content": [ | ||
| { | ||
| "verificationId": 1, | ||
| "challengeId": 101, | ||
| "challengeTitle": "미라클 모닝", | ||
| "type": "TEXT", | ||
| "title": "해피뉴이어! 올해 마지막 인증 올립니다", | ||
| "content": "여기엔 상세내용이 들어가유~", | ||
| "photoUrl": null, | ||
| "textUrl": "https://blog.example.com/post/123", | ||
| "verifiedAt": "2025-09-18T08:00:00Z" | ||
| }, | ||
| { | ||
| "verificationId": 2, | ||
| "challengeId": 102, | ||
| "challengeTitle": "매일 책 10페이지 읽기", | ||
| "type": "CAMERA", | ||
| "title": "오늘의 독서 인증", | ||
| "content": null, | ||
| "photoUrl": "https://example.com/verification_image_2.jpg", | ||
| "textUrl": null, | ||
| "verifiedAt": "2025-09-13T22:30:00Z" | ||
| } | ||
| ], | ||
| "currentPage": 0, | ||
| "size": 10, | ||
| "first": true, | ||
| "last": false, | ||
| "hasNext": true | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ) | ||
| ) | ||
| }) | ||
| public ApiResponse<SliceResponseDto<VerificationResponseDto.HistoryDto>> getVerificationHistory( | ||
| @Parameter(hidden = true) | ||
| @AuthenticationPrincipal CustomUserDetails customUserDetails, | ||
|
|
||
| @RequestParam(name = "page", defaultValue = "1") | ||
| @Min(1) | ||
| @Parameter(description = "페이지 번호 (1부터 시작)", example = "1") int page, | ||
|
|
||
| @RequestParam(name = "size", defaultValue = "10") | ||
| @Min(1) @Max(100) | ||
| @Parameter(description = "페이지당 데이터 개수", example = "10") int size | ||
| ) { | ||
| UpdateUserInfoResponseDto response = userService.updateUserInfo(userDetails.getUser().getId(), requestDto); | ||
| return ApiResponse.onSuccess(SuccessCode.USER_UPDATE_OK, response); | ||
| Long userId = customUserDetails.getUser().getId(); | ||
| SliceResponseDto<VerificationResponseDto.HistoryDto> response = | ||
| verificationService.getVerificationHistory(userId, page-1, size); | ||
|
|
||
| return ApiResponse.onSuccess(SuccessCode.OK, response); | ||
| } | ||
|
Comment on lines
+166
to
236
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API 엔드포인트 구현은 잘 되었으나, Swagger 문서의 일관성 이슈가 있습니다. 엔드포인트 구현 자체는 훌륭합니다:
하지만 Swagger 문서에 세 가지 개선이 필요합니다:
🔎 Swagger 예제 수정안 "verifiedAt": "2025-09-18T08:00:00Z"
+ "verifiedAt": "2025-09-18 08:00:00"
},
{
"verificationId": 2,
"challengeId": 102,
"challengeTitle": "매일 책 10페이지 읽기",
"type": "CAMERA",
"title": "오늘의 독서 인증",
"content": null,
"photoUrl": "https://example.com/verification_image_2.jpg",
"textUrl": null,
- "verifiedAt": "2025-09-13T22:30:00Z"
+ "verifiedAt": "2025-09-13 22:30:00"
}
],
- "currentPage": 0,
+ "currentPage": 1,
"size": 10,
"first": true, |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,4 +137,40 @@ public static class CreateResponseDto { | |
| /** 현재 라운드 인증 횟수 */ | ||
| private Integer verificationCount; | ||
| } | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Schema(description = "사용자 전체 인증 기록 DTO") | ||
| public static class HistoryDto { | ||
|
|
||
| @Schema(description = "인증 ID", example = "1") | ||
| private Long verificationId; | ||
|
|
||
| @Schema(description = "챌린지 ID", example = "101") | ||
| private Long challengeId; | ||
|
|
||
| @Schema(description = "챌린지 제목", example = "미라클 모닝") | ||
| private String challengeTitle; | ||
|
|
||
| @Schema(description = "인증 타입", example = "TEXT", allowableValues = {"TEXT", "CAMERA"}) | ||
| private String type; | ||
|
|
||
| @Schema(description = "인증 제목", example = "해피뉴이어! 올해 마지막 인증 올립니다") | ||
| private String title; | ||
|
|
||
| @Schema(description = "인증 내용", example = "여기엔 상세내용이 들어가유~") | ||
| private String content; | ||
|
|
||
| @Schema(description = "사진 URL (사진 인증)", example = "https://example.com/verification_image_1.jpg") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사진이 첨부된 인증글은 UI 상에서 클립 아이콘을 띄우는데 이를 적용하기 위해 프론트 측에 "사진이 첨부되지 않으면 null 이 반환된다" 등의 내용을 공유해주세요 |
||
| private String photoUrl; | ||
|
|
||
| @Schema(description = "글 URL (글 인증)", example = "https://blog.example.com/post/123") | ||
| private String textUrl; | ||
|
|
||
| @Schema(description = "인증 일시", example = "2025-09-18T08:00:00") | ||
| @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||
| private LocalDateTime verifiedAt; | ||
yerinchun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 일종의 하드코딩이라 변경사항이 누락될 위험이 있을 뿐더러 실제 응답과 다를 경우 혼란을 줄 수 있습니다. 제네릭 타입으로 인해 스웨거 등에 응답이 정확하지 않을 경우에만 넣어주시고 그 외에는 API 명세에 response 예시로 넣는 방향으로 해주세요