Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
18ed370
[SPOT-234][FIX] Update WebSecurity for To Create Test Member
dvlp-sy Mar 18, 2025
648db40
[SPOT-234][MODIFY] Update Member Domain
dvlp-sy Mar 18, 2025
5c6ff6d
[SPOT-234][FEAT] Implement Admin MemberRemoval API
dvlp-sy Mar 18, 2025
c66ce10
[SPOT-234][FEAT] Implement withdraw API
dvlp-sy Mar 18, 2025
cf92ffb
[SPOT-234][MODIFY] Add OrphanRemoval to Member
dvlp-sy Mar 18, 2025
bd7e8bb
[SPOT-234][FEAT] Add MemberRemovalScheduler
dvlp-sy Mar 18, 2025
a5818c2
[SPOT-234][CHORE] Remove Unused Packages
dvlp-sy Mar 18, 2025
e4bdc2a
[SPOT-234][MODIFY] Add String Log for MemberRemoval
dvlp-sy Mar 18, 2025
acfc7a0
[SPOT-234][FEAT] Add AuthService Inactive Exception Handling
dvlp-sy Mar 18, 2025
8a3f7e3
[SPOT-234][MERGE] 회원탈퇴 기능 구현
dvlp-sy Mar 18, 2025
65c4c94
[SPOT-235][FEAT] Implement getStudyHost
dvlp-sy Mar 18, 2025
43f4123
[SPOT-235][MERGE] 스터디 호스트 여부 조회 기능 구현
dvlp-sy Mar 18, 2025
dc62464
[SPOT-237][FIX] Add Study Owner Withdrawal Handling
dvlp-sy Mar 20, 2025
2069abf
[SPOT-237][MERGE] 호스트 회원탈퇴 예외처리 추가
dvlp-sy Mar 20, 2025
bbe437f
[SPOT-238][MODIFY] Add Performance Field to Study Domain
dvlp-sy Mar 20, 2025
ad58889
[SPOT-238][MODIFY] Update terminateStudy API
dvlp-sy Mar 20, 2025
1fb8adb
[SPOT-238][TEST] Rewrite Test for terminateStudy
dvlp-sy Mar 20, 2025
9857ac6
[SPOT-238][MERGE] 스터디 끝내기 성과 로직 추가
dvlp-sy Mar 20, 2025
329a4cd
[SPOT-239][FEAT] 스터디 호스트 탈퇴 API 명세서 작성
msk226 Mar 20, 2025
e4518db
[SPOT-239][FEAT] 호스트 위임을 위한 Setter와 이유 필드 추가
msk226 Mar 20, 2025
52a31e6
[SPOT-239][FEAT] 호스트 위임 세부 로직 작성
msk226 Mar 20, 2025
2c19756
[MERGE] Merge pull request #369 from SPOTeam/SPOT-239/modify
msk226 Mar 20, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum ErrorStatus implements BaseErrorCode {
_TERMS_NOT_AGREED(HttpStatus.FORBIDDEN, "COMMON4013", "이용 약관이 동의되지 않았습니다."),
_MEMBER_EMAIL_EXIST(HttpStatus.BAD_REQUEST, "COMMON4014", "이미 가입 된 이메일입니다. 다른 로그인 방식을 이용해주세요."),
_RSA_ERROR(HttpStatus.BAD_REQUEST, "COMMON4015", "RSA 에러가 발생했습니다."),
_NOT_ADMIN(HttpStatus.FORBIDDEN, "COMMON4016", "관리자 권한이 없습니다."),

// 네이버 소셜 로그인 관련 에러
_NAVER_SIGN_IN_INTEGRATION_FAILED(HttpStatus.UNAUTHORIZED, "NAVER4001", "네이버 로그인 연동에 실패하였습니다."),
Expand Down Expand Up @@ -63,8 +64,10 @@ public enum ErrorStatus implements BaseErrorCode {
_STUDY_OWNER_NOT_FOUND(HttpStatus.NOT_FOUND, "STUDY4002", "스터디장을 찾을 수 없습니다."),
_STUDY_ALREADY_APPLIED(HttpStatus.BAD_REQUEST, "STUDY4003", "이미 신청된 스터디입니다."),
_STUDY_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "STUDY4004", "스터디 회원을 찾을 수 없습니다."),
_STUDY_MEMBER_NOT_EXIST(HttpStatus.NOT_FOUND, "STUDY4008", "스터디 회원이 아닙니다."),
_STUDY_NOT_APPROVED(HttpStatus.FORBIDDEN, "STUDY4005", "승인되지 않은 스터디입니다."),
_STUDY_OWNER_CANNOT_WITHDRAW(HttpStatus.FORBIDDEN, "STUDY4006", "스터디장은 스터디를 탈퇴할 수 없습니다."),
_STUDY_OWNER_ONLY_CAN_WITHDRAW(HttpStatus.FORBIDDEN, "STUDY4007", "스터디장만 해당 API를 통해 스터디를 탈퇴할 수 있습니다."),
_STUDY_NOT_RECRUITING(HttpStatus.BAD_REQUEST, "STUDY4007", "스터디 모집기한이 아닙니다."),
_STUDY_APPLICANT_NOT_FOUND(HttpStatus.NOT_FOUND, "STUDY4009", "처리를 기다리는 스터디 신청을 찾을 수 없습니다."),
_STUDY_APPLY_ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, "STUDY4010","스터디 신청이 이미 처리된 회원입니다."),
Expand All @@ -77,6 +80,7 @@ public enum ErrorStatus implements BaseErrorCode {
_ALREADY_STUDY_MEMBER(HttpStatus.BAD_REQUEST, "STUDY4017", "이미 스터디 멤버입니다."),
_STUDY_OWNER_ONLY_CAN_TERMINATE(HttpStatus.BAD_REQUEST, "STUDY4018", "스터디장만 스터디를 종료할 수 있습니다."),
_STUDY_ALREADY_TERMINATED(HttpStatus.BAD_REQUEST, "STUDY4019", "이미 종료된 스터디입니다."),
_OWNED_STUDY_EXISTS(HttpStatus.BAD_REQUEST, "STUDY4020", "운영중인 스터디가 존재합니다."),

//스터디 게시글 관련 에러
_STUDY_POST_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4001", "스터디 게시글을 찾을 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public enum SuccessStatus implements BaseCode {
_STUDY_APPLICANT_UPDATED(HttpStatus.OK, "STUDY4011", "스터디 신청 처리 완료"),
_STUDY_APPLY_COMPLETED(HttpStatus.OK, "STUDY4012", "스터디 신청 완료"),
_HOT_KEYWORD_FOUND(HttpStatus.OK, "SEARCH2001", "인기 검색어 조회 완료"),
_STUDY_HOST_FOUND(HttpStatus.OK, "STUDY2013", "스터디 호스트 조회 완료"),

//스터디 출석 퀴즈 관련
_STUDY_QUIZ_CREATED(HttpStatus.CREATED, "QUIZ2001", "스터디 퀴즈 생성 완료"),
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/example/spot/config/WebSecurity.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected SecurityFilterChain configure(HttpSecurity http) throws Exception {
.requestMatchers(new AntPathRequestMatcher("/spot/login/kakao", "GET")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/kakao", "GET")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/kakao/redirect", "GET")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/member/test", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/members/test", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/spot/current-env", "GET")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/api-docs")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/v3/**", "GET")).permitAll()
Expand Down
49 changes: 25 additions & 24 deletions src/main/java/com/example/spot/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public class Member extends BaseEntity {
private Carrier carrier;

// 안 쓰면 지워도 될 것 같은데 사이드 이펙트 생길까봐 일단 놔둡니다..!
@Column(length = 15, unique = true)
@Column(length = 15)
private String phone;

@Column(nullable = false)
Expand All @@ -75,6 +75,7 @@ public class Member extends BaseEntity {
@Column(nullable = false)
private String profileImage;

@Setter
@Column
private LocalDateTime inactive;

Expand All @@ -95,12 +96,12 @@ public class Member extends BaseEntity {
private Status status;

//== 스터디 희망사유 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyReason> studyReasonList = new ArrayList<>();

//== 알림 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Notification> notificationList = new ArrayList<>();

Expand All @@ -110,106 +111,106 @@ public class Member extends BaseEntity {
private List<MemberReport> memberReportList = new ArrayList<>();

//== 회원이 선호하는 테마 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<MemberTheme> memberThemeList = new ArrayList<>();

//== 회원의 출석 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<MemberAttendance> memberAttendanceList = new ArrayList<>();

//== 회원이 참여하는 스터디 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<MemberStudy> memberStudyList = new ArrayList<>();

//== 회원이 찜한 스터디 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PreferredStudy> preferredStudyList = new ArrayList<>();

//== 회원이 선호하는 지역 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PreferredRegion> preferredRegionList = new ArrayList<>();

////== 회원이 작성한 게시글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Post> postList = new ArrayList<>();

////== 회원이 좋아요한 게시글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<LikedPost> likedPostList = new ArrayList<>();

////== 회원이 선호하는 지역 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PostReport> postReportList = new ArrayList<>();

////== 회원이 스크랩한 게시글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<MemberScrap> memberScrapList = new ArrayList<>();

////== 회원이 작성한 게시글 댓글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PostComment> postCommentList = new ArrayList<>();

////== 회원이 좋아요한 게시글 댓글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<LikedPostComment> likedCommentList = new ArrayList<>();

//== 회원이 작성한 스터디 게시글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyPost> studyPostList = new ArrayList<>();

//== 회원이 좋아요한 스터디 게시글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyLikedPost> studyLikedPostList = new ArrayList<>();

//== 회원이 작성한 스터디 게시글 댓글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyPostComment> studyPostCommentList = new ArrayList<>();

//== 회원이 좋아요한 게시글 댓글 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<StudyLikedComment> studyLikedCommentList = new ArrayList<>();

//== 회원이 생성한 투표 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Vote> voteList = new ArrayList<>();

//== 회원이 투표한 항목 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<MemberVote> memberVoteList = new ArrayList<>();

//== 회원이 선호하는 지역 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PreferredRegion> regions = new ArrayList<>();

//== 회원이 생성한 스터디 퀴즈 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Quiz> quizList = new ArrayList<>();

//== 회원이 생성한 스터디 일정 목록 ==//
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Schedule> scheduleList = new ArrayList<>();

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<ToDoList> toDoLists = new ArrayList<>();

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/example/spot/domain/Region.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ public class Region extends BaseEntity {
private String neighborhood;

@Builder.Default
@OneToMany(mappedBy = "region")
@OneToMany(mappedBy = "region", cascade = CascadeType.ALL)
private List<RegionStudy> regionStudyList = new ArrayList<>();

@Builder.Default
@OneToMany(mappedBy = "region")
@OneToMany(mappedBy = "region", cascade = CascadeType.ALL)
private List<PreferredRegion> prefferedRegionList = new ArrayList<>();


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ public class MemberStudy extends BaseEntity {
private ApplicationStatus status;

@Column(nullable = false, columnDefinition = "BIT DEFAULT 0")
@Setter
private Boolean isOwned;

@Column(columnDefinition = "text")
private String introduction;

// 해당 유저로 호스트를 위임하는 이유
@Column(columnDefinition = "text")
@Setter
private String reason;

//== 회원 ==//
@Setter
@ManyToOne(fetch = FetchType.LAZY)
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/example/spot/domain/study/Study.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public class Study extends BaseEntity {
@Enumerated(EnumType.STRING)
private StudyState studyState;

@Column(length = 30)
private String performance;

@Column(nullable = false)
private Boolean isOnline;

Expand All @@ -74,7 +77,6 @@ public class Study extends BaseEntity {
@Column(nullable = false)
private String title;

@Setter
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Status status;
Expand Down Expand Up @@ -199,4 +201,10 @@ public void deleteVote(Vote vote) {
public void addToDoList(ToDoList toDoList) {
toDoLists.add(toDoList);
}

public void terminateStudy(String performance) {
this.studyState = StudyState.COMPLETED;
this.status = Status.OFF;
this.performance = performance;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.example.spot.repository;

import com.example.spot.domain.Member;

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

import com.example.spot.domain.enums.LoginType;
Expand All @@ -23,4 +26,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
boolean existsByEmailAndLoginType(String email, LoginType loginType);

Optional<Member> findByEmailAndLoginType(String email, LoginType loginType);

List<Member> findAllByInactiveBefore(LocalDateTime stdTime);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.spot.repository;

import com.example.spot.domain.Member;
import com.example.spot.domain.enums.ApplicationStatus;
import com.example.spot.domain.mapping.MemberStudy;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -30,4 +31,7 @@ public interface MemberStudyRepository extends JpaRepository<MemberStudy, Long>

boolean existsByMemberIdAndStudyIdAndStatus(Long memberId, Long studyId, ApplicationStatus applicationStatus);

Optional<MemberStudy> findByStudyIdAndIsOwned(Long studyId, boolean b);

boolean existsByMemberIdAndIsOwned(Long memberId, boolean b);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.example.spot.domain.auth.RefreshToken;
import com.example.spot.domain.study.Option;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
Expand All @@ -13,4 +15,6 @@ public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long
void deleteAllByMemberId(Long memberId);

boolean existsByMemberId(Long memberId);

void deleteAllByMemberIdIn(List<Long> deletedMemberIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.spot.scheduler;

import com.example.spot.service.admin.AdminService;
import com.example.spot.web.dto.admin.AdminResponseDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Slf4j
@RequiredArgsConstructor
@Transactional
public class MemberRemovalScheduler {

private final AdminService adminService;

// 매일 오전 6시에 탈퇴 후 30일이 지난 회원을 삭제합니다.
@Scheduled(cron = "0 0 6 * * *")
public void deleteMembers() {
AdminResponseDTO.DeletedMemberListDTO deletedMemberListDTO = adminService.deleteInactiveMembers();
log.info("Deleted Members: {}", deletedMemberListDTO.getDeletedMembers().size());
deletedMemberListDTO.getDeletedMembers().forEach(member ->
log.info("Deleted Member: id={}, email={}", member.getMemberId(), member.getEmail())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,13 @@ public static String getVerifiedTempUserEmail() {
}
return authentication.getName();
}

/**
* 현재 인증된 사용자의 로그인 정보를 삭제합니다.
* 로그인 정보를 삭제하며 SecurityContext도 함께 삭제합니다.
*/
public static void deleteCurrentUser() {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/example/spot/service/admin/AdminService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.spot.service.admin;

import com.example.spot.web.dto.admin.AdminResponseDTO;

public interface AdminService {

/* ----------------------------- 회원 정보 관리 API ------------------------------------- */

boolean getIsAdmin();

AdminResponseDTO.DeletedMemberListDTO deleteInactiveMembers();

/* ----------------------------- 신고 내역 관리 API ------------------------------------- */

}
Loading
Loading