Skip to content

[FEAT] 회원 관리 리팩토링 및 리뷰 기능 구현 #18

@Uechann

Description

@Uechann

[FEAT] 회원 관리 리팩토링 및 리뷰 기능 구현

📋 이슈 개요

일반 회원(보호자)이 어르신을 관리하고, 기관을 이용하며, 리뷰를 작성할 수 있는 완전한 사용자 기능을 구현합니다.

🎯 핵심 구현 목표

✅ 반드시 구현해야 할 기능

  1. 회원 Auth 인증 연결 - Local/OAuth 회원가입 후 Member 생성 확인
  2. 회원 프로필 관리 - 내 정보 조회/수정/탈퇴
  3. 어르신 프로필 관리 - 여러 어르신 등록 및 관리
  4. 리뷰 관리 - 완료된 예약에 대한 리뷰 작성
  5. 리뷰 신고 - 부적절한 리뷰 신고
  6. 마이페이지 - 내 정보/리뷰 한눈에 보기

📋 전체 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순위 (핵심 기능)

  1. GET /api/v1/members/me - 내 정보 조회 (Auth 연동)
  2. PUT /api/v1/members/me - 내 정보 수정 (Auth 연동)
  3. POST /api/v1/reviews - 리뷰 작성
  4. GET /api/v1/reviews/my - 내 리뷰 목록
  5. GET /api/v1/institutions/{institutionId}/reviews - 기관 리뷰 목록 (공개)

2순위 (관리 기능)

  1. PUT /api/v1/reviews/{reviewId} - 리뷰 수정
  2. DELETE /api/v1/reviews/{reviewId} - 리뷰 삭제
  3. POST /api/v1/reviews/{reviewId}/report - 리뷰 신고
  4. DELETE /api/v1/members/me - 회원 탈퇴

3순위 (부가 기능)

  1. GET /api/v1/members/me/statistics - 활동 통계
  2. 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

구현 필요 (❌)

⚠️ 중요: /me 엔드포인트 추가 필요

현재 /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주)


🚨 핵심 주의사항

  1. 권한

    • 본인 정보만 조회/수정 가능
    • @AuthenticationPrincipal로 사용자 정보 추출
    • Member.id로 본인 확인
  2. 리뷰

    • 완료된 예약(COMPLETED)만 리뷰 작성 가능
    • 예약당 리뷰 1개만 (중복 방지)
    • 본인 리뷰는 신고 불가
    • 중복 신고 방지
    • 작성 후 30일 이내만 수정 가능
    • 완료 후 90일 이내만 작성 가능
  3. 회원 탈퇴

    • 진행 중인 예약(PENDING, CONFIRMED) 있으면 탈퇴 불가
    • Soft Delete (deleted = true)
    • 개인정보 마스킹
  4. 어르신 프로필

    • 주소 변경 시 위경도 자동 업데이트
    • 본인이 등록한 프로필만 수정/삭제
  5. 성능

    • N+1 문제 방지 (Fetch Join)
    • 페이징 처리
    • 리뷰 목록 조회 시 Join 최적화
  6. 로그

    • 중요 작업 시 로그 남기기
    • 리뷰 작성/신고는 필수 로그

📚 참고 파일

이미 존재하는 파일

✅ /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 문제가 없음
  • 로그가 정상적으로 남음

📝 담장자

@clainyun

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions