Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches: [ "main" ] # main 브랜치에 push될 때 실행
pull_request:
branches: [ "main" ] # main 브랜치에 pull_request될 때 실행
workflow_dispatch: # Actions 탭에서 수동으로 실행 가능하도록 설정

jobs:
build:
Expand Down
14 changes: 13 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@ repositories {
}

dependencies {
// Spring Data JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

// Spring Web
implementation 'org.springframework.boot:spring-boot-starter-web'

// Lombok
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'

// MySQL
runtimeOnly 'com.mysql:mysql-connector-j'

// SpringDoc (OpenAPI/Swagger for Spring Boot 3.x)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.example.umc9th.domain.inquiry.entity.enums.InquiryType;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.example.umc9th.domain.inquiry.entity;

import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.example.umc9th.domain.member.controller;

import com.example.umc9th.domain.member.dto.MemberResponse;
import com.example.umc9th.domain.member.repository.MemberRepository;
import com.example.umc9th.domain.member.service.MemberService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -17,8 +19,7 @@ public class MemberController {
private final MemberService memberService;

@DeleteMapping("/{memberId}")
public ResponseEntity<Void> deleteMember(@PathVariable Long memberId){
memberService.deleteMemberAndAll(memberId);
return ResponseEntity.notFound().build(); // 성공 시 204 No Content 응답
public ApiResponse<MemberResponse.MemberDeleteResultDTO> deleteMember(@PathVariable Long memberId){
return ApiResponse.onSuccess(memberService.deleteMemberAndAll(memberId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.umc9th.domain.member.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class MemberResponse {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "회원 탈퇴 응답 정보")
public static class MemberDeleteResultDTO{

@Schema(description = "유저 상세 정보 ID", example = "1")
private Long userId;

private String message;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.umc9th.domain.member.entity;

import com.example.umc9th.domain.store.entity.Store;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.example.umc9th.domain.member.entity.enums.SocialLoginType;
import com.example.umc9th.domain.mission.entity.MissionByMember;
import com.example.umc9th.domain.review.entity.Review;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.umc9th.domain.member.entity;

import com.example.umc9th.domain.member.entity.enums.AgreementType;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.example.umc9th.domain.member.entity;

import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.umc9th.domain.member.entity;

import com.example.umc9th.domain.member.entity.enums.NotificationType;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.umc9th.domain.member.entity;

import com.example.umc9th.domain.member.entity.enums.PointType;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

import com.example.umc9th.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {

/**
* 회원 ID를 기준으로 회원 정보를 조회
* JPA 기본 메서드인 findById(id)와 완전히 동일하게 동작 (사실상 불필요한 코드)
* @param memberId 조회할 회원의 ID
* @return Optional<Member>
*/
Optional<Member> findMemberById(Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.example.umc9th.domain.member.service;

import com.example.umc9th.domain.member.dto.MemberResponse;

public interface MemberService {

// 회원 탈퇴 + 연관된 모든 데이터 삭제
void deleteMemberAndAll(Long memberId);
MemberResponse.MemberDeleteResultDTO deleteMemberAndAll(Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.example.umc9th.domain.member.service;

import com.example.umc9th.domain.inquiry.repository.InquiryRepository;
import com.example.umc9th.domain.member.dto.MemberResponse;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.member.repository.*;
import com.example.umc9th.domain.mission.repository.MissionByMemberRepository;
import com.example.umc9th.domain.review.repository.ReviewRepository;
import com.example.umc9th.global.apiPayload.code.status.ErrorStatus;
import com.example.umc9th.global.apiPayload.exception.handler.ErrorHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -17,36 +17,20 @@
public class MemberServiceImpl implements MemberService {

private final MemberRepository memberRepository;
private final MemberAgreeRepository memberAgreeRepository;
private final PointLogRepository pointLogRepository;
private final NotificationRepository notificationRepository;
private final MemberPreferCategoryRepository memberPreferCategoryRepository;
private final MissionByMemberRepository missionByMemberRepository;
private final ReviewRepository reviewRepository;
private final InquiryRepository inquiryRepository;

@Transactional
@Override
public void deleteMemberAndAll(Long memberId){
public MemberResponse.MemberDeleteResultDTO deleteMemberAndAll(Long memberId){

log.info("회원 탈퇴를 시작합니다. memberId: {}", memberId);
// 1. 회원 조회 (없으면 예외 발생)
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("ID에 해당하는 회원을 찾을 수 없습니다: " + memberId));
.orElseThrow(() -> new ErrorHandler(ErrorStatus.MEMBER_NOT_FOUND));
log.info("회원 {}의 탈퇴 처리를 시작합니다.", member.getNickname());

// 2. 연관된 자식 데이터들을 Batch Delete로 먼저 삭제
log.info("회원 {}의 연관 데이터 삭제를 시작합니다.", member.getNickname());
memberAgreeRepository.deleteAllByMember(member);
pointLogRepository.deleteAllByMember(member);
notificationRepository.deleteAllByMember(member);
memberPreferCategoryRepository.deleteAllByMember(member);
missionByMemberRepository.deleteAllByMember(member);
reviewRepository.deleteAllByMember(member);
inquiryRepository.deleteAllByMember(member);
log.info("회원 {}의 연관 데이터 삭제 완료.", member.getNickname());

// 3. 부모 엔티티인 Member 삭제
// 2. Member 삭제
memberRepository.delete(member);
log.info("회원 {}의 탈퇴 처리가 성공적으로 완료되었습니다.", member.getNickname());

return new MemberResponse.MemberDeleteResultDTO(memberId, "회원 탈퇴가 성공적으로 처리되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.umc9th.domain.mission.controller;

import com.example.umc9th.domain.mission.dto.MissionResponse;
import com.example.umc9th.domain.mission.entity.enums.MissionStatus;
import com.example.umc9th.domain.mission.service.MissionService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/missions")
@RequiredArgsConstructor
public class MissionController {

private final MissionService missionService;

@GetMapping("/my")
@Operation(summary = "나의 미션 목록 조회 API", description = "진행중 또는 완료된 미션 목록을 커서 기반으로 조회합니다.")
public ApiResponse<MissionResponse.MyMissionListDTO> getMyMissions(
@RequestParam MissionStatus status,
@RequestParam(required = false) LocalDateTime cursorDeadline,
@RequestParam(required = false) Long cursorId,
@RequestParam(defaultValue = "10") int size
) {
MissionResponse.MyMissionListDTO response = missionService.getMyMissions(1L, status, cursorDeadline, cursorId, size);
return ApiResponse.onSuccess(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.umc9th.domain.mission.converter;

import com.example.umc9th.domain.mission.dto.MissionResponse;
import com.example.umc9th.domain.mission.entity.Mission;
import com.example.umc9th.domain.mission.entity.MissionByMember;
import org.springframework.data.domain.Slice;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

public class MissionConverter {

public static MissionResponse.MyMissionDTO toMyMissionDTO(MissionByMember missionByMember) {
Mission mission = missionByMember.getMission();
return MissionResponse.MyMissionDTO.builder()
.storeName(mission.getStore().getName())
.missionContent(mission.getContent())
.targetAmount(mission.getTargetAmount())
.rewardPoint(mission.getRewardPoint())
.deadline(mission.getDeadline())
.build();
}

public static MissionResponse.MyMissionListDTO toMyMissionListDTO(Slice<MissionByMember> missionByMemberSlice) {

// 각 MissionByMember 엔터티 -> MyMissionDTO로 변환 -> list로
List<MissionResponse.MyMissionDTO> missionDTOList = missionByMemberSlice.getContent().stream()
.map(MissionConverter::toMyMissionDTO)
.collect(Collectors.toList());

// 다음 페이지 조회를 위해 커서 값들을 초기화
LocalDateTime nextCursorDeadline = null;
Long nextCursorId = null;

// 현재 페이지의 미션 목록이 비어있지 않다면, 다음 페이지를 조회할 커서 설정
if (!missionDTOList.isEmpty()) {
// 현재 페이지의 마지막 미션 엔티티
Mission lastMission = missionByMemberSlice.getContent().get(missionByMemberSlice.getContent().size() - 1).getMission();

// 마지막 미션의 마감 시간과 ID를 다음 커서 값으로 설정
nextCursorDeadline = lastMission.getDeadline();
nextCursorId = lastMission.getId();
}

return MissionResponse.MyMissionListDTO.builder()
.missionList(missionDTOList)
.hasNext(missionByMemberSlice.hasNext())
.nextCursorDeadline(nextCursorDeadline)
.nextCursorId(nextCursorId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.umc9th.domain.mission.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

public class MissionResponse {

@Getter
@Builder
@AllArgsConstructor
@Schema(description = "나의 미션 목록의 개별 미션 응답")
public static class MyMissionDTO {

private String storeName;
private String missionContent;
private Integer rewardPoint;
private Integer targetAmount;
private LocalDateTime deadline;
}

@Getter
@Builder
@AllArgsConstructor
@Schema(description = "나의 미션 목록 조회 응답")
public static class MyMissionListDTO {

private List<MyMissionDTO> missionList;
private Boolean hasNext;
private LocalDateTime nextCursorDeadline;
private Long nextCursorId;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.umc9th.domain.mission.entity;

import com.example.umc9th.domain.store.entity.Store;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.mission.entity.enums.MissionStatus;
import com.example.umc9th.global.common.BaseEntity;
import com.example.umc9th.global.apiPayload.code.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
Expand Down
Loading