-
Notifications
You must be signed in to change notification settings - Fork 0
[hotfix] 회원가입 / 닉네임 변경 / 투표하기 api 수정 #224
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
64b64d3
de637f7
2b37ed0
cc28a24
d9e2eb8
469bf49
a4254dd
106b776
bdc6792
00803c3
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 |
|---|---|---|
|
|
@@ -46,14 +46,24 @@ public class OpenApiConfig { | |
|
|
||
| @Value("${server.http-url}") private String httpUrl; | ||
|
|
||
| @Value(("${server.profile}")) private String profile; | ||
|
|
||
| @Bean | ||
| public OpenAPI openAPI() { | ||
| List<Server> serverList = switch (profile) { | ||
| case "prod" -> List.of(new Server().url(httpsUrl).description("HTTPS 배포 서버")); | ||
| case "dev" -> List.of( | ||
| new Server().url(httpUrl).description("HTTP 개발 서버"), | ||
| new Server().url(httpsUrl).description("HTTPS 배포 서버"), | ||
| new Server().url("http://localhost:8080").description("로컬 개발 서버") | ||
| ); | ||
| default -> List.of( | ||
| new Server().url("http://localhost:8080").description("로컬 개발 서버") | ||
| ); | ||
| }; | ||
|
Comment on lines
+53
to
+63
Collaborator
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. 굳굳 |
||
|
|
||
| return new OpenAPI() | ||
| .servers(List.of( | ||
| new Server().url(httpsUrl).description("HTTPS 배포 서버"), | ||
| new Server().url(httpUrl).description("HTTP IP"), | ||
| new Server().url("http://localhost:8080").description("로컬 개발 서버") | ||
| )) | ||
| .servers(serverList) | ||
| .components(setComponents()) | ||
| .addSecurityItem(setSecurityItems()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import jakarta.validation.constraints.Pattern; | ||
| import jakarta.validation.constraints.Size; | ||
| import konkuk.thip.user.application.port.in.dto.UserSignupCommand; | ||
|
|
@@ -12,8 +13,9 @@ public record UserSignupRequest( | |
| @NotBlank(message = "aliasName은 필수입니다.") | ||
| String aliasName, | ||
|
|
||
| @Schema(description = "사용자 닉네임", example = "홍길동_123") | ||
| @Pattern(regexp = "[가-힣a-zA-Z0-9]+", message = "닉네임은 한글, 영어, 숫자로만 구성되어야 합니다.(공백불가)") | ||
| @Schema(description = "사용자 닉네임", example = "thip") | ||
| @NotNull(message = "닉네임은 필수입니다.") | ||
| @Pattern(regexp = "[가-힣a-z0-9]+", message = "닉네임은 한글, 영어 소문자, 숫자로만 구성되어야 합니다.(공백불가)") | ||
|
Comment on lines
+17
to
+18
Collaborator
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. 오호 이렇게 notnull + pattern 의 조합이면 [null 체크 + 공백 체크 + 패턴 체크] 가 전부 되겠네요!! 좋습니다!! @coderabbitai 내가 작성한 내용에 틀린 부분이 없지? 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. @seongjunnoh 맞습니다! 분석이 정확합니다.
이전의 🧠 Learnings used |
||
| @Size(max = 10, message = "닉네임은 최대 10자 입니다.") | ||
| String nickname | ||
| ) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ public class UserJpaEntity extends BaseJpaEntity { | |
| @Column(length = 60, nullable = false) | ||
| private String nickname; | ||
|
|
||
| @Column(name = "nickname_updated_at", nullable = false) | ||
| @Column(name = "nickname_updated_at", nullable = true) // 회원가입 시에는 null | ||
| private LocalDate nicknameUpdatedAt; // 날짜 형식으로 저장 (예: "2023-10-01") | ||
|
|
||
|
Comment on lines
+27
to
29
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. 💡 Verification agent 🧩 Analysis chainDB 스키마(DDL) 일치 여부 확인 필요: NOT NULL → NULL 변경에 대한 마이그레이션 JPA 매핑에서 다음 스크립트로 마이그레이션/DDL 흔적을 빠르게 점검할 수 있습니다: 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "후보 마이그레이션 디렉터리 탐색:"
fd -t d -HI '(db|database).*(migration|migrations)|migration|migrations' || true
echo
echo "nickname_updated_at 관련 스키마/변경 검색:"
rg -n -A3 -B3 -i 'nickname_updated_at|alter table.*users|drop not null|nullable' || true
echo
echo "Liquibase/Hibernate 설정 파일 내 컬럼 정의 검색(XML/YAML 등):"
rg -n -A3 -B3 -i 'nickname_updated_at|nullable' --glob '*.{xml,yml,yaml,sql}' || trueLength of output: 41636 DB 스키마(DDL) 마이그레이션 스크립트 확인 및 추가 필요
파일 위치
예시 SQL (프로젝트에 맞게 조정) -- MySQL
ALTER TABLE users
MODIFY COLUMN nickname_updated_at DATE NULL;
-- PostgreSQL
ALTER TABLE users
ALTER COLUMN nickname_updated_at DROP NOT NULL;마이그레이션 도구(Flyway, Liquibase 등) 디렉터리에 버전 관리되는 스크립트로 포함하시고, 🤖 Prompt for AI Agents
Collaborator
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. 오케이 DB 레벨에서도 alter sql 문을 통해서 설정을 변경해야겟네 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. @seongjunnoh 네, 맞습니다! JPA 엔티티 변경과 함께 데이터베이스 스키마도 동기화해야 합니다. 다음과 같이 진행하시면 됩니다: MySQL의 경우: ALTER TABLE users MODIFY COLUMN nickname_updated_at DATE NULL;PostgreSQL의 경우: ALTER TABLE users ALTER COLUMN nickname_updated_at DROP NOT NULL;배포 시 안전을 위해 스테이징 환경에서 먼저 테스트해보시고, 가능하다면 마이그레이션 스크립트로 버전 관리하시는 것을 권장합니다. 🧠 Learnings used
Contributor
Author
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.
수정해두었습니다!! 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.
|
||
| @Column(name = "oauth2_id", length = 50, nullable = false) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,4 +37,9 @@ public Map<Long, List<VoteItemQueryDto>> findVoteItemsByVoteIds(Set<Long> voteId | |
| ) | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public List<VoteItemQueryDto> findVoteItemsByVoteId(Long voteId, Long userId) { | ||
| return voteJpaRepository.findVoteItemsByVoteId(voteId, userId); | ||
| } | ||
|
Comment on lines
+41
to
+44
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. 💡 Verification agent 🧩 Analysis chain투표 항목 정렬 보장 필요(클라이언트 응답 일관성 관점). 현재 Repository가 반환하는 다음 스크립트로 Repository 구현에서 ORDER BY가 보장되는지 확인해 주세요. 정렬이 보장되지 않는 경우, 임시 방편으로 어댑터에서 ID 기준 정렬을 적용할 수 있습니다(비즈니스 정렬 기준이 따로 있다면 해당 기준을 사용). - public List<VoteItemQueryDto> findVoteItemsByVoteId(Long voteId, Long userId) {
- return voteJpaRepository.findVoteItemsByVoteId(voteId, userId);
- }
+ public List<VoteItemQueryDto> findVoteItemsByVoteId(Long voteId, Long userId) {
+ return voteJpaRepository.findVoteItemsByVoteId(voteId, userId)
+ .stream()
+ .sorted(java.util.Comparator.comparing(VoteItemQueryDto::voteItemId))
+ .toList();
+ }참고: 위 변경 시 import가 필요합니다. import java.util.Comparator;🏁 Script executed: #!/bin/bash
# findVoteItemsByVoteId 쿼리 정의 및 ORDER BY 여부 확인
rg -n "findVoteItemsByVoteId\\(" -A 10
rg -n "ORDER\\s+BY|order\\s+by" -A 2Length of output: 6109 투표 항목 정렬 보장 필요
🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,24 @@ | ||||||||||||||
| package konkuk.thip.vote.application.port.in.dto; | ||||||||||||||
|
|
||||||||||||||
| import java.util.List; | ||||||||||||||
|
|
||||||||||||||
| public record VoteResult( | ||||||||||||||
| Long voteItemId, | ||||||||||||||
| Long postId, | ||||||||||||||
| Long roomId, | ||||||||||||||
| Boolean type | ||||||||||||||
| List<VoteItemDto> voteItems | ||||||||||||||
| ) { | ||||||||||||||
| public static VoteResult of(Long voteItemId, Long roomId, Boolean type) { | ||||||||||||||
| return new VoteResult(voteItemId, roomId, type); | ||||||||||||||
| public record VoteItemDto( | ||||||||||||||
| Long voteItemId, | ||||||||||||||
| String itemName, | ||||||||||||||
| int percentage, | ||||||||||||||
| Boolean isVoted | ||||||||||||||
| ) { | ||||||||||||||
| public static VoteItemDto of(Long voteItemId, String itemName, int percentage, Boolean isVoted) { | ||||||||||||||
| return new VoteItemDto(voteItemId, itemName, percentage, isVoted); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| public static VoteResult of(Long postId, Long roomId, List<VoteItemDto> voteItems) { | ||||||||||||||
| return new VoteResult(postId, roomId, voteItems); | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+21
to
23
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. 🛠️ Refactor suggestion List 불변성 보장 필요 (외부 변경으로부터 VoteResult 보호) record 자체는 불변이지만, 생성자에 전달된 List는 외부에서 변경될 수 있습니다. 응답 DTO를 안전하게 유지하려면 방어적 복사를 권장합니다. 아래처럼 불변 리스트로 감싸주세요. - public static VoteResult of(Long postId, Long roomId, List<VoteItemDto> voteItems) {
- return new VoteResult(postId, roomId, voteItems);
+ public static VoteResult of(Long postId, Long roomId, List<VoteItemDto> voteItems) {
+ return new VoteResult(postId, roomId, List.copyOf(voteItems));
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -91,12 +91,15 @@ void vote_first_success() throws Exception { | |||||||||
| .requestAttr("userId", user.getUserId()) | ||||||||||
| .contentType(MediaType.APPLICATION_JSON) | ||||||||||
| .content(objectMapper.writeValueAsString(request))) | ||||||||||
| .andExpect(status().isOk()); | ||||||||||
| .andExpect(status().isOk()) | ||||||||||
| .andExpect(jsonPath("$.data.postId").value(vote.getPostId())) | ||||||||||
| .andExpect(jsonPath("$.data.roomId").value(room.getRoomId())) | ||||||||||
| .andExpect(jsonPath("$.data.voteItems[0].voteItemId").value(item.getVoteItemId())) | ||||||||||
| .andExpect(jsonPath("$.data.voteItems[0].isVoted").value(true)); | ||||||||||
|
|
||||||||||
| assertThat(voteParticipantJpaRepository.findAll()) | ||||||||||
| .hasSize(1) | ||||||||||
| .allMatch(vp -> vp.getVoteItemJpaEntity().getVoteItemId().equals(item.getVoteItemId())); | ||||||||||
|
|
||||||||||
| voteItemJpaRepository.findById(item.getVoteItemId()) | ||||||||||
| .ifPresent(voteItem -> assertThat(voteItem.getCount()).isEqualTo(1)); | ||||||||||
| } | ||||||||||
|
|
@@ -121,12 +124,13 @@ void vote_alreadyVoted_change_success() throws Exception { | |||||||||
| .requestAttr("userId", user.getUserId()) | ||||||||||
| .contentType(MediaType.APPLICATION_JSON) | ||||||||||
| .content(objectMapper.writeValueAsString(request))) | ||||||||||
| .andExpect(status().isOk()); | ||||||||||
| .andExpect(status().isOk()) | ||||||||||
| .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item2.getVoteItemId()).value(true)) | ||||||||||
| .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item1.getVoteItemId()).value(false)); | ||||||||||
|
Comment on lines
+127
to
+129
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. JsonPath 필터 결과는 배열입니다. 단일 값 비교 시 인덱싱이 필요합니다.
- .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item2.getVoteItemId()).value(true))
- .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item1.getVoteItemId()).value(false));
+ .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted[0]", item2.getVoteItemId()).value(true))
+ .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted[0]", item1.getVoteItemId()).value(false));대안: .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item2.getVoteItemId()).value(hasItem(true)))🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| assertThat(voteParticipantJpaRepository.findAll()) | ||||||||||
| .hasSize(1) | ||||||||||
| .allMatch(vp -> vp.getVoteItemJpaEntity().getVoteItemId().equals(item2.getVoteItemId())); | ||||||||||
|
|
||||||||||
| voteItemJpaRepository.findById(item1.getVoteItemId()) | ||||||||||
| .ifPresent(voteItem -> assertThat(voteItem.getCount()).isEqualTo(0)); | ||||||||||
| voteItemJpaRepository.findById(item2.getVoteItemId()) | ||||||||||
|
|
@@ -167,7 +171,8 @@ void vote_cancel_success() throws Exception { | |||||||||
| .requestAttr("userId", user.getUserId()) | ||||||||||
| .contentType(MediaType.APPLICATION_JSON) | ||||||||||
| .content(objectMapper.writeValueAsString(request))) | ||||||||||
| .andExpect(status().isOk()); | ||||||||||
| .andExpect(status().isOk()) | ||||||||||
| .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item.getVoteItemId()).value(false)); | ||||||||||
|
|
||||||||||
|
Comment on lines
+174
to
176
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. JsonPath 필터 결과 인덱싱 누락 동일 이슈. 여기도 배열 비교 이슈가 동일하게 발생할 수 있습니다. - .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted", item.getVoteItemId()).value(false));
+ .andExpect(jsonPath("$.data.voteItems[?(@.voteItemId == %s)].isVoted[0]", item.getVoteItemId()).value(false));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| assertThat(voteParticipantJpaRepository.findAll()).isEmpty(); | ||||||||||
| voteItemJpaRepository.findById(item.getVoteItemId()) | ||||||||||
|
|
@@ -208,4 +213,5 @@ void vote_userNotRoomMember_fail() throws Exception { | |||||||||
| .andExpect(status().isForbidden()) | ||||||||||
| .andExpect(jsonPath("$.code").value(ROOM_ACCESS_FORBIDDEN.getCode())); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| } | ||||||||||
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.
💡 Verification agent
🧩 Analysis chain
server.profile 미설정 시 구동 실패 가능성 — 기본값 지정 권장
운영 환경에서 항상
server.profile이 주입된다는 보장이 없다면 애플리케이션 부팅 시점에 실패할 수 있습니다. 안전한 기본값(local)을 지정하는 것을 권장합니다.아직
server.profile가 어떻게 사용·설정되는지 확인이 필요해 보입니다. 코드 및 설정 파일 전반에서 해당 프로퍼티가 어디에 선언/사용되는지 찾아보겠습니다.🏁 Script executed:
Length of output: 135
서버 프로필 기본값 지정 제안
server.profile프로퍼티가 없을 경우 애플리케이션 구동 시점에Could not resolve placeholder 'server.profile'예외가 발생할 수 있습니다.안전하게 기본값(
local)을 지정해 주세요.src/main/java/konkuk/thip/config/OpenApiConfig.java(라인 49)📝 Committable suggestion
🤖 Prompt for AI Agents