-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
[FEAT] 회원 관리 리팩토링 및 리뷰 기능 구현
📋 이슈 개요
일반 회원(보호자)이 어르신을 관리하고, 기관을 이용하며, 리뷰를 작성할 수 있는 완전한 사용자 기능을 구현합니다.
🎯 핵심 구현 목표
✅ 반드시 구현해야 할 기능
- 회원 Auth 인증 연결 - Local/OAuth 회원가입 후 Member 생성 확인
- 회원 프로필 관리 - 내 정보 조회/수정/탈퇴
- 어르신 프로필 관리 - 여러 어르신 등록 및 관리
- 리뷰 관리 - 완료된 예약에 대한 리뷰 작성
- 리뷰 신고 - 부적절한 리뷰 신고
- 마이페이지 - 내 정보/리뷰 한눈에 보기
📋 전체 API 엔드포인트 목록
Part 1: 회원 프로필 관리
| Method | Endpoint | 설명 | 권한 | 상태 |
|---|---|---|---|---|
| GET | /api/v1/members/{memberId} |
회원 조회 | 공개 | ✅ 완료 |
| GET | /api/v1/members/{memberId}/detail |
회원 상세 조회 (어르신 포함) | 공개 | ✅ 완료 |
| GET | /api/v1/members |
회원 목록 조회 (관리자) | ADMIN | ✅ 완료 |
| PUT | /api/v1/members/{memberId} |
회원 정보 수정 | USER | ✅ 완료 |
| DELETE | /api/v1/members/{memberId} |
회원 삭제 | USER | ✅ 완료 |
| GET | /api/v1/members/me |
내 정보 조회 | USER | ❌ 구현 필요 |
| PUT | /api/v1/members/me |
내 정보 수정 | USER | ❌ 구현 필요 |
| DELETE | /api/v1/members/me |
회원 탈퇴 (Soft) | USER | ❌ 구현 필요 |
| GET | /api/v1/members/me/statistics |
내 활동 통계 | USER | ❌ 구현 필요 |
Part 2: 어르신 프로필 관리
| Method | Endpoint | 설명 | 권한 | 상태 |
|---|---|---|---|---|
| POST | /api/v1/members/{memberId}/elderly-profiles |
어르신 프로필 등록 | USER | ✅ 완료 |
| GET | /api/v1/members/{memberId}/elderly-profiles |
어르신 프로필 목록 | USER | ✅ 완료 |
| GET | /api/v1/members/{memberId}/elderly-profiles/{profileId} |
어르신 프로필 상세 | USER | ✅ 완료 |
| PUT | /api/v1/members/{memberId}/elderly-profiles/{profileId} |
어르신 프로필 수정 | USER | ✅ 완료 |
| DELETE | /api/v1/members/{memberId}/elderly-profiles/{profileId} |
어르신 프로필 삭제 | USER | ✅ 완료 |
Part 3: 리뷰 관리
| Method | Endpoint | 설명 | 권한 | 상태 |
|---|---|---|---|---|
| POST | /api/v1/reviews |
리뷰 작성 | USER | ❌ 구현 필요 |
| GET | /api/v1/reviews/my |
내가 작성한 리뷰 목록 | USER | ❌ 구현 필요 |
| GET | /api/v1/reviews/{reviewId} |
리뷰 상세 조회 | 공개 | ❌ 구현 필요 |
| PUT | /api/v1/reviews/{reviewId} |
리뷰 수정 | USER | ❌ 구현 필요 |
| DELETE | /api/v1/reviews/{reviewId} |
리뷰 삭제 | USER | ❌ 구현 필요 |
| GET | /api/v1/institutions/{institutionId}/reviews |
기관의 리뷰 목록 (공개) | 공개 | ❌ 구현 필요 |
Part 4: 리뷰 신고
| Method | Endpoint | 설명 | 권한 | 상태 |
|---|---|---|---|---|
| POST | /api/v1/reviews/{reviewId}/report |
리뷰 신고 | USER | ❌ 구현 필요 |
Part 5: 마이페이지
| Method | Endpoint | 설명 | 권한 | 상태 |
|---|---|---|---|---|
| GET | /api/v1/members/me/mypage |
마이페이지 통합 데이터 | USER | ❌ 구현 필요 |
📊 통계
총 API 개수: 21개
✅ 완료: 10개 (회원 기본 5개, 어르신 프로필 5개)
❌ 구현 필요: 11개
Part별 개수:
- Part 1 (회원 프로필): 9개 (5개 완료, 4개 필요)
- Part 2 (어르신 프로필): 5개 (전체 완료 ✅)
- Part 3 (리뷰 관리): 6개 (전체 필요)
- Part 4 (리뷰 신고): 1개 (필요)
- Part 5 (마이페이지): 1개 (필요)
✅ API 우선순위
1순위 (핵심 기능)
GET /api/v1/members/me- 내 정보 조회 (Auth 연동)PUT /api/v1/members/me- 내 정보 수정 (Auth 연동)POST /api/v1/reviews- 리뷰 작성GET /api/v1/reviews/my- 내 리뷰 목록GET /api/v1/institutions/{institutionId}/reviews- 기관 리뷰 목록 (공개)
2순위 (관리 기능)
PUT /api/v1/reviews/{reviewId}- 리뷰 수정DELETE /api/v1/reviews/{reviewId}- 리뷰 삭제POST /api/v1/reviews/{reviewId}/report- 리뷰 신고DELETE /api/v1/members/me- 회원 탈퇴
3순위 (부가 기능)
GET /api/v1/members/me/statistics- 활동 통계GET /api/v1/members/me/mypage- 마이페이지
🔐 Part 1: 회원 Auth 인증 연결
현재 상황
- ✅ AuthController에서 Local/OAuth 회원가입 완료
- ✅ Member 엔티티 생성됨
- ✅ 기본 회원 정보 조회/수정 API 완료
확인 필요 사항
Local 회원가입
POST /api/v1/auth/register
입력: accountId, password, 이름, 전화번호, 생년월일, 성별, 주소
결과: Member 생성 (role = USER)
OAuth 회원가입
POST /api/v1/auth/oauth2/register
입력: provider, 이름, 전화번호, 생년월일, 성별, 주소
결과: Member 생성 (role = USER)
👤 Part 2: 회원 프로필 관리
구현 완료 (✅)
1. 회원 조회
GET /api/v1/members/{memberId}
권한: 공개
응답: 이름, 전화번호, 이메일, 성별, 생년월일, 주소, 프로필 이미지
2. 회원 상세 조회 (어르신 포함)
GET /api/v1/members/{memberId}/detail
권한: 공개
응답: 회원 정보 + 등록된 어르신 프로필 목록
3. 회원 목록 조회 (관리자)
GET /api/v1/members
권한: ADMIN
페이징: page, size
4. 회원 정보 수정
PUT /api/v1/members/{memberId}
권한: USER
수정 가능: 이름, 전화번호, 주소
5. 회원 삭제
DELETE /api/v1/members/{memberId}
권한: USER
동작: Soft Delete
구현 필요 (❌)
현재 /api/v1/members/{memberId} 형태로만 구현되어 있습니다.
@AuthenticationPrincipal을 활용한 /me 엔드포인트를 추가해야 합니다.
6. 내 정보 조회 (Auth 연동)
GET /api/v1/members/me
권한: USER
동작:
- @AuthenticationPrincipal MemberDetails로 사용자 ID 추출
- memberService.getMemberById(memberDetails.getId()) 호출
응답: 내 회원 정보
7. 내 정보 수정 (Auth 연동)
PUT /api/v1/members/me
권한: USER
동작:
- @AuthenticationPrincipal로 본인 확인
- memberService.updateMember(memberDetails.getId(), request)
8. 회원 탈퇴 (Soft Delete)
DELETE /api/v1/members/me
권한: USER
동작:
- Member.deleted = true
- 개인정보 마스킹 (이름, 전화번호 등)
- 진행 중인 예약이 있으면 탈퇴 불가
검증:
- 활성 예약(PENDING, CONFIRMED) 확인
- 탈퇴 사유 선택적 기록
9. 내 활동 통계
GET /api/v1/members/me/statistics
권한: USER
응답:
- 등록한 어르신 수
- 작성한 리뷰 개수
- 가입일
👵 Part 3: 어르신 프로필 관리
✅ 전체 완료!
1. 어르신 프로필 등록 ✅
POST /api/v1/members/me/elderly-profiles
권한: USER
입력:
- 이름, 성별, 생년월일 (필수)
- 혈액형, 전화번호
- 활동 능력, 인지 능력
- 특이사항, 주소
2. 내 어르신 프로필 목록 ✅
GET /api/v1/members/me/elderly-profiles
권한: USER
3. 어르신 프로필 상세 ✅
GET /api/v1/members/me/elderly-profiles/{profileId}
권한: USER (본인이 등록한 프로필만)
4. 어르신 프로필 수정 ✅
PUT /api/v1/members/me/elderly-profiles/{profileId}
권한: USER
5. 어르신 프로필 삭제 ✅
DELETE /api/v1/members/me/elderly-profiles/{profileId}
권한: USER
⭐ Part 4: 리뷰 관리
구현 필요 (❌)
1. 리뷰 작성
POST /api/v1/reviews
권한: USER
입력:
- reservationId (예약 ID)
- content (리뷰 내용, 10~500자)
- rating (별점, 1~5)
- tagIds (리뷰 태그, 선택, 최대 10개)
검증:
- 예약이 본인 것인지 확인
- 예약 상태가 COMPLETED인지 확인
- 이미 리뷰를 작성했는지 확인 (중복 방지)
- 예약 완료 후 90일 이내인지 확인
2. 내가 작성한 리뷰 목록
GET /api/v1/reviews/my
권한: USER
정렬: 최신순
페이징: page, size
3. 리뷰 상세 조회
GET /api/v1/reviews/{reviewId}
권한: 공개
응답:
- 리뷰 내용, 별점
- 작성자 정보 (이름, 프로필 이미지)
- 기관 정보
- 리뷰 태그
- 작성일시, 수정일시
4. 리뷰 수정
PUT /api/v1/reviews/{reviewId}
권한: USER (작성자 본인만)
수정 가능: 내용, 별점, 태그
제한: 작성 후 30일 이내만 수정 가능
5. 리뷰 삭제
DELETE /api/v1/reviews/{reviewId}
권한: USER (작성자 본인만)
동작: Soft Delete (deleted = true)
6. 기관의 리뷰 목록 (공개)
GET /api/v1/institutions/{institutionId}/reviews
권한: 공개
필터:
- 신고되지 않은 리뷰만 (isReported = false)
- 삭제되지 않은 리뷰만 (deleted = false)
정렬: 최신순, 별점순
페이징: page, size
🚨 Part 5: 리뷰 신고
구현 필요 (❌)
리뷰 신고
POST /api/v1/reviews/{reviewId}/report
권한: USER
입력:
- reportReason (신고 사유: 욕설, 광고, 허위사실, 기타)
- description (상세 설명, 선택, 최대 500자)
검증:
- 본인 리뷰는 신고 불가
- 중복 신고 불가 (동일 회원이 같은 리뷰 재신고 방지)
동작:
- ReviewReport 엔티티 생성
- Review.isReported = true
- 관리자 검토 대기 상태
🏠 Part 6: 마이페이지
구현 필요 (❌)
마이페이지 통합 데이터
GET /api/v1/members/me/mypage
권한: USER
응답:
- 회원 기본 정보 (이름, 프로필 이미지)
- 어르신 프로필 요약 (최대 3개)
- 작성한 리뷰 (최대 5개, 최신순)
- 통계
- 리뷰 수
- 가입일
🏗️ 엔티티 구조
Member (회원)
✅ 이미 존재
- id, name, phoneNumber, email
- gender, birthDate, profileImageUrl
- address, role, deleted
ElderlyProfile (어르신 프로필)
✅ 이미 존재
- id, member, name, gender, birthDate
- bloodType, phoneNumber
- activityLevel, cognitiveLevel
- specialNotes, address, location
Review (리뷰)
✅ 이미 존재 (확인 필요)
- id, reservation, member, institution
- content, rating
- isReported, deleted
- createdAt, updatedAt
ReviewTagMapping (리뷰 태그 매핑)
✅ 이미 존재
- id, review, tag
ReviewReport (리뷰 신고)
❌ 구현 필요
- id, review, reporter (Member)
- reportReason (Enum)
- description
- status (PENDING, APPROVED, REJECTED)
- createdAt
📋 구현 순서
Step 1: /me 엔드포인트 추가 (Auth 연동) (1일)
- GET /api/v1/members/me 구현
- @AuthenticationPrincipal MemberDetails 적용
- 기존 getMemberById() 재사용
- PUT /api/v1/members/me 구현
- @AuthenticationPrincipal로 본인 확인
- 기존 updateMember() 재사용
Step 2: 회원 프로필 완성 (1일)
- DELETE /api/v1/members/me 구현 (Soft Delete)
- 진행 중인 예약 확인 로직
- 개인정보 마스킹 로직
- GET /api/v1/members/me/statistics 구현
Step 3: Review Entity 및 Repository (1일)
- Review Entity 확인 및 개선
- ReviewRepository 구현
- ReviewTagMapping 확인
- QueryDSL 설정 (필터링/정렬용)
Step 4: 리뷰 작성 및 조회 (2일)
- POST /api/v1/reviews 구현
- 예약 완료 여부 검증
- 중복 리뷰 방지
- 태그 연결
- GET /api/v1/reviews/my 구현
- GET /api/v1/reviews/{reviewId} 구현
- GET /api/v1/institutions/{institutionId}/reviews 구현
Step 5: 리뷰 수정 및 삭제 (1일)
- PUT /api/v1/reviews/{reviewId} 구현
- 작성자 본인 확인
- 30일 이내 수정 제한
- DELETE /api/v1/reviews/{reviewId} 구현
Step 6: ReviewReport Entity 및 신고 기능 (1일)
- ReviewReport Entity 생성
- ReviewReportRepository 구현
- POST /api/v1/reviews/{reviewId}/report 구현
- 본인 리뷰 신고 방지
- 중복 신고 방지
Step 7: 마이페이지 (1일)
- 통합 조회 쿼리 작성 (QueryDSL)
- MyPageService 구현
- GET /api/v1/members/me/mypage 구현
Step 8: 테스트 (1일)
- Swagger 시나리오 테스트
- 권한 체크 검증
- 예외 처리 확인
예상 기간: 9일 (약 2주)
🚨 핵심 주의사항
-
권한
- 본인 정보만 조회/수정 가능
- @AuthenticationPrincipal로 사용자 정보 추출
- Member.id로 본인 확인
-
리뷰
- 완료된 예약(COMPLETED)만 리뷰 작성 가능
- 예약당 리뷰 1개만 (중복 방지)
- 본인 리뷰는 신고 불가
- 중복 신고 방지
- 작성 후 30일 이내만 수정 가능
- 완료 후 90일 이내만 작성 가능
-
회원 탈퇴
- 진행 중인 예약(PENDING, CONFIRMED) 있으면 탈퇴 불가
- Soft Delete (deleted = true)
- 개인정보 마스킹
-
어르신 프로필
- 주소 변경 시 위경도 자동 업데이트
- 본인이 등록한 프로필만 수정/삭제
-
성능
- N+1 문제 방지 (Fetch Join)
- 페이징 처리
- 리뷰 목록 조회 시 Join 최적화
-
로그
- 중요 작업 시 로그 남기기
- 리뷰 작성/신고는 필수 로그
📚 참고 파일
이미 존재하는 파일
✅ /api/controller/MemberController.java
✅ /api/controller/ElderlyProfileController.java
✅ /domain/user/guardian/entity/Member.java
✅ /domain/user/elderly/entity/ElderlyProfile.java
✅ /domain/user/guardian/service/MemberService.java
✅ /domain/user/elderly/service/ElderlyProfileService.java
✅ /domain/review/entity/Review.java (확인 필요)
✅ /domain/tag/entity/ReviewTagMapping.java
생성 필요한 파일
❌ /domain/review/entity/ReviewReport.java
❌ /domain/review/entity/ReportReason.java (Enum)
❌ /domain/review/repository/ReviewRepository.java
❌ /domain/review/repository/ReviewReportRepository.java
❌ /domain/review/service/ReviewService.java
❌ /api/controller/ReviewController.java
❌ /api/dto/review/ReviewCreateRequestDto.java
❌ /api/dto/review/ReviewResponseDto.java
❌ /api/dto/review/ReviewReportRequestDto.java
💡 구현 팁
1. @AuthenticationPrincipal 사용
@GetMapping("/me")
public ResponseEntity<MemberResponseDto> getMyInfo(
@AuthenticationPrincipal MemberDetails memberDetails
) {
Long memberId = memberDetails.getId();
MemberResponseDto response = memberService.getMyInfo(memberId);
return ResponseEntity.ok(response);
}2. 리뷰 중복 작성 방지
boolean exists = reviewRepository.existsByReservationIdAndMemberId(
reservationId, memberId
);
if (exists) {
throw new BusinessException(ErrorCode.REVIEW_ALREADY_EXISTS);
}3. 예약 완료 여부 확인
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(() -> new BusinessException(ErrorCode.RESERVATION_NOT_FOUND));
if (reservation.getStatus() != ReservationStatus.COMPLETED) {
throw new BusinessException(ErrorCode.RESERVATION_NOT_COMPLETED);
}
if (!reservation.getMember().getId().equals(memberId)) {
throw new BusinessException(ErrorCode.FORBIDDEN);
}4. 리뷰 목록 조회 (QueryDSL)
public Page<ReviewResponseDto> getInstitutionReviews(
Long institutionId,
Pageable pageable
) {
QReview review = QReview.review;
List<Review> reviews = queryFactory
.selectFrom(review)
.leftJoin(review.member).fetchJoin()
.leftJoin(review.reviewTagMappings).fetchJoin()
.where(
review.institution.id.eq(institutionId),
review.deleted.eq(false),
review.isReported.eq(false)
)
.orderBy(review.createdAt.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// ...
}5. 회원 탈퇴 시 진행 중인 예약 확인
boolean hasActiveReservation = reservationRepository
.existsByMemberIdAndStatusIn(
memberId,
List.of(ReservationStatus.PENDING, ReservationStatus.CONFIRMED)
);
if (hasActiveReservation) {
throw new BusinessException(ErrorCode.CANNOT_DELETE_MEMBER_WITH_ACTIVE_RESERVATION);
}🎯 완료 기준
- 모든 API가 Swagger에서 정상 동작
- 권한 체크가 모든 API에 적용됨
- 리뷰 중복 작성이 방지됨
- 본인 확인이 정확하게 동작함
- 리뷰 신고가 정상 동작함
- 회원 탈퇴 시 예약 확인이 동작함
- N+1 문제가 없음
- 로그가 정상적으로 남음
📝 담장자
Reactions are currently unavailable