From 71345a7b8e02bca2c92065f0a8ea032861eb4f91 Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Wed, 11 Feb 2026 17:28:00 +0900 Subject: [PATCH 01/10] =?UTF-8?q?refactor:=20#189-accountStatus=20?= =?UTF-8?q?=EB=AA=85=20->=20activeStatus=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homePopular/HomePopularPostService.java | 6 +- .../tackit/config/CommonDataInitializer.java | 66 +- .../tackit/config/jwt/TokenProvider.java | 4 +- .../domain/admin/dto/DeletedMemberDTO.java | 6 +- .../tackit/domain/admin/dto/MemberDTO.java | 4 +- .../repository/AdminFreePostRepository.java | 6 +- .../repository/AdminMemberRepository.java | 6 +- .../repository/AdminQnAPostRepository.java | 4 +- .../repository/AdminTipPostRepository.java | 4 +- .../admin/service/AdminFreePostService.java | 4 +- .../admin/service/AdminMemberService.java | 13 - .../admin/service/AdminQnAPostService.java | 4 +- .../admin/service/AdminTipPostService.java | 4 +- .../login/repository/MemberRepository.java | 24 + .../auth/login/service/AuthService.java | 265 ++++---- .../service/CustomUserDetailsService.java | 57 +- .../login/service/RejoinCheckService.java | 45 +- .../{AccountStatus.java => ActiveStatus.java} | 2 +- .../tackit/domain/entity/FreePost.java | 10 +- .../example/tackit/domain/entity/Member.java | 6 +- .../tackit/domain/entity/QnAComment.java | 2 +- .../example/tackit/domain/entity/QnAPost.java | 10 +- .../tackit/domain/entity/TipComment.java | 2 +- .../example/tackit/domain/entity/TipPost.java | 10 +- .../domain/event/service/EventService.java | 375 +++++----- .../repository/FreePostJPARepository.java | 12 +- .../Free_post/service/FreePostService.java | 642 +++++++++--------- .../FreeTagCustomRepositoryImpl.java | 7 +- .../mypage/service/MyPageQnAService.java | 5 + .../service/QnACommentService.java | 203 +++--- .../repository/QnAPostRepository.java | 12 +- .../QnA_post/service/QnAPostService.java | 91 +++ .../QnATagCustomRepositoryImpl.java | 8 +- .../report/dto/ReportContentDetailDto.java | 4 +- .../domain/report/dto/ReportListDto.java | 4 +- .../service/TipCommentService.java | 10 + .../repository/TipPostRepository.java | 6 +- .../Tip_post/service/TipPostService.java | 47 ++ .../TipTagCustomRepositoryImpl.java | 6 +- .../example/tackit/MemberSchedulerTest.java | 7 - 40 files changed, 1073 insertions(+), 930 deletions(-) create mode 100644 src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java rename src/main/java/org/example/tackit/domain/entity/{AccountStatus.java => ActiveStatus.java} (69%) diff --git a/src/main/java/org/example/tackit/common/homePopular/HomePopularPostService.java b/src/main/java/org/example/tackit/common/homePopular/HomePopularPostService.java index e5f7634..223ba75 100644 --- a/src/main/java/org/example/tackit/common/homePopular/HomePopularPostService.java +++ b/src/main/java/org/example/tackit/common/homePopular/HomePopularPostService.java @@ -38,7 +38,7 @@ public List getPopularPosts(Long orgId) { // Free 게시판 List freePosts = freePostRepository.findTop3PopularByOrg( - AccountStatus.ACTIVE, + ActiveStatus.ACTIVE, startOfWeek, now, orgId, @@ -48,7 +48,7 @@ public List getPopularPosts(Long orgId) { // QnA 게시판 List qnaPosts = qnaPostRepository.findTop3PopularByOrg( - AccountStatus.ACTIVE, + ActiveStatus.ACTIVE, startOfWeek, now, orgId, @@ -59,7 +59,7 @@ public List getPopularPosts(Long orgId) { // Tip 게시판 List tipPosts = tipPostRepository.findTop3PopularByOrg( - AccountStatus.ACTIVE, + ActiveStatus.ACTIVE, startOfWeek, now, orgId, diff --git a/src/main/java/org/example/tackit/config/CommonDataInitializer.java b/src/main/java/org/example/tackit/config/CommonDataInitializer.java index 120503e..585cf93 100644 --- a/src/main/java/org/example/tackit/config/CommonDataInitializer.java +++ b/src/main/java/org/example/tackit/config/CommonDataInitializer.java @@ -1,50 +1,50 @@ package org.example.tackit.config; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.entity.AccountStatus; -import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.organization.repository.SchoolRepository; +import org.example.tackit.domain.entity.*; +import org.example.tackit.domain.auth.login.repository.MemberRepository; import org.example.tackit.domain.entity.Org.School; import org.example.tackit.domain.entity.Org.SchoolType; -import org.example.tackit.domain.member.repository.MemberRepository; -import org.example.tackit.domain.organization.repository.SchoolRepository; import org.springframework.boot.CommandLineRunner; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +import java.time.LocalDateTime; + @Component @RequiredArgsConstructor public class CommonDataInitializer implements CommandLineRunner { - private final MemberRepository memberRepository; - private final PasswordEncoder passwordEncoder; - private final SchoolRepository schoolRepository; - - // commandLineRunner의 run 메서드는 String... args로 유지 (String[] args와 유사) -> 스프링 부트 공식 문서 참고 - // args를 꼭 배열로 넘겨야 하는 건 아니고 가변적으로 받을 수 있다는 의도를 나타냄 - @Override - public void run(String... args) throws Exception { - if (memberRepository.findByEmail("contact.tackit@gmail.com").isEmpty()) { - Member admin = Member.builder() - .email("contact.tackit@gmail.com") - .password(passwordEncoder.encode("admin1")) // BCrypt 인코딩 - .name("관리자") - .status(AccountStatus.ACTIVE) - .createdAt(LocalDateTime.now()) - .build(); - - memberRepository.save(admin); - } + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final SchoolRepository schoolRepository; + + // commandLineRunner의 run 메서드는 String... args로 유지 (String[] args와 유사) -> 스프링 부트 공식 문서 참고 + // args를 꼭 배열로 넘겨야 하는 건 아니고 가변적으로 받을 수 있다는 의도를 나타냄 + @Override + public void run(String... args) throws Exception { + if (memberRepository.findByEmail("contact.tackit@gmail.com").isEmpty()) { + Member admin = Member.builder() + .email("contact.tackit@gmail.com") + .password(passwordEncoder.encode("admin1")) // BCrypt 인코딩 + .name("관리자") + .status(AccountStatus.ACTIVE) + .createdAt(LocalDateTime.now()) + .build(); + + memberRepository.save(admin); + } - if (schoolRepository.findBySchoolName("숙명여자대학교").isEmpty()) { - School sookmyung = School.builder() - .schoolName("숙명여자대학교") - .schoolType(SchoolType.Main) - .regionId(1) - .address("서울특별시 용산구 청파로47길 100") - .build(); + if (schoolRepository.findBySchoolName("숙명여자대학교").isEmpty()) { + School sookmyung = School.builder() + .schoolName("숙명여자대학교") + .schoolType(SchoolType.Main) + .regionId(1) + .address("서울특별시 용산구 청파로47길 100") + .build(); - schoolRepository.save(sookmyung); + schoolRepository.save(sookmyung); + } } - } } diff --git a/src/main/java/org/example/tackit/config/jwt/TokenProvider.java b/src/main/java/org/example/tackit/config/jwt/TokenProvider.java index ccd88d5..0a66ff3 100644 --- a/src/main/java/org/example/tackit/config/jwt/TokenProvider.java +++ b/src/main/java/org/example/tackit/config/jwt/TokenProvider.java @@ -9,7 +9,7 @@ import org.example.tackit.domain.admin.repository.AdminMemberRepository; import org.example.tackit.domain.auth.login.security.CustomUserDetails; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.security.authentication.BadCredentialsException; import org.example.tackit.domain.auth.login.dto.TokenDto; import org.springframework.beans.factory.annotation.Value; @@ -173,7 +173,7 @@ public Authentication getAuthentication(String accessToken) { .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")); // 탈퇴 회원 차단 - if (member.getAccountStatus() == AccountStatus.DELETED) { + if (member.getActiveStatus() == ActiveStatus.DELETED) { throw new RuntimeException("탈퇴한 회원입니다."); } diff --git a/src/main/java/org/example/tackit/domain/admin/dto/DeletedMemberDTO.java b/src/main/java/org/example/tackit/domain/admin/dto/DeletedMemberDTO.java index 259082f..1ee9a53 100644 --- a/src/main/java/org/example/tackit/domain/admin/dto/DeletedMemberDTO.java +++ b/src/main/java/org/example/tackit/domain/admin/dto/DeletedMemberDTO.java @@ -4,7 +4,7 @@ import lombok.Builder; import lombok.Getter; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import java.time.LocalDateTime; @@ -21,7 +21,7 @@ public class DeletedMemberDTO { private String nickname; // 상태 - private AccountStatus accountStatus; + private ActiveStatus activeStatus; // 가입일자 private LocalDateTime createdAt; @@ -30,7 +30,7 @@ public static DeletedMemberDTO from(Member member) { return DeletedMemberDTO.builder() .email(member.getEmail()) .createdAt(member.getCreatedAt()) - .accountStatus(member.getAccountStatus()) + .activeStatus(member.getActiveStatus()) .build(); } } diff --git a/src/main/java/org/example/tackit/domain/admin/dto/MemberDTO.java b/src/main/java/org/example/tackit/domain/admin/dto/MemberDTO.java index db487f2..f1b6931 100644 --- a/src/main/java/org/example/tackit/domain/admin/dto/MemberDTO.java +++ b/src/main/java/org/example/tackit/domain/admin/dto/MemberDTO.java @@ -4,7 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import java.time.LocalDate; @@ -17,6 +17,6 @@ public class MemberDTO { private String nickname; private String email; private String organization; - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private LocalDate createdAt; } diff --git a/src/main/java/org/example/tackit/domain/admin/repository/AdminFreePostRepository.java b/src/main/java/org/example/tackit/domain/admin/repository/AdminFreePostRepository.java index c5c6c68..4c3e2e5 100644 --- a/src/main/java/org/example/tackit/domain/admin/repository/AdminFreePostRepository.java +++ b/src/main/java/org/example/tackit/domain/admin/repository/AdminFreePostRepository.java @@ -1,7 +1,7 @@ package org.example.tackit.domain.admin.repository; import org.example.tackit.domain.entity.FreePost; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,9 +9,7 @@ public interface AdminFreePostRepository extends JpaRepository { - Page findAllByAccountStatusAndReportCountGreaterThanEqual(AccountStatus accountStatus, int reportCount, Pageable pageable); - - + Page findAllByActiveStatusAndReportCountGreaterThanEqual(ActiveStatus activeStatus, int reportCount, Pageable pageable); } diff --git a/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java b/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java index d69995a..90476a2 100644 --- a/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java +++ b/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java @@ -1,10 +1,8 @@ package org.example.tackit.domain.admin.repository; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.MemberType; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -28,7 +26,7 @@ public interface AdminMemberRepository extends JpaRepository { Long countJoinedAfter(@Param("date")LocalDateTime date); // 탈퇴 회원 통계 - List findByStatus(AccountStatus accountStatus); + List findByStatus(ActiveStatus activeStatus); // 1년마다 뉴비 -> 시니어 자동 갱신 diff --git a/src/main/java/org/example/tackit/domain/admin/repository/AdminQnAPostRepository.java b/src/main/java/org/example/tackit/domain/admin/repository/AdminQnAPostRepository.java index 03c369f..53cddc3 100644 --- a/src/main/java/org/example/tackit/domain/admin/repository/AdminQnAPostRepository.java +++ b/src/main/java/org/example/tackit/domain/admin/repository/AdminQnAPostRepository.java @@ -1,12 +1,12 @@ package org.example.tackit.domain.admin.repository; import org.example.tackit.domain.entity.QnAPost; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface AdminQnAPostRepository extends JpaRepository { - Page findAllByAccountStatusAndReportCountGreaterThanEqual(AccountStatus accountStatus, int reportCount, Pageable pageable); + Page findAllByActiveStatusAndReportCountGreaterThanEqual(ActiveStatus activeStatus, int reportCount, Pageable pageable); } diff --git a/src/main/java/org/example/tackit/domain/admin/repository/AdminTipPostRepository.java b/src/main/java/org/example/tackit/domain/admin/repository/AdminTipPostRepository.java index 201f67e..554d193 100644 --- a/src/main/java/org/example/tackit/domain/admin/repository/AdminTipPostRepository.java +++ b/src/main/java/org/example/tackit/domain/admin/repository/AdminTipPostRepository.java @@ -1,6 +1,6 @@ package org.example.tackit.domain.admin.repository; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.TipPost; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -8,5 +8,5 @@ public interface AdminTipPostRepository extends JpaRepository { - Page findAllByAccountStatusAndReportCountGreaterThanEqual(AccountStatus accountStatus, int reportCount, Pageable pageable); + Page findAllByActiveStatusAndReportCountGreaterThanEqual(ActiveStatus activeStatus, int reportCount, Pageable pageable); } diff --git a/src/main/java/org/example/tackit/domain/admin/service/AdminFreePostService.java b/src/main/java/org/example/tackit/domain/admin/service/AdminFreePostService.java index 0912bc6..2a9d106 100644 --- a/src/main/java/org/example/tackit/domain/admin/service/AdminFreePostService.java +++ b/src/main/java/org/example/tackit/domain/admin/service/AdminFreePostService.java @@ -7,7 +7,7 @@ import org.example.tackit.domain.admin.dto.ReportedPostDTO; import org.example.tackit.domain.admin.repository.AdminFreePostRepository; import org.example.tackit.domain.entity.FreePost; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -22,7 +22,7 @@ public class AdminFreePostService implements ReportedPostService{ // 비활성화 자유 게시글 전체 조회 @Override public Page getDeletedPosts(Pageable pageable) { - return adminFreePostRepository.findAllByAccountStatusAndReportCountGreaterThanEqual(AccountStatus.DELETED, 3, pageable) + return adminFreePostRepository.findAllByActiveStatusAndReportCountGreaterThanEqual(ActiveStatus.DELETED, 3, pageable) .map(ReportedPostDTO::fromEntity); } diff --git a/src/main/java/org/example/tackit/domain/admin/service/AdminMemberService.java b/src/main/java/org/example/tackit/domain/admin/service/AdminMemberService.java index 5c2950e..abcdda5 100644 --- a/src/main/java/org/example/tackit/domain/admin/service/AdminMemberService.java +++ b/src/main/java/org/example/tackit/domain/admin/service/AdminMemberService.java @@ -1,21 +1,8 @@ package org.example.tackit.domain.admin.service; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.admin.dto.DeletedMemberDTO; -import org.example.tackit.domain.admin.dto.DeletedMemberResp; -import org.example.tackit.domain.admin.dto.MemberDTO; -import org.example.tackit.domain.admin.dto.MemberStatisticsDTO; import org.example.tackit.domain.admin.repository.AdminMemberRepository; -import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.temporal.ChronoField; -import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor diff --git a/src/main/java/org/example/tackit/domain/admin/service/AdminQnAPostService.java b/src/main/java/org/example/tackit/domain/admin/service/AdminQnAPostService.java index fcc614d..9b2ad9c 100644 --- a/src/main/java/org/example/tackit/domain/admin/service/AdminQnAPostService.java +++ b/src/main/java/org/example/tackit/domain/admin/service/AdminQnAPostService.java @@ -6,7 +6,7 @@ import org.example.tackit.domain.admin.dto.ReportedPostDTO; import org.example.tackit.domain.admin.repository.AdminQnAPostRepository; import org.example.tackit.domain.entity.QnAPost; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -21,7 +21,7 @@ public class AdminQnAPostService implements ReportedPostService { @Override public Page getDeletedPosts(Pageable pageable) { - return adminQnAPostRepository.findAllByAccountStatusAndReportCountGreaterThanEqual(AccountStatus.DELETED, 3, pageable) + return adminQnAPostRepository.findAllByActiveStatusAndReportCountGreaterThanEqual(ActiveStatus.DELETED, 3, pageable) .map(ReportedPostDTO::fromEntity); } diff --git a/src/main/java/org/example/tackit/domain/admin/service/AdminTipPostService.java b/src/main/java/org/example/tackit/domain/admin/service/AdminTipPostService.java index 0da343a..b23680b 100644 --- a/src/main/java/org/example/tackit/domain/admin/service/AdminTipPostService.java +++ b/src/main/java/org/example/tackit/domain/admin/service/AdminTipPostService.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; import org.example.tackit.domain.admin.dto.ReportedPostDTO; import org.example.tackit.domain.admin.repository.AdminTipPostRepository; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.TipPost; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -20,7 +20,7 @@ public class AdminTipPostService implements ReportedPostService{ // 비활성화 게시글 전체 조회 @Override public Page getDeletedPosts(Pageable pageable) { - return adminTipPostRepository.findAllByAccountStatusAndReportCountGreaterThanEqual(AccountStatus.DELETED, 3, pageable) + return adminTipPostRepository.findAllByActiveStatusAndReportCountGreaterThanEqual(ActiveStatus.DELETED, 3, pageable) .map(ReportedPostDTO::fromEntity); } diff --git a/src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java b/src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java new file mode 100644 index 0000000..aab20c2 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java @@ -0,0 +1,24 @@ +package org.example.tackit.domain.auth.login.repository; + +import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.entity.AccountStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface MemberRepository extends JpaRepository { + Optional findByEmail(String email); //그 유저 실제 정보 추출 + boolean existsByEmail(String email); // 이메일 존재 확인 + // boolean existsByNickname(String nickname); //닉네임 중복 확인 + boolean existsByEmailAndStatus(String email, AccountStatus accountStatus); // 이메일+상태 존재 확인 + Optional findByEmailAndStatus(String email, AccountStatus accountStatus); // 이메일+상태 정보 추출 + + // Optional findByOrganizationAndName(String organization, String name); + + // Optional findByEmailAndOrganization(String email, String organization); + + // Optional findByNameAndOrganizationAndEmail(String name, String organization, String email); +} + diff --git a/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java b/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java index bb6add1..ea97030 100644 --- a/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java +++ b/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java @@ -1,24 +1,17 @@ package org.example.tackit.domain.auth.login.service; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.example.tackit.config.Redis.RedisUtil; import org.example.tackit.config.jwt.TokenProvider; import org.example.tackit.domain.admin.repository.AdminMemberRepository; -import org.example.tackit.domain.auth.login.dto.MultiProfileDto; -import org.example.tackit.domain.auth.login.dto.SignInDto; -import org.example.tackit.domain.auth.login.dto.SignInResponse; -import org.example.tackit.domain.auth.login.dto.SignUpDto; -import org.example.tackit.domain.auth.login.dto.TokenDto; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.auth.login.dto.*; +import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; +import org.example.tackit.domain.auth.login.repository.MemberRepository; import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.entity.AccountStatus; import org.example.tackit.domain.entity.Org.MemberOrg; -import org.example.tackit.domain.member.repository.MemberOrgRepository; -import org.example.tackit.domain.member.repository.MemberRepository; +import org.example.tackit.domain.entity.Org.OrgType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -29,35 +22,40 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + @Slf4j @Service @RequiredArgsConstructor public class AuthService { - private final MemberRepository memberRepository; - private final PasswordEncoder passwordEncoder; - private final AuthenticationManager authenticationManager; - private final TokenProvider tokenProvider; - private final RedisUtil redisUtil; - private final AdminMemberRepository adminMemberRepository; - private final MemberOrgRepository memberOrgRepository; - - @Transactional - public void signup(SignUpDto signUpDto) { - if (memberRepository.existsByEmail(signUpDto.getEmail())) { - throw new RuntimeException("이미 가입되어 있는 유저입니다"); - } + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final AuthenticationManager authenticationManager; + private final TokenProvider tokenProvider; + private final RedisUtil redisUtil; + private final AdminMemberRepository adminMemberRepository; + private final MemberOrgRepository memberOrgRepository; - Member member = Member.builder() - .email(signUpDto.getEmail()) - .password(passwordEncoder.encode(signUpDto.getPassword())) - .name(signUpDto.getName()) - .accountStatus(AccountStatus.ACTIVE) - .createdAt(LocalDateTime.now()) - .build(); + @Transactional + public void signup(SignUpDto signUpDto) { + if (memberRepository.existsByEmail(signUpDto.getEmail())) { + throw new RuntimeException("이미 가입되어 있는 유저입니다"); + } + + Member member = Member.builder() + .email(signUpDto.getEmail()) + .password(passwordEncoder.encode(signUpDto.getPassword())) + .name(signUpDto.getName()) + .accountStatus(AccountStatus.ACTIVE) + .createdAt(LocalDateTime.now()) + .build(); - memberRepository.save(member); - } + memberRepository.save(member); + } /* @Transactional @@ -79,84 +77,83 @@ public TokenDto signIn(SignInDto signInDto) { } */ - @Transactional - public SignInResponse signIn(SignInDto signInDto) { - // 인증 토큰 생성 - UsernamePasswordAuthenticationToken authenticationToken = - new UsernamePasswordAuthenticationToken(signInDto.getEmail(), signInDto.getPassword()); - - try { - log.info("로그인 시도: {}", signInDto.getEmail()); - Authentication authentication = authenticationManager.authenticate(authenticationToken); - log.info("로그인 성공: {}", authentication.getName()); - - TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); - redisUtil.save(signInDto.getEmail(), tokenDto.getRefreshToken()); - - // 멀티 프로필 목록 조회 - List memberOrgs = memberOrgRepository.findAllByMemberEmail(signInDto.getEmail()); - - List profiles = memberOrgs.stream() - .map(org -> MultiProfileDto.builder() - .memberOrgId(org.getId()) - .orgName(org.getOrganization().getName()) - .nickname(org.getNickname()) - .profileImage(org.getProfileImageUrl()) - .orgType(org.getOrgType().name()) - .memberRole(org.getMemberRole().name()) - .memberType(org.getMemberType().name()) - .build()) - .collect(Collectors.toList()); - - return new SignInResponse( - tokenDto, - profiles - ); - } catch (Exception e) { - log.error("로그인 실패: {}", signInDto.getEmail(), e); - throw e; + @Transactional + public SignInResponse signIn(SignInDto signInDto) { + // 인증 토큰 생성 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(signInDto.getEmail(), signInDto.getPassword()); + + try { + log.info("로그인 시도: {}", signInDto.getEmail()); + Authentication authentication = authenticationManager.authenticate(authenticationToken); + log.info("로그인 성공: {}", authentication.getName()); + + TokenDto tokenDto = tokenProvider.generateTokenDto(authentication); + redisUtil.save(signInDto.getEmail(), tokenDto.getRefreshToken()); + + // 멀티 프로필 목록 조회 + List memberOrgs = memberOrgRepository.findAllByMemberEmail(signInDto.getEmail()); + + List profiles = memberOrgs.stream() + .map(org -> MultiProfileDto.builder() + .memberOrgId(org.getId()) + .orgName(org.getOrganization().getName()) + .nickname(org.getNickname()) + .profileImage(org.getProfileImageUrl()) + .orgType(org.getOrgType().name()) + .memberRole(org.getMemberRole().name()) + .memberType(org.getMemberType().name()) + .build()) + .collect(Collectors.toList()); + + return new SignInResponse( + tokenDto, + profiles + ); + } catch (Exception e) { + log.error("로그인 실패: {}", signInDto.getEmail(), e); + throw e; + } + } + + // Bearer 제거 및 형식 검증 + public String resolveBearerToken(String refreshToken) { + if (refreshToken == null || !refreshToken.startsWith("Bearer ")) { + throw new BadCredentialsException("리프레시 토큰이 누락되었거나 올바르지 않습니다."); + } + return refreshToken.substring(7); + } + + // 토큰 재발급 처리 + @Transactional + public TokenDto reissue(String bearerToken) { + String refreshToken = resolveBearerToken(bearerToken); + return tokenProvider.reissueAccessToken(refreshToken); } - } - // Bearer 제거 및 형식 검증 - public String resolveBearerToken(String refreshToken) { - if (refreshToken == null || !refreshToken.startsWith("Bearer ")) { - throw new BadCredentialsException("리프레시 토큰이 누락되었거나 올바르지 않습니다."); + // 특정 소속 선택 + public SignInResponse selectProfile(Long memberOrgId, String email) { + // 소속 검증 + MemberOrg selectedOrg = memberOrgRepository.findById(memberOrgId) + .filter(org -> org.getMember().getEmail().equals(email)) + .orElseThrow( () -> new RuntimeException("해당 소속 권한이 없거나 존재하지 않습니다.")); + + // 권한 리스트 생성 : Role + Type + List authorities = Arrays.asList( + new SimpleGrantedAuthority("ROLE_" + selectedOrg.getMemberRole().name()), + new SimpleGrantedAuthority("ROLE_" + selectedOrg.getMemberType().name()) + ); + + // 인증 객체 생성 + Authentication authentication = new UsernamePasswordAuthenticationToken(email, null, authorities); + + // MemberOrgId 포함하여 토큰 생성 + TokenDto orgToken = tokenProvider.generateTokenDtoWithProfile(authentication, memberOrgId); + + return SignInResponse.of(orgToken); } - return refreshToken.substring(7); - } - - // 토큰 재발급 처리 - @Transactional - public TokenDto reissue(String bearerToken) { - String refreshToken = resolveBearerToken(bearerToken); - return tokenProvider.reissueAccessToken(refreshToken); - } - - // 특정 소속 선택 - public SignInResponse selectProfile(Long memberOrgId, String email) { - // 소속 검증 - MemberOrg selectedOrg = memberOrgRepository.findById(memberOrgId) - .filter(org -> org.getMember().getEmail().equals(email)) - .orElseThrow(() -> new RuntimeException("해당 소속 권한이 없거나 존재하지 않습니다.")); - - // 권한 리스트 생성 : Role + Type - List authorities = Arrays.asList( - new SimpleGrantedAuthority("ROLE_" + selectedOrg.getMemberRole().name()), - new SimpleGrantedAuthority("ROLE_" + selectedOrg.getMemberType().name()) - ); - - // 인증 객체 생성 - Authentication authentication = new UsernamePasswordAuthenticationToken(email, null, - authorities); - - // MemberOrgId 포함하여 토큰 생성 - TokenDto orgToken = tokenProvider.generateTokenDtoWithProfile(authentication, memberOrgId); - - return SignInResponse.of(orgToken); - } - - // 이메일 찾기 + + // 이메일 찾기 /* @Transactional public FindEmailRespDto findEmailbyOrgAndNickname(String organization, String name) { @@ -217,36 +214,36 @@ public ResetTokenDto findPwByIdentity(String name, String organization, String e */ - // 비밀번호 찾기 ) 비밀번호 재설정 - @Transactional - public void resetPassword(String authorizationHeader, String newPassword) { - // 1. 토큰 추출 및 형식 검증 - String resetToken = resolveBearerToken(authorizationHeader); + // 비밀번호 찾기 ) 비밀번호 재설정 + @Transactional + public void resetPassword(String authorizationHeader, String newPassword) { + // 1. 토큰 추출 및 형식 검증 + String resetToken = resolveBearerToken(authorizationHeader); - // 2. JWT 유효성 및 용도 확인 - if (!tokenProvider.validateToken(resetToken) || !tokenProvider.isResetToken(resetToken)) { - throw new BadCredentialsException("유효하지 않거나 만료된 재설정 토큰입니다."); - } + // 2. JWT 유효성 및 용도 확인 + if( !tokenProvider.validateToken(resetToken) || !tokenProvider.isResetToken(resetToken) ) { + throw new BadCredentialsException("유효하지 않거나 만료된 재설정 토큰입니다."); + } - // 3. 토큰에서 이메일 추출 - String email = tokenProvider.getEmailFromToken(resetToken); + // 3. 토큰에서 이메일 추출 + String email = tokenProvider.getEmailFromToken(resetToken); - // 4. Redis에 저장된 토큰과 일치하는지 확인 - String token = redisUtil.getData("reset:" + email); - if (token == null || !token.equals(resetToken)) { - // 이미 사용되었거나 만료된 토큰 - throw new BadCredentialsException("이미 사용되었거나 유효하지 않은 토큰입니다."); - } + // 4. Redis에 저장된 토큰과 일치하는지 확인 + String token = redisUtil.getData("reset:" + email); + if( token == null || !token.equals(resetToken) ) { + // 이미 사용되었거나 만료된 토큰 + throw new BadCredentialsException("이미 사용되었거나 유효하지 않은 토큰입니다."); + } - // 5. 비밀번호 업데이트 - Member member = adminMemberRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException(email + " not found")); + // 5. 비밀번호 업데이트 + Member member = adminMemberRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException(email + " not found")); - String encodedNewPassword = passwordEncoder.encode(newPassword); - member.changePassword(encodedNewPassword); + String encodedNewPassword = passwordEncoder.encode(newPassword); + member.changePassword(encodedNewPassword); - // 6. Redis에서 토큰 삭제 - redisUtil.delete("reset:" + email); - } + // 6. Redis에서 토큰 삭제 + redisUtil.delete("reset:" + email); + } } diff --git a/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java b/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java index d81009a..4d44192 100644 --- a/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java +++ b/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java @@ -1,50 +1,51 @@ package org.example.tackit.domain.auth.login.service; -import java.util.ArrayList; -import java.util.List; import lombok.RequiredArgsConstructor; +import org.example.tackit.domain.auth.login.repository.MemberRepository; import org.example.tackit.domain.auth.login.security.CustomUserDetails; -import org.example.tackit.domain.entity.AccountStatus; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.member.repository.MemberRepository; +import org.example.tackit.domain.entity.AccountStatus; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; + import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; + @Service @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { + private final MemberRepository memberRepository; - private final MemberRepository memberRepository; + @Override + @Transactional + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException(email + " -> 데이터베이스에서 찾을 수 없습니다.")); - @Override - @Transactional - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException(email + " -> 데이터베이스에서 찾을 수 없습니다.")); + // 상태 확인 추가 + if (member.getAccountStatus() == AccountStatus.DELETED) { + throw new UsernameNotFoundException(email + " -> 탈퇴한 회원입니다."); + } - // 상태 확인 추가 - if (member.getAccountStatus() == AccountStatus.DELETED) { - throw new UsernameNotFoundException(email + " -> 탈퇴한 회원입니다."); + return createUserDetails(member); } - return createUserDetails(member); - } - - private UserDetails createUserDetails(Member member) { - // 최소 권한 부여 - List authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority("ROLE_USER")); - - return new CustomUserDetails( - member.getId(), - member.getEmail(), - member.getPassword(), - authorities - ); - } + private UserDetails createUserDetails(Member member) { + // 최소 권한 부여 + List authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + + return new CustomUserDetails( + member.getId(), + member.getEmail(), + member.getPassword(), + authorities + ); + } } diff --git a/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java b/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java index 275357b..95d71d2 100644 --- a/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java +++ b/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java @@ -1,38 +1,37 @@ package org.example.tackit.domain.auth.login.service; -import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.auth.login.repository.MemberRepository; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.member.repository.MemberRepository; +import org.example.tackit.domain.entity.AccountStatus; import org.springframework.stereotype.Service; +import java.util.Optional; + @Service @RequiredArgsConstructor public class RejoinCheckService { + private final MemberRepository memberRepository; - private final MemberRepository memberRepository; - - // 회원 상태 조회 - public String checkEmailStatus(String email) { - if (memberRepository.existsByEmailAndStatus(email, AccountStatus.DELETED)) { - return "DELETED"; - } else if (memberRepository.existsByEmailAndStatus(email, AccountStatus.ACTIVE)) { - return "ACTIVE"; - } else { - return "AVAILABLE"; + // 회원 상태 조회 + public String checkEmailStatus(String email) { + if (memberRepository.existsByEmailAndStatus(email, AccountStatus.DELETED)) { + return "DELETED"; + } else if (memberRepository.existsByEmailAndStatus(email, AccountStatus.ACTIVE)) { + return "ACTIVE"; + } else { + return "AVAILABLE"; + } } - } - // 탈퇴한 회원 삭제 - public boolean deleteDeletedMember(String email) { - Optional deletedMember = memberRepository.findByEmailAndStatus(email, - AccountStatus.DELETED); - if (deletedMember.isPresent()) { - memberRepository.delete(deletedMember.get()); - return true; + // 탈퇴한 회원 삭제 + public boolean deleteDeletedMember(String email) { + Optional deletedMember = memberRepository.findByEmailAndStatus(email, AccountStatus.DELETED); + if (deletedMember.isPresent()) { + memberRepository.delete(deletedMember.get()); + return true; + } + return false; } - return false; - } } diff --git a/src/main/java/org/example/tackit/domain/entity/AccountStatus.java b/src/main/java/org/example/tackit/domain/entity/ActiveStatus.java similarity index 69% rename from src/main/java/org/example/tackit/domain/entity/AccountStatus.java rename to src/main/java/org/example/tackit/domain/entity/ActiveStatus.java index 534ae49..6a15ea1 100644 --- a/src/main/java/org/example/tackit/domain/entity/AccountStatus.java +++ b/src/main/java/org/example/tackit/domain/entity/ActiveStatus.java @@ -1,5 +1,5 @@ package org.example.tackit.domain.entity; -public enum AccountStatus { +public enum ActiveStatus { ACTIVE, DELETED } diff --git a/src/main/java/org/example/tackit/domain/entity/FreePost.java b/src/main/java/org/example/tackit/domain/entity/FreePost.java index eae70e6..5925148 100644 --- a/src/main/java/org/example/tackit/domain/entity/FreePost.java +++ b/src/main/java/org/example/tackit/domain/entity/FreePost.java @@ -42,7 +42,7 @@ public class FreePost implements ReportablePost { private String tag; @Enumerated(EnumType.STRING) - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private int reportCount = 0; private Long viewCount = 0L; @@ -84,22 +84,22 @@ public void update(String title, String content, String tag) { */ public void delete() { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } public void increaseReportCount() { this.reportCount++; if (this.reportCount >= 3) { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } } public void activate(){ - if (this.accountStatus != AccountStatus.DELETED) { + if (this.activeStatus != ActiveStatus.DELETED) { throw new IllegalStateException("삭제되지 않은 게시글은 활성화할 수 없습니다."); } - this.accountStatus = AccountStatus.ACTIVE; + this.activeStatus = ActiveStatus.ACTIVE; this.reportCount = 0; } diff --git a/src/main/java/org/example/tackit/domain/entity/Member.java b/src/main/java/org/example/tackit/domain/entity/Member.java index 4275b13..599d8f5 100644 --- a/src/main/java/org/example/tackit/domain/entity/Member.java +++ b/src/main/java/org/example/tackit/domain/entity/Member.java @@ -28,11 +28,11 @@ public class Member { @Column(nullable = false) private String password; - private AccountStatus status; + private ActiveStatus status; private LocalDateTime createdAt = LocalDateTime.now(); - private AccountStatus accountStatus; // 탈퇴 계정을 위해 + private ActiveStatus activeStatus; // 탈퇴 계정을 위해 // 연차 계산 책임은 Member 도메인 내부에 분리 // member 도메인 내에서만 쓰이기 때문에 private @@ -65,7 +65,7 @@ public void changePassword(String encodedNewPassword) { // 자신을 비활성화는 책임 부여 public void deactivate() { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } /* diff --git a/src/main/java/org/example/tackit/domain/entity/QnAComment.java b/src/main/java/org/example/tackit/domain/entity/QnAComment.java index 8b3d0c0..b51efb5 100644 --- a/src/main/java/org/example/tackit/domain/entity/QnAComment.java +++ b/src/main/java/org/example/tackit/domain/entity/QnAComment.java @@ -35,7 +35,7 @@ public class QnAComment { @Column(nullable = false, updatable = false) private LocalDateTime createdAt; - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private int reportCount; public void updateContent(String content) { diff --git a/src/main/java/org/example/tackit/domain/entity/QnAPost.java b/src/main/java/org/example/tackit/domain/entity/QnAPost.java index 11a7da1..ee10582 100644 --- a/src/main/java/org/example/tackit/domain/entity/QnAPost.java +++ b/src/main/java/org/example/tackit/domain/entity/QnAPost.java @@ -36,7 +36,7 @@ public class QnAPost implements ReportablePost { private String organization; @Enumerated(EnumType.STRING) - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private int reportCount; @Builder.Default @@ -68,22 +68,22 @@ public void update(String title, String content){ } public void markAsDeleted() { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } public void increaseReportCount() { this.reportCount++; if (this.reportCount >= 3) { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } } public void activate(){ - if (this.accountStatus != AccountStatus.DELETED) { + if (this.activeStatus != ActiveStatus.DELETED) { throw new IllegalStateException("삭제되지 않은 게시글은 활성화할 수 없습니다."); } - this.accountStatus = AccountStatus.ACTIVE; + this.activeStatus = ActiveStatus.ACTIVE; this.reportCount = 0; } diff --git a/src/main/java/org/example/tackit/domain/entity/TipComment.java b/src/main/java/org/example/tackit/domain/entity/TipComment.java index e04e85e..081ed95 100644 --- a/src/main/java/org/example/tackit/domain/entity/TipComment.java +++ b/src/main/java/org/example/tackit/domain/entity/TipComment.java @@ -37,7 +37,7 @@ public class TipComment { @Column(nullable = false, updatable = false) private LocalDateTime createdAt; - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private int reportCount; public void updateContent(String content) { diff --git a/src/main/java/org/example/tackit/domain/entity/TipPost.java b/src/main/java/org/example/tackit/domain/entity/TipPost.java index 36cec63..f1bc7c4 100644 --- a/src/main/java/org/example/tackit/domain/entity/TipPost.java +++ b/src/main/java/org/example/tackit/domain/entity/TipPost.java @@ -33,7 +33,7 @@ public class TipPost implements ReportablePost { private LocalDateTime createdAt; @Enumerated(EnumType.STRING) - private AccountStatus accountStatus = AccountStatus.ACTIVE; + private ActiveStatus activeStatus = ActiveStatus.ACTIVE; private Post type; private int reportCount = 0; private Long viewCount = 0L; @@ -60,7 +60,7 @@ public class TipPost implements ReportablePost { public void increaseReportCount() { this.reportCount++; if (this.reportCount >= 3) { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } } @@ -82,15 +82,15 @@ public void update(String title, String content) { } public void delete() { - this.accountStatus = AccountStatus.DELETED; + this.activeStatus = ActiveStatus.DELETED; } public void activate(){ - if (this.accountStatus != AccountStatus.DELETED) { + if (this.activeStatus != ActiveStatus.DELETED) { throw new IllegalStateException("삭제되지 않은 게시글은 활성화할 수 없습니다."); } - this.accountStatus = AccountStatus.ACTIVE; + this.activeStatus = ActiveStatus.ACTIVE; this.reportCount = 0; } diff --git a/src/main/java/org/example/tackit/domain/event/service/EventService.java b/src/main/java/org/example/tackit/domain/event/service/EventService.java index 2d32f96..e7129bf 100644 --- a/src/main/java/org/example/tackit/domain/event/service/EventService.java +++ b/src/main/java/org/example/tackit/domain/event/service/EventService.java @@ -1,200 +1,221 @@ package org.example.tackit.domain.event.service; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.YearMonth; -import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.entity.Event; -import org.example.tackit.domain.entity.EventParticipant; -import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.Organization.repository.OrganizationRepository; +import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; +import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.entity.*; import org.example.tackit.domain.entity.Org.MemberOrg; +import org.example.tackit.domain.entity.Org.OrgStatus; import org.example.tackit.domain.entity.Org.Organization; -import org.example.tackit.domain.event.dto.EventCreateReqDto; -import org.example.tackit.domain.event.dto.EventDetailResDto; -import org.example.tackit.domain.event.dto.EventSimpleResDto; -import org.example.tackit.domain.event.dto.EventUpdateReqDto; +import org.example.tackit.domain.event.dto.*; import org.example.tackit.domain.event.repository.EventRepository; -import org.example.tackit.domain.member.component.MemberOrgValidator; -import org.example.tackit.domain.member.dto.SimpleMemberProfileDto; -import org.example.tackit.domain.member.repository.MemberOrgRepository; -import org.example.tackit.domain.organization.repository.OrganizationRepository; +import org.example.tackit.global.exception.ErrorCode; +import org.example.tackit.global.exception.MemberNotFoundException; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.YearMonth; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class EventService { - private final EventRepository eventRepository; - private final OrganizationRepository organizationRepository; - private final MemberOrgRepository memberOrgRepository; - private final MemberOrgValidator memberOrgValidator; - - // 일정 생성 - @Transactional - public Long createEvent(EventCreateReqDto reqDto, Long requesterMemberOrgId) { - MemberOrg memberOrg = memberOrgValidator.validateExecutive(reqDto.getOrgId(), - requesterMemberOrgId); - - Member creator = memberOrg.getMember(); - - Organization organization = organizationRepository.findById(reqDto.getOrgId()) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 조직입니다.")); - - Event event = Event.builder() - .organization(organization) - .creator(creator) - .title(reqDto.getTitle()) - .startsAt(reqDto.getStartsAt()) - .endsAt(reqDto.getEndsAt()) - .description(reqDto.getDescription()) - .colorChip(reqDto.getColorChip()) - .eventScope(reqDto.getEventScope()) - .build(); - - // 참여자 추가 - addParticipants(event, reqDto.getParticipants()); - - return eventRepository.save(event).getId(); - } - - // 일정 수정 - @Transactional - public void updateEvent(Long eventId, EventUpdateReqDto reqDto, Long requesterMemberOrgId) { - Event event = findEventOrThrow(eventId); - - memberOrgValidator.validateExecutive(event.getOrganization().getId(), requesterMemberOrgId); - - event.update( - reqDto.getTitle(), - reqDto.getStartsAt(), - reqDto.getEndsAt(), - reqDto.getDescription(), - reqDto.getColorChip(), - reqDto.getEventScope() - ); - - // 참여자 목록 수정 (기존의 데이터 전부 삭제 후 다시 추가) - if (reqDto.getParticipants() != null) { - event.clearParticipants(); - addParticipants(event, reqDto.getParticipants()); + private final EventRepository eventRepository; + private final OrganizationRepository organizationRepository; + private final MemberOrgRepository memberOrgRepository; + + // 일정 생성 + @Transactional + public Long createEvent(EventCreateReqDto reqDto, Long requesterId) { + MemberOrg memberOrg = validateExecutive(reqDto.getOrgId(), requesterId); + + Member creator = memberOrg.getMember(); + + Organization organization = organizationRepository.findById(reqDto.getOrgId()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 조직입니다.")); + + Event event = Event.builder() + .organization(organization) + .creator(creator) + .title(reqDto.getTitle()) + .startsAt(reqDto.getStartsAt()) + .endsAt(reqDto.getEndsAt()) + .description(reqDto.getDescription()) + .colorChip(reqDto.getColorChip()) + .eventScope(reqDto.getEventScope()) + .build(); + + // 참여자 추가 + addParticipants(event, reqDto.getParticipants()); + + return eventRepository.save(event).getId(); + } + + // 일정 수정 + @Transactional + public void updateEvent(Long eventId, EventUpdateReqDto reqDto, Long requesterId) { + Event event = findEventOrThrow(eventId); + + validateExecutive(event.getOrganization().getId(), requesterId); + + event.update( + reqDto.getTitle(), + reqDto.getStartsAt(), + reqDto.getEndsAt(), + reqDto.getDescription(), + reqDto.getColorChip(), + reqDto.getEventScope() + ); + + // 참여자 목록 수정 (기존의 데이터 전부 삭제 후 다시 추가) + if (reqDto.getParticipants() != null) { + event.clearParticipants(); + addParticipants(event, reqDto.getParticipants()); + } + } + + // 일정 삭제 + @Transactional + public void deleteEvent(Long eventId, Long requesterId) { + Event event = findEventOrThrow(eventId); + + validateExecutive(event.getOrganization().getId(), requesterId); + + eventRepository.delete(event); + } + + // 월간 일정 조회 + public List getMonthlyEvents(Long orgId, int year, int month, Long requesterId) { + validateMembership(orgId, requesterId); + + YearMonth yearMonth = YearMonth.of(year, month); + LocalDateTime startDateTime = yearMonth.atDay(1).atStartOfDay(); + LocalDateTime endDateTime = yearMonth.atEndOfMonth().atTime(LocalTime.MAX); + + List events = eventRepository.findAllByOrganizationIdAndDateRange(orgId, startDateTime, endDateTime); + + return events.stream() + .map(event -> EventSimpleResDto.builder() + .eventId(event.getId()) + .title(event.getTitle()) + .startsAt(event.getStartsAt()) + .endsAt(event.getEndsAt()) + .colorChip(event.getColorChip()) + .build()) + .collect(Collectors.toList()); + } + + // 일정 상세 조회 + public EventDetailResDto getEventDetail(Long eventId, Long requesterId) { + Event event = findEventOrThrow(eventId); + + validateMembership(event.getOrganization().getId(), requesterId); + + List participantDtos = event.getParticipants().stream() + .map(ep -> EventParticipantDto.builder() + .orgMemberId(ep.getMemberOrg().getId()) + .profileImageUrl(ep.getMemberOrg().getProfileImageUrl()) + .nickname(ep.getMemberOrg().getNickname()) + .build()) + .collect(Collectors.toList()); + + return EventDetailResDto.builder() + .eventId(event.getId()) + .title(event.getTitle()) + .startsAt(event.getStartsAt()) + .endsAt(event.getEndsAt()) + .description(event.getDescription()) + .colorChip(event.getColorChip()) + .participants(participantDtos) + .build(); } - } - - // 일정 삭제 - @Transactional - public void deleteEvent(Long eventId, Long requesterMemberOrgId) { - Event event = findEventOrThrow(eventId); - - memberOrgValidator.validateExecutive(event.getOrganization().getId(), requesterMemberOrgId); - - eventRepository.delete(event); - } - - // 월간 일정 조회 - public List getMonthlyEvents(Long orgId, int year, int month, - Long requesterMemberOrgId) { - memberOrgValidator.validateActiveMembership(orgId, requesterMemberOrgId); - - YearMonth yearMonth = YearMonth.of(year, month); - LocalDateTime startDateTime = yearMonth.atDay(1).atStartOfDay(); - LocalDateTime endDateTime = yearMonth.atEndOfMonth().atTime(LocalTime.MAX); - - List events = eventRepository.findAllByOrganizationIdAndDateRange(orgId, startDateTime, - endDateTime); - - return events.stream() - .map(event -> EventSimpleResDto.builder() - .eventId(event.getId()) - .title(event.getTitle()) - .startsAt(event.getStartsAt()) - .endsAt(event.getEndsAt()) - .colorChip(event.getColorChip()) - .build()) - .collect(Collectors.toList()); - } - - // 일정 상세 조회 - public EventDetailResDto getEventDetail(Long eventId, Long requesterMemberOrgId) { - Event event = findEventOrThrow(eventId); - - memberOrgValidator.validateActiveMembership(event.getOrganization().getId(), - requesterMemberOrgId); - - List participantDtos = event.getParticipants().stream() - .map(ep -> SimpleMemberProfileDto.builder() - .orgMemberId(ep.getMemberOrg().getId()) - .profileImageUrl(ep.getMemberOrg().getProfileImageUrl()) - .nickname(ep.getMemberOrg().getNickname()) - .build()) - .collect(Collectors.toList()); - - return EventDetailResDto.builder() - .eventId(event.getId()) - .title(event.getTitle()) - .startsAt(event.getStartsAt()) - .endsAt(event.getEndsAt()) - .description(event.getDescription()) - .colorChip(event.getColorChip()) - .participants(participantDtos) - .build(); - } - - // 다가오는 일정 조회 - public List getUpcomingEvents(Long orgId, Long requesterMemberOrgId) { - memberOrgValidator.validateActiveMembership(orgId, requesterMemberOrgId); - - List events = eventRepository.findByOrganizationIdAndStartsAtAfterOrderByStartsAtAsc( - orgId, - LocalDateTime.now() - ); - - return events.stream() - .map(event -> EventSimpleResDto.builder() - .eventId(event.getId()) - .title(event.getTitle()) - .startsAt(event.getStartsAt()) - .endsAt(event.getEndsAt()) - .colorChip(event.getColorChip()) - .build()) - .collect(Collectors.toList()); - } - - // 이벤트 참가자 추가 메서드 - private void addParticipants(Event event, List memberOrgIds) { - if (memberOrgIds == null || memberOrgIds.isEmpty()) { - return; + + // 다가오는 일정 조회 + public List getUpcomingEvents(Long orgId, Long requesterId) { + validateMembership(orgId, requesterId); + + List events = eventRepository.findByOrganizationIdAndStartsAtAfterOrderByStartsAtAsc( + orgId, + LocalDateTime.now() + ); + + return events.stream() + .map(event -> EventSimpleResDto.builder() + .eventId(event.getId()) + .title(event.getTitle()) + .startsAt(event.getStartsAt()) + .endsAt(event.getEndsAt()) + .colorChip(event.getColorChip()) + .build()) + .collect(Collectors.toList()); } - List memberOrgs = memberOrgRepository.findAllById(memberOrgIds); + // 이벤트 참가자 추가 메서드 + private void addParticipants(Event event, List memberOrgIds) { + if (memberOrgIds == null || memberOrgIds.isEmpty()) return; + + List memberOrgs = memberOrgRepository.findAllById(memberOrgIds); + + // 개수 검증 + if (memberOrgs.size() != memberOrgIds.size()) { + throw new IllegalArgumentException("존재하지 않는 부원 ID가 포함되어 있습니다."); + } + + for (MemberOrg memberOrg : memberOrgs) { + // 소속 동아리 일치 여부 검증 + if (!memberOrg.getOrganization().getId().equals(event.getOrganization().getId())) { + throw new IllegalArgumentException("해당 동아리의 소속 부원이 아닙니다."); + } + + // 참여자 생성 + EventParticipant participant = EventParticipant.builder() + .event(event) + .memberOrg(memberOrg) + .build(); + + participant.assignEvent(event); + } + } + + // 이벤트 존재 확인 메서드 + private Event findEventOrThrow(Long eventId) { + return eventRepository.findById(eventId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 일정입니다.")); + } - // 개수 검증 - if (memberOrgs.size() != memberOrgIds.size()) { - throw new IllegalArgumentException("존재하지 않는 부원 ID가 포함되어 있습니다."); + // 활동 중인 멤버(운영진 포함)인지 확인하는 메서드 + private void validateMembership(Long orgId, Long memberId) { + boolean isActiveMember = memberOrgRepository.existsByMemberIdAndOrganizationIdAndOrgStatus( + memberId, + orgId, + OrgStatus.ACTIVE + ); + + if (!isActiveMember) { + throw new IllegalArgumentException("해당 조직의 활동 중인 회원만 접근할 수 있습니다."); + } } - for (MemberOrg memberOrg : memberOrgs) { - // 소속 동아리 일치 여부 검증 - if (!memberOrg.getOrganization().getId().equals(event.getOrganization().getId())) { - throw new IllegalArgumentException("해당 동아리의 소속 부원이 아닙니다."); - } + // 활동 중인 운영진인지 확인하는 메서드 + private MemberOrg validateExecutive(Long orgId, Long memberId) { + MemberOrg memberOrg = memberOrgRepository.findByMemberIdAndOrganizationIdAndOrgStatus( + memberId, + orgId, + OrgStatus.ACTIVE + ).orElseThrow(() -> new IllegalArgumentException("해당 조직의 활동 중인 회원만 접근할 수 있습니다.")); - // 참여자 생성 - EventParticipant participant = EventParticipant.builder() - .event(event) - .memberOrg(memberOrg) - .build(); + if (memberOrg.getMemberRole() != MemberRole.EXECUTIVE) { + throw new IllegalArgumentException("해당 조직의 운영진만 일정을 관리할 수 있습니다."); + } - participant.assignEvent(event); + return memberOrg; } - } - - // 이벤트 존재 확인 메서드 - private Event findEventOrThrow(Long eventId) { - return eventRepository.findById(eventId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 일정입니다.")); - } -} \ No newline at end of file +} diff --git a/src/main/java/org/example/tackit/domain/freeBoard/Free_post/repository/FreePostJPARepository.java b/src/main/java/org/example/tackit/domain/freeBoard/Free_post/repository/FreePostJPARepository.java index 1021263..ab0dbf6 100644 --- a/src/main/java/org/example/tackit/domain/freeBoard/Free_post/repository/FreePostJPARepository.java +++ b/src/main/java/org/example/tackit/domain/freeBoard/Free_post/repository/FreePostJPARepository.java @@ -1,7 +1,7 @@ package org.example.tackit.domain.freeBoard.Free_post.repository; import org.example.tackit.domain.entity.FreePost; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -14,20 +14,20 @@ public interface FreePostJPARepository extends JpaRepository { - Page findByWriterIdAndAccountStatus(Long writerId, AccountStatus accountStatus, Pageable pageable); + Page findByWriterIdAndActiveStatus(Long writerId, ActiveStatus activeStatus, Pageable pageable); - Page findAllByOrganizationIdAndAccountStatus(Long orgId, AccountStatus status, Pageable pageable); + Page findAllByOrganizationIdAndActiveStatus(Long orgId, ActiveStatus status, Pageable pageable); - List findTop3ByOrganizationIdAndAccountStatusOrderByViewCountDescScrapCountDesc(Long orgId, AccountStatus accountStatus); + List findTop3ByOrganizationIdAndActiveStatusOrderByViewCountDescScrapCountDesc(Long orgId, ActiveStatus activeStatus); // 인기 3개 @Query("SELECT f FROM FreePost f " + - "WHERE f.accountStatus = :status " + + "WHERE f.activeStatus = :status " + "AND f.createdAt BETWEEN :start AND :end " + "AND f.writer.organization.id = :orgId " + "ORDER BY f.viewCount DESC, f.scrapCount DESC") List findTop3PopularByOrg( - @Param("status") AccountStatus status, + @Param("status") ActiveStatus status, @Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("orgId") Long orgId, diff --git a/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java b/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java index 58082ed..4ee1fb6 100644 --- a/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java +++ b/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java @@ -1,363 +1,346 @@ package org.example.tackit.domain.freeBoard.Free_post.service; -import static org.example.tackit.global.exception.ErrorCode.MEMBER_NOT_FOUND; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.example.tackit.common.dto.PageResponseDTO; import org.example.tackit.config.S3.S3UploadService; -import org.example.tackit.domain.entity.AccountStatus; -import org.example.tackit.domain.entity.FreePost; -import org.example.tackit.domain.entity.FreePostImage; -import org.example.tackit.domain.entity.FreeReport; -import org.example.tackit.domain.entity.FreeScrap; -import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.MemberRole; -import org.example.tackit.domain.entity.MemberType; -import org.example.tackit.domain.entity.Notification; -import org.example.tackit.domain.entity.NotificationType; -import org.example.tackit.domain.entity.Org.MemberOrg; -import org.example.tackit.domain.entity.Post; import org.example.tackit.domain.freeBoard.Free_post.dto.request.FreePostReqDto; import org.example.tackit.domain.freeBoard.Free_post.dto.request.UpdateFreeReqDto; import org.example.tackit.domain.freeBoard.Free_post.dto.response.FreePopularPostRespDto; import org.example.tackit.domain.freeBoard.Free_post.dto.response.FreePostRespDto; import org.example.tackit.domain.freeBoard.Free_post.dto.response.FreeScrapResponseDto; -import org.example.tackit.domain.freeBoard.Free_post.repository.FreePostImageRepository; -import org.example.tackit.domain.freeBoard.Free_post.repository.FreePostJPARepository; -import org.example.tackit.domain.freeBoard.Free_post.repository.FreePostReportRepository; -import org.example.tackit.domain.freeBoard.Free_post.repository.FreeScrapJPARepository; +import org.example.tackit.domain.freeBoard.Free_post.repository.*; import org.example.tackit.domain.freeBoard.Free_tag.repository.FreePostTagMapRepository; -import org.example.tackit.domain.member.repository.MemberOrgRepository; -import org.example.tackit.domain.member.repository.MemberRepository; +import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; +import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.entity.*; +import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.notification.service.NotificationService; -import org.example.tackit.global.exception.AccessDeniedCustomException; -import org.example.tackit.global.exception.ErrorCode; -import org.example.tackit.global.exception.MemberNotFoundException; -import org.example.tackit.global.exception.PostInactiveException; -import org.example.tackit.global.exception.PostNotFoundException; +import org.example.tackit.common.dto.PageResponseDTO; +import org.example.tackit.global.exception.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.example.tackit.global.exception.ErrorCode.MEMBER_NOT_FOUND; + @Service @RequiredArgsConstructor public class FreePostService { - - private final FreePostJPARepository freePostJPARepository; - private final MemberOrgRepository memberOrgRepository; - private final MemberRepository memberRepository; - private final FreePostTagService tagService; - private final FreeScrapJPARepository freeScrapJPARepository; - private final FreePostTagMapRepository freePostTagMapRepository; - private final FreePostReportRepository freePostReportRepository; - private final S3UploadService s3UploadService; - private final FreePostImageRepository freePostImageRepository; - private final NotificationService notificationService; - - // [ 게시글 전체 조회 ] - @Transactional - public PageResponseDTO findAll(String email, Long profileId, Pageable pageable) { - // 현재 접속한 멀티프로필 정보 조회 - MemberOrg currProfile = memberOrgRepository.findByMemberEmailAndId(email, profileId) - .orElseThrow(() -> new RuntimeException("해당 조직에 대한 접근 권한이 없거나 유효하지 않은 프로필입니다.")); - - // 해당 프로필이 속한 조직의 ID 가져오기 - Long orgId = currProfile.getOrganization().getId(); - - // 해당 조직의 게시글만 조회 - Page page = freePostJPARepository.findAllByOrganizationIdAndAccountStatus( - orgId, - AccountStatus.ACTIVE, - pageable - ); - - return PageResponseDTO.from(page, post -> { - List tags = freePostTagMapRepository.findByFreePost(post).stream() - .map(mapping -> mapping.getTag().getTagName()) - .toList(); - - String imageUrl = post.getImages().isEmpty() ? null - : post.getImages().get(0).getImageUrl(); - - String profileImageUrl = post.getWriter().getProfileImageUrl(); - - return FreePostRespDto.builder() - .id(post.getId()) - .writer(post.isAnonymous() ? "익명" : post.getWriter().getNickname()) - .profileImageUrl(post.isAnonymous() ? null : profileImageUrl) - .title(post.getTitle()) - .content(post.getContent()) - .createdAt(post.getCreatedAt()) - .tags(tags) - .imageUrl(imageUrl) - .isAnonymous(post.isAnonymous()) - .build(); - }); - } - - // [ 게시글 상세 조회 ] - @Transactional - public FreePostRespDto getPostById(Long id, Long orgId, Long memberId) { - FreePost post = freePostJPARepository.findById(id) - .orElseThrow(() -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND)); - - MemberOrg currProfile = memberOrgRepository.findById(orgId) - .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); - - if (post.getOrganization().getId().equals(currProfile.getOrganization().getId())) { - throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_ORGANIZATION); + private final FreePostJPARepository freePostJPARepository; + private final MemberOrgRepository memberOrgRepository; + private final MemberRepository memberRepository; + private final FreePostTagService tagService; + private final FreeScrapJPARepository freeScrapJPARepository; + private final FreePostTagMapRepository freePostTagMapRepository; + private final FreePostReportRepository freePostReportRepository; + private final S3UploadService s3UploadService; + private final FreePostImageRepository freePostImageRepository; + private final NotificationService notificationService; + + // [ 게시글 전체 조회 ] + @Transactional + public PageResponseDTO findAll(String email, Long profileId, Pageable pageable) { + // 현재 접속한 멀티프로필 정보 조회 + MemberOrg currProfile = memberOrgRepository.findByMemberEmailAndId(email, profileId) + .orElseThrow(() -> new RuntimeException("해당 조직에 대한 접근 권한이 없거나 유효하지 않은 프로필입니다.")); + + // 해당 프로필이 속한 조직의 ID 가져오기 + Long orgId = currProfile.getOrganization().getId(); + + // 해당 조직의 게시글만 조회 + Page page = freePostJPARepository.findAllByOrganizationIdAndAccountStatus( + orgId, + AccountStatus.ACTIVE, + pageable + ); + + + + return PageResponseDTO.from(page, post -> { + List tags = freePostTagMapRepository.findByFreePost(post).stream() + .map(mapping -> mapping.getTag().getTagName()) + .toList(); + + String imageUrl = post.getImages().isEmpty() ? null + : post.getImages().get(0).getImageUrl(); + + String profileImageUrl = post.getWriter().getProfileImageUrl(); + + return FreePostRespDto.builder() + .id(post.getId()) + .writer(post.isAnonymous() ? "익명" : post.getWriter().getNickname()) + .profileImageUrl(post.isAnonymous() ? null : profileImageUrl) + .title(post.getTitle()) + .content(post.getContent()) + .createdAt(post.getCreatedAt()) + .tags(tags) + .imageUrl(imageUrl) + .isAnonymous(post.isAnonymous()) + .build(); + }); } - if (!post.getAccountStatus().equals(AccountStatus.ACTIVE)) { - throw new PostInactiveException(ErrorCode.POST_IS_INACTIVE); - } + // [ 게시글 상세 조회 ] + @Transactional + public FreePostRespDto getPostById(Long id, Long orgId, Long memberId) { + FreePost post = freePostJPARepository.findById(id) + .orElseThrow( () -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND) ); - post.increaseViewCount(); - - List tagNames = tagService.getTagNamesByPost(post); - - String imageUrl = post.getImages().isEmpty() ? null - : post.getImages().get(0).getImageUrl(); - - String profileImageUrl = post.getWriter().getProfileImageUrl(); - - // 스크랩 여부 조회 - boolean isScrap = freeScrapJPARepository.existsByFreePostIdAndMemberId(id, memberId); - - return FreePostRespDto.builder() - .id(post.getId()) - .writer(post.isAnonymous() ? "익명" : post.getWriter().getNickname()) - .profileImageUrl(post.isAnonymous() ? null : profileImageUrl) - .title(post.getTitle()) - .content(post.getContent()) - .tags(tagNames) - .imageUrl(imageUrl) - .createdAt(post.getCreatedAt()) - .isScrap(isScrap) - .isAnonymous(post.isAnonymous()) - .build(); - } - - // [ 게시글 작성 ] - @Transactional - public FreePostRespDto createPost(FreePostReqDto dto, String email, Long profileId) - throws IOException { - - // 1. 유저 조회 - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); - - // 2. Member의 Id와 Profile Id로 조회 - MemberOrg memberOrg = memberOrgRepository.findByMemberIdAndProfileId(member.getId(), profileId) - .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); - - // 2. 게시글 생성 - FreePost post = FreePost.builder() - .writer(memberOrg) - .organization(memberOrg.getOrganization()) - .title(dto.getTitle()) - .content(dto.getContent()) - .isAnonymous(dto.isAnonymous()) - .createdAt(LocalDateTime.now()) - .type(Post.Free) - .accountStatus(AccountStatus.ACTIVE) - .reportCount(0) - .build(); - - freePostJPARepository.save(post); - - // 3. 이미지 업로드 → PostImage 저장 - String imageUrl = null; - if (dto.getImage() != null && !dto.getImage().isEmpty()) { - imageUrl = s3UploadService.saveFile(dto.getImage()); - - FreePostImage image = FreePostImage.builder() - .imageUrl(imageUrl) - .freePost(post) - .build(); - - freePostImageRepository.save(image); // 따로 JPARepository 필요 - } + MemberOrg currProfile = memberOrgRepository.findById(orgId) + .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); - List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); + if (post.getOrganization().getId().equals(currProfile.getOrganization().getId()) ) { + throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_ORGANIZATION); + } + + if (!post.getAccountStatus().equals(AccountStatus.ACTIVE)) { + throw new PostInactiveException(ErrorCode.POST_IS_INACTIVE); + } - return FreePostRespDto.builder() - .id(post.getId()) - .writer(post.isAnonymous() ? "익명" : memberOrg.getNickname()) - .title(post.getTitle()) - .content(post.getContent()) - .createdAt(post.getCreatedAt()) - .tags(tagNames) - .imageUrl(imageUrl) - .isAnonymous(post.isAnonymous()) - .build(); + post.increaseViewCount(); - } + List tagNames = tagService.getTagNamesByPost(post); - // [ 게시글 수정 ] : 작성자만 - @Transactional - public FreePostRespDto update(Long id, UpdateFreeReqDto req, String email, Long orgId) - throws IOException { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + String imageUrl = post.getImages().isEmpty() ? null + : post.getImages().get(0).getImageUrl(); - FreePost post = freePostJPARepository.findById(id) - .orElseThrow(() -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND)); + String profileImageUrl = post.getWriter().getProfileImageUrl(); - boolean isWriter = post.getWriter().getId().equals(member.getId()); + // 스크랩 여부 조회 + boolean isScrap = freeScrapJPARepository.existsByFreePostIdAndMemberId(id, memberId); - if (!isWriter) { - throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_EDIT); + return FreePostRespDto.builder() + .id(post.getId()) + .writer(post.isAnonymous() ? "익명" : post.getWriter().getNickname()) + .profileImageUrl(post.isAnonymous() ? null : profileImageUrl) + .title(post.getTitle()) + .content(post.getContent()) + .tags(tagNames) + .imageUrl(imageUrl) + .createdAt(post.getCreatedAt()) + .isScrap(isScrap) + .isAnonymous(post.isAnonymous()) + .build(); } - post.update(req.getTitle(), req.getContent()); + // [ 게시글 작성 ] + @Transactional + public FreePostRespDto createPost(FreePostReqDto dto, String email, Long profileId) throws IOException { + + // 1. 유저 조회 + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + + // 2. Member의 Id와 Profile Id로 조회 + MemberOrg memberOrg = memberOrgRepository.findByMemberIdAndProfileId(member.getId(), profileId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + + // 2. 게시글 생성 + FreePost post = FreePost.builder() + .writer(memberOrg) + .organization(memberOrg.getOrganization()) + .title(dto.getTitle()) + .content(dto.getContent()) + .isAnonymous(dto.isAnonymous()) + .createdAt(LocalDateTime.now()) + .type(Post.Free) + .accountStatus(AccountStatus.ACTIVE) + .reportCount(0) + .build(); + + freePostJPARepository.save(post); + + // 3. 이미지 업로드 → PostImage 저장 + String imageUrl = null; + if (dto.getImage() != null && !dto.getImage().isEmpty()) { + imageUrl = s3UploadService.saveFile(dto.getImage()); + + FreePostImage image = FreePostImage.builder() + .imageUrl(imageUrl) + .freePost(post) + .build(); + + freePostImageRepository.save(image); // 따로 JPARepository 필요 + } - tagService.deleteTagsByPost(post); - List tagNames = tagService.assignTagsToPost(post, req.getTagIds()); + List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); - String imageUrl = null; - // 1. "이미지 제거" 요청 - if (req.isRemoveImage()) { - freePostImageRepository.findByFreePostId(post.getId()) - .forEach(oldImage -> { - s3UploadService.deleteImage(oldImage.getImageUrl()); // S3 삭제 - freePostImageRepository.delete(oldImage); // DB 삭제 - }); - } + return FreePostRespDto.builder() + .id(post.getId()) + .writer(post.isAnonymous() ? "익명" : memberOrg.getNickname()) + .title(post.getTitle()) + .content(post.getContent()) + .createdAt(post.getCreatedAt()) + .tags(tagNames) + .imageUrl(imageUrl) + .isAnonymous(post.isAnonymous()) + .build(); - // 2. 새 이미지 업로드 - else if (req.getImage() != null && !req.getImage().isEmpty()) { - // 기존 이미지 제거 - freePostImageRepository.findByFreePostId(post.getId()) - .forEach(oldImage -> { - s3UploadService.deleteImage(oldImage.getImageUrl()); - freePostImageRepository.delete(oldImage); - }); - - // 새 이미지 저장 - imageUrl = s3UploadService.saveFile(req.getImage()); - FreePostImage newImage = FreePostImage.builder() - .imageUrl(imageUrl) - .freePost(post) - .build(); - - freePostImageRepository.save(newImage); } - // 3. 아무 요청 없으면 기존 이미지 유지 - else { - List images = freePostImageRepository.findByFreePostId(post.getId()); - if (!images.isEmpty()) { - imageUrl = images.get(0).getImageUrl(); - } - } + // [ 게시글 수정 ] : 작성자만 + @Transactional + public FreePostRespDto update(Long id, UpdateFreeReqDto req, String email, Long orgId) throws IOException { + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); - return FreePostRespDto.builder() - .id(post.getId()) - .writer(post.isAnonymous() ? "익명" : member.getNickname()) - .title(post.getTitle()) - .content(post.getContent()) - .createdAt(post.getCreatedAt()) - .tags(tagNames) - //.imageUrl(imageUrl) - .imageUrl(post.isAnonymous() ? null : imageUrl) - .isAnonymous(post.isAnonymous()) - .build(); - } - - // [ 게시글 삭제 ] : 작성자, 관리자만 - @Transactional - public void delete(Long id, String email, Long orgId) { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); - - FreePost post = freePostJPARepository.findById(id) - .orElseThrow(() -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND)); - - boolean isWriter = post.getWriter().getId().equals(member.getId()); - boolean isAdmin = (member.getMemberRole() == MemberRole.ADMIN) - && (member.getMemberType() == MemberType.ADMIN); - - if (!isAdmin && !isWriter) { - throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_DELETE); - } + FreePost post = freePostJPARepository.findById(id) + .orElseThrow( () -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND) ); - post.delete(); // Soft Deleted - } + boolean isWriter = post.getWriter().getId().equals(member.getId()); - // [ 게시글 신고 ] - @Transactional - public String report(Long postId, Long orgId) { - MemberOrg member = memberOrgRepository.findById(orgId) - .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + if (!isWriter) { + throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_EDIT); + } - FreePost post = freePostJPARepository.findById(postId) - .orElseThrow(() -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND)); + post.update(req.getTitle(), req.getContent()); - boolean alreadyReported = freePostReportRepository.existsByMemberAndFreePost(member, post); + tagService.deleteTagsByPost(post); + List tagNames = tagService.assignTagsToPost(post, req.getTagIds()); - if (alreadyReported) { - return "이미 신고한 게시글입니다."; - } - freePostReportRepository.save( - FreeReport.builder() - .member(member) - .freePost(post) - .build() - ); - // 신고 횟수 증가 - post.increaseReportCount(); - return "게시글을 신고하였습니다."; - } - - - // [ 게시글 스크랩 ] - @Transactional - public FreeScrapResponseDto toggleScrap(Long postId, String email, Long orgId) { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); - - FreePost post = freePostJPARepository.findById(postId) - .orElseThrow(() -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND)); - - if (!post.getOrganization().equals(orgId)) { - throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_ORGANIZATION); + String imageUrl = null; + // 1. "이미지 제거" 요청 + if (req.isRemoveImage()) { + freePostImageRepository.findByFreePostId(post.getId()) + .forEach(oldImage -> { + s3UploadService.deleteImage(oldImage.getImageUrl()); // S3 삭제 + freePostImageRepository.delete(oldImage); // DB 삭제 + }); + } + + // 2. 새 이미지 업로드 + else if (req.getImage() != null && !req.getImage().isEmpty()) { + // 기존 이미지 제거 + freePostImageRepository.findByFreePostId(post.getId()) + .forEach(oldImage -> { + s3UploadService.deleteImage(oldImage.getImageUrl()); + freePostImageRepository.delete(oldImage); + }); + + // 새 이미지 저장 + imageUrl = s3UploadService.saveFile(req.getImage()); + FreePostImage newImage = FreePostImage.builder() + .imageUrl(imageUrl) + .freePost(post) + .build(); + + freePostImageRepository.save(newImage); + } + + // 3. 아무 요청 없으면 기존 이미지 유지 + else { + List images = freePostImageRepository.findByFreePostId(post.getId()); + if (!images.isEmpty()) { + imageUrl = images.get(0).getImageUrl(); + } + } + + return FreePostRespDto.builder() + .id(post.getId()) + .writer(post.isAnonymous() ? "익명" : member.getNickname()) + .title(post.getTitle()) + .content(post.getContent()) + .createdAt(post.getCreatedAt()) + .tags(tagNames) + //.imageUrl(imageUrl) + .imageUrl(post.isAnonymous() ? null : imageUrl) + .isAnonymous(post.isAnonymous()) + .build(); } - Optional existing = freeScrapJPARepository.findByMemberAndFreePost(member, post); + // [ 게시글 삭제 ] : 작성자, 관리자만 + @Transactional + public void delete(Long id, String email, Long orgId) { + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + + FreePost post = freePostJPARepository.findById(id) + .orElseThrow( () -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND) ); + + boolean isWriter = post.getWriter().getId().equals(member.getId()); + boolean isAdmin = (member.getMemberRole() == MemberRole.ADMIN) + && (member.getMemberType() == MemberType.ADMIN); - if (existing.isPresent()) { - freeScrapJPARepository.delete(existing.get()); - post.decreaseScrapCount(); - return new FreeScrapResponseDto(false, null); + if (!isAdmin && !isWriter) { + throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_DELETE); + } + + post.delete(); // Soft Deleted } - FreeScrap scrap = FreeScrap.builder() - .member(member) - .freePost(post) - .savedAt(LocalDateTime.now()) - .build(); - - freeScrapJPARepository.save(scrap); - post.increaseScrapCount(); - - // 알림 전송 - if (!post.getWriter().getId().equals(member.getId())) { - notificationService.send(Notification.builder() - .member(post.getWriter().getMember()) - .memberOrgId(post.getWriter().getId()) - .type(NotificationType.SCRAP) - .message(member.getNickname() + "님이 글을 스크랩하였습니다.") - .fromMemberOrgId(member.getId()) - .relatedUrl("/api/free-posts/" + post.getId()) - .build()); + // [ 게시글 신고 ] + @Transactional + public String report(Long postId, Long orgId) { + MemberOrg member = memberOrgRepository.findById(orgId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + + FreePost post = freePostJPARepository.findById(postId) + .orElseThrow( () -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND) ); + + boolean alreadyReported = freePostReportRepository.existsByMemberAndFreePost(member, post); + + if (alreadyReported) { + return "이미 신고한 게시글입니다."; + } + freePostReportRepository.save( + FreeReport.builder() + .member(member) + .freePost(post) + .build() + ); + // 신고 횟수 증가 + post.increaseReportCount(); + return "게시글을 신고하였습니다."; } - return new FreeScrapResponseDto(true, scrap.getSavedAt()); + + + // [ 게시글 스크랩 ] + @Transactional + public FreeScrapResponseDto toggleScrap(Long postId, String email, Long orgId) { + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND)); + + FreePost post = freePostJPARepository.findById(postId) + .orElseThrow( () -> new PostNotFoundException(ErrorCode.POST_NOT_FOUND) ); + + if (!post.getOrganization().equals(orgId)) { + throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_ORGANIZATION); + } + + Optional existing = freeScrapJPARepository.findByMemberAndFreePost(member, post); + + if (existing.isPresent()) { + freeScrapJPARepository.delete(existing.get()); + post.decreaseScrapCount(); + return new FreeScrapResponseDto(false, null); + } + + FreeScrap scrap = FreeScrap.builder() + .member(member) + .freePost(post) + .savedAt(LocalDateTime.now()) + .build(); + + freeScrapJPARepository.save(scrap); + post.increaseScrapCount(); + + // 알림 전송 + if( !post.getWriter().getId().equals(member.getId())) { + notificationService.send(Notification.builder() + .member(post.getWriter().getMember()) + .memberOrgId(post.getWriter().getId()) + .type(NotificationType.SCRAP) + .message(member.getNickname() + "님이 글을 스크랩하였습니다.") + .fromMemberOrgId(member.getId()) + .relatedUrl("/api/free-posts/" + post.getId()) + .build()); + } + return new FreeScrapResponseDto(true, scrap.getSavedAt()); /* if(!post.getWriter().getId().equals(member.getId())){ Member postWriter = post.getWriter(); @@ -379,23 +362,22 @@ public FreeScrapResponseDto toggleScrap(Long postId, String email, Long orgId) { return new FreeScrapResponseDto(true, scrap.getSavedAt()); */ - } - - /* - public List getPopularPosts(Long orgId) { - return freePostJPARepository.findTop3ByWriterIdAndAccountStatusOrderByViewCountDescScrapCountDesc(orgId, AccountStatus.ACTIVE) - .stream() - .map(FreePopularPostRespDto::from) - .toList(); - */ - // 인기 3개 - @Transactional(readOnly = true) - public List getPopularPosts(Long orgId) { - return freePostJPARepository.findTop3ByOrganizationIdAndAccountStatusOrderByViewCountDescScrapCountDesc( - orgId, AccountStatus.ACTIVE) - .stream() - .map(FreePopularPostRespDto::from) - .toList(); + } + + /* + public List getPopularPosts(Long orgId) { + return freePostJPARepository.findTop3ByWriterIdAndAccountStatusOrderByViewCountDescScrapCountDesc(orgId, AccountStatus.ACTIVE) + .stream() + .map(FreePopularPostRespDto::from) + .toList(); + */ + // 인기 3개 + @Transactional(readOnly = true) + public List getPopularPosts(Long orgId) { + return freePostJPARepository.findTop3ByOrganizationIdAndAccountStatusOrderByViewCountDescScrapCountDesc(orgId, AccountStatus.ACTIVE) + .stream() + .map(FreePopularPostRespDto::from) + .toList(); /* .filter(post -> post.getWriter().getOrganization().equals(organization)) .sorted(Comparator @@ -412,6 +394,6 @@ public List getPopularPosts(Long orgId) { .map(FreePopularPostRespDto::from) .toList(); */ - } + } } diff --git a/src/main/java/org/example/tackit/domain/freeBoard/Free_tag/repository/FreeTagCustomRepositoryImpl.java b/src/main/java/org/example/tackit/domain/freeBoard/Free_tag/repository/FreeTagCustomRepositoryImpl.java index b367a4c..afea6d4 100644 --- a/src/main/java/org/example/tackit/domain/freeBoard/Free_tag/repository/FreeTagCustomRepositoryImpl.java +++ b/src/main/java/org/example/tackit/domain/freeBoard/Free_tag/repository/FreeTagCustomRepositoryImpl.java @@ -5,7 +5,7 @@ import org.example.tackit.domain.entity.FreePost; import org.example.tackit.domain.entity.FreePostImage; import org.example.tackit.domain.entity.FreeTagMap; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -15,7 +15,6 @@ import java.util.stream.Collectors; import static org.example.tackit.domain.entity.QFreePostImage.freePostImage; -import static org.example.tackit.domain.entity.QMember.member; import static org.example.tackit.domain.entity.QFreePost.freePost; import static org.example.tackit.domain.entity.QFreeTag.freeTag; import static org.example.tackit.domain.entity.QFreeTagMap.freeTagMap; @@ -46,7 +45,7 @@ public Page findPostsByTagId(Long tagId, String organiza .selectFrom(freePost) .join(freePost.writer).fetchJoin() .where(freePost.id.in(postIds), - freePost.accountStatus.eq(AccountStatus.ACTIVE) + freePost.activeStatus.eq(ActiveStatus.ACTIVE) // freePost.writer.organization.eq(organization) ) .orderBy(freePost.createdAt.desc()) @@ -105,7 +104,7 @@ public Page findPostsByTagId(Long tagId, String organiza .join(freeTagMap.tag, freeTag) .where( freeTag.id.eq(tagId), - freePost.accountStatus.eq(AccountStatus.ACTIVE) + freePost.activeStatus.eq(ActiveStatus.ACTIVE) ) .fetchOne(); return new PageImpl<>(content, pageable, total); diff --git a/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java b/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java index 10910ab..7a3dc1f 100644 --- a/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java +++ b/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java @@ -29,12 +29,17 @@ public class MyPageQnAService { private final QnACommentRepository qnACommentRepository; private final QnAPostTagMapRepository qnAPostTagMapRepository; +<<<<<<< HEAD // 내가 쓴 게시글 조회 @Transactional(readOnly = true) public PageResponseDTO getMyPosts(String email, Long orgId, Pageable pageable) { MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); +======= + Page postPage = qnAPostRepository.findByWriterAndActiveStatus(member, ActiveStatus.ACTIVE, pageable); + List posts = postPage.getContent(); +>>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) Page postPage = qnAPostRepository.findByWriterAndAccountStatus(member, AccountStatus.ACTIVE, pageable); diff --git a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java index eb84eaf..ab269a3 100644 --- a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java +++ b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java @@ -1,145 +1,138 @@ package org.example.tackit.domain.qnaBoard.QnA_comment.service; import jakarta.persistence.EntityNotFoundException; -import java.time.LocalDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.entity.AccountStatus; -import org.example.tackit.domain.entity.MemberRole; -import org.example.tackit.domain.entity.MemberType; -import org.example.tackit.domain.entity.Notification; -import org.example.tackit.domain.entity.NotificationType; -import org.example.tackit.domain.entity.Org.MemberOrg; -import org.example.tackit.domain.entity.QnAComment; -import org.example.tackit.domain.entity.QnAPost; -import org.example.tackit.domain.member.repository.MemberOrgRepository; -import org.example.tackit.domain.notification.service.NotificationService; import org.example.tackit.domain.qnaBoard.QnA_comment.dto.request.QnACommentCreateDto; import org.example.tackit.domain.qnaBoard.QnA_comment.dto.request.QnACommentUpdateDto; import org.example.tackit.domain.qnaBoard.QnA_comment.dto.response.QnACommentResponseDto; import org.example.tackit.domain.qnaBoard.QnA_comment.repository.QnACommentRepository; import org.example.tackit.domain.qnaBoard.QnA_post.repository.QnAPostRepository; +import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; +import org.example.tackit.domain.entity.*; +import org.example.tackit.domain.entity.Org.MemberOrg; +import org.example.tackit.domain.notification.service.NotificationService; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.List; + @Service @RequiredArgsConstructor public class QnACommentService { + private final QnACommentRepository qnACommentRepository; + private final QnAPostRepository qnAPostRepository; + private final MemberOrgRepository memberOrgRepository; + private final NotificationService notificationService; + + // 댓글 생성 + @Transactional + public QnACommentResponseDto createComment(QnACommentCreateDto dto, String email, Long orgId){ + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); + + QnAPost post = qnAPostRepository.findById(dto.getQnaPostId()) + .orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다.")); + + QnAComment comment = QnAComment.builder() + .writer(member) + .accountStatus(AccountStatus.ACTIVE) + .qnAPost(post) + .content(dto.getContent()) + .createdAt(LocalDateTime.now()) + .build(); + + // 댓글 DB 저장 + QnAComment savedComment = qnACommentRepository.save(comment); + + // 알림 전송 + if (!post.getWriter().getId().equals(member.getId())) { + MemberOrg postWriter = post.getWriter(); + + notificationService.send(Notification.builder() + .member(postWriter.getMember()) // 수신자 계정 + .memberOrgId(postWriter.getId()) // 수신자 프로필 ID + .type(NotificationType.COMMENT) + .message(member.getNickname() + "님이 QnA 글에 댓글을 남겼습니다.") + .relatedUrl("/api/qna-posts/" + post.getId()) + .fromMemberOrgId(member.getId()) // 보낸 사람 프로필 ID + .build()); + } + return new QnACommentResponseDto(savedComment); - private final QnACommentRepository qnACommentRepository; - private final QnAPostRepository qnAPostRepository; - private final MemberOrgRepository memberOrgRepository; - private final NotificationService notificationService; - - // 댓글 생성 - @Transactional - public QnACommentResponseDto createComment(QnACommentCreateDto dto, String email, Long orgId) { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); - - QnAPost post = qnAPostRepository.findById(dto.getQnaPostId()) - .orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다.")); - - QnAComment comment = QnAComment.builder() - .writer(member) - .accountStatus(AccountStatus.ACTIVE) - .qnAPost(post) - .content(dto.getContent()) - .createdAt(LocalDateTime.now()) - .build(); - - // 댓글 DB 저장 - QnAComment savedComment = qnACommentRepository.save(comment); - - // 알림 전송 - if (!post.getWriter().getId().equals(member.getId())) { - MemberOrg postWriter = post.getWriter(); - - notificationService.send(Notification.builder() - .member(postWriter.getMember()) // 수신자 계정 - .memberOrgId(postWriter.getId()) // 수신자 프로필 ID - .type(NotificationType.COMMENT) - .message(member.getNickname() + "님이 QnA 글에 댓글을 남겼습니다.") - .relatedUrl("/api/qna-posts/" + post.getId()) - .fromMemberOrgId(member.getId()) // 보낸 사람 프로필 ID - .build()); } - return new QnACommentResponseDto(savedComment); - } + // 전체 댓글 조회 + @Transactional (readOnly = true) + public List getCommentByPost(Long postId, Long orgId){ + QnAPost post = qnAPostRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다.")); - // 전체 댓글 조회 - @Transactional(readOnly = true) - public List getCommentByPost(Long postId, Long orgId) { - QnAPost post = qnAPostRepository.findById(postId) - .orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다.")); + if (!post.getWriter().getId().equals(orgId)) { + throw new AccessDeniedException("해당 조직의 게시글만 조회할 수 있습니다."); + } - if (!post.getWriter().getId().equals(orgId)) { - throw new AccessDeniedException("해당 조직의 게시글만 조회할 수 있습니다."); + return qnACommentRepository.findByQnAPost(post) + .stream() + .map(QnACommentResponseDto::new) + .toList(); } - return qnACommentRepository.findByQnAPost(post) - .stream() - .map(QnACommentResponseDto::new) - .toList(); - } + // 댓글 수정 (작성자만 가능) + @Transactional + public QnACommentResponseDto updateComment(Long commentId, QnACommentUpdateDto dto, String email, Long orgId){ + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); - // 댓글 수정 (작성자만 가능) - @Transactional - public QnACommentResponseDto updateComment(Long commentId, QnACommentUpdateDto dto, String email, - Long orgId) { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); + QnAComment comment = qnACommentRepository.findById(commentId) + .orElseThrow(() -> new EntityNotFoundException("댓글이 존재하지 않습니다.")); - QnAComment comment = qnACommentRepository.findById(commentId) - .orElseThrow(() -> new EntityNotFoundException("댓글이 존재하지 않습니다.")); + boolean isWriter = comment.getWriter().getId().equals(member.getId()); - boolean isWriter = comment.getWriter().getId().equals(member.getId()); + if (!isWriter ) { + throw new AccessDeniedException("작성자만 수정할 수 있습니다."); + } - if (!isWriter) { - throw new AccessDeniedException("작성자만 수정할 수 있습니다."); - } + comment.updateContent(dto.getContent()); - comment.updateContent(dto.getContent()); + return new QnACommentResponseDto(comment); + } - return new QnACommentResponseDto(comment); - } + // 댓글 삭제 (작성자, 관리자만 가능) + @Transactional + public void deleteComment(Long commentId, String email, Long orgId) { + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); - // 댓글 삭제 (작성자, 관리자만 가능) - @Transactional - public void deleteComment(Long commentId, String email, Long orgId) { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); + QnAComment comment = qnACommentRepository.findById(commentId) + .orElseThrow(() -> new EntityNotFoundException("댓글이 존재하지 않습니다.")); - QnAComment comment = qnACommentRepository.findById(commentId) - .orElseThrow(() -> new EntityNotFoundException("댓글이 존재하지 않습니다.")); + boolean isWriter = comment.getWriter().getId().equals(member.getId()); + boolean isAdmin = (member.getMemberRole() == MemberRole.ADMIN) + && (member.getMemberType() == MemberType.ADMIN); - boolean isWriter = comment.getWriter().getId().equals(member.getId()); - boolean isAdmin = (member.getMemberRole() == MemberRole.ADMIN) - && (member.getMemberType() == MemberType.ADMIN); + if (!isWriter && !isAdmin) { + throw new AccessDeniedException("작성자 또는 관리자만 삭제할 수 있습니다."); + } - if (!isWriter && !isAdmin) { - throw new AccessDeniedException("작성자 또는 관리자만 삭제할 수 있습니다."); + qnACommentRepository.delete(comment); // hard delete } - qnACommentRepository.delete(comment); // hard delete - } - - // 댓글 신고하기 - @Transactional - public void increaseCommentReportCount(Long id, Long orgId) { - QnAComment comment = qnACommentRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); + // 댓글 신고하기 + @Transactional + public void increaseCommentReportCount(Long id, Long orgId) { + QnAComment comment = qnACommentRepository.findById(id) + .orElseThrow( () -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); - if (!comment.getWriter().getId().equals(orgId)) { - throw new AccessDeniedException("해당 조직의 댓글만 신고할 수 있습니다."); - } - comment.increaseReportCount(); + if (!comment.getWriter().getId().equals(orgId)) { + throw new AccessDeniedException("해당 조직의 댓글만 신고할 수 있습니다."); + } + comment.increaseReportCount(); - if (comment.getReportCount() >= 3) { - qnACommentRepository.delete(comment); // hard delete + if (comment.getReportCount() >= 3) { + qnACommentRepository.delete(comment); // hard delete + } } - } } diff --git a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/repository/QnAPostRepository.java b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/repository/QnAPostRepository.java index 97ace5a..a0cb124 100644 --- a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/repository/QnAPostRepository.java +++ b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/repository/QnAPostRepository.java @@ -2,7 +2,7 @@ import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.entity.QnAPost; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -16,19 +16,19 @@ @Repository public interface QnAPostRepository extends JpaRepository { - Page findByWriterAndAccountStatus(MemberOrg writer, AccountStatus status, Pageable pageable); - Page findByWriterIdAndAccountStatus(Long orgId, AccountStatus status, Pageable pageable); + Page findByWriterAndActiveStatus(MemberOrg writer, ActiveStatus status, Pageable pageable); + Page findByWriterIdAndActiveStatus(Long orgId, ActiveStatus status, Pageable pageable); - List findTop3ByWriterIdAndAccountStatusOrderByViewCountDescScrapCountDesc(Long orgId, AccountStatus status); + List findTop3ByWriterIdAndActiveStatusOrderByViewCountDescScrapCountDesc(Long orgId, ActiveStatus status); // 인기 3개 @Query("SELECT q FROM QnAPost q JOIN q.writer w " + - "WHERE q.accountStatus = :status AND q.createdAt BETWEEN :start AND :end " + + "WHERE q.activeStatus = :status AND q.createdAt BETWEEN :start AND :end " + "AND w.organization.id = :orgId " + // w(MemberOrg) 내의 organization 필드 참조 "ORDER BY q.viewCount DESC, q.scrapCount DESC") List findTop3PopularByOrg( - @Param("status") AccountStatus status, + @Param("status") ActiveStatus status, @Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("orgId") Long orgId, diff --git a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java index 032807e..0770187 100644 --- a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java +++ b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java @@ -48,8 +48,40 @@ public QnAPostRespDto createPost(QnAPostReqDto dto, String email, Long orgId) th MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); +<<<<<<< HEAD if (member.getMemberType() != MemberType.NEWBIE) { throw new AccessDeniedException("NEWBIE만 질문을 작성할 수 있습니다."); +======= + if (member.getMemberType() != MemberType.NEWBIE) { + throw new AccessDeniedException("NEWBIE만 질문을 작성할 수 있습니다."); + } + + QnAPost post = QnAPost.builder() + .writer(member) + .title(dto.getTitle()) + .content(dto.getContent()) + .createdAt(LocalDateTime.now()) + .type(Post.QnA) + .activeStatus(ActiveStatus.ACTIVE) + .reportCount(0) + .isAnonymous(dto.isAnonymous()) + .build(); + + // 이미지가 있으면 추가 + if (dto.getImageUrl() != null && !dto.getImageUrl().isEmpty()) { + String imageUrl = s3UploadService.saveFile(dto.getImageUrl()); + QnAPostImage image = new QnAPostImage(); + image.setImageUrl(imageUrl); + image.setPost(post); + post.addImage(image); + } + + qnAPostRepository.save(post); + + List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); + + return QnAPostRespDto.fromEntity(post, tagNames, false); +>>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) } QnAPost post = QnAPost.builder() @@ -190,16 +222,24 @@ public String reportQnAPost(Long postId, Long orgId) { return "이미 신고한 게시글입니다."; } +<<<<<<< HEAD qnAPostReportRepository.save(QnAReport.builder() .member(member) .qnaPost(post) .build()); +======= + // 게시글 전체 조회 + public PageResponseDTO findAll(Long orgId, Pageable pageable) { + Page page = qnAPostRepository.findByWriterIdAndActiveStatus(orgId, ActiveStatus.ACTIVE, pageable); + List posts = page.getContent(); +>>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) post.increaseReportCount(); return "게시글을 신고하였습니다."; } +<<<<<<< HEAD // 인기 3개 @Transactional(readOnly = true) public List getPopularPosts(Long orgId) { @@ -208,6 +248,57 @@ public List getPopularPosts(Long orgId) { .stream() .map(QnAPopularPostRespDto::from) .toList(); +======= + + // 게시글 상세 조회 + public QnAPostRespDto getPostById(Long id, Long orgId, Long memberId) { + QnAPost post = qnAPostRepository.findById(id) + .orElseThrow( () -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); + + if (!post.getWriter().getId().equals(orgId)) { + throw new AccessDeniedException("해당 조직의 게시글만 조회할 수 있습니다."); + } + + post.increaseViewCount(); + List tagNames = tagService.getTagNamesByPost(post); + + // 스크랩 여부 조회 + boolean isScrap = qnAScrapRepository.existsByQnaPostIdAndMemberId(id, memberId); + + return QnAPostRespDto.fromEntity(post, tagNames, isScrap); + } + + // 게시글 신고하기 + @Transactional + public String reportQnAPost(Long postId, Long orgId) { + QnAPost post = qnAPostRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); + + MemberOrg member = memberOrgRepository.findById(orgId) + .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); + + if (qnAPostReportRepository.existsByMemberAndQnaPost(member, post)) { + return "이미 신고한 게시글입니다."; + } + + qnAPostReportRepository.save(QnAReport.builder() + .member(member) + .qnaPost(post) + .build()); + + post.increaseReportCount(); + + return "게시글을 신고하였습니다."; + } + + // 인기 3개 + @Transactional(readOnly = true) + public List getPopularPosts(Long orgId) { + return qnAPostRepository.findTop3ByWriterIdAndActiveStatusOrderByViewCountDescScrapCountDesc(orgId, ActiveStatus.ACTIVE) + .stream() + .map(QnAPopularPostRespDto::from) + .toList(); +>>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) /* .stream() .filter(post -> post.getWriter().getOrganization().equals(organization)) diff --git a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_tag/repository/QnATagCustomRepositoryImpl.java b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_tag/repository/QnATagCustomRepositoryImpl.java index a9659ec..7917d51 100644 --- a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_tag/repository/QnATagCustomRepositoryImpl.java +++ b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_tag/repository/QnATagCustomRepositoryImpl.java @@ -5,18 +5,16 @@ import org.example.tackit.domain.entity.QnAPost; import org.example.tackit.domain.entity.QnAPostImage; import org.example.tackit.domain.entity.QnATagMap; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; -import static org.example.tackit.domain.entity.QFreePost.freePost; import static org.example.tackit.domain.entity.QQnAPost.qnAPost; import static org.example.tackit.domain.entity.QQnAPostImage.qnAPostImage; import static org.example.tackit.domain.entity.QQnATagMap.qnATagMap; import static org.example.tackit.domain.entity.QQnATag.qnATag; -import static org.example.tackit.domain.entity.QMember.member; import java.util.List; import java.util.Map; @@ -51,7 +49,7 @@ public Page findPostsByTagId(Long tagId, String organizat .selectFrom(qnAPost) .join(qnAPost.writer).fetchJoin() .where(qnAPost.id.in(postIds), - qnAPost.accountStatus.eq(AccountStatus.ACTIVE) + qnAPost.activeStatus.eq(ActiveStatus.ACTIVE) // qnAPost.writer.organization.eq(organization) ) .orderBy(qnAPost.createdAt.desc()) @@ -110,7 +108,7 @@ public Page findPostsByTagId(Long tagId, String organizat .join(qnATagMap.tag, qnATag) .where( qnATag.id.eq(tagId), - qnAPost.accountStatus.eq(AccountStatus.ACTIVE) + qnAPost.activeStatus.eq(ActiveStatus.ACTIVE) ) .fetchOne(); diff --git a/src/main/java/org/example/tackit/domain/report/dto/ReportContentDetailDto.java b/src/main/java/org/example/tackit/domain/report/dto/ReportContentDetailDto.java index 6a6bdbf..3bbe037 100644 --- a/src/main/java/org/example/tackit/domain/report/dto/ReportContentDetailDto.java +++ b/src/main/java/org/example/tackit/domain/report/dto/ReportContentDetailDto.java @@ -2,7 +2,7 @@ import lombok.Builder; import lombok.Getter; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.TargetType; import java.util.List; @@ -15,7 +15,7 @@ public class ReportContentDetailDto { private String postType; private String contentTitle; private String contentWriter; - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private List reportLogs; diff --git a/src/main/java/org/example/tackit/domain/report/dto/ReportListDto.java b/src/main/java/org/example/tackit/domain/report/dto/ReportListDto.java index e483580..e1cc9b4 100644 --- a/src/main/java/org/example/tackit/domain/report/dto/ReportListDto.java +++ b/src/main/java/org/example/tackit/domain/report/dto/ReportListDto.java @@ -2,7 +2,7 @@ import lombok.Builder; import lombok.Getter; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.TargetType; import java.time.LocalDateTime; @@ -13,7 +13,7 @@ public class ReportListDto { private Long targetId; private TargetType targetType; private String title; - private AccountStatus accountStatus; + private ActiveStatus activeStatus; private int reportCount; // 가장 최근 신고 일자 private LocalDateTime lastReportedAt; diff --git a/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java b/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java index 247a9ad..0a1305e 100644 --- a/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java +++ b/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java @@ -38,8 +38,18 @@ public TipCommentResponseDto createComment(TipCommentCreateDto dto, String email MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); +<<<<<<< HEAD TipPost post = tipPostRepository.findById(dto.getTipPostId()) .orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다.")); +======= + TipComment comment = TipComment.builder() + .writer(member) + .activeStatus(ActiveStatus.ACTIVE) + .tipPost(post) + .content(dto.getContent()) + .createdAt(LocalDateTime.now()) + .build(); +>>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) TipComment comment = TipComment.builder() .writer(member) diff --git a/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/repository/TipPostRepository.java b/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/repository/TipPostRepository.java index 8f6bfa2..77c2027 100644 --- a/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/repository/TipPostRepository.java +++ b/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/repository/TipPostRepository.java @@ -1,6 +1,6 @@ package org.example.tackit.domain.tipBoard.Tip_post.repository; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.TipPost; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -17,11 +17,11 @@ public interface TipPostRepository extends JpaRepository { // 인기 3개 @Query("SELECT t FROM TipPost t JOIN t.writer w " + - "WHERE t.accountStatus = :status AND t.createdAt BETWEEN :start AND :end " + + "WHERE t.activeStatus = :status AND t.createdAt BETWEEN :start AND :end " + "AND w.organization.id = :orgId " + // 통합된 organization 필드 참조 "ORDER BY t.viewCount DESC, t.scrapCount DESC") List findTop3PopularByOrg( - @Param("status") AccountStatus status, + @Param("status") ActiveStatus status, @Param("start") LocalDateTime start, @Param("end") LocalDateTime end, @Param("orgId") Long orgId, diff --git a/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java b/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java index 24cb284..b910b69 100644 --- a/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java +++ b/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java @@ -142,6 +142,7 @@ public TipPostRespDto createPost(TipPostReqDto dto, String email, Long orgId, Mu .isAnonymous(dto.isAnonymous()) .build(); +<<<<<<< HEAD // 3. 이미지 업로드 & 연관관계 매핑 (단일 파일만) if (image != null && !image.isEmpty()) { String imageUrl = s3UploadService.saveFile(image); @@ -149,6 +150,52 @@ public TipPostRespDto createPost(TipPostReqDto dto, String email, Long orgId, Mu .imageUrl(imageUrl) .build(); post.addImage(imageEntity); // 기존 이미지 clear 후 하나만 저장 +======= + if (member.getMemberType() != MemberType.SENIOR) { + throw new AccessDeniedException("SENIOR만 게시글을 작성할 수 있습니다."); + } + + // 2. 게시글 생성 + TipPost post = TipPost.builder() + .writer(member) + .title(dto.getTitle()) + .content(dto.getContent()) + .createdAt(LocalDateTime.now()) + .type(Post.Tip) + .activeStatus(ActiveStatus.ACTIVE) + .reportCount(0) + .isAnonymous(dto.isAnonymous()) + .build(); + + // 3. 이미지 업로드 & 연관관계 매핑 (단일 파일만) + if (image != null && !image.isEmpty()) { + String imageUrl = s3UploadService.saveFile(image); + TipPostImage imageEntity = TipPostImage.builder() + .imageUrl(imageUrl) + .build(); + post.addImage(imageEntity); // 기존 이미지 clear 후 하나만 저장 + } + + tipPostRepository.save(post); + + List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); + + boolean anonymous = post.isAnonymous(); + + // 응답 DTO 구성 (imageUrl 하나만) + return TipPostRespDto.builder() + .id(post.getId()) + .writer(anonymous ? "익명" : member.getNickname()) + .profileImageUrl(anonymous ? null : member.getProfileImageUrl()) + .title(post.getTitle()) + .content(post.getContent()) + .createdAt(post.getCreatedAt()) + .tags(tagNames) + .imageUrl(post.getImages().isEmpty() ? null : post.getImages().get(0).getImageUrl()) + .isAnonymous(anonymous) + .isScrap(false) + .build(); +>>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) } tipPostRepository.save(post); diff --git a/src/main/java/org/example/tackit/domain/tipBoard/Tip_tag/repository/TipTagCustomRepositoryImpl.java b/src/main/java/org/example/tackit/domain/tipBoard/Tip_tag/repository/TipTagCustomRepositoryImpl.java index 0c28dba..13ad335 100644 --- a/src/main/java/org/example/tackit/domain/tipBoard/Tip_tag/repository/TipTagCustomRepositoryImpl.java +++ b/src/main/java/org/example/tackit/domain/tipBoard/Tip_tag/repository/TipTagCustomRepositoryImpl.java @@ -2,7 +2,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import org.example.tackit.domain.tipBoard.Tip_tag.dto.response.TipTagPostResponseDto; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.TipPost; import org.example.tackit.domain.entity.TipPostImage; import org.example.tackit.domain.entity.TipTagMap; @@ -52,7 +52,7 @@ public Page findPostsByTagId(Long tagId, Long orgId, Page .selectFrom(tipPost) .join(tipPost.writer).fetchJoin() .where(tipPost.id.in(postIds), - tipPost.accountStatus.eq(AccountStatus.ACTIVE) + tipPost.activeStatus.eq(ActiveStatus.ACTIVE) // memberOrg.id.eq(orgId) ) .orderBy(tipPost.createdAt.desc()) @@ -111,7 +111,7 @@ public Page findPostsByTagId(Long tagId, Long orgId, Page .join(tipTagMap.tag, tipTag) .where( tipTag.id.eq(tagId), - tipPost.accountStatus.eq(AccountStatus.ACTIVE) + tipPost.activeStatus.eq(ActiveStatus.ACTIVE) ) .fetchOne(); diff --git a/src/test/java/org/example/tackit/MemberSchedulerTest.java b/src/test/java/org/example/tackit/MemberSchedulerTest.java index 8cb39a8..b499fee 100644 --- a/src/test/java/org/example/tackit/MemberSchedulerTest.java +++ b/src/test/java/org/example/tackit/MemberSchedulerTest.java @@ -1,13 +1,6 @@ package org.example.tackit; -import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; -import org.example.tackit.domain.admin.repository.AdminMemberRepository; -import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.MemberType; -import org.example.tackit.domain.entity.AccountStatus; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.assertj.core.api.Assertions.assertThat; From cb8cac94b6bcc82a20cd09848fb0e3b41607feed Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Wed, 18 Feb 2026 15:12:17 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20#189-=EC=9A=B4=EC=98=81=EC=A7=84?= =?UTF-8?q?=20API=20=EA=B8=B0=EB=B3=B8=20=EA=B5=AC=EC=A1=B0=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExecutiveMemberController.java | 42 +++++++++++ .../controller/ExecutiveReportController.java | 62 ++++++++++++++++ .../dto/request/MemberStatusRequest.java | 8 +++ .../dto/response/MemberDetailResponse.java | 4 ++ .../dto/response/MemberListResponse.java | 11 +++ .../repository/ExecutiveMemberRepository.java | 8 +++ .../service/ExecutiveMemberService.java | 72 +++++++++++++++++++ 7 files changed, 207 insertions(+) create mode 100644 src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java create mode 100644 src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java create mode 100644 src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java create mode 100644 src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java create mode 100644 src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java create mode 100644 src/main/java/org/example/tackit/domain/executive/repository/ExecutiveMemberRepository.java create mode 100644 src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java new file mode 100644 index 0000000..337aa95 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java @@ -0,0 +1,42 @@ +package org.example.tackit.domain.executive.controller; + +import lombok.RequiredArgsConstructor; +import org.example.tackit.domain.executive.dto.response.MemberListResponse; +import org.springframework.http.ResponseEntity; +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.util.List; + +@RestController +@RequestMapping("/api/executive/members") +@RequiredArgsConstructor +public class ExecutiveMemberController { + + + // [ 전체 회원 조회 ] + @GetMapping + public ResponseEntity> getAllMembers( + @RequestParam(required = false) String status + ) { + return ResponseEntity.ok(users); + } + + /* + // [ 대기 회원 조회 ] + @GetMapping("/deleted") + public ResponseEntity getDeletedMembers() { + DeletedMemberResp deletedMembers = adminMemberService.getDeletedMembers(); + return ResponseEntity.ok(deletedMembers); + } + + // [ 사용 중 회원 조회 ] + + // [ 탈퇴 회원 조회 ] + + // [ 회원 승인 ] + // [ 회원 거부 ] + */ +} diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java new file mode 100644 index 0000000..84231fb --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java @@ -0,0 +1,62 @@ +package org.example.tackit.domain.executive.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/executive/reports") +@RequiredArgsConstructor +public class ExecutiveReportController { + + /* + // [ 신고 게시글 전체 조회 ] + @GetMapping("/{postType}/posts") + public ResponseEntity> getReportedPosts( + @PathVariable("postType") Post postType, + @PageableDefault(size = 5, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { + ReportedPostService service = reportedPostServices.get(postType); + + if (service == null) { + throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); + } + + Page posts = service.getDeletedPosts(pageable); + return ResponseEntity.ok(PageResponseDTO.from(posts)); + } + + // [ 신고 게시글 상세 조회 ] + + // [ 신고 게시글 복구 ] + + // [ 신고 게시글 완전 삭제 ] + @DeleteMapping("/{postType}/posts/{postId}") + public ResponseEntity deleteReportedPost( + @PathVariable("postType") Post postType, + @PathVariable("postId") Long postId) { + + ReportedPostService service = reportedPostServices.get(postType); + if (service == null) { + throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); + } + + service.deletePost(postId); + return ResponseEntity.noContent().build(); + } + + // 게시글 활성화 + @PatchMapping("/{postType}/posts/{postId}/activate") + public ResponseEntity activateReportedPost( + @PathVariable("postType") Post postType, + @PathVariable("postId") Long postId) { + + ReportedPostService service = reportedPostServices.get(postType); + if (service == null) { + throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); + } + + service.activatePost(postId); + return ResponseEntity.ok("게시글이 활성화되었습니다."); + } + */ +} diff --git a/src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java b/src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java new file mode 100644 index 0000000..20e2f38 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java @@ -0,0 +1,8 @@ +package org.example.tackit.domain.executive.dto.request; + +import lombok.Getter; + +@Getter +public class MemberStatusRequest { + private String status; +} diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java new file mode 100644 index 0000000..3876953 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java @@ -0,0 +1,4 @@ +package org.example.tackit.domain.executive.dto.response; + +public class MemberDetailResponse { +} diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java new file mode 100644 index 0000000..65c8587 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java @@ -0,0 +1,11 @@ +package org.example.tackit.domain.executive.dto.response; + +import java.time.LocalDateTime; + +public class MemberListResponse { + private Long memberId; + private String nickname; + private String email; + private String orgStatus; + private LocalDateTime createdAt; +} diff --git a/src/main/java/org/example/tackit/domain/executive/repository/ExecutiveMemberRepository.java b/src/main/java/org/example/tackit/domain/executive/repository/ExecutiveMemberRepository.java new file mode 100644 index 0000000..928459b --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/repository/ExecutiveMemberRepository.java @@ -0,0 +1,8 @@ +package org.example.tackit.domain.executive.repository; + + +import org.example.tackit.domain.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExecutiveMemberRepository extends JpaRepository { +} diff --git a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java new file mode 100644 index 0000000..5a40857 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java @@ -0,0 +1,72 @@ +package org.example.tackit.domain.executive.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.executive.dto.request.MemberStatusRequest; +import org.example.tackit.domain.executive.dto.response.MemberListResponse; +import org.example.tackit.domain.executive.repository.ExecutiveMemberRepository; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ExecutiveMemberService { + private final ExecutiveMemberRepository executiveMemberRepository; + private final MemberRepository memberRepository; + + // [ 모든 멤버 조회 ] + @Transactional + public List getMembers(MemberStatusRequest request) { + String status = request.getStatus(); + + List members; + + // 상태 조건 없다면 -> 전체 + if( status == null || status.isBlank() ) + members = memberRepository.findAll(); + + // 특정 상태(대기, 이용 중, 탈퇴) 조회 + else { + + // members = memberRepository.findByStatus(status); + } + + return members.stream() + .map() + } + + /* + // [ 총 회원 수, 이번 달 신규 회원 수, 이번 주 신규 회원 수 ] + @Transactional(readOnly = true) + public MemberStatisticsDTO getMemberStatistics() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime startOfMonth = now.withDayOfMonth(1).with(LocalTime.MIN); + LocalDateTime startOfWeek = now.with(ChronoField.DAY_OF_WEEK, 1).toLocalDate().atStartOfDay(); + + long totalCount = adminMemberRepository.countAll(); + long monthlyCount = adminMemberRepository.countJoinedAfter(startOfMonth); + long weeklyCount = adminMemberRepository.countJoinedAfter(startOfWeek); + + return new MemberStatisticsDTO(totalCount, monthlyCount, weeklyCount); + } + + // [ 탈퇴 회원 수 조회 ] + @Transactional(readOnly = true) + public DeletedMemberResp getDeletedMembers() { + List deletedMembers = adminMemberRepository.findByStatus(AccountStatus.DELETED); + + List deletedMemberDTOS = deletedMembers.stream() + .map(member -> DeletedMemberDTO.from(member)) + .collect(Collectors.toList()); + + Long deletedCount = (long) deletedMembers.size(); + + return new DeletedMemberResp(deletedMemberDTOS, deletedCount); + } + + */ + +} From b9cf69afb1bdd60872844462c9705fe0ba4d434a Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Wed, 18 Feb 2026 16:22:56 +0900 Subject: [PATCH 03/10] =?UTF-8?q?refactor:=20#189-=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD(accountStatus=20->=20activeStatu?= =?UTF-8?q?s),=20memberRepository=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95(auth=20->=20member)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tackit/config/CommonDataInitializer.java | 4 +- .../repository/AdminMemberRepository.java | 6 +- .../login/repository/MemberRepository.java | 24 ---- .../auth/login/service/AuthService.java | 9 +- .../service/CustomUserDetailsService.java | 6 +- .../login/service/RejoinCheckService.java | 10 +- .../example/tackit/domain/entity/Member.java | 2 - .../domain/event/service/EventService.java | 37 +++--- .../controller/ExecutiveMemberController.java | 3 + .../service/ExecutiveMemberService.java | 7 +- .../Free_post/service/FreePostService.java | 14 +- .../member/repository/MemberRepository.java | 6 +- .../mypage/service/MyPageQnAService.java | 10 +- .../service/QnACommentService.java | 4 +- .../QnA_post/service/QnAPostService.java | 124 +++--------------- .../service/TipCommentService.java | 25 ++-- .../Tip_post/service/TipPostService.java | 79 +++-------- 17 files changed, 98 insertions(+), 272 deletions(-) delete mode 100644 src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java diff --git a/src/main/java/org/example/tackit/config/CommonDataInitializer.java b/src/main/java/org/example/tackit/config/CommonDataInitializer.java index 585cf93..6e8701c 100644 --- a/src/main/java/org/example/tackit/config/CommonDataInitializer.java +++ b/src/main/java/org/example/tackit/config/CommonDataInitializer.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.example.tackit.domain.organization.repository.SchoolRepository; import org.example.tackit.domain.entity.*; -import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.member.repository.MemberRepository; import org.example.tackit.domain.entity.Org.School; import org.example.tackit.domain.entity.Org.SchoolType; import org.springframework.boot.CommandLineRunner; @@ -29,7 +29,7 @@ public void run(String... args) throws Exception { .email("contact.tackit@gmail.com") .password(passwordEncoder.encode("admin1")) // BCrypt 인코딩 .name("관리자") - .status(AccountStatus.ACTIVE) + .activeStatus(ActiveStatus.ACTIVE) .createdAt(LocalDateTime.now()) .build(); diff --git a/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java b/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java index 90476a2..09c1eac 100644 --- a/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java +++ b/src/main/java/org/example/tackit/domain/admin/repository/AdminMemberRepository.java @@ -12,8 +12,8 @@ public interface AdminMemberRepository extends JpaRepository { @Query("SELECT u FROM Member u WHERE u.email <> 'admin' ORDER BY " + - "CASE WHEN u.status = 0 THEN 0 ELSE 1 END") - List findAllOrderByStatus(); + "CASE WHEN u.activeStatus = 0 THEN 0 ELSE 1 END") + List findAllOrderByActiveStatus(); Optional findByEmail(String email); @@ -26,7 +26,7 @@ public interface AdminMemberRepository extends JpaRepository { Long countJoinedAfter(@Param("date")LocalDateTime date); // 탈퇴 회원 통계 - List findByStatus(ActiveStatus activeStatus); + List findByActiveStatus(ActiveStatus activeStatus); // 1년마다 뉴비 -> 시니어 자동 갱신 diff --git a/src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java b/src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java deleted file mode 100644 index aab20c2..0000000 --- a/src/main/java/org/example/tackit/domain/auth/login/repository/MemberRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.example.tackit.domain.auth.login.repository; - -import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface MemberRepository extends JpaRepository { - Optional findByEmail(String email); //그 유저 실제 정보 추출 - boolean existsByEmail(String email); // 이메일 존재 확인 - // boolean existsByNickname(String nickname); //닉네임 중복 확인 - boolean existsByEmailAndStatus(String email, AccountStatus accountStatus); // 이메일+상태 존재 확인 - Optional findByEmailAndStatus(String email, AccountStatus accountStatus); // 이메일+상태 정보 추출 - - // Optional findByOrganizationAndName(String organization, String name); - - // Optional findByEmailAndOrganization(String email, String organization); - - // Optional findByNameAndOrganizationAndEmail(String name, String organization, String email); -} - diff --git a/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java b/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java index ea97030..f3a817c 100644 --- a/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java +++ b/src/main/java/org/example/tackit/domain/auth/login/service/AuthService.java @@ -6,12 +6,11 @@ import org.example.tackit.config.jwt.TokenProvider; import org.example.tackit.domain.admin.repository.AdminMemberRepository; import org.example.tackit.domain.auth.login.dto.*; -import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; -import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.member.repository.MemberOrgRepository; +import org.example.tackit.domain.member.repository.MemberRepository; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.Org.MemberOrg; -import org.example.tackit.domain.entity.Org.OrgType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -50,7 +49,7 @@ public void signup(SignUpDto signUpDto) { .email(signUpDto.getEmail()) .password(passwordEncoder.encode(signUpDto.getPassword())) .name(signUpDto.getName()) - .accountStatus(AccountStatus.ACTIVE) + .activeStatus(ActiveStatus.ACTIVE) .createdAt(LocalDateTime.now()) .build(); diff --git a/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java b/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java index 4d44192..57f7f4f 100644 --- a/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java +++ b/src/main/java/org/example/tackit/domain/auth/login/service/CustomUserDetailsService.java @@ -1,10 +1,10 @@ package org.example.tackit.domain.auth.login.service; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.member.repository.MemberRepository; import org.example.tackit.domain.auth.login.security.CustomUserDetails; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -29,7 +29,7 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep .orElseThrow(() -> new UsernameNotFoundException(email + " -> 데이터베이스에서 찾을 수 없습니다.")); // 상태 확인 추가 - if (member.getAccountStatus() == AccountStatus.DELETED) { + if (member.getActiveStatus() == ActiveStatus.DELETED) { throw new UsernameNotFoundException(email + " -> 탈퇴한 회원입니다."); } diff --git a/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java b/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java index 95d71d2..b51134a 100644 --- a/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java +++ b/src/main/java/org/example/tackit/domain/auth/login/service/RejoinCheckService.java @@ -1,9 +1,9 @@ package org.example.tackit.domain.auth.login.service; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.auth.login.repository.MemberRepository; import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; +import org.example.tackit.domain.member.repository.MemberRepository; import org.springframework.stereotype.Service; import java.util.Optional; @@ -15,9 +15,9 @@ public class RejoinCheckService { // 회원 상태 조회 public String checkEmailStatus(String email) { - if (memberRepository.existsByEmailAndStatus(email, AccountStatus.DELETED)) { + if (memberRepository.existsByEmailAndActiveStatus(email, ActiveStatus.DELETED)) { return "DELETED"; - } else if (memberRepository.existsByEmailAndStatus(email, AccountStatus.ACTIVE)) { + } else if (memberRepository.existsByEmailAndActiveStatus(email, ActiveStatus.ACTIVE)) { return "ACTIVE"; } else { return "AVAILABLE"; @@ -26,7 +26,7 @@ public String checkEmailStatus(String email) { // 탈퇴한 회원 삭제 public boolean deleteDeletedMember(String email) { - Optional deletedMember = memberRepository.findByEmailAndStatus(email, AccountStatus.DELETED); + Optional deletedMember = memberRepository.findByEmailAndActiveStatus(email, ActiveStatus.DELETED); if (deletedMember.isPresent()) { memberRepository.delete(deletedMember.get()); return true; diff --git a/src/main/java/org/example/tackit/domain/entity/Member.java b/src/main/java/org/example/tackit/domain/entity/Member.java index 599d8f5..f8d7765 100644 --- a/src/main/java/org/example/tackit/domain/entity/Member.java +++ b/src/main/java/org/example/tackit/domain/entity/Member.java @@ -28,8 +28,6 @@ public class Member { @Column(nullable = false) private String password; - private ActiveStatus status; - private LocalDateTime createdAt = LocalDateTime.now(); private ActiveStatus activeStatus; // 탈퇴 계정을 위해 diff --git a/src/main/java/org/example/tackit/domain/event/service/EventService.java b/src/main/java/org/example/tackit/domain/event/service/EventService.java index e7129bf..8320bd5 100644 --- a/src/main/java/org/example/tackit/domain/event/service/EventService.java +++ b/src/main/java/org/example/tackit/domain/event/service/EventService.java @@ -1,28 +1,27 @@ package org.example.tackit.domain.event.service; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.YearMonth; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.Organization.repository.OrganizationRepository; -import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; -import org.example.tackit.domain.auth.login.repository.MemberRepository; -import org.example.tackit.domain.entity.*; +import org.example.tackit.domain.entity.Event; +import org.example.tackit.domain.entity.EventParticipant; +import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.entity.MemberRole; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.entity.Org.OrgStatus; import org.example.tackit.domain.entity.Org.Organization; import org.example.tackit.domain.event.dto.*; import org.example.tackit.domain.event.repository.EventRepository; -import org.example.tackit.global.exception.ErrorCode; -import org.example.tackit.global.exception.MemberNotFoundException; -import org.springframework.data.domain.PageRequest; +import org.example.tackit.domain.member.component.MemberOrgValidator; +import org.example.tackit.domain.member.dto.SimpleMemberProfileDto; +import org.example.tackit.domain.member.repository.MemberOrgRepository; +import org.example.tackit.domain.organization.repository.OrganizationRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.YearMonth; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -31,6 +30,7 @@ public class EventService { private final EventRepository eventRepository; private final OrganizationRepository organizationRepository; private final MemberOrgRepository memberOrgRepository; + private final MemberOrgValidator memberOrgValidator; // 일정 생성 @Transactional @@ -114,13 +114,14 @@ public List getMonthlyEvents(Long orgId, int year, int month, } // 일정 상세 조회 - public EventDetailResDto getEventDetail(Long eventId, Long requesterId) { + public EventDetailResDto getEventDetail(Long eventId, Long requesterMemberOrgId) { Event event = findEventOrThrow(eventId); - validateMembership(event.getOrganization().getId(), requesterId); + memberOrgValidator.validateActiveMembership(event.getOrganization().getId(), + requesterMemberOrgId); - List participantDtos = event.getParticipants().stream() - .map(ep -> EventParticipantDto.builder() + List participantDtos = event.getParticipants().stream() + .map(ep -> SimpleMemberProfileDto.builder() .orgMemberId(ep.getMemberOrg().getId()) .profileImageUrl(ep.getMemberOrg().getProfileImageUrl()) .nickname(ep.getMemberOrg().getNickname()) diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java index 337aa95..445bae1 100644 --- a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java @@ -17,6 +17,7 @@ public class ExecutiveMemberController { // [ 전체 회원 조회 ] + /* @GetMapping public ResponseEntity> getAllMembers( @RequestParam(required = false) String status @@ -24,6 +25,8 @@ public ResponseEntity> getAllMembers( return ResponseEntity.ok(users); } + */ + /* // [ 대기 회원 조회 ] @GetMapping("/deleted") diff --git a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java index 5a40857..c801351 100644 --- a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java +++ b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java @@ -2,7 +2,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.member.repository.MemberRepository; import org.example.tackit.domain.entity.Member; import org.example.tackit.domain.executive.dto.request.MemberStatusRequest; import org.example.tackit.domain.executive.dto.response.MemberListResponse; @@ -17,6 +17,7 @@ public class ExecutiveMemberService { private final ExecutiveMemberRepository executiveMemberRepository; private final MemberRepository memberRepository; + /* // [ 모든 멤버 조회 ] @Transactional public List getMembers(MemberStatusRequest request) { @@ -67,6 +68,8 @@ public DeletedMemberResp getDeletedMembers() { return new DeletedMemberResp(deletedMemberDTOS, deletedCount); } - */ +} + + */ } diff --git a/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java b/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java index 4ee1fb6..1b4f4c3 100644 --- a/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java +++ b/src/main/java/org/example/tackit/domain/freeBoard/Free_post/service/FreePostService.java @@ -9,8 +9,8 @@ import org.example.tackit.domain.freeBoard.Free_post.dto.response.FreeScrapResponseDto; import org.example.tackit.domain.freeBoard.Free_post.repository.*; import org.example.tackit.domain.freeBoard.Free_tag.repository.FreePostTagMapRepository; -import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; -import org.example.tackit.domain.auth.login.repository.MemberRepository; +import org.example.tackit.domain.member.repository.MemberOrgRepository; +import org.example.tackit.domain.member.repository.MemberRepository; import org.example.tackit.domain.entity.*; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.notification.service.NotificationService; @@ -53,9 +53,9 @@ public PageResponseDTO findAll(String email, Long profileId, Pa Long orgId = currProfile.getOrganization().getId(); // 해당 조직의 게시글만 조회 - Page page = freePostJPARepository.findAllByOrganizationIdAndAccountStatus( + Page page = freePostJPARepository.findAllByOrganizationIdAndActiveStatus( orgId, - AccountStatus.ACTIVE, + ActiveStatus.ACTIVE, pageable ); @@ -98,7 +98,7 @@ public FreePostRespDto getPostById(Long id, Long orgId, Long memberId) { throw new AccessDeniedCustomException(ErrorCode.ACCESS_DENIED_ORGANIZATION); } - if (!post.getAccountStatus().equals(AccountStatus.ACTIVE)) { + if (!post.getActiveStatus().equals(ActiveStatus.ACTIVE)) { throw new PostInactiveException(ErrorCode.POST_IS_INACTIVE); } @@ -149,7 +149,7 @@ public FreePostRespDto createPost(FreePostReqDto dto, String email, Long profile .isAnonymous(dto.isAnonymous()) .createdAt(LocalDateTime.now()) .type(Post.Free) - .accountStatus(AccountStatus.ACTIVE) + .activeStatus(ActiveStatus.ACTIVE) .reportCount(0) .build(); @@ -374,7 +374,7 @@ public List getPopularPosts(Long orgId) { // 인기 3개 @Transactional(readOnly = true) public List getPopularPosts(Long orgId) { - return freePostJPARepository.findTop3ByOrganizationIdAndAccountStatusOrderByViewCountDescScrapCountDesc(orgId, AccountStatus.ACTIVE) + return freePostJPARepository.findTop3ByOrganizationIdAndActiveStatusOrderByViewCountDescScrapCountDesc(orgId, ActiveStatus.ACTIVE) .stream() .map(FreePopularPostRespDto::from) .toList(); diff --git a/src/main/java/org/example/tackit/domain/member/repository/MemberRepository.java b/src/main/java/org/example/tackit/domain/member/repository/MemberRepository.java index 6f9102b..663a1bd 100644 --- a/src/main/java/org/example/tackit/domain/member/repository/MemberRepository.java +++ b/src/main/java/org/example/tackit/domain/member/repository/MemberRepository.java @@ -1,7 +1,7 @@ package org.example.tackit.domain.member.repository; import java.util.Optional; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -14,9 +14,9 @@ public interface MemberRepository extends JpaRepository { boolean existsByEmail(String email); // 이메일 존재 확인 // boolean existsByNickname(String nickname); //닉네임 중복 확인 - boolean existsByEmailAndStatus(String email, AccountStatus accountStatus); // 이메일+상태 존재 확인 + boolean existsByEmailAndActiveStatus(String email, ActiveStatus activeStatus); // 이메일+상태 존재 확인 - Optional findByEmailAndStatus(String email, AccountStatus accountStatus); // 이메일+상태 정보 추출 + Optional findByEmailAndActiveStatus(String email, ActiveStatus activeStatus); // 이메일+상태 정보 추출 // Optional findByOrganizationAndName(String organization, String name); diff --git a/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java b/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java index 7a3dc1f..305fdca 100644 --- a/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java +++ b/src/main/java/org/example/tackit/domain/mypage/service/MyPageQnAService.java @@ -5,7 +5,7 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.example.tackit.common.dto.PageResponseDTO; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.entity.QnAComment; import org.example.tackit.domain.entity.QnAPost; @@ -29,21 +29,15 @@ public class MyPageQnAService { private final QnACommentRepository qnACommentRepository; private final QnAPostTagMapRepository qnAPostTagMapRepository; -<<<<<<< HEAD // 내가 쓴 게시글 조회 @Transactional(readOnly = true) public PageResponseDTO getMyPosts(String email, Long orgId, Pageable pageable) { MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); -======= + Page postPage = qnAPostRepository.findByWriterAndActiveStatus(member, ActiveStatus.ACTIVE, pageable); List posts = postPage.getContent(); ->>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) - - Page postPage = qnAPostRepository.findByWriterAndAccountStatus(member, - AccountStatus.ACTIVE, pageable); - List posts = postPage.getContent(); // 태그 일괄 조회 (N+1 방지) Map> tagMap = qnAPostTagMapRepository.findByQnaPostIn(posts).stream() diff --git a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java index ab269a3..6b5e580 100644 --- a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java +++ b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_comment/service/QnACommentService.java @@ -7,7 +7,7 @@ import org.example.tackit.domain.qnaBoard.QnA_comment.dto.response.QnACommentResponseDto; import org.example.tackit.domain.qnaBoard.QnA_comment.repository.QnACommentRepository; import org.example.tackit.domain.qnaBoard.QnA_post.repository.QnAPostRepository; -import org.example.tackit.domain.auth.login.repository.MemberOrgRepository; +import org.example.tackit.domain.member.repository.MemberOrgRepository; import org.example.tackit.domain.entity.*; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.notification.service.NotificationService; @@ -37,7 +37,7 @@ public QnACommentResponseDto createComment(QnACommentCreateDto dto, String email QnAComment comment = QnAComment.builder() .writer(member) - .accountStatus(AccountStatus.ACTIVE) + .activeStatus(ActiveStatus.ACTIVE) .qnAPost(post) .content(dto.getContent()) .createdAt(LocalDateTime.now()) diff --git a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java index 0770187..50ef171 100644 --- a/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java +++ b/src/main/java/org/example/tackit/domain/qnaBoard/QnA_post/service/QnAPostService.java @@ -7,7 +7,7 @@ import lombok.RequiredArgsConstructor; import org.example.tackit.common.dto.PageResponseDTO; import org.example.tackit.config.S3.S3UploadService; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.MemberRole; import org.example.tackit.domain.entity.MemberType; import org.example.tackit.domain.entity.Org.MemberOrg; @@ -42,16 +42,12 @@ public class QnAPostService { private final S3UploadService s3UploadService; private final QnAScrapRepository qnAScrapRepository; - // 게시글 작성 (NEWBIE만 가능) - @Transactional - public QnAPostRespDto createPost(QnAPostReqDto dto, String email, Long orgId) throws IOException { - MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) - .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); + // 게시글 작성 (NEWBIE만 가능) + @Transactional + public QnAPostRespDto createPost(QnAPostReqDto dto, String email, Long orgId) throws IOException { + MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) + .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); -<<<<<<< HEAD - if (member.getMemberType() != MemberType.NEWBIE) { - throw new AccessDeniedException("NEWBIE만 질문을 작성할 수 있습니다."); -======= if (member.getMemberType() != MemberType.NEWBIE) { throw new AccessDeniedException("NEWBIE만 질문을 작성할 수 있습니다."); } @@ -81,35 +77,8 @@ public QnAPostRespDto createPost(QnAPostReqDto dto, String email, Long orgId) th List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); return QnAPostRespDto.fromEntity(post, tagNames, false); ->>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) - } - - QnAPost post = QnAPost.builder() - .writer(member) - .title(dto.getTitle()) - .content(dto.getContent()) - .createdAt(LocalDateTime.now()) - .type(Post.QnA) - .accountStatus(AccountStatus.ACTIVE) - .reportCount(0) - .isAnonymous(dto.isAnonymous()) - .build(); - - // 이미지가 있으면 추가 - if (dto.getImageUrl() != null && !dto.getImageUrl().isEmpty()) { - String imageUrl = s3UploadService.saveFile(dto.getImageUrl()); - QnAPostImage image = new QnAPostImage(); - image.setImageUrl(imageUrl); - image.setPost(post); - post.addImage(image); } - qnAPostRepository.save(post); - - List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); - - return QnAPostRespDto.fromEntity(post, tagNames, false); - } // 게시글 수정 (작성자만 가능) @Transactional @@ -177,8 +146,8 @@ public void delete(long id, String email, Long orgId) { // 게시글 전체 조회 public PageResponseDTO findAll(Long orgId, Pageable pageable) { - Page page = qnAPostRepository.findByWriterIdAndAccountStatus(orgId, - AccountStatus.ACTIVE, pageable); + Page page = qnAPostRepository.findByWriterIdAndActiveStatus(orgId, + ActiveStatus.ACTIVE, pageable); List posts = page.getContent(); Map> tagMap = tagService.getTagNamesByPosts(posts); @@ -222,84 +191,24 @@ public String reportQnAPost(Long postId, Long orgId) { return "이미 신고한 게시글입니다."; } -<<<<<<< HEAD qnAPostReportRepository.save(QnAReport.builder() .member(member) .qnaPost(post) .build()); -======= - // 게시글 전체 조회 - public PageResponseDTO findAll(Long orgId, Pageable pageable) { - Page page = qnAPostRepository.findByWriterIdAndActiveStatus(orgId, ActiveStatus.ACTIVE, pageable); - List posts = page.getContent(); ->>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) - - post.increaseReportCount(); - return "게시글을 신고하였습니다."; + post.increaseReportCount(); + return "게시글을 신고하였습니다."; } -<<<<<<< HEAD // 인기 3개 @Transactional(readOnly = true) public List getPopularPosts(Long orgId) { - return qnAPostRepository.findTop3ByWriterIdAndAccountStatusOrderByViewCountDescScrapCountDesc( - orgId, AccountStatus.ACTIVE) - .stream() - .map(QnAPopularPostRespDto::from) - .toList(); -======= - - // 게시글 상세 조회 - public QnAPostRespDto getPostById(Long id, Long orgId, Long memberId) { - QnAPost post = qnAPostRepository.findById(id) - .orElseThrow( () -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); - - if (!post.getWriter().getId().equals(orgId)) { - throw new AccessDeniedException("해당 조직의 게시글만 조회할 수 있습니다."); - } - - post.increaseViewCount(); - List tagNames = tagService.getTagNamesByPost(post); - - // 스크랩 여부 조회 - boolean isScrap = qnAScrapRepository.existsByQnaPostIdAndMemberId(id, memberId); - - return QnAPostRespDto.fromEntity(post, tagNames, isScrap); - } - - // 게시글 신고하기 - @Transactional - public String reportQnAPost(Long postId, Long orgId) { - QnAPost post = qnAPostRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다.")); - - MemberOrg member = memberOrgRepository.findById(orgId) - .orElseThrow(() -> new MemberNotFoundException(ErrorCode.MEMBER_NOT_FOUND)); - - if (qnAPostReportRepository.existsByMemberAndQnaPost(member, post)) { - return "이미 신고한 게시글입니다."; - } - - qnAPostReportRepository.save(QnAReport.builder() - .member(member) - .qnaPost(post) - .build()); - - post.increaseReportCount(); - - return "게시글을 신고하였습니다."; - } - - // 인기 3개 - @Transactional(readOnly = true) - public List getPopularPosts(Long orgId) { - return qnAPostRepository.findTop3ByWriterIdAndActiveStatusOrderByViewCountDescScrapCountDesc(orgId, ActiveStatus.ACTIVE) - .stream() - .map(QnAPopularPostRespDto::from) - .toList(); ->>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) - /* + return qnAPostRepository.findTop3ByWriterIdAndActiveStatusOrderByViewCountDescScrapCountDesc( + orgId, ActiveStatus.ACTIVE) + .stream() + .map(QnAPopularPostRespDto::from) + .toList(); + /* .stream() .filter(post -> post.getWriter().getOrganization().equals(organization)) .sorted(Comparator @@ -319,5 +228,4 @@ public List getPopularPosts(Long orgId) { */ } - } diff --git a/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java b/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java index 0a1305e..5b59642 100644 --- a/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java +++ b/src/main/java/org/example/tackit/domain/tipBoard/Tip_comment/service/TipCommentService.java @@ -4,7 +4,7 @@ import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.MemberRole; import org.example.tackit.domain.entity.MemberType; import org.example.tackit.domain.entity.Notification; @@ -38,26 +38,17 @@ public TipCommentResponseDto createComment(TipCommentCreateDto dto, String email MemberOrg member = memberOrgRepository.findByMemberEmailAndId(email, orgId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); -<<<<<<< HEAD TipPost post = tipPostRepository.findById(dto.getTipPostId()) .orElseThrow(() -> new EntityNotFoundException("게시글을 찾을 수 없습니다.")); -======= - TipComment comment = TipComment.builder() - .writer(member) - .activeStatus(ActiveStatus.ACTIVE) - .tipPost(post) - .content(dto.getContent()) - .createdAt(LocalDateTime.now()) - .build(); ->>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) TipComment comment = TipComment.builder() - .writer(member) - .accountStatus(AccountStatus.ACTIVE) - .tipPost(post) - .content(dto.getContent()) - .createdAt(LocalDateTime.now()) - .build(); + .writer(member) + .activeStatus(ActiveStatus.ACTIVE) + .tipPost(post) + .content(dto.getContent()) + .createdAt(LocalDateTime.now()) + .build(); + // 댓글 DB 저장 TipComment savedComment = tipCommentRepository.save(comment); diff --git a/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java b/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java index b910b69..d6e6212 100644 --- a/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java +++ b/src/main/java/org/example/tackit/domain/tipBoard/Tip_post/service/TipPostService.java @@ -9,7 +9,7 @@ import lombok.RequiredArgsConstructor; import org.example.tackit.common.dto.PageResponseDTO; import org.example.tackit.config.S3.S3UploadService; -import org.example.tackit.domain.entity.AccountStatus; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.MemberType; import org.example.tackit.domain.entity.Notification; import org.example.tackit.domain.entity.NotificationType; @@ -137,65 +137,18 @@ public TipPostRespDto createPost(TipPostReqDto dto, String email, Long orgId, Mu .content(dto.getContent()) .createdAt(LocalDateTime.now()) .type(Post.Tip) - .accountStatus(AccountStatus.ACTIVE) + .activeStatus(ActiveStatus.ACTIVE) .reportCount(0) .isAnonymous(dto.isAnonymous()) .build(); -<<<<<<< HEAD // 3. 이미지 업로드 & 연관관계 매핑 (단일 파일만) if (image != null && !image.isEmpty()) { String imageUrl = s3UploadService.saveFile(image); TipPostImage imageEntity = TipPostImage.builder() - .imageUrl(imageUrl) - .build(); + .imageUrl(imageUrl) + .build(); post.addImage(imageEntity); // 기존 이미지 clear 후 하나만 저장 -======= - if (member.getMemberType() != MemberType.SENIOR) { - throw new AccessDeniedException("SENIOR만 게시글을 작성할 수 있습니다."); - } - - // 2. 게시글 생성 - TipPost post = TipPost.builder() - .writer(member) - .title(dto.getTitle()) - .content(dto.getContent()) - .createdAt(LocalDateTime.now()) - .type(Post.Tip) - .activeStatus(ActiveStatus.ACTIVE) - .reportCount(0) - .isAnonymous(dto.isAnonymous()) - .build(); - - // 3. 이미지 업로드 & 연관관계 매핑 (단일 파일만) - if (image != null && !image.isEmpty()) { - String imageUrl = s3UploadService.saveFile(image); - TipPostImage imageEntity = TipPostImage.builder() - .imageUrl(imageUrl) - .build(); - post.addImage(imageEntity); // 기존 이미지 clear 후 하나만 저장 - } - - tipPostRepository.save(post); - - List tagNames = tagService.assignTagsToPost(post, dto.getTagIds()); - - boolean anonymous = post.isAnonymous(); - - // 응답 DTO 구성 (imageUrl 하나만) - return TipPostRespDto.builder() - .id(post.getId()) - .writer(anonymous ? "익명" : member.getNickname()) - .profileImageUrl(anonymous ? null : member.getProfileImageUrl()) - .title(post.getTitle()) - .content(post.getContent()) - .createdAt(post.getCreatedAt()) - .tags(tagNames) - .imageUrl(post.getImages().isEmpty() ? null : post.getImages().get(0).getImageUrl()) - .isAnonymous(anonymous) - .isScrap(false) - .build(); ->>>>>>> 9ab4484 (refactor: #189-accountStatus 명 -> activeStatus로 변경) } tipPostRepository.save(post); @@ -206,18 +159,18 @@ public TipPostRespDto createPost(TipPostReqDto dto, String email, Long orgId, Mu // 응답 DTO 구성 (imageUrl 하나만) return TipPostRespDto.builder() - .id(post.getId()) - .writer(anonymous ? "익명" : member.getNickname()) - .profileImageUrl(anonymous ? null : member.getProfileImageUrl()) - .title(post.getTitle()) - .content(post.getContent()) - .createdAt(post.getCreatedAt()) - .tags(tagNames) - .imageUrl(post.getImages().isEmpty() ? null : post.getImages().get(0).getImageUrl()) - .isAnonymous(anonymous) - .isScrap(false) - .build(); - } + .id(post.getId()) + .writer(anonymous ? "익명" : member.getNickname()) + .profileImageUrl(anonymous ? null : member.getProfileImageUrl()) + .title(post.getTitle()) + .content(post.getContent()) + .createdAt(post.getCreatedAt()) + .tags(tagNames) + .imageUrl(post.getImages().isEmpty() ? null : post.getImages().get(0).getImageUrl()) + .isAnonymous(anonymous) + .isScrap(false) + .build(); + } // [ 게시글 수정 ] : 작성자만 From da067907561b8f80aa07e15de2c42f24dfcfd7de Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Wed, 18 Feb 2026 17:38:21 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor:=20#189-=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tackit/domain/entity/Org/MemberOrg.java | 14 ++++---------- .../executive/dto/request/MemberStatusRequest.java | 8 -------- .../dto/response/MemberDetailResponse.java | 4 ---- 3 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java delete mode 100644 src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java diff --git a/src/main/java/org/example/tackit/domain/entity/Org/MemberOrg.java b/src/main/java/org/example/tackit/domain/entity/Org/MemberOrg.java index 3625bcf..a182b35 100644 --- a/src/main/java/org/example/tackit/domain/entity/Org/MemberOrg.java +++ b/src/main/java/org/example/tackit/domain/entity/Org/MemberOrg.java @@ -35,16 +35,6 @@ public class MemberOrg { @JoinColumn(name = "org_id") private Organization organization; - /* - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "club_id") - private Club club; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "community_id") - private Community community; - */ - @Enumerated(EnumType.STRING) private OrgType orgType; // CLUB, COMMUNITY @@ -63,4 +53,8 @@ public class MemberOrg { @Builder.Default private LocalDateTime createdAt = LocalDateTime.now(); + + public void updateStatus(OrgStatus status) { + this.orgStatus = status; + } } diff --git a/src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java b/src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java deleted file mode 100644 index 20e2f38..0000000 --- a/src/main/java/org/example/tackit/domain/executive/dto/request/MemberStatusRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.example.tackit.domain.executive.dto.request; - -import lombok.Getter; - -@Getter -public class MemberStatusRequest { - private String status; -} diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java deleted file mode 100644 index 3876953..0000000 --- a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberDetailResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.example.tackit.domain.executive.dto.response; - -public class MemberDetailResponse { -} From 33019004dc53f497898cd5a301bb29423b05d7fb Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Wed, 18 Feb 2026 17:40:00 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20#189-=EC=9A=B4=EC=98=81=EC=A7=84?= =?UTF-8?q?=20API:=20=ED=9A=8C=EC=9B=90=20=EC=A1=B0=ED=9A=8C,=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=8A=B9=EC=9D=B8=20=EB=B0=8F=20=EB=B0=98=EB=A0=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExecutiveMemberController.java | 40 +++++----- .../dto/response/MemberListResponse.java | 9 ++- .../service/ExecutiveMemberService.java | 76 +++++++++---------- .../repository/MemberOrgRepository.java | 4 + 4 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java index 445bae1..0cc4771 100644 --- a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java @@ -2,11 +2,9 @@ import lombok.RequiredArgsConstructor; import org.example.tackit.domain.executive.dto.response.MemberListResponse; +import org.example.tackit.domain.executive.service.ExecutiveMemberService; import org.springframework.http.ResponseEntity; -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 org.springframework.web.bind.annotation.*; import java.util.List; @@ -14,32 +12,30 @@ @RequestMapping("/api/executive/members") @RequiredArgsConstructor public class ExecutiveMemberController { - + private final ExecutiveMemberService executiveMemberService; // [ 전체 회원 조회 ] - /* @GetMapping public ResponseEntity> getAllMembers( - @RequestParam(required = false) String status + @RequestParam Long orgId, + @RequestParam(required = false) String orgStatus ) { - return ResponseEntity.ok(users); + List responses = executiveMemberService.getMembers(orgId, orgStatus); + return ResponseEntity.ok(responses); } - */ - - /* - // [ 대기 회원 조회 ] - @GetMapping("/deleted") - public ResponseEntity getDeletedMembers() { - DeletedMemberResp deletedMembers = adminMemberService.getDeletedMembers(); - return ResponseEntity.ok(deletedMembers); + // [ 멤버 승인 ] + @PatchMapping("/approve") + public ResponseEntity approveMember(@RequestParam Long memberOrgId) { + executiveMemberService.approveMember(memberOrgId); + return ResponseEntity.noContent().build(); } - // [ 사용 중 회원 조회 ] - - // [ 탈퇴 회원 조회 ] + // [ 멤버 반려 ] + @PatchMapping("/reject") + public ResponseEntity rejectMember(@RequestParam Long memberOrgId) { + executiveMemberService.rejectMember(memberOrgId); + return ResponseEntity.noContent().build(); + } - // [ 회원 승인 ] - // [ 회원 거부 ] - */ } diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java index 65c8587..76237c2 100644 --- a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java +++ b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java @@ -1,9 +1,16 @@ package org.example.tackit.domain.executive.dto.response; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + import java.time.LocalDateTime; +@Getter +@Builder +@AllArgsConstructor public class MemberListResponse { - private Long memberId; + private Long memberOrgId; private String nickname; private String email; private String orgStatus; diff --git a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java index c801351..cc2a1f5 100644 --- a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java +++ b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java @@ -2,74 +2,66 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.example.tackit.domain.entity.Org.MemberOrg; +import org.example.tackit.domain.entity.Org.OrgStatus; +import org.example.tackit.domain.member.repository.MemberOrgRepository; import org.example.tackit.domain.member.repository.MemberRepository; -import org.example.tackit.domain.entity.Member; -import org.example.tackit.domain.executive.dto.request.MemberStatusRequest; import org.example.tackit.domain.executive.dto.response.MemberListResponse; import org.example.tackit.domain.executive.repository.ExecutiveMemberRepository; import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor +@Transactional public class ExecutiveMemberService { private final ExecutiveMemberRepository executiveMemberRepository; private final MemberRepository memberRepository; + private final MemberOrgRepository memberOrgRepository; - /* // [ 모든 멤버 조회 ] - @Transactional - public List getMembers(MemberStatusRequest request) { - String status = request.getStatus(); - - List members; + public List getMembers(Long orgId, String orgStatus) { + List memberOrgs; // 상태 조건 없다면 -> 전체 - if( status == null || status.isBlank() ) - members = memberRepository.findAll(); + if( orgStatus == null || orgStatus.isBlank() ) + memberOrgs = memberOrgRepository.findByOrganizationId(orgId); // 특정 상태(대기, 이용 중, 탈퇴) 조회 else { - - // members = memberRepository.findByStatus(status); + OrgStatus status = OrgStatus.valueOf(orgStatus.toUpperCase()); + memberOrgs = memberOrgRepository.findByOrganizationIdAndOrgStatus(orgId, status); } - return members.stream() - .map() - } - - /* - // [ 총 회원 수, 이번 달 신규 회원 수, 이번 주 신규 회원 수 ] - @Transactional(readOnly = true) - public MemberStatisticsDTO getMemberStatistics() { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime startOfMonth = now.withDayOfMonth(1).with(LocalTime.MIN); - LocalDateTime startOfWeek = now.with(ChronoField.DAY_OF_WEEK, 1).toLocalDate().atStartOfDay(); - - long totalCount = adminMemberRepository.countAll(); - long monthlyCount = adminMemberRepository.countJoinedAfter(startOfMonth); - long weeklyCount = adminMemberRepository.countJoinedAfter(startOfWeek); + return memberOrgs.stream() + .map( mo -> MemberListResponse.builder() + .memberOrgId(mo.getId()) + .nickname(mo.getNickname()) + .email(mo.getMember().getEmail()) + .orgStatus(mo.getOrgStatus().name()) + .createdAt(mo.getCreatedAt()) + .build()) + .collect(Collectors.toList()); - return new MemberStatisticsDTO(totalCount, monthlyCount, weeklyCount); } - // [ 탈퇴 회원 수 조회 ] - @Transactional(readOnly = true) - public DeletedMemberResp getDeletedMembers() { - List deletedMembers = adminMemberRepository.findByStatus(AccountStatus.DELETED); + // [ 멤버 승인 ] + public void approveMember(Long memberOrgId) { + MemberOrg memberOrg = memberOrgRepository.findById(memberOrgId) + .orElseThrow( () -> new IllegalArgumentException("해당 가입 신청을 찾을 수 없습니다.")); - List deletedMemberDTOS = deletedMembers.stream() - .map(member -> DeletedMemberDTO.from(member)) - .collect(Collectors.toList()); - - Long deletedCount = (long) deletedMembers.size(); - - return new DeletedMemberResp(deletedMemberDTOS, deletedCount); + // 상태를 ACTIVE로 변경 + memberOrg.updateStatus(OrgStatus.ACTIVE); } + // [ 멤버 반려 ] + public void rejectMember(Long memberOrgId) { + MemberOrg memberOrg = memberOrgRepository.findById(memberOrgId) + .orElseThrow(() -> new IllegalArgumentException("해당 가입 신청을 찾을 수 없습니다.")); -} - - */ + // 상태를 REJECTED로 변경 + memberOrg.updateStatus(OrgStatus.REJECTED); + } } diff --git a/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java b/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java index 2a0d00e..d588d5c 100644 --- a/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java +++ b/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java @@ -52,4 +52,8 @@ Optional findByMemberIdAndOrganizationIdAndOrgStatus(Long memberId, L OrgStatus status); int countByOrganizationIdAndOrgStatus(Long orgId, OrgStatus orgStatus); + + List findByOrganizationId(Long orgId); // 특정 조직의 모든 멤버 관계 조회 + + List findByOrganizationIdAndOrgStatus(Long orgId, OrgStatus orgStatus); // 특정 조직 + 특정 상태(PENDING, ACTIVE 등)의 멤버 관계 조회 } \ No newline at end of file From 6e4a94f95c456b31ab918e4ba72e4015fd65a85a Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Thu, 19 Feb 2026 11:52:55 +0900 Subject: [PATCH 06/10] =?UTF-8?q?refactor:=20#189-=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/tackit/domain/entity/Report.java | 65 ++++++++++++------- .../dto/response/MemberListResponse.java | 18 ----- 2 files changed, 42 insertions(+), 41 deletions(-) delete mode 100644 src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java diff --git a/src/main/java/org/example/tackit/domain/entity/Report.java b/src/main/java/org/example/tackit/domain/entity/Report.java index 88cc883..aad2c84 100644 --- a/src/main/java/org/example/tackit/domain/entity/Report.java +++ b/src/main/java/org/example/tackit/domain/entity/Report.java @@ -1,48 +1,67 @@ package org.example.tackit.domain.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.report.dto.ReportRequestDto; import java.time.LocalDateTime; @Entity -@NoArgsConstructor -@AllArgsConstructor -@Builder @Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Table(name = "report") public class Report { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 신고자 @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "reporter_id") + @JoinColumn(name = "reporter_id", nullable = false) private MemberOrg reporter; + // 작성자 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "writer_id", nullable = false) + private MemberOrg writer; + + @Column(nullable = false) private Long targetId; - @Enumerated(EnumType.STRING) // DB에 문자열 형태로 저장 + // POST / COMMENT + @Enumerated(EnumType.STRING) + @Column(nullable = false) private TargetType targetType; + // QNA / TIP / FREE @Enumerated(EnumType.STRING) - private ReportReason reason; - - private LocalDateTime createdAt = LocalDateTime.now(); - - public static Report fromDto(ReportRequestDto dto, MemberOrg reporter) { - Report report = new Report(); - report.reporter = reporter; - report.targetId = dto.getTargetId(); - report.targetType = dto.getTargetType(); - report.reason = dto.getReason(); - report.createdAt = LocalDateTime.now(); - return report; + @Column(nullable = false) + private Post postType; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ReportReason reportReason; + + @Column(nullable = false) + private LocalDateTime reportedAt; + + @Enumerated(EnumType.STRING) + private ActiveStatus activeStatus; + + public static Report from(ReportRequestDto dto, MemberOrg reporter, MemberOrg writer) { + return Report.builder() + .reporter(reporter) + .writer(writer) + .targetId(dto.getTargetId()) + .targetType(dto.getTargetType()) + .postType(dto.getPostType()) + .reportReason(dto.getReason()) + .activeStatus(ActiveStatus.ACTIVE) // 초기값 설정 + .build(); } + } diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java deleted file mode 100644 index 76237c2..0000000 --- a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.example.tackit.domain.executive.dto.response; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -@Builder -@AllArgsConstructor -public class MemberListResponse { - private Long memberOrgId; - private String nickname; - private String email; - private String orgStatus; - private LocalDateTime createdAt; -} From d3eaee44fd2bc5e937ab79bb64914c5bda1ca2a5 Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Thu, 19 Feb 2026 11:54:06 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20#189-=EC=9A=B4=EC=98=81=EC=A7=84?= =?UTF-8?q?=20=EC=8B=A0=EA=B3=A0=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExecutiveMemberController.java | 8 ++-- .../controller/ExecutiveReportController.java | 37 +++++++++------- .../dto/response/MemberListResDto.java | 18 ++++++++ .../dto/response/ReportedPostResDto.java | 32 ++++++++++++++ .../service/ExecutiveMemberService.java | 6 +-- .../service/ExecutiveReportedPostService.java | 42 +++++++++++++++++++ 6 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResDto.java create mode 100644 src/main/java/org/example/tackit/domain/executive/dto/response/ReportedPostResDto.java create mode 100644 src/main/java/org/example/tackit/domain/executive/service/ExecutiveReportedPostService.java diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java index 0cc4771..37766b8 100644 --- a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java @@ -1,7 +1,7 @@ package org.example.tackit.domain.executive.controller; import lombok.RequiredArgsConstructor; -import org.example.tackit.domain.executive.dto.response.MemberListResponse; +import org.example.tackit.domain.executive.dto.response.MemberListResDto; import org.example.tackit.domain.executive.service.ExecutiveMemberService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,11 +16,11 @@ public class ExecutiveMemberController { // [ 전체 회원 조회 ] @GetMapping - public ResponseEntity> getAllMembers( + public ResponseEntity> getAllMembers( @RequestParam Long orgId, - @RequestParam(required = false) String orgStatus + @RequestParam(value = "orgStatus", defaultValue = "ALL") String orgStatus ) { - List responses = executiveMemberService.getMembers(orgId, orgStatus); + List responses = executiveMemberService.getMembers(orgId, orgStatus); return ResponseEntity.ok(responses); } diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java index 84231fb..90ba255 100644 --- a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveReportController.java @@ -1,35 +1,44 @@ package org.example.tackit.domain.executive.controller; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.example.tackit.common.dto.PageResponseDTO; +import org.example.tackit.domain.admin.dto.ReportedPostDTO; +import org.example.tackit.domain.entity.Post; +import org.example.tackit.domain.executive.dto.response.ReportedPostResDto; +import org.example.tackit.domain.executive.service.ExecutiveReportedPostService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/executive/reports") @RequiredArgsConstructor public class ExecutiveReportController { + private final ExecutiveReportedPostService executiveReportedPostService; - /* // [ 신고 게시글 전체 조회 ] - @GetMapping("/{postType}/posts") - public ResponseEntity> getReportedPosts( - @PathVariable("postType") Post postType, - @PageableDefault(size = 5, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { - ReportedPostService service = reportedPostServices.get(postType); + @GetMapping + public ResponseEntity> getReportList( + @RequestParam(value = "status", defaultValue = "ALL") String status, + @PageableDefault(size = 10, sort = "reportedAt", direction = Sort.Direction.DESC) Pageable pageable) { - if (service == null) { - throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType); - } - - Page posts = service.getDeletedPosts(pageable); - return ResponseEntity.ok(PageResponseDTO.from(posts)); + Page reports = executiveReportedPostService.getReportList(status, pageable); + return ResponseEntity.ok(PageResponseDTO.from(reports)); } // [ 신고 게시글 상세 조회 ] + @GetMapping("/{reportId}") + public ResponseEntity getReportDetail(@PathVariable Long reportId) { + return ResponseEntity.ok(executiveReportedPostService.getReportDetail(reportId)); + } // [ 신고 게시글 복구 ] // [ 신고 게시글 완전 삭제 ] + /* @DeleteMapping("/{postType}/posts/{postId}") public ResponseEntity deleteReportedPost( @PathVariable("postType") Post postType, diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResDto.java b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResDto.java new file mode 100644 index 0000000..d1e89e8 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/dto/response/MemberListResDto.java @@ -0,0 +1,18 @@ +package org.example.tackit.domain.executive.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +@AllArgsConstructor +public class MemberListResDto { + private Long memberOrgId; + private String nickname; + private String email; + private String orgStatus; + private LocalDateTime createdAt; +} diff --git a/src/main/java/org/example/tackit/domain/executive/dto/response/ReportedPostResDto.java b/src/main/java/org/example/tackit/domain/executive/dto/response/ReportedPostResDto.java new file mode 100644 index 0000000..d7c355b --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/dto/response/ReportedPostResDto.java @@ -0,0 +1,32 @@ +package org.example.tackit.domain.executive.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.example.tackit.domain.entity.ActiveStatus; +import org.example.tackit.domain.entity.Report; + +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +public class ReportedPostResDto { + private Long reportId; + private String reporter; + private String writer; + private String postType; + private String reason; + private LocalDateTime reportedAt; + private ActiveStatus activeStatus; + + public static ReportedPostResDto from(Report post) { + return new ReportedPostResDto( + post.getId(), + post.getReporter().getNickname(), + post.getWriter().getNickname(), + post.getPostType().name(), + post.getReportReason().name(), + post.getReportedAt(), + post.getActiveStatus() + ); + } +} diff --git a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java index cc2a1f5..9641d17 100644 --- a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java +++ b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java @@ -6,7 +6,7 @@ import org.example.tackit.domain.entity.Org.OrgStatus; import org.example.tackit.domain.member.repository.MemberOrgRepository; import org.example.tackit.domain.member.repository.MemberRepository; -import org.example.tackit.domain.executive.dto.response.MemberListResponse; +import org.example.tackit.domain.executive.dto.response.MemberListResDto; import org.example.tackit.domain.executive.repository.ExecutiveMemberRepository; import org.springframework.stereotype.Service; @@ -22,7 +22,7 @@ public class ExecutiveMemberService { private final MemberOrgRepository memberOrgRepository; // [ 모든 멤버 조회 ] - public List getMembers(Long orgId, String orgStatus) { + public List getMembers(Long orgId, String orgStatus) { List memberOrgs; // 상태 조건 없다면 -> 전체 @@ -36,7 +36,7 @@ public List getMembers(Long orgId, String orgStatus) { } return memberOrgs.stream() - .map( mo -> MemberListResponse.builder() + .map( mo -> MemberListResDto.builder() .memberOrgId(mo.getId()) .nickname(mo.getNickname()) .email(mo.getMember().getEmail()) diff --git a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveReportedPostService.java b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveReportedPostService.java new file mode 100644 index 0000000..79d4144 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveReportedPostService.java @@ -0,0 +1,42 @@ +package org.example.tackit.domain.executive.service; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.example.tackit.domain.entity.ActiveStatus; +import org.example.tackit.domain.entity.Report; +import org.example.tackit.domain.entity.ReportPost; +import org.example.tackit.domain.executive.dto.response.ReportedPostResDto; +import org.example.tackit.domain.report.repository.ReportRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class ExecutiveReportedPostService { + private final ReportRepository reportRepository; + + // 신고 전체 조회 : 게시글만 O + public Page getReportList(String status, Pageable pageable) { + ActiveStatus filterStatus = null; + + if ("ACTIVE".equalsIgnoreCase(status)) filterStatus = ActiveStatus.ACTIVE; + else if ("DELETED".equalsIgnoreCase(status)) filterStatus = ActiveStatus.DELETED; + + Page reports = reportRepository.findAllByActiveStatus(filterStatus, pageable); + + return reports.map(ReportedPostResDto::from); + } + + // 신고 상세 조회 + public ReportedPostResDto getReportDetail(Long reportId) { + Report report = reportRepository.findById(reportId) + .orElseThrow(() -> new EntityNotFoundException("해당 신고 내역이 존재하지 않습니다. ID: " + reportId)); + return ReportedPostResDto.from(report); + } + + + +} From 7264c63a4c72fa9b84af65d75c18ee9f2fb5dd86 Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Thu, 19 Feb 2026 11:54:33 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20=ED=83=80=EC=9E=85=EC=97=90?= =?UTF-8?q?=20tip=5Fcomment=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/tackit/domain/entity/TargetType.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/tackit/domain/entity/TargetType.java b/src/main/java/org/example/tackit/domain/entity/TargetType.java index 4fbbd02..e103899 100644 --- a/src/main/java/org/example/tackit/domain/entity/TargetType.java +++ b/src/main/java/org/example/tackit/domain/entity/TargetType.java @@ -4,6 +4,7 @@ public enum TargetType { TIP_POST, FREE_POST, QNA_POST, + TIP_COMMENT, FREE_COMMENT, - QNA_COMMENT + QNA_COMMENT, } From 69801e43e0bd7223c01796424ab5bb30de378ea5 Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Thu, 19 Feb 2026 11:55:15 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20#189-=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81(?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=ED=95=A9?= =?UTF-8?q?=EC=B9=98=EA=B8=B0=20=EB=93=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tackit/domain/entity/ReportPost.java | 60 +++++++++++++++++++ .../domain/report/dto/ReportLogDto.java | 4 +- .../domain/report/dto/ReportRequestDto.java | 4 +- .../report/repository/ReportRepository.java | 10 +++- 4 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/example/tackit/domain/entity/ReportPost.java diff --git a/src/main/java/org/example/tackit/domain/entity/ReportPost.java b/src/main/java/org/example/tackit/domain/entity/ReportPost.java new file mode 100644 index 0000000..6bc8692 --- /dev/null +++ b/src/main/java/org/example/tackit/domain/entity/ReportPost.java @@ -0,0 +1,60 @@ +package org.example.tackit.domain.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.example.tackit.domain.entity.Org.MemberOrg; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "report_post") +public class ReportPost { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 신고자 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "reporter_id", nullable = false) + private MemberOrg reporter; + + // 작성자 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "writer_id", nullable = false) + private MemberOrg writer; + + @Column(nullable = false) + private Long targetId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Post postType; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ReportReason reportReason; + + @Column(nullable = false) + private LocalDateTime reportedAt; + + // 비활성화 여부 + @Enumerated(EnumType.STRING) + private ActiveStatus activeStatus; + + @Builder + public ReportPost(MemberOrg reporter, MemberOrg writer, Long targetId, + Post postType, ReportReason reportReason, ActiveStatus activeStatus) { + this.reporter = reporter; + this.writer = writer; + this.targetId = targetId; + this.postType = postType; + this.reportReason = reportReason; + this.reportedAt = LocalDateTime.now(); + this.activeStatus = activeStatus != null ? activeStatus : ActiveStatus.ACTIVE; + } +} diff --git a/src/main/java/org/example/tackit/domain/report/dto/ReportLogDto.java b/src/main/java/org/example/tackit/domain/report/dto/ReportLogDto.java index e2c7423..2d1408b 100644 --- a/src/main/java/org/example/tackit/domain/report/dto/ReportLogDto.java +++ b/src/main/java/org/example/tackit/domain/report/dto/ReportLogDto.java @@ -19,8 +19,8 @@ public static ReportLogDto from(Report report) { return ReportLogDto.builder() .reportId(report.getId()) .reporterNickname(report.getReporter().getNickname()) - .reportReason(report.getReason()) - .createdAt(report.getCreatedAt()) + .reportReason(report.getReportReason()) + .createdAt(report.getReportedAt()) .build(); } } diff --git a/src/main/java/org/example/tackit/domain/report/dto/ReportRequestDto.java b/src/main/java/org/example/tackit/domain/report/dto/ReportRequestDto.java index 1e966db..ef855f7 100644 --- a/src/main/java/org/example/tackit/domain/report/dto/ReportRequestDto.java +++ b/src/main/java/org/example/tackit/domain/report/dto/ReportRequestDto.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.example.tackit.domain.entity.Post; import org.example.tackit.domain.entity.ReportReason; import org.example.tackit.domain.entity.TargetType; @@ -12,7 +13,8 @@ @NoArgsConstructor @AllArgsConstructor public class ReportRequestDto { - private Long targetId; // 신고 대상 글/댓글 ID + private Long targetId; private TargetType targetType; // POST / COMMENT + private Post postType; private ReportReason reason; } diff --git a/src/main/java/org/example/tackit/domain/report/repository/ReportRepository.java b/src/main/java/org/example/tackit/domain/report/repository/ReportRepository.java index 314ac55..6427000 100644 --- a/src/main/java/org/example/tackit/domain/report/repository/ReportRepository.java +++ b/src/main/java/org/example/tackit/domain/report/repository/ReportRepository.java @@ -1,5 +1,6 @@ package org.example.tackit.domain.report.repository; +import org.example.tackit.domain.entity.ActiveStatus; import org.example.tackit.domain.entity.Report; import org.example.tackit.domain.entity.TargetType; import org.springframework.data.domain.Page; @@ -14,6 +15,13 @@ @Repository public interface ReportRepository extends JpaRepository { + // 전체/필터링 조회 (Fetch Join으로 신고자와 작성자 정보를 한 번에 가져옴) + @Query("select r from Report r " + + "join fetch r.reporter " + + "join fetch r.writer " + + "where (:status is null or r.activeStatus = :status)") + Page findAllByActiveStatus(@Param("status")ActiveStatus activeStatus, Pageable pageable); + // 필요하다면 신고 중복 방지를 위한 조회 메서드도 추가 가능 boolean existsByReporterIdAndTargetIdAndTargetType(Long reporterId, Long targetId, TargetType targetType); @@ -25,7 +33,7 @@ interface ReportedTargetInfo { } // 신고된 컨텐츠 그룹화하는 쿼리 @Query("SELECT r.targetId as targetId, r.targetType as targetType, " + - " COUNT(r) as reportCount, MAX(r.createdAt) as lastReportedAt " + + " COUNT(r) as reportCount, MAX(r.reportedAt) as lastReportedAt " + "FROM Report r " + "GROUP BY r.targetId, r.targetType") Page findReportedTargets(Pageable pageable); From 139a3ad625154872f8e4c06e6e8de553bb425c0b Mon Sep 17 00:00:00 2001 From: yeongsinkeem Date: Mon, 23 Feb 2026 23:11:49 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20#189-=EC=9A=B4=EC=98=81?= =?UTF-8?q?=EC=A7=84=20api=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=9A=B4?= =?UTF-8?q?=EC=98=81=EC=A7=84=20=EA=B6=8C=ED=95=9C=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=8F=20ai=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExecutiveMemberController.java | 22 +++++-- .../service/ExecutiveMemberService.java | 64 +++++++++++++------ .../repository/MemberOrgRepository.java | 2 + .../service/OrganizationService.java | 9 ++- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java index 37766b8..d2a84f3 100644 --- a/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java +++ b/src/main/java/org/example/tackit/domain/executive/controller/ExecutiveMemberController.java @@ -4,6 +4,8 @@ import org.example.tackit.domain.executive.dto.response.MemberListResDto; import org.example.tackit.domain.executive.service.ExecutiveMemberService; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -18,23 +20,31 @@ public class ExecutiveMemberController { @GetMapping public ResponseEntity> getAllMembers( @RequestParam Long orgId, - @RequestParam(value = "orgStatus", defaultValue = "ALL") String orgStatus + @RequestParam(value = "orgStatus", defaultValue = "ALL") String orgStatus, + @AuthenticationPrincipal UserDetails userDetails ) { - List responses = executiveMemberService.getMembers(orgId, orgStatus); + List responses = executiveMemberService.getMembers(orgId, userDetails.getUsername(), orgStatus); return ResponseEntity.ok(responses); } // [ 멤버 승인 ] @PatchMapping("/approve") - public ResponseEntity approveMember(@RequestParam Long memberOrgId) { - executiveMemberService.approveMember(memberOrgId); + public ResponseEntity approveMember( + @RequestParam Long orgId, + @RequestParam Long memberOrgId, + @AuthenticationPrincipal UserDetails userDetails) + { + executiveMemberService.approveMember(orgId, userDetails.getUsername(), memberOrgId); return ResponseEntity.noContent().build(); } // [ 멤버 반려 ] @PatchMapping("/reject") - public ResponseEntity rejectMember(@RequestParam Long memberOrgId) { - executiveMemberService.rejectMember(memberOrgId); + public ResponseEntity rejectMember( + @RequestParam Long orgId, + @RequestParam Long memberOrgId, + @AuthenticationPrincipal UserDetails userDetails) { + executiveMemberService.rejectMember(orgId, userDetails.getUsername(), memberOrgId ); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java index 9641d17..22f5603 100644 --- a/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java +++ b/src/main/java/org/example/tackit/domain/executive/service/ExecutiveMemberService.java @@ -2,12 +2,12 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.example.tackit.domain.entity.MemberRole; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.entity.Org.OrgStatus; import org.example.tackit.domain.member.repository.MemberOrgRepository; -import org.example.tackit.domain.member.repository.MemberRepository; import org.example.tackit.domain.executive.dto.response.MemberListResDto; -import org.example.tackit.domain.executive.repository.ExecutiveMemberRepository; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import java.util.List; @@ -17,26 +17,39 @@ @RequiredArgsConstructor @Transactional public class ExecutiveMemberService { - private final ExecutiveMemberRepository executiveMemberRepository; - private final MemberRepository memberRepository; private final MemberOrgRepository memberOrgRepository; + // 공통 권한 검증 : 요청자가 해당 조직의 멤버인지, 운영진인지 확인 + private void validateExecutiveRole(Long orgId, String email) { + MemberOrg requester = memberOrgRepository.findByOrganizationIdAndMemberEmail(orgId, email) + .orElseThrow(() -> new IllegalArgumentException("해당 조직의 멤버가 아닙니다.")); + + if (requester.getMemberRole() != MemberRole.EXECUTIVE) { + throw new AccessDeniedException("운영진 권한이 없습니다."); + } + + if (requester.getOrgStatus() != OrgStatus.ACTIVE) { + throw new IllegalStateException("활성화되지 않은 운영진 계정입니다."); + } + } + // [ 모든 멤버 조회 ] - public List getMembers(Long orgId, String orgStatus) { + public List getMembers(Long orgId, String email, String orgStatus) { + // 1. 운영진 권한 체크 + validateExecutiveRole(orgId, email); + List memberOrgs; - // 상태 조건 없다면 -> 전체 - if( orgStatus == null || orgStatus.isBlank() ) + // 2. 조회 + if ("ALL".equalsIgnoreCase(orgStatus)) { memberOrgs = memberOrgRepository.findByOrganizationId(orgId); - - // 특정 상태(대기, 이용 중, 탈퇴) 조회 - else { + } else { OrgStatus status = OrgStatus.valueOf(orgStatus.toUpperCase()); memberOrgs = memberOrgRepository.findByOrganizationIdAndOrgStatus(orgId, status); } return memberOrgs.stream() - .map( mo -> MemberListResDto.builder() + .map(mo -> MemberListResDto.builder() .memberOrgId(mo.getId()) .nickname(mo.getNickname()) .email(mo.getMember().getEmail()) @@ -44,24 +57,33 @@ public List getMembers(Long orgId, String orgStatus) { .createdAt(mo.getCreatedAt()) .build()) .collect(Collectors.toList()); - } // [ 멤버 승인 ] - public void approveMember(Long memberOrgId) { - MemberOrg memberOrg = memberOrgRepository.findById(memberOrgId) - .orElseThrow( () -> new IllegalArgumentException("해당 가입 신청을 찾을 수 없습니다.")); + public void approveMember(Long orgId, String email, Long memberOrgId) { + // 1. 운영진 권한 체크 + validateExecutiveRole(orgId, email); + + // 2. 승인 대상 조회 및 상태 변경 + MemberOrg targetMember = memberOrgRepository.findById(memberOrgId) + .orElseThrow(() -> new IllegalArgumentException("해당 가입 신청을 찾을 수 없습니다.")); - // 상태를 ACTIVE로 변경 - memberOrg.updateStatus(OrgStatus.ACTIVE); + targetMember.updateStatus(OrgStatus.ACTIVE); } // [ 멤버 반려 ] - public void rejectMember(Long memberOrgId) { - MemberOrg memberOrg = memberOrgRepository.findById(memberOrgId) + public void rejectMember(Long orgId, String email, Long memberOrgId) { + // 1. 운영진 권한 체크 + validateExecutiveRole(orgId, email); + + // 2. 반려 대상 조회 및 상태 변경 + MemberOrg targetMember = memberOrgRepository.findById(memberOrgId) .orElseThrow(() -> new IllegalArgumentException("해당 가입 신청을 찾을 수 없습니다.")); - // 상태를 REJECTED로 변경 - memberOrg.updateStatus(OrgStatus.REJECTED); + if (!targetMember.getOrganization().getId().equals(orgId)) { + throw new IllegalArgumentException("해당 조직의 가입 신청이 아닙니다."); + } + + targetMember.updateStatus(OrgStatus.REJECTED); } } diff --git a/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java b/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java index d588d5c..6f94729 100644 --- a/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java +++ b/src/main/java/org/example/tackit/domain/member/repository/MemberOrgRepository.java @@ -12,6 +12,8 @@ @Repository public interface MemberOrgRepository extends JpaRepository { + Optional findByOrganizationIdAndMemberEmail(Long orgId, String email); + // 특정 소속(Org) 내에서 닉네임 중복 확인 boolean existsByOrganizationIdAndNickname(Long orgId, String nickname); diff --git a/src/main/java/org/example/tackit/domain/organization/service/OrganizationService.java b/src/main/java/org/example/tackit/domain/organization/service/OrganizationService.java index 448214a..075a934 100644 --- a/src/main/java/org/example/tackit/domain/organization/service/OrganizationService.java +++ b/src/main/java/org/example/tackit/domain/organization/service/OrganizationService.java @@ -4,6 +4,7 @@ import java.util.NoSuchElementException; import lombok.AllArgsConstructor; import org.example.tackit.domain.entity.Member; +import org.example.tackit.domain.entity.MemberRole; import org.example.tackit.domain.entity.Org.MemberOrg; import org.example.tackit.domain.entity.Org.OrgStatus; import org.example.tackit.domain.entity.Org.OrgType; @@ -82,6 +83,12 @@ public void joinOrg(Long orgId, String email, OrgJoinReqDto dto) { // 최초 가입자 여부 확인 boolean isFirstMember = !memberOrgRepository.existsByOrganizationId(orgId); + // 최초 가입자 && 운영진 -> ACTIVE 되도록 + OrgStatus status = OrgStatus.PENDING; + if (isFirstMember && dto.getMemberRole() == MemberRole.EXECUTIVE) { + status = OrgStatus.ACTIVE; + } + MemberOrg memberOrg = MemberOrg.builder() .member(member) .organization(organization) // 통합된 필드 사용 @@ -90,7 +97,7 @@ public void joinOrg(Long orgId, String email, OrgJoinReqDto dto) { .memberRole(dto.getMemberRole()) .memberType(dto.getMemberType()) .joinedYear(LocalDate.now().getYear()) - .orgStatus(isFirstMember ? OrgStatus.ACTIVE : OrgStatus.PENDING) // 삼항 연산자로 깔끔하게 + .orgStatus(status) .build(); memberOrgRepository.save(memberOrg);