Conversation
- ADMIN role인 관리자만 문장을 새롭게 추가할 수 있도록 Controller 및 Service를 구현했습니다. - @PreAuthorize 어노테이션을 통해서 ADMIN 계정이 아닌 경우, 403 FORBIDDEN 예외를 반환합니다. issue #51
- Member Role Enum에도 ADMIN 타입을 추가합니다. - 이전에 누락된 ADMIN role을 모든 Controller에 추가합니다. issue #51
- 하드코딩 되어있는 Role(GUEST, USER) 로직을 제거하고, ADMIN 역할도 포함할 수 있도록 파라미터로 분리했습니다. - 예외 코드에 HttpStatus를 추가하여 보다 자세한 예외 응답을 반환할 수 있도록 추가했습니다. - 접두사 "ROLE_" 이 없어 생기는 오류를 해결하기 위해서 접두사를 붙였습니다. issue #51
There was a problem hiding this comment.
Pull request overview
Implements admin capabilities by introducing an ADMIN role, issuing JWTs with role claims, and adding an admin-only bulk phrase creation API.
Changes:
- Add
ADMINrole and update Spring Security role handling to consistently use theROLE_prefix. - Include
rolein JWT claims and build authentication authorities from the token’s role claim. - Introduce admin-only phrase bulk creation controller/service and improve exception codes with
HttpStatus.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/dasi/typing/jwt/JwtTokenProvider.java | Adds role claim support to JWTs and exposes role extraction from token. |
| src/main/java/dasi/typing/handler/OAuth2AuthenticationSuccessHandler.java | Generates JWTs using member role and updates redirect path. |
| src/main/java/dasi/typing/filter/JwtAuthenticationFilter.java | Builds authorities from JWT role claim with ROLE_ prefix. |
| src/main/java/dasi/typing/filter/GuestAuthenticationFilter.java | Aligns guest authority to ROLE_GUEST. |
| src/main/java/dasi/typing/exception/Code.java | Extends error codes to include HttpStatus for richer responses. |
| src/main/java/dasi/typing/domain/member/Role.java | Adds ADMIN role. |
| src/main/java/dasi/typing/api/service/member/MemberService.java | Uses role-aware JWT issuance, adds logout, refactors temp token key handling and sign-up validation. |
| src/main/java/dasi/typing/api/service/admin/phrase/request/PhraseCreateServiceRequest.java | Adds service-layer request record for admin phrase creation. |
| src/main/java/dasi/typing/api/service/admin/phrase/AdminPhraseService.java | Implements bulk phrase creation and assigns random randId. |
| src/main/java/dasi/typing/api/controller/typing/TypingController.java | Allows ADMIN to access typing endpoints. |
| src/main/java/dasi/typing/api/controller/ranking/RankingController.java | Allows ADMIN to access ranking endpoints. |
| src/main/java/dasi/typing/api/controller/phrase/PhraseController.java | Allows ADMIN to access phrase endpoints. |
| src/main/java/dasi/typing/api/controller/mypage/MyPageController.java | Allows ADMIN access to mypage endpoints. |
| src/main/java/dasi/typing/api/controller/member/MemberController.java | Allows ADMIN reissue + adds logout endpoint clearing cookies and refresh token. |
| src/main/java/dasi/typing/api/controller/consent/ConsentController.java | Allows ADMIN to access consent endpoints. |
| src/main/java/dasi/typing/api/controller/admin/phrase/request/PhraseCreateRequest.java | Adds controller DTO and mapping to service request. |
| src/main/java/dasi/typing/api/controller/admin/phrase/request/PhraseBulkCreateRequest.java | Adds bulk request wrapper and conversion to service requests. |
| src/main/java/dasi/typing/api/controller/admin/phrase/AdminPhraseController.java | Adds admin-only endpoint for bulk phrase creation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| List<Consent> consents = consentRepository.findByTypeInAndActiveTrue(request.getAgreements()); | ||
|
|
||
| if (consents.size() != REQUIRED_CONSENT_COUNT) { | ||
| if (consents.size() < REQUIRED_CONSENT_COUNT) { |
There was a problem hiding this comment.
consents.size() < REQUIRED_CONSENT_COUNT can incorrectly pass when the request includes extra consent types while still missing one of the required ones (size meets the threshold but required set is incomplete). Prefer validating that all required consent types are present (e.g., set inclusion check against required types) rather than relying on list size.
| if (consents.size() < REQUIRED_CONSENT_COUNT) { | |
| if (request.getAgreements() == null | |
| || request.getAgreements().size() != REQUIRED_CONSENT_COUNT | |
| || consents.size() != REQUIRED_CONSENT_COUNT) { |
| if (isNotEmpty(token) && (isReissueRequest(requestURI) || jwtTokenProvider.validateAccessToken(token))) { | ||
|
|
||
| String kakaoId = jwtTokenProvider.getKakaoId(token); | ||
| List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("USER"); | ||
| String role = jwtTokenProvider.getRole(token); | ||
| List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_" + role); |
There was a problem hiding this comment.
This parses JWT claims at least twice per request (getKakaoId and getRole each call into token parsing). Consider extracting claims once (e.g., a single provider method returning the parsed claims or a DTO containing both kakaoId and role) to avoid duplicated parsing work on every authenticated request.
| .author(req.author()) | ||
| .lang(req.lang()) | ||
| .type(req.type()) | ||
| .randId(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE)) |
There was a problem hiding this comment.
Assigning randId via nextInt(Integer.MAX_VALUE) risks collisions, especially over time or under bulk inserts. If randId is used for lookup or has a uniqueness constraint, prefer a collision-resistant approach (DB-generated value/sequence, UUID, or a repository-level uniqueness strategy) and/or ensure uniqueness within the batch before saveAll.
| public List<PhraseCreateServiceRequest> toServiceRequests() { | ||
| return phrases.stream() | ||
| .map(PhraseCreateRequest::toServiceRequest) | ||
| .toList(); | ||
| } |
There was a problem hiding this comment.
phrases.stream() will throw a NullPointerException if the request body omits phrases (or sends it as null). Consider adding bean validation (@NotNull / @NotEmpty) to phrases and using @Valid on the controller parameter to fail fast with a controlled 400 response instead of an NPE.
#️⃣ 연관된 이슈
#51
📝 작업 내용
Feat
Refactor
💬 리뷰 요구사항