8주차 미션 [카카]#17
Open
KateteDeveloper wants to merge 30 commits into
Open
Conversation
refactor: DTO 클래스를 record 타입으로 변환 및 Enum 상수 추가 - Home, Mission, Member, Review 도메인의 DTO 클래스(@Getter, @builder)를 Java record로 리팩토링 - DTO 내부 클래스의 불필요한 'Class' 접미사 제거 (예: MyDataReqClass -> MyDataReq) - Controller, Service, Converter 계층에 record 타입 변경 사항(생성자 및 접근자) 반영 - Gender, Term, SocialType Enum에 필요한 상수(MALE, FEMALE, REQUIRED, OPTIONAL 등) 추가 ```
```text
refactor: 리뷰 생성 API 수정 (marketId PathVariable 적용 및 ID 타입 Long 변경)
```
**Breakdown of the changes covered:**
* **Controller**: Updated the review creation endpoint to accept `marketId` as a `@PathVariable` (`/v1/create/{marketId}`).
* **DTO**: Removed `marketId` and `regionId` from `CreateReviewReq` since `marketId` is now handled via the URL path.
* **Entity**: Changed the data types of `marketId` and `regionId` in the `Review` entity from `Integer` to `Long`.
* **Service & Converter**: Updated methods to pass `marketId` from the controller down to the entity builder.
…based on your examples: ```text feat: update CreateReviewRes DTO and ReviewConverter, remove regionId from Review entity ``` **Here is a quick breakdown of the specific changes mapped in this commit:** * **`Review.java`**: Removed the `regionId` field and its corresponding column mapping. * **`ReviewResDTO.java`**: Populated the `CreateReviewRes` record with `reviewId`, `stars`, and `content` fields (previously an empty record). * **`ReviewConverter.java`**: Updated the `toCreateReview` method to properly map the `Review` entity data to the newly updated `CreateReviewRes` DTO instead of returning an empty object.
```text feat: enhance domain entities, introduce BaseEntity, and configure JPA mappings ``` **Breakdown of the specific changes covered in this commit:** * **Global**: * Introduced an abstract `BaseEntity` class with JPA auditing (`createdAt`, `updateAt`, `deleatedAt`) to unify and automate timestamp management across the application. * **Member Domain**: * Updated the `Member` entity to extend `BaseEntity` and added new fields for `detailAddress`, `socialUid`, and `socialType`. * Configured `Food` and `Term` entities, along with `MemberFood` and `MemberTerm` mapping entities to establish many-to-many relationships with the user. * Created the `FoodName` enum and updated the `Term` enum with new constants (AGE, SERVICE, PRIVACY, LOCATION, MARKETING). * **Review Domain**: * Created a new `Reply` entity (extending `BaseEntity`). * Updated the `Review` entity to include a `@OneToOne` relationship with `Reply` and changed its ID generation strategy to `IDENTITY`. * **Mission Domain**: * Refactored the `Mission` entity to extend `BaseEntity`, stripping out redundant explicitly defined timestamp and status fields. * Created the `MemberMission` mapping entity to manage the many-to-many relationship between users and missions, accompanied by a new `MissionStatus` enum (`IDLE`, `CHALLENGING`, `COMPLETE`).
- `Review.java`: 불필요한 `marketId` 필드를 삭제하고 `Store` 엔티티와의 `@ManyToOne` 연관관계 추가 - `StoreRepository.java`, `MemberMissionRepository.java`: 엔티티 조회를 위한 JpaRepository 인터페이스 신규 추가 - `ReviewService.java`, `ReviewConverter.java`: 리뷰 생성 시 전달받은 `storeId`로 `Store` 엔티티를 직접 조회하여 객체에 매핑하도록 로직 수정 - `MissionService.java`, `MissionConverter.java`: 사용자 미션 목록 조회 기준을 `Mission` 엔티티에서 `MemberMission` 매핑 엔티티로 변경 - `MissionResDTO.java`: `MissionList` DTO 내 `status` 필드 타입을 `String`에서 `MissionStatus` Enum 클래스로 변경 ```
- MemberController: 마이페이지 조회 API(`/v1/users/me`)의 HTTP 메서드를 POST에서 GET으로 변경하고, @RequestBody(DTO) 대신 @RequestParam으로 `id`를 받도록 수정 - MemberService: `getInfo` 메서드 파라미터를 `MemberReqDTO.GetInfo` 객체에서 `Long memberId`로 변경 - Member Entity: `socialType` 필드 타입을 String에서 `SocialType` Enum으로 변경 - Term Entity 및 Enum: 기존 `Term` Enum 클래스명을 `TermName`으로 변경하고 엔티티의 `name` 필드 타입에 반영 - MemberTerm, MemberFood Entity: 잘못 임포트된 Spring Data의 `@Id` 어노테이션을 JPA(`jakarta.persistence.Id`)로 수정 및 불필요한 import 제거 - MissionRepository: 사용하지 않는 `findByMemberId` 메서드 삭제 ```
- `MemberMissionRepository`에 `@Query`를 사용한 `findByMemberIdAndStatusIn` 커스텀 쿼리 메서드 추가 - N+1 문제를 방지하기 위해 `Mission`과 `Store` 엔티티를 `JOIN FETCH`로 함께 조회하도록 쿼리 작성 - `MissionService.getMissions()` 메서드에서 진행 중(`CHALLENGING`)이거나 완료(`COMPLETE`)된 미션만 조회하도록 상태 조건 필터링 추가 - `PageRequest` 객체를 사용해 데이터베이스 단에서 페이징 처리가 이루어지도록 로직을 개선하고, `hasNext` 판별 로직 수정 ```
…회 기능 추가 - HomeController: 미션 목록 조회 API(`/v1/missions`)에 특정 지역 필터링을 위한 `@RequestParam Long locationId` 파라미터 추가 - HomeService: 기존 전체 조회(findAll) 임시 로직을 제거하고, `Pageable`과 `locationId`를 활용해 해당 지역의 미션만 조회하도록 로직 변경 - HomeService: 다음 페이지 존재 여부(hasNext) 판단 기준을 `missions.size() == size`로 수정하여 페이지네이션 로직 개선 - MissionRepository: 스토어(store)와 위치(location) 정보를 페치 조인(JOIN FETCH)하여, 전달받은 `locationId`와 일치하는 미션 목록을 최신순(ORDER BY m.id DESC)으로 반환하는 `findByLocationId` 쿼리 메서드 추가 ```
- MissionRepository의 findByLocationId 메서드 반환 타입을 List에서 Slice로 변경 - HomeService의 getMissions 메서드에서 리스트 크기로 다음 페이지 존재 여부를 수동 계산하던 방식을 Slice의 hasNext() 메서드를 활용하도록 수정 ```
- HomeService의 getMyData 메서드 내 임시로 사용하던 RuntimeException("멤버 없음")을 커스텀 예외인 MemberException으로 교체
- 예외 발생 시 MemberErrorCode.MEMBER_NOT_FOUND 에러 코드를 사용하도록 수정
```
- SignUpReqDTO의 SignUpReqBody 레코드 내 필드에 @notblank, @Email, @pattern, @NotNull 등 유효성 검증 어노테이션 추가 - MemberController의 getSignUp 엔드포인트에서 요청 객체를 검증하도록 @Valid 어노테이션 적용 - MemberService에 있던 Member 엔티티 빌더 생성 로직을 MemberConverter.toMember()로 이동하여 계층 역할 분리 - MissionConverter.toCompleteMission() 응답 시 하드코딩된 완료 메시지를 Service 단에서 파라미터로 전달받도록 메서드 시그니처 수정 ```
- MemberMissionRepository: findByMemberIdAndStatusIn 메서드 반환 타입을 List에서 Slice로 변경 - MissionService: Slice 객체의 hasNext()를 활용하도록 페이징 처리 로직 수정 - MissionService: completeMission() 수행 시 완료 상태(COMPLETE)의 MemberMission을 DB에 저장하는 로직 추가 - MissionService: 하드코딩된 성공 응답 메시지를 MissionSuccessCode.OK 상수 값으로 대체 - MissionConverter: Mission 엔티티를 MemberMission으로 변환하는 toMemberMission() 메서드 추가 - Store: location 필드의 @manytoone 연관관계에 지연 로딩(FetchType.LAZY) 속성 적용 ```
- mission 패키지에 위치하던 Store 관련 클래스(Store, Location, StoreRepository)를 store 패키지로 이동 및 관련 import 구문 수정 - ReviewReqDTO.CreateReviewReq DTO에 커스텀 별점 검증(@ValidStars) 및 내용 글자 수 검증(@notblank, @SiZe) 어노테이션 추가 - 별점 입력을 0.5 단위, 0.5~5.0 범위로 검증하는 커스텀 어노테이션(@ValidStars) 및 검증 로직(ValidStarsValidator) 추가 - Review 엔티티에 Member 엔티티와의 @manytoone 연관관계(member_id) 매핑 추가 - Store 전용 커스텀 예외(StoreException) 및 에러 코드(StoreErrorCode)를 추가하고, ReviewService의 Store 조회 로직에 적용 ```
- `Address` Enum에 특별시/광역시 및 서울 주요 구 지역 상수 추가 - `SignUpReqDTO`의 `address` 필드 검증용 `@Pattern` 정규식에 추가된 `Address` 상수 목록 반영 - `Location` 엔티티의 식별자 컬럼명을 `location_id`로 명시하고, 주소 매핑 컬럼명을 `address`에서 `name`으로 변경 및 `nullable = false` 제약조건 추가 - `Store` 엔티티의 식별자 컬럼명을 `store_id`로 명시하고, `managerNumber` 및 `detailAddress` 필드 추가 - `Store` 엔티티의 `name`, `managerNumber`, `detailAddress`, `location_id`(연관관계) 컬럼에 `nullable = false` 제약조건 적용
- MissionController의 기본 URL을 `/api/v1`으로 변경하고, 가게 미션 생성(`POST /stores/{storeId}/missions`) 및 커서 기반 조회(`GET /stores/{storeId}/missions`) API 추가
- MissionService에 `StoreRepository`를 연동하여 새로운 미션을 생성하는 로직과 커서(Cursor) 기반 페이지네이션을 적용한 미션 조회 로직 구현
- MissionRepository에 가게 ID와 커서(ID 내림차순)를 조건으로 미션을 조회하는 메서드(`findMissionsByStore_IdAndIdLessThanOrderByIdDesc` 등) 추가
- MissionReqDTO에 미션 생성 요청용 `CreateMission` record를 추가하고 데이터 유효성 검증(Validation) 어노테이션 적용
- MissionResDTO에 응답용 `GetMissionRes`와 커서 페이징 데이터 처리를 위한 공통 포맷인 `Pagination` record 추가
- MissionConverter에 신규 DTO와 Entity 간의 데이터 변환을 처리하는 매핑 메서드(`toMission`, `toGetMission`, `toPagination` 등) 추가
- MissionErrorCode에 잘못된 쿼리 요청을 처리하는 `QUERY_NOT_VALID` 예외 코드를 추가하고, MissionSuccessCode에 `CREATED` 성공 코드 추가
- `GeneralExceptionAdvice`에 `MethodArgumentNotValidException` 핸들러를 추가하여 필드별 유효성 검증 에러 메시지 반환 로직 구현 - `SignUpReqDTO`, `MissionReqDTO`, `ReviewReqDTO`의 검증 어노테이션(@notblank, @pattern, @SiZe, @NotNull 등)에 구체적인 `message` 속성 추가 - `MissionController`의 가게 미션 조회 API(`getMissions`) 반환 타입을 `List`에서 `Pagination` 구조로 변경 - 프로젝트 전반에 걸쳐 record 기반 DTO 클래스의 빈 중괄호 포맷팅 수정 - 다수의 Controller, Service, Repository, Entity 계층에서 사용하지 않는 불필요한 import 문 제거 및 와일드카드 import(`lombok.*`)를 명시적 선언으로 변경
- `MissionController`에 내가 진행중인 미션 조회 엔드포인트(`/missions/my`) 추가 - `MissionReqDTO`에 회원 ID 및 페이지네이션 정보를 받는 `GetMyMissionsReq` DTO 추가 - `MissionResDTO`에 미션 목록 응답을 처리하는 `MyMissionList`, `MyMissionPage` DTO 추가 - `MissionService`에 `CHALLENGING`, `COMPLETE` 상태의 미션을 페이징하여 반환하는 `getMyMissions` 비즈니스 로직 구현 ``` --- ```text ♻️ refactor(converter): 미션 응답 Converter 파라미터 및 메서드명 수정 - `MissionConverter`의 변환 메서드명을 `toMissionList` -> `toMyMissionList`, `toMissionPage` -> `toMyMissionPage`로 각각 변경 - 응답 변환 로직에서 `List<MemberMission>` 대신 `Slice<MemberMission>`을 직접 파라미터로 받도록 개선하여 불필요한 `hasNext` 인자 제거 - 데이터 리스트 변환 시 `Collectors.toList()` 호출을 `toList()`로 간소화 ``` --- ```text 🗄️ db(entity): Store 엔티티 식별자 매핑 컬럼명 변경 - `Store` 엔티티의 기본키 컬럼 매핑을 `@Column(name = "store_id")`에서 `@Column(name = "id")`로 변경 ```
- page 파라미터에 `@Min(value = 0)` 어노테이션을 추가하여 0 이상 값만 허용하도록 설정 - size 파라미터에 `@Min(value = 1)` 어노테이션을 추가하여 1 이상 값만 허용하도록 설정
- `nextCursor` 변수의 초기값을 "-1"로 할당 - `missionList`의 결과가 비어있지 않은 경우에만 `getLast()`를 호출하여 `nextCursor`를 업데이트하도록 조건문 추가 (빈 리스트 접근으로 인한 예외 방지) - `query` 파라미터 검증 로직의 불필요한 `switch` 문을 `equalsIgnoreCase`를 활용한 `if` 문으로 단순화하여 가독성 개선
- ReviewService의 별점 순 정렬 시 커서를 `stars:id` 형태로 파싱하여 동일 별점 데이터도 정상적으로 페이징되도록 로직 수정 - ReviewRepository에 별점 및 ID 복합 조건을 처리하는 `findByMemberIdAndStarsLessThanOrStarsAndIdLessThan` 커스텀 쿼리 메서드 추가 - 유효하지 않은 정렬 기준 요청 시 사용할 `QUERY_NOT_VALID` 에러 코드를 ReviewErrorCode에 추가 - ReviewService에서 잘못된 정렬 기준 처리 시 기존 RuntimeException 대신 추가된 커스텀 예외(ReviewException)를 던지도록 변경
✨ feat(config): Spring Security 설정 및 의존성 추가 - build.gradle에 spring-boot-starter-security 및 spring-security-test 의존성 추가 - SecurityConfig 클래스를 생성하여 SecurityFilterChain 및 PasswordEncoder(BCryptPasswordEncoder) 빈 등록 - Swagger 관련 URI(/swagger-ui/**, /v3/api-docs/** 등) 및 /auth/** 경로에 대한 접근을 허용(permitAll)하도록 설정 - 기본 폼 로그인 및 로그아웃(URL 매핑 및 성공 후 리다이렉트 URL) 설정 적용 - global/config 패키지 내부에 빈 내용의 SwaggerConfig 클래스 파일 생성 ```
- SignUpReqDTO에 약관 동의 정보(AgreeReq), 선호 음식 리스트(foodList), 상세 주소(detailAddress) 필드 추가 및 불필요한 필드(phoneNumber, agreedId) 제거 - TermRepository, FoodRepository, MemberTermRepository, MemberFoodRepository JpaRepository 인터페이스 4종 신규 생성 - MemberErrorCode에 약관(TERM_NOT_FOUND) 및 음식(FOOD_NOT_FOUND) 조회를 위한 예외 코드 추가 - MemberConverter에 회원과 약관, 회원과 음식을 연결하는 매핑 엔티티(MemberTerm, MemberFood) 변환 메서드 추가 - MemberService의 signUp() 메서드에서 회원가입 요청 시 전달받은 약관 동의 내역과 선호 음식을 검증하고 연관관계 엔티티로 저장하도록 수정
✨ feat(security): Spring Security 인증 및 예외 처리 클래스 추가 - UserDetails 인터페이스를 구현하여 Member 엔티티 정보를 담는 AuthMember 클래스 추가 - MemberRepository를 통해 이메일 기반으로 사용자를 조회하는 CustomUserDetailsService 추가 - 인증 예외 발생 시 UNAUTHORIZED 커스텀 JSON 응답을 반환하는 CustomEntryPoint 추가 - 인가 예외 발생 시 UNAUTHORIZED 커스텀 JSON 응답을 반환하는 CustomAccessDenied 추가 ```
… 컬럼명 수정 - FoodName.java: 'Italian' 상수를 대문자 'ITALIAN'으로 변경 - FoodRepository.java: findByName() 메서드의 파라미터 타입을 String에서 FoodName Enum 타입으로 변경 - Location.java: ID 컬럼 매핑 이름을 'location_id'에서 'id'로 변경
- Umc10thApplication 메인 클래스에 @EnableJpaAuditing 어노테이션 추가
- HttpSecurity 설정에 `exceptionHandling()`을 추가하여 커스텀 예외 처리기 등록 - 접근 권한 거부 처리를 위한 `customAccessDenied()` 메서드 및 Bean 추가 - 인증 실패 처리를 위한 `customEntryPoint()` 메서드 및 Bean 추가
- SignUpReq 레코드의 `birth`, `foodList` 필드에 API 문서화를 위한 `@Schema` 어노테이션(description, example) 추가 - AgreeReq 레코드의 모든 boolean 필드(`age`, `service`, `privacy`, `location`, `marketing`)에 `@Schema(example = "true")` 속성 명시
✨ feat(service): 회원가입 비밀번호 암호화 로직 추가 및 음식 카테고리 Enum 매핑 수정 - `MemberService`의 회원가입(`getSignUp`) 로직에 `PasswordEncoder`를 활용한 비밀번호 암호화 처리 추가 - `MemberConverter`의 `toMember` 메서드가 암호화된 비밀번호(`encodedPassword`)를 주입받아 엔티티를 생성하도록 매개변수 수정 - `MemberService` 내 선호 음식 리스트 조회 시, 문자열을 대문자로 변환(`toUpperCase()`) 후 `FoodName` Enum 상수와 매핑되도록 로직 수정 --- ♻️ refactor(controller): 마이페이지 조회 API 컨트롤러 분리 및 미사용 API 주석 처리 - 기존 `MemberController`에 위치하던 마이페이지 조회 API(`/v1/users/me`)를 신규 생성한 `MemberMyPageController`로 이동하여 분리 - `MemberController` 내 불필요한 테스트용 API(`/test`, `/singleParameter`)를 블록 주석 처리하여 비활성화 --- 🔧 chore(config): API 경로 보안 인증 규칙 추가 - `SecurityConfig` 파일에서 `/api/**` 하위 모든 요청에 대해 인증(`authenticated()`)을 요구하도록 보안 설정 추가 --- 🗄️ db(repository): 이메일 기반 회원 조회 메서드 추가 - `MemberRepository`에 이메일을 조건으로 회원을 조회하는 `findByEmail` 쿼리 메서드 추가
yangjiae12
approved these changes
May 24, 2026
Comment on lines
+41
to
+44
| @Transactional // 나중에 DB 연결 | ||
| public SignUpResDTO.SignUpResBody getSignUp( | ||
| SignUpReqDTO.SignUpReqBody dto | ||
| ) { |
Member
There was a problem hiding this comment.
회원가입 전에 동일 이메일로 이미 가입된 회원이 있는지 확인하는 로직이 추가되면 좋을 것 같습니다!
Comment on lines
+58
to
+62
| termMap.forEach((termName, isAgreed) -> { | ||
| if (isAgreed) { | ||
| Term term = termRepository.findByName(termName) | ||
| .orElseThrow(() -> new MemberException(MemberErrorCode.TERM_NOT_FOUND)); | ||
| memberTermRepository.save(MemberConverter.toMemberTerm(member, term)); |
Member
There was a problem hiding this comment.
필수 약관 미동의 시 예외를 발생시키는 검증이 추가되면 좋을 것 같습니다.
Comment on lines
+67
to
+70
| FoodName enumFoodName = FoodName.valueOf(foodStr.toUpperCase()); | ||
|
|
||
| Food food = foodRepository.findByName(enumFoodName) | ||
| .orElseThrow(() -> new MemberException(MemberErrorCode.FOOD_NOT_FOUND)); |
Member
There was a problem hiding this comment.
findByName()에서 음식이 없을 경우 MemberException으로 처리하고 있는 점은 좋습니다. 다만 그 전에 FoodName.valueOf()에서 enum에 없는 값이 들어오면 IllegalArgumentException이 먼저 발생해 FOOD_NOT_FOUND 예외로 처리되지 않을 수 있습니다. 잘못된 음식 값에 대한 응답 형식을 통일하려면 enum 변환 부분도 예외 처리해주면 좋을 것 같습니다!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🛠️ 주요 구현 내용
1. 비밀번호 BCrypt 암호화 적용
SecurityConfig에BCryptPasswordEncoder를 빈으로 등록하여 활용함.MemberService에서 회원가입 요청을 처리할 때, Plain Text로 들어온 비밀번호를passwordEncoder.encode()를 통해 암호화(솔트 처리)한 후 데이터베이스에 적재하도록 구현함.MemberConverter를 수정하여 엔티티 변환 시 암호화된 패스워드가 안전하게 주입되도록 보장함.2. Public / Private API 경로 분리 (
SecurityConfig)"/auth/")을allowUris배열로 묶어.permitAll()처리함."/api/"경로로 설정하고.authenticated()를 지정하여 비인증 사용자의 접근을 차단함.MemberController(/auth)와 인증 회원용 통로인MemberMyPageController(/api)로 분리 운영함.3. 인증/인가 예외 처리 통일 (Exception Handling)
AuthenticationEntryPoint를 구현한CustomEntryPoint를 등록하여 인증 실패(401) 시 규격화된 커스텀 JSON 응답이 반환되도록 함.AccessDeniedHandler를 구현한CustomAccessDenied를 등록하여 인가 실패(403) 시에도 동일한 포맷의 예외 응답을 반환하여 전역 예외 처리 규격을 통일함.📸 테스트 결과 및 검증
회원가입 API 테스트 (Public)
/auth/v1/signup요청 시 토큰 없이도200 OK성공 응답이 반환테이블 조회 및 인텔리제이 디버거를 통해
password컬럼에 원래 문자열이 아닌$2a$10$...형태의 복잡마이페이지 API 테스트 (Private & 예외 처리)
/api/v1/users/me를 찔렀을 때,anyRequest().authenticated()정책에 의해 차단되는 것을 확인함.CustomEntryPoint가 가로채어 미리 정의한401 인증되지 않은 사용자입니다.규격의 공통 JSON 바디를 정확하게 응답하는 것을 확인함.🤔 질문
.
💬 기타 공유 사항
.