From b1eb2daed76e83ec751cec6b9931c7c44bd0098d Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:21:16 +0900 Subject: [PATCH 001/132] Add files via upload --- .github/pull_request_template.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e74b4a8 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## #️⃣연관된 이슈 + +> ex) #이슈번호, #이슈번호 + +## 📝작업 내용 + +> 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능) + +### 스크린샷 (선택) + + +> 스크린샷 너무 크니까 보기 불편해서 만든 템플릿 + +## 💬리뷰 요구사항(선택) + +> 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 +> +> ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? From c496b246a7aff9d1e8b820087a0b5186a99c627f Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 1 Jul 2025 18:41:30 +0900 Subject: [PATCH 002/132] connect userinvite --- .../ccapp/ccgo/controller/AuthController.java | 66 +++++--- .../ccgo/controller/InviteCodeController.java | 14 +- .../com/ccapp/ccgo/jwt/LoginUserDetails.java | 10 +- .../com/ccapp/ccgo/jwt/SecurityConfig.java | 154 +++++++++--------- .../ccapp/ccgo/service/InviteCodeService.java | 2 +- .../java/com/ccapp/ccgo/team/TeamMember.java | 2 +- src/main/resources/application.properties | 2 +- 7 files changed, 142 insertions(+), 108 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/controller/AuthController.java index 9867f2c..965ccb5 100644 --- a/src/main/java/com/ccapp/ccgo/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/controller/AuthController.java @@ -20,6 +20,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import org.springframework.http.HttpHeaders; @@ -40,8 +41,9 @@ public class AuthController { private final LoginUserDetailsService loginUserDetailsService; private final TeamRepository teamRepository; + //@authenticatedPrincipal UserDetails authenticatedPrincipal @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto) { + public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) { log.info("로그인 요청 받음: {}", requestDto.getEmail()); log.info("로그인 요청 받음: {}", requestDto.getPassword()); @@ -61,41 +63,55 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto) { // ✅ 팀이 없으면 팀 생성 + 팀장 등록 Optional existingTeamMember = teamMemberRepository.findByUserAndIsActiveTrue(user); - if (existingTeamMember.isEmpty() && user.getRole().equals("TeamLeader")) { - - // 1. 새 팀 생성 - Team team = new Team(); - team.setTeamName(user.getName() + "의 팀"); // 원하는 네이밍 규칙 사용 - team.setCreatedAt(LocalDateTime.now()); - team.setCreatedBy(user.getId()); - teamRepository.save(team); - - // 2. 팀장 본인을 팀원으로 등록 - TeamMember teamMember = new TeamMember(); - teamMember.setUser(user); - teamMember.setTeam(team); - teamMember.setRole("TeamLeader"); // 또는 enum 등 - teamMember.setActive(true); - teamMember.setJoinedAt(LocalDateTime.now()); - teamMemberRepository.save(teamMember); - - log.info("🆕 새 팀 생성 및 팀장 등록 완료"); + + if (existingTeamMember.isEmpty()) { + if (user.getRole().equals("TeamLeader")) { + // 팀 생성 및 팀장 등록 + Team team = new Team(); + team.setTeamName(user.getName() + "의 팀"); + team.setCreatedAt(LocalDateTime.now()); + team.setCreatedBy(user.getId()); + teamRepository.save(team); + + TeamMember teamMember = new TeamMember(); + teamMember.setUser(user); + teamMember.setTeam(team); + teamMember.setRole("TeamLeader"); + teamMember.setActive(true); + teamMember.setJoinedAt(LocalDateTime.now()); + teamMemberRepository.save(teamMember); + + log.info("🆕 새 팀 생성 및 팀장 등록 완료"); + } else if (user.getRole().equals("TeamMember")) { + // 팀원 등록 (팀 없음) + TeamMember teamMember = new TeamMember(); + teamMember.setUser(user); + teamMember.setTeam(null); // 나중에 초대코드로 팀이 지정될 예정 + teamMember.setRole("TeamMember"); + teamMember.setActive(true); + teamMember.setJoinedAt(LocalDateTime.now()); + teamMemberRepository.save(teamMember); + + log.info("🆕 팀원 등록 완료 (팀 없음)"); + } } // ✅ 다시 조회 (혹은 Optional.get으로 바로 사용 가능) TeamMember teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) - .orElseThrow(() -> new RuntimeException("소속된 팀이 없습니다.")); + .orElseThrow(() -> new RuntimeException("TeamMember 레코드가 없습니다.")); HttpHeaders headers = createTokenCookies(accessToken, refreshToken); + Team team = teamMember.getTeam(); + LoginResponseDto response = LoginResponseDto.builder() .userId(user.getId()) .email(user.getEmail()) .name(user.getName()) - .teamId(teamMember.getTeam().getTeamId()) - .teamName(teamMember.getTeam().getTeamName()) + .teamId(team != null ? team.getTeamId() : null) + .teamName(team != null ? team.getTeamName() : null) .role(teamMember.getRole()) - .accessToken(accessToken) // 추가 + .accessToken(accessToken) .refreshToken(refreshToken) .build(); @@ -104,7 +120,7 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto) { .body(response); } catch (BadCredentialsException e) { - log.error("❌ 로그인 실패: 자격 증명 오류", e); + return ResponseEntity.status(401).body(Map.of("message", "이메일 또는 비밀번호가 잘못되었습니다.")); } catch (RuntimeException e) { log.error("❌ 로그인 중 런타임 예외", e); diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java index 51d3ece..aeb1abd 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java @@ -22,17 +22,27 @@ public class InviteCodeController { @PostMapping("/create") public ResponseEntity createInviteCode( @AuthenticationPrincipal LoginUserDetails userDetails) { + System.out.println("초대코드 생성 요청 들어옴"); + System.out.println("userDetails: " + userDetails); + + if (userDetails != null) { + System.out.println("인증된 사용자 이메일: " + userDetails.getUsername()); + System.out.println("사용자 권한: " + userDetails.getAuthorities()); + } else { + System.out.println("userDetails가 null입니다. 인증 정보 없음."); + } User user = userDetails.getUser(); + System.out.print("코드 만듭니당"); // 초대코드 생성 서비스 호출 InviteCode inviteCode = inviteCodeService.createInviteCode(user); - + System.out.print("만들었어용"); InviteCodeCreateResponseDto responseDto = InviteCodeCreateResponseDto.builder() .code(inviteCode.getCode()) .expiresAt(inviteCode.getExpiresAt()) .build(); - + System.out.print("무슨 dto생성해용"); return ResponseEntity.ok(responseDto); } } diff --git a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java b/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java index 5e7cedf..3737ec4 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java +++ b/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java @@ -4,9 +4,12 @@ import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + import java.util.Collection; import java.util.Collections; +import java.util.List; @Getter public class LoginUserDetails implements UserDetails { @@ -20,7 +23,11 @@ public LoginUserDetails(User user) { // ✅ 기본 권한 비워둠 (나중에 ROLE_ 추가 가능) @Override public Collection getAuthorities() { - return Collections.emptyList(); + if (user.getRole() == null || user.getRole().isEmpty()) { + return Collections.emptyList(); + } + // "TeamLeader" -> "ROLE_TeamLeader" + return List.of(new SimpleGrantedAuthority(user.getRole())); } // ✅ 로그인 시 사용할 비밀번호 @@ -40,4 +47,5 @@ public String getUsername() { @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } + } diff --git a/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java index 313c30a..69b6ff5 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java @@ -1,77 +1,77 @@ -//package com.ccapp.ccgo.jwt; -// -//import lombok.RequiredArgsConstructor; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.security.authentication.AuthenticationManager; -//import org.springframework.security.authentication.AuthenticationProvider; -//import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -//import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -//import org.springframework.security.config.annotation.web.builders.HttpSecurity; -//import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -//import org.springframework.security.config.http.SessionCreationPolicy; -//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -//import org.springframework.security.web.SecurityFilterChain; -//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -//import org.springframework.web.cors.CorsConfiguration; -//import org.springframework.web.cors.CorsConfigurationSource; -//import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -//import java.util.List; -// -//@Configuration -//@RequiredArgsConstructor -//public class SecurityConfig { -// -// private final JwtAuthenticationFilter jwtAuthenticationFilter; -// private final LoginUserDetailsService loginUserDetailsService; -// -// // 🔐 비밀번호 인코더 등록 -// @Bean -// public BCryptPasswordEncoder passwordEncoder() { -// return new BCryptPasswordEncoder(); -// } -// -// // 🔐 로그인 시 사용할 인증 제공자 (UserDetailsService + PasswordEncoder) -// @Bean -// public AuthenticationProvider authenticationProvider() { -// DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); -// provider.setUserDetailsService(loginUserDetailsService); -// provider.setPasswordEncoder(passwordEncoder()); -// return provider; -// } -// -// // 🔐 인증 매니저 (로그인 인증 처리 시 필요) -// @Bean -// public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { -// return config.getAuthenticationManager(); -// } -// -// // 🔐 필터 체인 설정 -// @Bean -// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { -// return http -// .cors(cors -> cors.configurationSource(corsConfigurationSource())) -// .csrf(AbstractHttpConfigurer::disable) -// .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) -// .authorizeHttpRequests(auth -> auth -// .requestMatchers("/api/auth/login", "/register").permitAll() // 로그인, 회원가입 허용 -// .anyRequest().authenticated() // 그 외는 인증 필요 -// ) -// .authenticationProvider(authenticationProvider()) -// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) -// .build(); -// } -// //cors -// @Bean -// public CorsConfigurationSource corsConfigurationSource() { -// CorsConfiguration config = new CorsConfiguration(); -// config.setAllowedOriginPatterns(List.of("*")); // 변경된 부분 -// config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); -// config.setAllowedHeaders(List.of("*")); -// config.setAllowCredentials(true); -// -// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); -// source.registerCorsConfiguration("/**", config); -// return source; -// } -//} +package com.ccapp.ccgo.jwt; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final LoginUserDetailsService loginUserDetailsService; + + // 🔐 비밀번호 인코더 등록 + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + // 🔐 로그인 시 사용할 인증 제공자 (UserDetailsService + PasswordEncoder) + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(loginUserDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } + + // 🔐 인증 매니저 (로그인 인증 처리 시 필요) + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + // 🔐 필터 체인 설정 + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth/login", "/register").permitAll() // 로그인, 회원가입 허용 + .anyRequest().authenticated() // 그 외는 인증 필요 + ) + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } + //cors + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOriginPatterns(List.of("*")); // 변경된 부분 + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } +} diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java index acc94e9..937ca70 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java @@ -68,7 +68,7 @@ public void joinTeamByInviteCode(User user, String inviteCode) { public InviteCode createInviteCode(User user) { var teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) .orElseThrow(() -> new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST)); - if (!"TEAM_LEADER".equals(teamMember.getRole())) { + if (!"TeamLeader".equals(teamMember.getRole())) { throw new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN); } diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/TeamMember.java index 9d0e65d..a9c2948 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/TeamMember.java @@ -34,7 +34,7 @@ public class TeamMember { // FK - 팀 ID @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "team_id", nullable = false) + @JoinColumn(name = "team_id") private Team team; // FK - 유저 ID diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e3dc7df..61bed74 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = qkrwlsdn +spring.datasource.password = 06250730cc jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 90bbe465f56aabcdb1a16c6d5928ea70c0e6f7b5 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 3 Jul 2025 15:22:49 +0900 Subject: [PATCH 003/132] Seperate Role --- src/main/java/com/ccapp/ccgo/common/Role.java | 6 ++++++ .../com/ccapp/ccgo/config/SchedulingConfig.java | 9 +++++++++ .../com/ccapp/ccgo/controller/AuthController.java | 11 ++++++----- .../ccgo/controller/InviteCodeController.java | 2 ++ .../java/com/ccapp/ccgo/dto/LoginResponseDto.java | 2 ++ src/main/java/com/ccapp/ccgo/dto/UserMapper.java | 2 +- .../java/com/ccapp/ccgo/dto/UserRequestDto.java | 3 ++- .../java/com/ccapp/ccgo/jwt/LoginUserDetails.java | 6 +++--- .../ccgo/repository/InviteCodeRepository.java | 5 +++++ .../com/ccapp/ccgo/service/InviteCodeService.java | 15 ++++++++++++--- src/main/java/com/ccapp/ccgo/team/TeamMember.java | 4 +++- src/main/java/com/ccapp/ccgo/user/User.java | 14 ++++++++++---- 12 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/common/Role.java create mode 100644 src/main/java/com/ccapp/ccgo/config/SchedulingConfig.java diff --git a/src/main/java/com/ccapp/ccgo/common/Role.java b/src/main/java/com/ccapp/ccgo/common/Role.java new file mode 100644 index 0000000..01e4d85 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/common/Role.java @@ -0,0 +1,6 @@ +package com.ccapp.ccgo.common; + +public enum Role { + LEADER, + MEMBER +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/config/SchedulingConfig.java b/src/main/java/com/ccapp/ccgo/config/SchedulingConfig.java new file mode 100644 index 0000000..a39d8d7 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/config/SchedulingConfig.java @@ -0,0 +1,9 @@ +package com.ccapp.ccgo.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling // 💡 스케줄링 활성화! +public class SchedulingConfig { +} diff --git a/src/main/java/com/ccapp/ccgo/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/controller/AuthController.java index 965ccb5..51d93f7 100644 --- a/src/main/java/com/ccapp/ccgo/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/controller/AuthController.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.controller; +import com.ccapp.ccgo.common.Role; import com.ccapp.ccgo.dto.TokenResponseDto; import com.ccapp.ccgo.jwt.LoginUserDetailsService; import com.ccapp.ccgo.repository.TeamMemberRepository; @@ -65,7 +66,7 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) Optional existingTeamMember = teamMemberRepository.findByUserAndIsActiveTrue(user); if (existingTeamMember.isEmpty()) { - if (user.getRole().equals("TeamLeader")) { + if (user.getRole().equals(Role.LEADER)) { // 팀 생성 및 팀장 등록 Team team = new Team(); team.setTeamName(user.getName() + "의 팀"); @@ -76,18 +77,18 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) TeamMember teamMember = new TeamMember(); teamMember.setUser(user); teamMember.setTeam(team); - teamMember.setRole("TeamLeader"); + teamMember.setRole(Role.LEADER); teamMember.setActive(true); teamMember.setJoinedAt(LocalDateTime.now()); teamMemberRepository.save(teamMember); log.info("🆕 새 팀 생성 및 팀장 등록 완료"); - } else if (user.getRole().equals("TeamMember")) { + } else if (user.getRole().equals(Role.MEMBER)) { // 팀원 등록 (팀 없음) TeamMember teamMember = new TeamMember(); teamMember.setUser(user); teamMember.setTeam(null); // 나중에 초대코드로 팀이 지정될 예정 - teamMember.setRole("TeamMember"); + teamMember.setRole(Role.MEMBER); teamMember.setActive(true); teamMember.setJoinedAt(LocalDateTime.now()); teamMemberRepository.save(teamMember); @@ -110,7 +111,7 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) .name(user.getName()) .teamId(team != null ? team.getTeamId() : null) .teamName(team != null ? team.getTeamName() : null) - .role(teamMember.getRole()) + .role(teamMember.getRole().name()) .accessToken(accessToken) .refreshToken(refreshToken) .build(); diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java index aeb1abd..8006cd8 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java @@ -35,6 +35,8 @@ public ResponseEntity createInviteCode( User user = userDetails.getUser(); System.out.print("코드 만듭니당"); + System.out.println("ROLE: " + user.getRole()); + // 초대코드 생성 서비스 호출 InviteCode inviteCode = inviteCodeService.createInviteCode(user); System.out.print("만들었어용"); diff --git a/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java index 7eafe6b..65c6aa9 100644 --- a/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java @@ -17,4 +17,6 @@ public class LoginResponseDto { //이 정보가 필요할지는 고민해봐야한다. private Long teamId; private String teamName; + + private boolean isInterestCompleted; // 기본값 false } diff --git a/src/main/java/com/ccapp/ccgo/dto/UserMapper.java b/src/main/java/com/ccapp/ccgo/dto/UserMapper.java index b76cfd6..b31aa16 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserMapper.java +++ b/src/main/java/com/ccapp/ccgo/dto/UserMapper.java @@ -29,7 +29,7 @@ public static UserResponseDto toDto(User user) { .gender(user.getGender()) .birthdate(user.getBirthdate()) .createdAt(user.getCreatedAt()) - .role(user.getRole()) + .role(user.getRole().name()) .build(); } } diff --git a/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java index 8daccab..c85bda4 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.dto; +import com.ccapp.ccgo.common.Role; import lombok.Getter; import java.time.LocalDate; import jakarta.validation.constraints.*; @@ -16,5 +17,5 @@ public class UserRequestDto { private String name; private String gender; private LocalDate birthdate; - private String role; + private Role role; } diff --git a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java b/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java index 3737ec4..b384fdf 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java +++ b/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java @@ -23,11 +23,11 @@ public LoginUserDetails(User user) { // ✅ 기본 권한 비워둠 (나중에 ROLE_ 추가 가능) @Override public Collection getAuthorities() { - if (user.getRole() == null || user.getRole().isEmpty()) { + if (user.getRole() == null) { return Collections.emptyList(); } - // "TeamLeader" -> "ROLE_TeamLeader" - return List.of(new SimpleGrantedAuthority(user.getRole())); + String roleName = user.getRole().name(); // enum 이름 기반 권한 + return List.of(new SimpleGrantedAuthority(roleName)); } // ✅ 로그인 시 사용할 비밀번호 diff --git a/src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java b/src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java index f98c9d4..96d3f33 100644 --- a/src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java @@ -1,6 +1,7 @@ package com.ccapp.ccgo.repository; import com.ccapp.ccgo.team.InviteCode; +import com.ccapp.ccgo.team.Team; import org.springframework.data.jpa.repository.JpaRepository; import java.time.LocalDateTime; @@ -22,4 +23,8 @@ public interface InviteCodeRepository extends JpaRepository // 만료 코드 삭제 void deleteByExpiresAtBefore(LocalDateTime now); + + // 특정 팀의 기존 초대코드 삭제 + void deleteByTeam(Team team); + } diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java index 937ca70..2b0b1bd 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java @@ -1,11 +1,13 @@ package com.ccapp.ccgo.service; +import com.ccapp.ccgo.common.Role; import com.ccapp.ccgo.exception.CustomException; import com.ccapp.ccgo.repository.InviteCodeRepository; import com.ccapp.ccgo.repository.TeamMemberRepository; import com.ccapp.ccgo.team.InviteCode; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.team.TeamMember; +import org.springframework.scheduling.annotation.Scheduled; import com.ccapp.ccgo.user.User; import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional; @@ -55,7 +57,7 @@ public void joinTeamByInviteCode(User user, String inviteCode) { TeamMember newMember = TeamMember.builder() .user(user) .team(code.getTeam()) - .role("TEAM_MEMBER") + .role(Role.MEMBER) .isActive(true) .joinedAt(LocalDateTime.now()) .build(); @@ -68,11 +70,13 @@ public void joinTeamByInviteCode(User user, String inviteCode) { public InviteCode createInviteCode(User user) { var teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) .orElseThrow(() -> new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST)); - if (!"TeamLeader".equals(teamMember.getRole())) { + if (teamMember.getRole() != Role.LEADER) { throw new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN); } Team team = teamMember.getTeam(); + //기존 코드 삭제 + inviteCodeRepository.deleteByTeam(team); String code; do { @@ -87,5 +91,10 @@ public InviteCode createInviteCode(User user) { return inviteCodeRepository.save(inviteCode); } - // + //현재 시각보다 이전인 초대코드를 삭제 + @Scheduled(fixedRate = 60 * 60 * 1000) // 1시간마다 실행 (ms 단위) + @Transactional + public void deleteExpiredInviteCodes() { + inviteCodeRepository.deleteByExpiresAtBefore(LocalDateTime.now()); + } } diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/TeamMember.java index a9c2948..3b9ca19 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/TeamMember.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.team; +import com.ccapp.ccgo.common.Role; import com.ccapp.ccgo.user.User; import jakarta.persistence.*; import lombok.*; @@ -43,8 +44,9 @@ public class TeamMember { private User user; // 팀 내 역할 ("TEAM_LEADER" or "TEAM_MEMBER") + @Enumerated(EnumType.STRING) @Column(nullable = false) - private String role; + private Role role; // 팀 가입 일시 private LocalDateTime joinedAt; diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index 48cccf0..78c7fc8 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -1,9 +1,10 @@ package com.ccapp.ccgo.user; +import com.ccapp.ccgo.common.Role; import jakarta.persistence.*; - import lombok.*; +import lombok.*; - import java.time.LocalDate; +import java.time.LocalDate; import java.time.LocalDateTime; /** @@ -48,15 +49,20 @@ public class User { @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; - //role + //팀내 역할 (팀장/팀원) + @Enumerated(EnumType.STRING) @Column(nullable = false) - private String role; + private Role role; // 회원 가입 시 자동으로 현재 시간 설정 @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); } + + //관심사 설문조사 완료여부 + @Column(name = "is_interest_completed", nullable = false) + private boolean isInterestCompleted = false; } From a23893cbffe024641366187c3ff6d3b7fd1486ee Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 3 Jul 2025 19:02:45 +0900 Subject: [PATCH 004/132] fix teaminvitecode --- .../ccgo/controller/InviteCodeController.java | 74 +++++++++++++++++++ .../ccgo/dto/InviteCodeJoinRequestDto.java | 5 ++ .../ccgo/dto/InviteCodeJoinResponseDto.java | 11 ++- .../com/ccapp/ccgo/dto/TeamRequestDto.java | 8 +- .../com/ccapp/ccgo/dto/TeamResponseDto.java | 7 +- .../ccgo/repository/TeamMemberRepository.java | 5 ++ .../ccapp/ccgo/service/InviteCodeService.java | 20 +++++ 7 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java index 8006cd8..89869e5 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java @@ -1,23 +1,37 @@ package com.ccapp.ccgo.controller; +import com.ccapp.ccgo.common.Role; import com.ccapp.ccgo.dto.InviteCodeCreateResponseDto; +import com.ccapp.ccgo.dto.InviteCodeJoinRequestDto; +import com.ccapp.ccgo.dto.InviteCodeJoinResponseDto; +import com.ccapp.ccgo.dto.TeamRequestDto; +import com.ccapp.ccgo.repository.InviteCodeRepository; +import com.ccapp.ccgo.repository.TeamMemberRepository; +import com.ccapp.ccgo.repository.UserRepository; import com.ccapp.ccgo.service.InviteCodeService; import com.ccapp.ccgo.team.InviteCode; +import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.TeamMember; import com.ccapp.ccgo.user.User; import com.ccapp.ccgo.jwt.LoginUserDetails; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; + @RestController @RequestMapping("/api/invitecode") @RequiredArgsConstructor public class InviteCodeController { private final InviteCodeService inviteCodeService; + private final InviteCodeRepository inviteCodeRepository; + private final TeamMemberRepository teamMemberRepository; @PostMapping("/create") public ResponseEntity createInviteCode( @@ -47,4 +61,64 @@ public ResponseEntity createInviteCode( System.out.print("무슨 dto생성해용"); return ResponseEntity.ok(responseDto); } + + + //시작하기를 누르면 팀 이름을 db에 저장 + @PostMapping("/teamname") + public ResponseEntity saveTeamName( + @AuthenticationPrincipal LoginUserDetails userDetails, + @RequestBody TeamRequestDto requestDto) { + if (userDetails == null) { + return ResponseEntity.status(401).build(); // 인증 정보 없으면 401 반환 + } + User user = userDetails.getUser(); + String teamName = requestDto.getTeamName(); + // 팀 이름 저장 서비스 호출 + inviteCodeService.saveTeamName(user, teamName); + return ResponseEntity.ok().build(); + } + + //팀원이 코드를 보냈으면 처리 + @PostMapping("/join") + public ResponseEntity joinByInviteCode( + @RequestBody InviteCodeJoinRequestDto requestDto, + @AuthenticationPrincipal LoginUserDetails userDetails) { + + if (userDetails == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("인증 정보가 없습니다."); + } + + String code = requestDto.getInviteCode(); + User user = userDetails.getUser(); + + // 유효한 초대 코드인지 확인 + InviteCode inviteCode = inviteCodeRepository + .findByCodeAndExpiresAtAfter(code, LocalDateTime.now()) + .orElseThrow(() -> new RuntimeException("초대 코드가 없거나 만료되었습니다.")); + + Team team = inviteCode.getTeam(); + if (team == null) { + throw new RuntimeException("초대코드에 연결된 팀이 없습니다."); + } + + // 이미 등록된 TeamMember 가져오기 + TeamMember teamMember = teamMemberRepository.findByUser(user) + .orElseThrow(() -> new RuntimeException("TeamMember 정보가 없습니다.")); + + // 이미 팀이 설정되어 있다면 중복 가입 방지 + if (teamMember.getTeam() != null) { + return ResponseEntity.badRequest().body("이미 다른 팀에 가입되어 있습니다."); + } + + // 팀 할당 및 기타 정보 설정 + teamMember.setTeam(team); + teamMember.setRole(Role.MEMBER); // 팀원으로 설정 + teamMember.setJoinedAt(LocalDateTime.now()); + teamMember.setActive(true); + teamMemberRepository.save(teamMember); // 업데이트 저장 + + return ResponseEntity.ok(new InviteCodeJoinResponseDto(team.getTeamName())); + } + + } diff --git a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java index 22093a5..1cf527c 100644 --- a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java @@ -1,5 +1,10 @@ package com.ccapp.ccgo.dto; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor public class InviteCodeJoinRequestDto { private String inviteCode; } diff --git a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java index 92d8c3a..0c17413 100644 --- a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java @@ -1,7 +1,14 @@ package com.ccapp.ccgo.dto; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class InviteCodeJoinResponseDto { - private Long teamId; private String teamName; - private String role; // TEAM_MEMBER } diff --git a/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java index fa58a33..71cc0e9 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java @@ -1,6 +1,12 @@ package com.ccapp.ccgo.dto; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor public class TeamRequestDto { private String teamName; } - diff --git a/src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java index 9d54809..8063ae4 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java @@ -1,6 +1,11 @@ package com.ccapp.ccgo.dto; +import lombok.Getter; +import lombok.AllArgsConstructor; + +@Getter +@AllArgsConstructor public class TeamResponseDto { private Long teamId; private String teamName; -} \ No newline at end of file +} diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java index 4cae0e3..9a8309c 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java @@ -19,7 +19,12 @@ public interface TeamMemberRepository extends JpaRepository { // 현재 소속 중인 팀 찾기 (Soft Delete 고려) Optional findByUserAndIsActiveTrue(User user); + //teammember에서 유저 조회 + Optional findByUser(User user); // 팀별 멤버 목록 List findAllByTeamAndIsActiveTrue(Team team); + + // 이미 특정 유저가 특정 팀 인지 확인 + boolean existsByUserAndTeam(User user, Team team); } diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java index 2b0b1bd..25a1e3a 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java @@ -4,9 +4,11 @@ import com.ccapp.ccgo.exception.CustomException; import com.ccapp.ccgo.repository.InviteCodeRepository; import com.ccapp.ccgo.repository.TeamMemberRepository; +import com.ccapp.ccgo.repository.TeamRepository; import com.ccapp.ccgo.team.InviteCode; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.team.TeamMember; +import lombok.NonNull; import org.springframework.scheduling.annotation.Scheduled; import com.ccapp.ccgo.user.User; import org.springframework.http.HttpStatus; @@ -23,6 +25,8 @@ public class InviteCodeService { private final InviteCodeRepository inviteCodeRepository; private final TeamMemberRepository teamMemberRepository; + private final TeamRepository teamRepository; // 팀 저장소 추가 + private static final String CODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private static final int CODE_LENGTH = 8; @@ -97,4 +101,20 @@ public InviteCode createInviteCode(User user) { public void deleteExpiredInviteCodes() { inviteCodeRepository.deleteByExpiresAtBefore(LocalDateTime.now()); } + + @Transactional + public void saveTeamName(@NonNull User user, @NonNull String teamName) { + // 사용자 팀 멤버 조회 + TeamMember teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) + .orElseThrow(() -> new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST)); + + // 팀장만 팀 이름 변경 가능 (원한다면 이 조건 제거 가능) + if (teamMember.getRole() != Role.LEADER) { + throw new CustomException("팀장만 팀 이름을 변경할 수 있습니다.", HttpStatus.FORBIDDEN); + } + + Team team = teamMember.getTeam(); + team.setTeamName(teamName); // 팀 이름 변경 + teamRepository.save(team); + } } From 28013dd49ba05cf1f446a87329c861a8db616257 Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 4 Jul 2025 10:24:39 +0900 Subject: [PATCH 005/132] refactoring --- .gitignore | 2 +- src/main/java/com/ccapp/ccgo/controller/AuthController.java | 2 ++ src/main/java/com/ccapp/ccgo/service/InviteCodeService.java | 1 + src/main/java/com/ccapp/ccgo/team/TeamMember.java | 3 +++ src/main/java/com/ccapp/ccgo/user/User.java | 2 +- src/main/resources/application.properties | 2 +- 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6cd1517..4143e02 100644 --- a/.gitignore +++ b/.gitignore @@ -203,6 +203,6 @@ application-*.properties /bin/ # resources -/src/main/resources/application.properties +src/main/resources/application.properties # End of https://www.toptal.com/developers/gitignore/api/windows,intellij,java,gradle \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/controller/AuthController.java index 51d93f7..e1adda8 100644 --- a/src/main/java/com/ccapp/ccgo/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/controller/AuthController.java @@ -80,6 +80,7 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) teamMember.setRole(Role.LEADER); teamMember.setActive(true); teamMember.setJoinedAt(LocalDateTime.now()); + teamMember.setUserName(user.getName()); teamMemberRepository.save(teamMember); log.info("🆕 새 팀 생성 및 팀장 등록 완료"); @@ -91,6 +92,7 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) teamMember.setRole(Role.MEMBER); teamMember.setActive(true); teamMember.setJoinedAt(LocalDateTime.now()); + teamMember.setUserName(user.getName()); teamMemberRepository.save(teamMember); log.info("🆕 팀원 등록 완료 (팀 없음)"); diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java index 2b0b1bd..db11918 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java @@ -60,6 +60,7 @@ public void joinTeamByInviteCode(User user, String inviteCode) { .role(Role.MEMBER) .isActive(true) .joinedAt(LocalDateTime.now()) + .userName(user.getName()) .build(); teamMemberRepository.save(newMember); diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/TeamMember.java index 3b9ca19..8f30687 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/TeamMember.java @@ -43,6 +43,9 @@ public class TeamMember { @JoinColumn(name = "user_id", nullable = false) private User user; + @Column(name = "user_name", nullable = false) + private String userName; + // 팀 내 역할 ("TEAM_LEADER" or "TEAM_MEMBER") @Enumerated(EnumType.STRING) @Column(nullable = false) diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index 78c7fc8..34c99f6 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -61,7 +61,7 @@ public void prePersist() { } //관심사 설문조사 완료여부 - @Column(name = "is_interest_completed", nullable = false) + @Column(name = "test_completed", nullable = false) private boolean isInterestCompleted = false; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 61bed74..e3dc7df 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = 06250730cc +spring.datasource.password = qkrwlsdn jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 9d0a34030ddcfa57cc0c8450751d2b1dbedfcf9c Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 4 Jul 2025 13:38:28 +0900 Subject: [PATCH 006/132] teaminvite work! --- .../java/com/ccapp/ccgo/controller/InviteCodeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java index 89869e5..aa14058 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java @@ -112,7 +112,7 @@ public ResponseEntity joinByInviteCode( // 팀 할당 및 기타 정보 설정 teamMember.setTeam(team); - teamMember.setRole(Role.MEMBER); // 팀원으로 설정 + teamMember.setRole(user.getRole()); // 유저의 역할로 설정 teamMember.setJoinedAt(LocalDateTime.now()); teamMember.setActive(true); teamMemberRepository.save(teamMember); // 업데이트 저장 From e8287fbe61a01ac2f22e4b970ca5cb34cce478ec Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 4 Jul 2025 14:44:08 +0900 Subject: [PATCH 007/132] ok --- .idea/sqldialects.xml | 8 +++ build.gradle | 1 + src/main/resources/MBTIdata.sql | 80 +++++++++++++++++++++++ src/main/resources/MBTIschema.sql | 6 ++ src/main/resources/application.properties | 5 +- 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .idea/sqldialects.xml create mode 100644 src/main/resources/MBTIdata.sql create mode 100644 src/main/resources/MBTIschema.sql diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..d21819a --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index ff67e6e..ff1237a 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'com.h2database:h2' // DB 정보 저장용 디펜던시 } diff --git a/src/main/resources/MBTIdata.sql b/src/main/resources/MBTIdata.sql new file mode 100644 index 0000000..6c393f5 --- /dev/null +++ b/src/main/resources/MBTIdata.sql @@ -0,0 +1,80 @@ +INSERT INTO mbti_score (from_mbti, to_mbti, score) VALUES + ('ENTJ','ISFP',16),('ENTJ','INFP',15),('ENTJ','ESFP',14),('ENTJ','ESTP',13), + ('ENTJ','ISTP',12),('ENTJ','INTP',11),('ENTJ','ENFP',10),('ENTJ','INFJ',9), + ('ENTJ','INTJ',8),('ENTJ','ENFJ',7),('ENTJ','ISTJ',6),('ENTJ','ENTP',5), + ('ENTJ','ESTJ',4),('ENTJ','ENTJ',3),('ENTJ','ESFJ',2),('ENTJ','ISFJ',1), + + ('ENTP','ISFJ',16),('ENTP','ISTJ',15),('ENTP','ENTP',14),('ENTP','ESTJ',13), + ('ENTP','ESFJ',12),('ENTP','INFJ',11),('ENTP','INTJ',10),('ENTP','INFP',9), + ('ENTP','ENFJ',8),('ENTP','INTP',7),('ENTP','ISTP',6),('ENTP','ENFP',5), + ('ENTP','ESTP',4),('ENTP','ENTJ',3),('ENTP','ESFP',2),('ENTP','ISFP',1), + + ('INTJ','ESFP',16),('INTJ','ESTP',15),('INTJ','ISFP',14),('INTJ','INFP',13), + ('INTJ','INFJ',12),('INTJ','ENFP',11),('INTJ','ENTP',10),('INTJ','ISTP',9), + ('INTJ','ENFJ',8),('INTJ','INTJ',7),('INTJ','ISTJ',6),('INTJ','ENTJ',5), + ('INTJ','INTP',4),('INTJ','ESTJ',3),('INTJ','ISFJ',2),('INTJ','ESFJ',1), + + ('INTP','ESFJ',16),('INTP','ENFJ',15),('INTP','ISFJ',14),('INTP','INFJ',13), + ('INTP','ESTJ',12),('INTP','ISTJ',11),('INTP','ENTJ',10),('INTP','ENFP',9), + ('INTP','ENTP',8),('INTP','INTP',7),('INTP','INTJ',6),('INTP','ISTP',5), + ('INTP','INFP',4),('INTP','ESTP',3),('INTP','ISFP',2),('INTP','ESFP',1), + + ('ESTJ','INFP',16),('ESTJ','ISFP',15),('ESTJ','INTP',14),('ESTJ','ENTP',13), + ('ESTJ','ISTP',12),('ESTJ','ESFP',11),('ESTJ','ENFP',10),('ESTJ','ISTJ',9), + ('ESTJ','ISFJ',8),('ESTJ','ESTJ',7),('ESTJ','ESFJ',6),('ESTJ','INTJ',5), + ('ESTJ','ENTJ',4),('ESTJ','ESTP',3),('ESTJ','ENFJ',2),('ESTJ','INFJ',1), + + ('ESFJ','INTP',16),('ESFJ','ISTP',15),('ESFJ','ENTP',14),('ESFJ','ENFP',13), + ('ESFJ','INFP',12),('ESFJ','ISTJ',11),('ESFJ','ESFJ',10),('ESFJ','ESTP',9), + ('ESFJ','ISFP',8),('ESFJ','ENFJ',7),('ESFJ','ISFJ',6),('ESFJ','INFJ',5), + ('ESFJ','ESTJ',4),('ESFJ','ESFP',3),('ESFJ','ENTJ',2),('ESFJ','INTJ',1), + + ('ISTJ','ENFP',16),('ISTJ','ENTP',15),('ISTJ','ISFP',14),('ISTJ','INFP',13), + ('ISTJ','ESTP',12),('ISTJ','ESFP',11),('ISTJ','INTP',10),('ISTJ','ESTJ',9), + ('ISTJ','ESFJ',8),('ISTJ','ISTJ',7),('ISTJ','INTJ',6),('ISTJ','ISFJ',5), + ('ISTJ','ISTP',4),('ISTJ','ENTJ',3),('ISTJ','INFJ',2),('ISTJ','ENFJ',1), + + ('ISFJ','ENTP',16),('ISFJ','ENFP',15),('ISFJ','INTP',14),('ISFJ','ISTP',13), + ('ISFJ','ESFP',12),('ISFJ','ESTP',11),('ISFJ','ESTJ',10),('ISFJ','INFP',9), + ('ISFJ','ESFJ',8),('ISFJ','ISTJ',7),('ISFJ','ISFJ',6),('ISFJ','ENFJ',5), + ('ISFJ','INFJ',4),('ISFJ','ISFP',3),('ISFJ','INTJ',2),('ISFJ','ENTJ',1), + + ('ENFJ','ISTP',16),('ENFJ','INTP',15),('ENFJ','ESTP',14),('ENFJ','ESFP',13), + ('ENFJ','ENFJ',12),('ENFJ','INFP',11),('ENFJ','ISFP',10),('ENFJ','ENTP',9), + ('ENFJ','INTJ',8),('ENFJ','ESFJ',7),('ENFJ','INFJ',6),('ENFJ','ENFP',5), + ('ENFJ','ENTJ',4),('ENFJ','ISFJ',3),('ENFJ','ESTJ',2),('ENFJ','ISTJ',1), + + ('ENFP','ISTJ',16),('ENFP','ISFJ',15),('ENFP','ESFJ',14),('ENFP','ESTJ',13), + ('ENFP','INFJ',12),('ENFP','INTJ',11),('ENFP','ENTJ',10),('ENFP','ISFP',9), + ('ENFP','ENFP',8),('ENFP','INTP',7),('ENFP','INFP',6),('ENFP','ENFJ',5), + ('ENFP','ENTP',4),('ENFP','ESFP',3),('ENFP','ESTP',2),('ENFP','ISTP',1), + + ('INFJ','ESTP',16),('INFJ','ESFP',15),('INFJ','ISTP',14),('INFJ','INTP',13), + ('INFJ','ENFP',12),('INFJ','ENTP',11),('INFJ','INTJ',10),('INFJ','ENTJ',9), + ('INFJ','INFJ',8),('INFJ','ISFP',7),('INFJ','ENFJ',6),('INFJ','ESFJ',5), + ('INFJ','ISFJ',4),('INFJ','INFP',3),('INFJ','ISTJ',2),('INFJ','ESTJ',1), + + ('INFP','ESTJ',16),('INFP','ENTJ',15),('INFP','INTJ',14),('INFP','ISTJ',13), + ('INFP','ENFJ',12),('INFP','ESFJ',11),('INFP','ENTP',10),('INFP','INFP',9), + ('INFP','ISFJ',8),('INFP','INTP',7),('INFP','ESFP',6),('INFP','ENFP',5), + ('INFP','ISFP',4),('INFP','INFJ',3),('INFP','ISTP',2),('INFP','ESTP',1), + + ('ESTP','INFJ',16),('ESTP','INTJ',15),('ESTP','ENFJ',14),('ESTP','ENTJ',13), + ('ESTP','ISFJ',12),('ESTP','ISTP',11),('ESTP','ISTJ',10),('ESTP','ESFJ',9), + ('ESTP','ESTP',8),('ESTP','ISFP',7),('ESTP','ESFP',6),('ESTP','INTP',5), + ('ESTP','ENTP',4),('ESTP','ESTJ',3),('ESTP','ENFP',2),('ESTP','INFP',1), + + ('ESFP','INTJ',16),('ESFP','INFJ',15),('ESFP','ENTJ',14),('ESFP','ENFJ',13), + ('ESFP','ESTJ',12),('ESFP','ISTJ',11),('ESFP','ISFJ',10),('ESFP','ISFP',9), + ('ESFP','ISTP',8),('ESFP','INFP',7),('ESFP','ESFP',6),('ESFP','ESTP',5), + ('ESFP','ESFJ',4),('ESFP','ENFP',3),('ESFP','ENTP',2),('ESFP','INTP',1), + + ('ISTP','ENFJ',16),('ISTP','ESFJ',15),('ISTP','INFJ',14),('ISTP','ISFJ',13), + ('ISTP','ENTJ',12),('ISTP','ESTJ',11),('ISTP','ESFP',10),('ISTP','ESTP',9), + ('ISTP','INTJ',8),('ISTP','ISTP',7),('ISTP','INTP',6),('ISTP','ENTP',5), + ('ISTP','ISTJ',4),('ISTP','ISFP',3),('ISTP','INFP',2),('ISTP','ENFP',1), + + ('ISFP','ENTJ',16),('ISFP','ESTJ',15),('ISFP','INTJ',14),('ISFP','ISTJ',13), + ('ISFP','ENFJ',12),('ISFP','ESFJ',11),('ISFP','INFJ',10),('ISFP','ESFP',9), + ('ISFP','ISFP',8),('ISFP','ESTP',7),('ISFP','ENFP',6),('ISFP','INFP',5), + ('ISFP','ISTP',4),('ISFP','ISFJ',3),('ISFP','INTP',2),('ISFP','ENTP',1); diff --git a/src/main/resources/MBTIschema.sql b/src/main/resources/MBTIschema.sql new file mode 100644 index 0000000..ac9f504 --- /dev/null +++ b/src/main/resources/MBTIschema.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS mbti_score ( + from_mbti VARCHAR(4) NOT NULL, + to_mbti VARCHAR(4) NOT NULL, + score INT NOT NULL, + PRIMARY KEY (from_mbti, to_mbti) +); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e3dc7df..22f7f7e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,4 +17,7 @@ spring.jpa.hibernate.ddl-auto=update #spring.jpa.show-sql=true #spring.jpa.properties.hibernate.format_sql=true -spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect \ No newline at end of file +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect + +#resources ?? ?? sql ? ?? +spring.sql.init.mode=always \ No newline at end of file From a0ba1077fc433e0bcfd8c7c5319454cf351a9f8e Mon Sep 17 00:00:00 2001 From: bendy41 Date: Sat, 5 Jul 2025 22:08:44 +0900 Subject: [PATCH 008/132] fix db_structure --- .../ccapp/ccgo/controller/AuthController.java | 60 +++++------------ .../ccgo/controller/InviteCodeController.java | 66 ++++++++++++++----- .../com/ccapp/ccgo/dto/LoginResponseDto.java | 18 +++-- .../com/ccapp/ccgo/dto/TeamRequestDto.java | 8 +-- .../java/com/ccapp/ccgo/dto/UserMapper.java | 2 - .../com/ccapp/ccgo/dto/UserRequestDto.java | 1 - .../com/ccapp/ccgo/dto/UserResponseDto.java | 1 - .../com/ccapp/ccgo/jwt/LoginUserDetails.java | 6 +- .../ccgo/repository/TeamMemberRepository.java | 3 + .../ccapp/ccgo/repository/TeamRepository.java | 7 +- .../ccapp/ccgo/service/InviteCodeService.java | 13 ++-- .../com/ccapp/ccgo/service/UserService.java | 1 - src/main/java/com/ccapp/ccgo/team/Team.java | 12 ++-- .../java/com/ccapp/ccgo/team/TeamMember.java | 7 +- src/main/java/com/ccapp/ccgo/user/User.java | 8 --- 15 files changed, 107 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/controller/AuthController.java index 51d93f7..d08f181 100644 --- a/src/main/java/com/ccapp/ccgo/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/controller/AuthController.java @@ -27,6 +27,7 @@ import java.time.LocalDateTime; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -54,7 +55,6 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) requestDto.getEmail(), requestDto.getPassword() ) ); - log.info("✅ 인증 성공: {}", authentication.getName()); String accessToken = jwtProvider.createAccessToken(authentication); String refreshToken = jwtProvider.createRefreshToken(authentication); @@ -62,56 +62,27 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) User user = userDetails.getUser(); log.info("🔍 로그인한 사용자: {}", user.getEmail()); - // ✅ 팀이 없으면 팀 생성 + 팀장 등록 - Optional existingTeamMember = teamMemberRepository.findByUserAndIsActiveTrue(user); - - if (existingTeamMember.isEmpty()) { - if (user.getRole().equals(Role.LEADER)) { - // 팀 생성 및 팀장 등록 - Team team = new Team(); - team.setTeamName(user.getName() + "의 팀"); - team.setCreatedAt(LocalDateTime.now()); - team.setCreatedBy(user.getId()); - teamRepository.save(team); - - TeamMember teamMember = new TeamMember(); - teamMember.setUser(user); - teamMember.setTeam(team); - teamMember.setRole(Role.LEADER); - teamMember.setActive(true); - teamMember.setJoinedAt(LocalDateTime.now()); - teamMemberRepository.save(teamMember); - - log.info("🆕 새 팀 생성 및 팀장 등록 완료"); - } else if (user.getRole().equals(Role.MEMBER)) { - // 팀원 등록 (팀 없음) - TeamMember teamMember = new TeamMember(); - teamMember.setUser(user); - teamMember.setTeam(null); // 나중에 초대코드로 팀이 지정될 예정 - teamMember.setRole(Role.MEMBER); - teamMember.setActive(true); - teamMember.setJoinedAt(LocalDateTime.now()); - teamMemberRepository.save(teamMember); - - log.info("🆕 팀원 등록 완료 (팀 없음)"); - } - } - - // ✅ 다시 조회 (혹은 Optional.get으로 바로 사용 가능) - TeamMember teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) - .orElseThrow(() -> new RuntimeException("TeamMember 레코드가 없습니다.")); + // 유저가 활성화된 팀멤버 목록 조회 + List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); - HttpHeaders headers = createTokenCookies(accessToken, refreshToken); - Team team = teamMember.getTeam(); + // 팀멤버 정보를 LoginResponseDto.TeamInfo 리스트로 변환 + List teams = teamMembers.stream() + .map(tm -> LoginResponseDto.TeamInfo.builder() + .teamId(tm.getTeam().getTeamId()) + .teamName(tm.getTeam().getTeamName()) + .role(tm.getRole().name()) + .isSurveyCompleted(tm.isSurveyCompleted()) // 팀별 설문 완료 여부 + .build()) + .toList(); + + HttpHeaders headers = createTokenCookies(accessToken, refreshToken); LoginResponseDto response = LoginResponseDto.builder() .userId(user.getId()) .email(user.getEmail()) .name(user.getName()) - .teamId(team != null ? team.getTeamId() : null) - .teamName(team != null ? team.getTeamName() : null) - .role(teamMember.getRole().name()) + .teams(teams) .accessToken(accessToken) .refreshToken(refreshToken) .build(); @@ -121,7 +92,6 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) .body(response); } catch (BadCredentialsException e) { - return ResponseEntity.status(401).body(Map.of("message", "이메일 또는 비밀번호가 잘못되었습니다.")); } catch (RuntimeException e) { log.error("❌ 로그인 중 런타임 예외", e); diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java index aa14058..33cfe53 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java @@ -5,8 +5,10 @@ import com.ccapp.ccgo.dto.InviteCodeJoinRequestDto; import com.ccapp.ccgo.dto.InviteCodeJoinResponseDto; import com.ccapp.ccgo.dto.TeamRequestDto; +import com.ccapp.ccgo.exception.CustomException; import com.ccapp.ccgo.repository.InviteCodeRepository; import com.ccapp.ccgo.repository.TeamMemberRepository; +import com.ccapp.ccgo.repository.TeamRepository; import com.ccapp.ccgo.repository.UserRepository; import com.ccapp.ccgo.service.InviteCodeService; import com.ccapp.ccgo.team.InviteCode; @@ -23,6 +25,7 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; +import java.util.List; @RestController @RequestMapping("/api/invitecode") @@ -32,7 +35,9 @@ public class InviteCodeController { private final InviteCodeService inviteCodeService; private final InviteCodeRepository inviteCodeRepository; private final TeamMemberRepository teamMemberRepository; + private final TeamRepository teamRepository; + //코드 만드는 부분 @PostMapping("/create") public ResponseEntity createInviteCode( @AuthenticationPrincipal LoginUserDetails userDetails) { @@ -49,10 +54,19 @@ public ResponseEntity createInviteCode( User user = userDetails.getUser(); System.out.print("코드 만듭니당"); - System.out.println("ROLE: " + user.getRole()); + + List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); + + TeamMember teamMember = teamMembers.stream() + .filter(tm -> tm.getRole() == Role.LEADER) + .findFirst() + .orElseThrow(() -> new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN)); + + System.out.println("여까진 됌 "); // 초대코드 생성 서비스 호출 InviteCode inviteCode = inviteCodeService.createInviteCode(user); + System.out.print("만들었어용"); InviteCodeCreateResponseDto responseDto = InviteCodeCreateResponseDto.builder() .code(inviteCode.getCode()) @@ -63,7 +77,7 @@ public ResponseEntity createInviteCode( } - //시작하기를 누르면 팀 이름을 db에 저장 + //팀 생성하기를 누르면 팀이 만들어집니당 @PostMapping("/teamname") public ResponseEntity saveTeamName( @AuthenticationPrincipal LoginUserDetails userDetails, @@ -73,8 +87,26 @@ public ResponseEntity saveTeamName( } User user = userDetails.getUser(); String teamName = requestDto.getTeamName(); - // 팀 이름 저장 서비스 호출 - inviteCodeService.saveTeamName(user, teamName); + + //팀 생성 코드 + // 팀 엔티티 생성 및 저장 + Team team = Team.builder() + .teamName(teamName) + .createdBy(user.getId()) // 팀장 ID + .createdAt(LocalDateTime.now()) + .build(); + teamRepository.save(team); + + // 팀장도 팀원으로 자동 등록 + TeamMember teamMember = TeamMember.builder() + .team(team) + .user(user) + .joinedAt(LocalDateTime.now()) + .isActive(true) + .role(Role.LEADER) // 팀장 역할 + .build(); + teamMemberRepository.save(teamMember); + return ResponseEntity.ok().build(); } @@ -101,21 +133,21 @@ public ResponseEntity joinByInviteCode( throw new RuntimeException("초대코드에 연결된 팀이 없습니다."); } - // 이미 등록된 TeamMember 가져오기 - TeamMember teamMember = teamMemberRepository.findByUser(user) - .orElseThrow(() -> new RuntimeException("TeamMember 정보가 없습니다.")); - - // 이미 팀이 설정되어 있다면 중복 가입 방지 - if (teamMember.getTeam() != null) { - return ResponseEntity.badRequest().body("이미 다른 팀에 가입되어 있습니다."); + boolean alreadyMember = teamMemberRepository.existsByUserAndTeam(user, team); + if (alreadyMember) { + return ResponseEntity.badRequest().body("이미 이 팀에 가입되어 있습니다."); } - // 팀 할당 및 기타 정보 설정 - teamMember.setTeam(team); - teamMember.setRole(user.getRole()); // 유저의 역할로 설정 - teamMember.setJoinedAt(LocalDateTime.now()); - teamMember.setActive(true); - teamMemberRepository.save(teamMember); // 업데이트 저장 + // 새 TeamMember 생성 및 저장 + TeamMember newMember = TeamMember.builder() + .user(user) + .team(team) + .joinedAt(LocalDateTime.now()) + .isActive(true) + .role(Role.MEMBER) // 기본 역할로 MEMBER 지정, 필요하면 변경 + .build(); + teamMemberRepository.save(newMember); + return ResponseEntity.ok(new InviteCodeJoinResponseDto(team.getTeamName())); } diff --git a/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java index 65c6aa9..8bba79c 100644 --- a/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java @@ -1,8 +1,11 @@ package com.ccapp.ccgo.dto; +import jakarta.persistence.Column; import lombok.Builder; import lombok.Getter; +import java.util.List; + @Getter @Builder public class LoginResponseDto { @@ -12,11 +15,16 @@ public class LoginResponseDto { private Long userId; private String email; private String name; - private String role; - //이 정보가 필요할지는 고민해봐야한다. - private Long teamId; - private String teamName; + private List teams; + + @Getter + @Builder + public static class TeamInfo { + private Long teamId; + private String teamName; + private String role; // 팀 내 역할 (예: "LEADER", "MEMBER") + private boolean isSurveyCompleted; + } - private boolean isInterestCompleted; // 기본값 false } diff --git a/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java index 71cc0e9..9477da0 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java @@ -1,12 +1,10 @@ package com.ccapp.ccgo.dto; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; +import lombok.*; @Getter -@NoArgsConstructor -@AllArgsConstructor +@Setter +@Builder public class TeamRequestDto { private String teamName; } diff --git a/src/main/java/com/ccapp/ccgo/dto/UserMapper.java b/src/main/java/com/ccapp/ccgo/dto/UserMapper.java index b31aa16..f4dbd1d 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserMapper.java +++ b/src/main/java/com/ccapp/ccgo/dto/UserMapper.java @@ -13,7 +13,6 @@ public static User toEntity(UserRequestDto dto, String encodedPassword) { .password(encodedPassword) .name(dto.getName()) .gender(dto.getGender()) - .role(dto.getRole()) .birthdate(dto.getBirthdate()) .build(); } @@ -29,7 +28,6 @@ public static UserResponseDto toDto(User user) { .gender(user.getGender()) .birthdate(user.getBirthdate()) .createdAt(user.getCreatedAt()) - .role(user.getRole().name()) .build(); } } diff --git a/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java index c85bda4..de0d7fb 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java @@ -17,5 +17,4 @@ public class UserRequestDto { private String name; private String gender; private LocalDate birthdate; - private Role role; } diff --git a/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java index 41d8190..c3fe00b 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java @@ -14,6 +14,5 @@ public class UserResponseDto { private String gender; private LocalDate birthdate; private LocalDateTime createdAt; - private String role; } diff --git a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java b/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java index b384fdf..81db6c0 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java +++ b/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java @@ -23,11 +23,7 @@ public LoginUserDetails(User user) { // ✅ 기본 권한 비워둠 (나중에 ROLE_ 추가 가능) @Override public Collection getAuthorities() { - if (user.getRole() == null) { - return Collections.emptyList(); - } - String roleName = user.getRole().name(); // enum 이름 기반 권한 - return List.of(new SimpleGrantedAuthority(roleName)); + return Collections.emptyList(); } // ✅ 로그인 시 사용할 비밀번호 diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java index 9a8309c..e7516d2 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java @@ -22,6 +22,9 @@ public interface TeamMemberRepository extends JpaRepository { //teammember에서 유저 조회 Optional findByUser(User user); + // 유저(user)에 대해 isActive가 true인 TeamMember 리스트 반환 + List findAllByUserAndIsActiveTrue(User user); + // 팀별 멤버 목록 List findAllByTeamAndIsActiveTrue(Team team); diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamRepository.java index b82f481..d844e81 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamRepository.java @@ -3,6 +3,7 @@ import com.ccapp.ccgo.team.Team; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; /** @@ -10,12 +11,12 @@ */ public interface TeamRepository extends JpaRepository { - // 특정 유저가 이미 팀을 만들었는지 확인 - boolean existsByCreatedBy(Long createdBy); - // 팀 ID로 조회 Optional findByTeamId(Long teamId); + // 유저 ID (팀장)로 팀 찾기 + List findAllByCreatedBy(Long createdBy); + // 팀 이름 중복 방지 boolean existsByTeamName(String teamName); diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java index 25a1e3a..ce1f76b 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java @@ -54,14 +54,19 @@ public void joinTeamByInviteCode(User user, String inviteCode) { throw new CustomException("초대코드가 만료되었습니다.", HttpStatus.BAD_REQUEST); } - if (teamMemberRepository.findByUserAndIsActiveTrue(user).isPresent()) { - throw new CustomException("이미 팀에 소속되어 있습니다.", HttpStatus.BAD_REQUEST); + Team teamToJoin = code.getTeam(); + + // 유저가 이미 이 팀에 가입했는지 체크 + boolean alreadyJoined = teamMemberRepository.findAllByUserAndIsActiveTrue(user).stream() + .anyMatch(tm -> tm.getTeam().equals(teamToJoin)); + if (alreadyJoined) { + throw new CustomException("이미 이 팀에 가입되어 있습니다.", HttpStatus.BAD_REQUEST); } TeamMember newMember = TeamMember.builder() .user(user) - .team(code.getTeam()) - .role(Role.MEMBER) + .team(teamToJoin) + .role(Role.MEMBER) // 기본 역할은 MEMBER .isActive(true) .joinedAt(LocalDateTime.now()) .build(); diff --git a/src/main/java/com/ccapp/ccgo/service/UserService.java b/src/main/java/com/ccapp/ccgo/service/UserService.java index 49e6059..d592bcc 100644 --- a/src/main/java/com/ccapp/ccgo/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/service/UserService.java @@ -73,7 +73,6 @@ public UserResponseDto updateUser(Long id, UserRequestDto dto) { user.setPassword(passwordEncoder.encode(dto.getPassword())); } user.setName(dto.getName()); - user.setRole(dto.getRole()); user.setGender(dto.getGender()); user.setBirthdate(dto.getBirthdate()); diff --git a/src/main/java/com/ccapp/ccgo/team/Team.java b/src/main/java/com/ccapp/ccgo/team/Team.java index e7543c6..dc7f07a 100644 --- a/src/main/java/com/ccapp/ccgo/team/Team.java +++ b/src/main/java/com/ccapp/ccgo/team/Team.java @@ -17,12 +17,8 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Table( - name = "team", - uniqueConstraints = { - @UniqueConstraint(columnNames = "createdBy") - } -) +@Table(name = "team") + public class Team { // PK - 팀 ID @@ -34,8 +30,8 @@ public class Team { @Column(nullable = false) private String teamName; - // 팀장 유저 ID (User.id 참조), UNIQUE - @Column(nullable = false, unique = true, name="created_by") + // 팀장 유저 ID (User.id 참조) + @Column(nullable = false, name = "created_by") private Long createdBy; //생성된 시간 diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/TeamMember.java index 3b9ca19..8fd4c50 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/TeamMember.java @@ -23,9 +23,10 @@ @Table( name = "team_member", uniqueConstraints = { - @UniqueConstraint(columnNames = "user_id") + @UniqueConstraint(columnNames = {"user_id", "team_id"}) // ✅ 한 유저가 한 팀에만 한 번 참여 가능하도록 제한 } ) + public class TeamMember { // PK - 팀 멤버 ID @@ -55,4 +56,8 @@ public class TeamMember { @Column(nullable = false) private boolean isActive; + //설문조사를 햇슴까 + @Column(nullable = false) + private boolean isSurveyCompleted = false; // 설문조사 기본값 false + } diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index 78c7fc8..cc3344a 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -49,20 +49,12 @@ public class User { @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; - //팀내 역할 (팀장/팀원) - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private Role role; - // 회원 가입 시 자동으로 현재 시간 설정 @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); } - //관심사 설문조사 완료여부 - @Column(name = "is_interest_completed", nullable = false) - private boolean isInterestCompleted = false; } From 60c7feac8956bfc1ebb61988830117d9f2b6f628 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Sun, 6 Jul 2025 14:29:05 +0900 Subject: [PATCH 009/132] Fix invitecontroller&service --- .../ccgo/controller/InviteCodeController.java | 74 +++-------------- .../ccgo/repository/TeamMemberRepository.java | 2 +- .../ccapp/ccgo/service/InviteCodeService.java | 79 +++++++++++-------- 3 files changed, 55 insertions(+), 100 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java index 33cfe53..efa6b12 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java @@ -44,6 +44,7 @@ public ResponseEntity createInviteCode( System.out.println("초대코드 생성 요청 들어옴"); System.out.println("userDetails: " + userDetails); + //작동확인 주석 if (userDetails != null) { System.out.println("인증된 사용자 이메일: " + userDetails.getUsername()); System.out.println("사용자 권한: " + userDetails.getAuthorities()); @@ -52,28 +53,15 @@ public ResponseEntity createInviteCode( } User user = userDetails.getUser(); - System.out.print("코드 만듭니당"); - - List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); - - TeamMember teamMember = teamMembers.stream() - .filter(tm -> tm.getRole() == Role.LEADER) - .findFirst() - .orElseThrow(() -> new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN)); - - System.out.println("여까진 됌 "); - - // 초대코드 생성 서비스 호출 InviteCode inviteCode = inviteCodeService.createInviteCode(user); - - System.out.print("만들었어용"); InviteCodeCreateResponseDto responseDto = InviteCodeCreateResponseDto.builder() .code(inviteCode.getCode()) .expiresAt(inviteCode.getExpiresAt()) .build(); - System.out.print("무슨 dto생성해용"); + return ResponseEntity.ok(responseDto); + } @@ -83,31 +71,14 @@ public ResponseEntity saveTeamName( @AuthenticationPrincipal LoginUserDetails userDetails, @RequestBody TeamRequestDto requestDto) { if (userDetails == null) { - return ResponseEntity.status(401).build(); // 인증 정보 없으면 401 반환 + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } + User user = userDetails.getUser(); - String teamName = requestDto.getTeamName(); - - //팀 생성 코드 - // 팀 엔티티 생성 및 저장 - Team team = Team.builder() - .teamName(teamName) - .createdBy(user.getId()) // 팀장 ID - .createdAt(LocalDateTime.now()) - .build(); - teamRepository.save(team); - - // 팀장도 팀원으로 자동 등록 - TeamMember teamMember = TeamMember.builder() - .team(team) - .user(user) - .joinedAt(LocalDateTime.now()) - .isActive(true) - .role(Role.LEADER) // 팀장 역할 - .build(); - teamMemberRepository.save(teamMember); + inviteCodeService.createTeamWithLeader(user, requestDto.getTeamName()); return ResponseEntity.ok().build(); + } //팀원이 코드를 보냈으면 처리 @@ -120,36 +91,11 @@ public ResponseEntity joinByInviteCode( return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("인증 정보가 없습니다."); } - String code = requestDto.getInviteCode(); User user = userDetails.getUser(); + System.out.println("1. 컨트롤러 진입"); + String teamName = inviteCodeService.joinTeamByInviteCode(requestDto.getInviteCode(), user); - // 유효한 초대 코드인지 확인 - InviteCode inviteCode = inviteCodeRepository - .findByCodeAndExpiresAtAfter(code, LocalDateTime.now()) - .orElseThrow(() -> new RuntimeException("초대 코드가 없거나 만료되었습니다.")); - - Team team = inviteCode.getTeam(); - if (team == null) { - throw new RuntimeException("초대코드에 연결된 팀이 없습니다."); - } - - boolean alreadyMember = teamMemberRepository.existsByUserAndTeam(user, team); - if (alreadyMember) { - return ResponseEntity.badRequest().body("이미 이 팀에 가입되어 있습니다."); - } - - // 새 TeamMember 생성 및 저장 - TeamMember newMember = TeamMember.builder() - .user(user) - .team(team) - .joinedAt(LocalDateTime.now()) - .isActive(true) - .role(Role.MEMBER) // 기본 역할로 MEMBER 지정, 필요하면 변경 - .build(); - teamMemberRepository.save(newMember); - - - return ResponseEntity.ok(new InviteCodeJoinResponseDto(team.getTeamName())); + return ResponseEntity.ok(new InviteCodeJoinResponseDto(teamName)); } diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java index e7516d2..02ad54f 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java @@ -17,7 +17,7 @@ public interface TeamMemberRepository extends JpaRepository { boolean existsByUser(User user); // 현재 소속 중인 팀 찾기 (Soft Delete 고려) - Optional findByUserAndIsActiveTrue(User user); + List findByUserAndIsActiveTrue(User user); //teammember에서 유저 조회 Optional findByUser(User user); diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java index ce1f76b..e7d01d4 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java @@ -8,7 +8,6 @@ import com.ccapp.ccgo.team.InviteCode; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.team.TeamMember; -import lombok.NonNull; import org.springframework.scheduling.annotation.Scheduled; import com.ccapp.ccgo.user.User; import org.springframework.http.HttpStatus; @@ -18,6 +17,7 @@ import java.time.LocalDateTime; import java.security.SecureRandom; +import java.util.List; @Service @RequiredArgsConstructor @@ -45,45 +45,45 @@ private String generateRandomCode() { //코드로팀가입 @Transactional - public void joinTeamByInviteCode(User user, String inviteCode) { - InviteCode code = inviteCodeRepository.findById(inviteCode) - .orElseThrow(() -> new CustomException("초대코드가 유효하지 않습니다.", HttpStatus.BAD_REQUEST)); + public String joinTeamByInviteCode(String code, User user) { - if (code.isExpired()) { - inviteCodeRepository.delete(code); // 만료된 코드는 삭제 - throw new CustomException("초대코드가 만료되었습니다.", HttpStatus.BAD_REQUEST); - } + InviteCode inviteCode = inviteCodeRepository + .findByCodeAndExpiresAtAfter(code, LocalDateTime.now()) + .orElseThrow(() -> new CustomException("초대 코드가 없거나 만료되었습니다.", HttpStatus.BAD_REQUEST)); - Team teamToJoin = code.getTeam(); + Team team = inviteCode.getTeam(); - // 유저가 이미 이 팀에 가입했는지 체크 - boolean alreadyJoined = teamMemberRepository.findAllByUserAndIsActiveTrue(user).stream() - .anyMatch(tm -> tm.getTeam().equals(teamToJoin)); - if (alreadyJoined) { + boolean alreadyMember = teamMemberRepository.existsByUserAndTeam(user, team); + if (alreadyMember) { throw new CustomException("이미 이 팀에 가입되어 있습니다.", HttpStatus.BAD_REQUEST); } - TeamMember newMember = TeamMember.builder() .user(user) - .team(teamToJoin) - .role(Role.MEMBER) // 기본 역할은 MEMBER - .isActive(true) + .team(team) .joinedAt(LocalDateTime.now()) + .isActive(true) + .role(Role.MEMBER) .build(); teamMemberRepository.save(newMember); + + return team.getTeamName(); } - //보안상 냅둬 + //초대코드생성 부분 @Transactional public InviteCode createInviteCode(User user) { - var teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) - .orElseThrow(() -> new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST)); - if (teamMember.getRole() != Role.LEADER) { - throw new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN); + List teamMembers = teamMemberRepository.findByUserAndIsActiveTrue(user); + if (teamMembers.isEmpty()) { + throw new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST); } - Team team = teamMember.getTeam(); + TeamMember leader = teamMembers.stream() + .filter(tm -> tm.getRole() == Role.LEADER) + .findFirst() + .orElseThrow(() -> new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN)); + + Team team = leader.getTeam(); //기존 코드 삭제 inviteCodeRepository.deleteByTeam(team); @@ -107,19 +107,28 @@ public void deleteExpiredInviteCodes() { inviteCodeRepository.deleteByExpiresAtBefore(LocalDateTime.now()); } - @Transactional - public void saveTeamName(@NonNull User user, @NonNull String teamName) { - // 사용자 팀 멤버 조회 - TeamMember teamMember = teamMemberRepository.findByUserAndIsActiveTrue(user) - .orElseThrow(() -> new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST)); - - // 팀장만 팀 이름 변경 가능 (원한다면 이 조건 제거 가능) - if (teamMember.getRole() != Role.LEADER) { - throw new CustomException("팀장만 팀 이름을 변경할 수 있습니다.", HttpStatus.FORBIDDEN); - } - Team team = teamMember.getTeam(); - team.setTeamName(teamName); // 팀 이름 변경 + + @Transactional + public void createTeamWithLeader(User user, String teamName) { + // 팀 생성 + Team team = Team.builder() + .teamName(teamName) + .createdBy(user.getId()) + .createdAt(LocalDateTime.now()) + .build(); teamRepository.save(team); + + // 팀장 등록 + TeamMember teamMember = TeamMember.builder() + .team(team) + .user(user) + .joinedAt(LocalDateTime.now()) + .isActive(true) + .role(Role.LEADER) + .build(); + teamMemberRepository.save(teamMember); } + + } From 0d95cd22ea2b2e58adae678fd8cf1b528eaa746a Mon Sep 17 00:00:00 2001 From: bendy41 Date: Sun, 6 Jul 2025 16:12:41 +0900 Subject: [PATCH 010/132] Add TeamController --- .../ccapp/ccgo/controller/TeamController.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/com/ccapp/ccgo/controller/TeamController.java diff --git a/src/main/java/com/ccapp/ccgo/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/controller/TeamController.java new file mode 100644 index 0000000..10def72 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/controller/TeamController.java @@ -0,0 +1,41 @@ +package com.ccapp.ccgo.controller; +import com.ccapp.ccgo.dto.TeamResponseDto; +import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.jwt.LoginUserDetails; +import com.ccapp.ccgo.repository.TeamMemberRepository; + +import lombok.RequiredArgsConstructor; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/team") +public class TeamController { + + private final TeamMemberRepository teamMemberRepository; + + @GetMapping("/mine") + public ResponseEntity> getMyTeams( + @AuthenticationPrincipal LoginUserDetails userDetails) { + + User user = userDetails.getUser(); + + List result = teamMemberRepository + .findAllByUserAndIsActiveTrue(user) + .stream() + .map(tm -> new TeamResponseDto( + tm.getTeam().getTeamId(), // getTeamId()로 변경 + tm.getTeam().getTeamName())) + .collect(Collectors.toList()); + + return ResponseEntity.ok(result); + } +} \ No newline at end of file From 5db78758df9b5c32800796d9a937bde5669fe278 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 7 Jul 2025 13:31:34 +0900 Subject: [PATCH 011/132] match service complete --- .idea/modules/CC_Maker_BE.main.iml | 1 + .idea/modules/CC_Maker_BE.test.iml | 1 + .../ccgo/controller/MatchingController.java | 51 ++ .../com/ccapp/ccgo/dto/AnswerRequestDto.java | 14 + .../ccapp/ccgo/dto/MatchingResponseDto.java | 15 + .../com/ccapp/ccgo/dto/MatchingResultDto.java | 15 + .../ccapp/ccgo/dto/QuestionRequestDto.java | 14 + .../ccapp/ccgo/dto/QuestionResponseDto.java | 12 + .../com/ccapp/ccgo/dto/QuestionUpdateDto.java | 11 + .../com/ccapp/ccgo/dto/SingleAnswerDto.java | 12 + .../com/ccapp/ccgo/dto/UserResponseDto.java | 1 + .../java/com/ccapp/ccgo/matching/Answer.java | 39 ++ .../com/ccapp/ccgo/matching/MbtiScore.java | 26 ++ .../com/ccapp/ccgo/matching/MbtiScoreId.java | 13 + .../ccgo/matching/MbtiScoreProvider.java | 37 ++ .../com/ccapp/ccgo/matching/Question.java | 33 ++ .../com/ccapp/ccgo/matching/SubGroup.java | 33 ++ .../ccapp/ccgo/matching/SubGroupMember.java | 33 ++ .../ccgo/repository/AnswerRepository.java | 13 + .../ccgo/repository/MbtiScoreRepository.java | 11 + .../ccgo/repository/QuestionRepository.java | 10 + .../repository/SubGroupMemberRepository.java | 9 + .../ccgo/repository/SubGroupRepository.java | 9 + .../ccgo/repository/TeamMemberRepository.java | 2 + .../ccapp/ccgo/service/MatchingService.java | 439 ++++++++++++++++++ src/main/java/com/ccapp/ccgo/user/User.java | 4 + 26 files changed, 858 insertions(+) create mode 100644 src/main/java/com/ccapp/ccgo/controller/MatchingController.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java create mode 100644 src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/Answer.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/MbtiScore.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/Question.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/SubGroup.java create mode 100644 src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java create mode 100644 src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/service/MatchingService.java diff --git a/.idea/modules/CC_Maker_BE.main.iml b/.idea/modules/CC_Maker_BE.main.iml index 5491e61..ff0e05e 100644 --- a/.idea/modules/CC_Maker_BE.main.iml +++ b/.idea/modules/CC_Maker_BE.main.iml @@ -37,6 +37,7 @@ + diff --git a/.idea/modules/CC_Maker_BE.test.iml b/.idea/modules/CC_Maker_BE.test.iml index 3b1bc40..b041ee2 100644 --- a/.idea/modules/CC_Maker_BE.test.iml +++ b/.idea/modules/CC_Maker_BE.test.iml @@ -21,6 +21,7 @@ + diff --git a/src/main/java/com/ccapp/ccgo/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/controller/MatchingController.java new file mode 100644 index 0000000..fa48689 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/controller/MatchingController.java @@ -0,0 +1,51 @@ +package com.ccapp.ccgo.controller; + +import com.ccapp.ccgo.dto.*; +import com.ccapp.ccgo.service.MatchingService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/matching") // 경로수정필요 +public class MatchingController { + + private final MatchingService matchingService; + + /** + * 팀장이 매칭 시작 버튼 누를 때 호출 + */ +// @PreAuthorize("hasRole('TEAM_LEADER')") 매칭권한 + @PostMapping("/start/{teamId}") + public MatchingResponseDto startMatching(@PathVariable Long teamId) { + return matchingService.performMatching(teamId); + } + + @PostMapping("/answer") + public void saveAnswers(@RequestBody AnswerRequestDto requestDto) { + matchingService.saveAnswers(requestDto); + } + + @PostMapping("/question") + public void createQuestions(@RequestBody QuestionRequestDto requestDto) { + matchingService.createQuestions(requestDto); + } + + @GetMapping("/question") + public List getQuestions(@RequestParam Long teamId) { + return matchingService.getQuestions(teamId); + } + + @PutMapping("/question/{questionId}") + public void updateQuestion(@PathVariable Long questionId, + @RequestBody QuestionUpdateDto dto) { + matchingService.updateQuestion(questionId, dto); + } + + @DeleteMapping("/question/{questionId}") + public void deleteQuestion(@PathVariable Long questionId) { + matchingService.deleteQuestion(questionId); + } +} diff --git a/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java new file mode 100644 index 0000000..d127921 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java @@ -0,0 +1,14 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AnswerRequestDto { + private Long userId; + private List answers; +} diff --git a/src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java new file mode 100644 index 0000000..4edc3e6 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java @@ -0,0 +1,15 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MatchingResponseDto { + private Long teamId; + private String teamName; + private List subGroups; +} diff --git a/src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java b/src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java new file mode 100644 index 0000000..8250a4f --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java @@ -0,0 +1,15 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MatchingResultDto { + private Long subGroupId; // SubGroup PK + private String groupName; // SubGroup 이름 (팀이름+인덱스) + private List members; // 그룹 멤버 리스트 +} diff --git a/src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java new file mode 100644 index 0000000..6b3599e --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java @@ -0,0 +1,14 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class QuestionRequestDto { + private Long teamId; + private List questions; +} diff --git a/src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java new file mode 100644 index 0000000..58227f7 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java @@ -0,0 +1,12 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class QuestionResponseDto { + private Long id; + private String text; +} diff --git a/src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java b/src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java new file mode 100644 index 0000000..d615536 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java @@ -0,0 +1,11 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class QuestionUpdateDto { + private String text; +} diff --git a/src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java b/src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java new file mode 100644 index 0000000..145700e --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java @@ -0,0 +1,12 @@ +package com.ccapp.ccgo.dto; + +import lombok.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SingleAnswerDto { + private Long questionId; + private Integer score; +} diff --git a/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java b/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java index 41d8190..eecaff7 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java @@ -15,5 +15,6 @@ public class UserResponseDto { private LocalDate birthdate; private LocalDateTime createdAt; private String role; + private String mbti; // mbti 값 } diff --git a/src/main/java/com/ccapp/ccgo/matching/Answer.java b/src/main/java/com/ccapp/ccgo/matching/Answer.java new file mode 100644 index 0000000..c9ce69f --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/Answer.java @@ -0,0 +1,39 @@ +package com.ccapp.ccgo.matching; + +import com.ccapp.ccgo.user.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "answer") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Answer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 답변한 사용자 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + /** + * 어떤 질문에 대한 답변인지 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "question_id", nullable = false) + private Question question; + + /** + * 유저가 선택한 점수 (1~5) + */ + @Column(nullable = false) + private Integer score; +} diff --git a/src/main/java/com/ccapp/ccgo/matching/MbtiScore.java b/src/main/java/com/ccapp/ccgo/matching/MbtiScore.java new file mode 100644 index 0000000..cc9c1f1 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/MbtiScore.java @@ -0,0 +1,26 @@ +package com.ccapp.ccgo.matching; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@IdClass(MbtiScoreId.class) +@Table(name = "mbti_score") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MbtiScore { + + @Id + @Column(name = "from_mbti", length = 4) + private String fromMbti; + + @Id + @Column(name = "to_mbti", length = 4) + private String toMbti; + + @Column(nullable = false) + private Integer score; +} diff --git a/src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java b/src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java new file mode 100644 index 0000000..fd1fb0f --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java @@ -0,0 +1,13 @@ +package com.ccapp.ccgo.matching; + +import lombok.*; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MbtiScoreId implements Serializable { + private String fromMbti; + private String toMbti; +} diff --git a/src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java b/src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java new file mode 100644 index 0000000..a5e67d2 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java @@ -0,0 +1,37 @@ +package com.ccapp.ccgo.matching; + +import com.ccapp.ccgo.repository.MbtiScoreRepository; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// Map 캐시 만들어주는 역할 + +@Component +@RequiredArgsConstructor +public class MbtiScoreProvider { + + private final MbtiScoreRepository mbtiScoreRepository; + + private final Map> scoreMap = new HashMap<>(); + + @PostConstruct + public void loadMbtiScores() { + List scores = mbtiScoreRepository.findAll(); + for (MbtiScore score : scores) { + scoreMap + .computeIfAbsent(score.getFromMbti(), k -> new HashMap<>()) + .put(score.getToMbti(), score.getScore()); + } + } + + public int getScore(String fromMbti, String toMbti) { + return scoreMap + .getOrDefault(fromMbti, new HashMap<>()) + .getOrDefault(toMbti, 0); + } +} diff --git a/src/main/java/com/ccapp/ccgo/matching/Question.java b/src/main/java/com/ccapp/ccgo/matching/Question.java new file mode 100644 index 0000000..5986dec --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/Question.java @@ -0,0 +1,33 @@ +package com.ccapp.ccgo.matching; + +import com.ccapp.ccgo.team.Team; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "question") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Question { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 어떤 팀에서 작성한 질문인지 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id", nullable = false) + private Team team; + + /** + * 질문 텍스트 + * ex) "당신의 주말 취미는 무엇인가요?" + */ + @Column(nullable = false, length = 1000) + private String text; +} diff --git a/src/main/java/com/ccapp/ccgo/matching/SubGroup.java b/src/main/java/com/ccapp/ccgo/matching/SubGroup.java new file mode 100644 index 0000000..228df01 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/SubGroup.java @@ -0,0 +1,33 @@ +package com.ccapp.ccgo.matching; + +import com.ccapp.ccgo.team.Team; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "sub_group") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SubGroup { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 어떤 팀 안에서 만들어진 그룹인지 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id", nullable = false) + private Team team; + + /** + * 그룹 이름 + * 예) "A팀 매칭 그룹 1번" + */ + @Column(nullable = false, length = 255) + private String name; +} diff --git a/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java b/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java new file mode 100644 index 0000000..cd084a1 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java @@ -0,0 +1,33 @@ +package com.ccapp.ccgo.matching; + +import com.ccapp.ccgo.user.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "sub_group_member") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SubGroupMember { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 어떤 SubGroup 에 소속되었는지 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sub_group_id", nullable = false) + private SubGroup subGroup; + + /** + * 소속된 사용자 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java b/src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java new file mode 100644 index 0000000..6196564 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java @@ -0,0 +1,13 @@ +package com.ccapp.ccgo.repository; + +import com.ccapp.ccgo.matching.Answer; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface AnswerRepository extends JpaRepository { + + List findByUser_Id(Long userId); + + List findByQuestion_Id(Long questionId); + +} diff --git a/src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java b/src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java new file mode 100644 index 0000000..96a6f88 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java @@ -0,0 +1,11 @@ +package com.ccapp.ccgo.repository; + +import com.ccapp.ccgo.matching.MbtiScore; +import com.ccapp.ccgo.matching.MbtiScoreId; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface MbtiScoreRepository extends JpaRepository { + List findAll(); +} diff --git a/src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java b/src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java new file mode 100644 index 0000000..62c0333 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java @@ -0,0 +1,10 @@ +package com.ccapp.ccgo.repository; + +import com.ccapp.ccgo.matching.Question; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface QuestionRepository extends JpaRepository { + + List findByTeam_TeamId(Long teamId); +} diff --git a/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java new file mode 100644 index 0000000..767ba1f --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java @@ -0,0 +1,9 @@ +package com.ccapp.ccgo.repository; + +import com.ccapp.ccgo.matching.SubGroupMember; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface SubGroupMemberRepository extends JpaRepository { + List findBySubGroup_Id(Long subGroupId); +} diff --git a/src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java b/src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java new file mode 100644 index 0000000..9c9ea59 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java @@ -0,0 +1,9 @@ +package com.ccapp.ccgo.repository; + +import com.ccapp.ccgo.matching.SubGroup; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface SubGroupRepository extends JpaRepository { + List findByTeam_TeamId(Long teamId); +} diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java index 9a8309c..26ceeac 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java @@ -27,4 +27,6 @@ public interface TeamMemberRepository extends JpaRepository { // 이미 특정 유저가 특정 팀 인지 확인 boolean existsByUserAndTeam(User user, Team team); + + List findActiveMembersByTeamId(Long teamId); } diff --git a/src/main/java/com/ccapp/ccgo/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/service/MatchingService.java new file mode 100644 index 0000000..d5df5ce --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/service/MatchingService.java @@ -0,0 +1,439 @@ +package com.ccapp.ccgo.service; + +import com.ccapp.ccgo.dto.*; +import com.ccapp.ccgo.matching.*; +import com.ccapp.ccgo.repository.*; +import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.TeamMember; +import com.ccapp.ccgo.user.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MatchingService { + + private final TeamMemberRepository teamMemberRepository; + private final QuestionRepository questionRepository; + private final AnswerRepository answerRepository; + private final SubGroupRepository subGroupRepository; + private final SubGroupMemberRepository subGroupMemberRepository; + // 가중치 + private static final double MBTI_WEIGHT = 0.5; + private static final double SIMILARITY_WEIGHT = 0.5; + + private final MbtiScoreProvider mbtiScoreProvider; + + @Transactional + public MatchingResponseDto performMatching(Long teamId) { + + // 1. 팀 정보 가져오기 + // 해당 팀에 소속된 팀원 전체 가져옴 + // 팀원이 없다면 (비어있다면) IllegalArgumentException + List members = teamMemberRepository.findActiveMembersByTeamId(teamId); + Map memberMap = members.stream() + .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); + + + if (members.isEmpty()) { + throw new IllegalArgumentException("해당 팀(" + teamId + ")에 유저가 없습니다."); + } + + Team team = members.get(0).getTeam(); + + // 2. 남/여 그룹 나누기 + + // 남자 리스트 + List males = members.stream() + .filter(m -> "MALE".equalsIgnoreCase(m.getUser().getGender())) + .collect(Collectors.toList()); + + // 여자 리스트 + List females = members.stream() + .filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())) + .collect(Collectors.toList()); + + List groupA; + List groupB; + + // 기준 잡기 + // 수가 적은 쪽이 기준 / 수가 같을 경우 남자가 기준 + if (males.size() < females.size()) { + groupA = males; + groupB = females; + } else if (females.size() < males.size()) { + groupA = females; + groupB = males; + } else { + groupA = males; + groupB = females; + } + + // 3. 후보 리스트 Map 생성 + // Map을 통한 후보 리스트 조합 생성 (groupA 의 각 멤버별로 groupB 와 전부 조합) + Map> candidateMap = createCandidateMap(groupA, groupB, teamId); + + // 4. 모든 쌍 점수 계산 + // 후보 Map에 담긴 후보들로 A-B 모든 쌍 생성, 다시 MBTI + 유사도 점수 합산 + // PairMatch 객체에 저장 + List pairMatches = generateAllPairMatches(candidateMap, memberMap); + + // 5. 점수 내림차순 정렬 + pairMatches.sort(Comparator.comparingDouble(PairMatch::getTotalScore).reversed()); + + // 6. Greedy 매칭 수행 + // 이미 매칭된 유저가 포함된 쌍은 건너뜀 + // 새로 생성되는 그룹은 SubGroup + // SubGroupMember 테이블에 두 사람 매핑 + // 매칭 시 그룹명은 팀이름 + index (홍길통A1, 홍길동A2 ...) + Set matchedUserIds = new HashSet<>(); + List subGroups = new ArrayList<>(); + int groupIndex = 1; + + for (PairMatch pair : pairMatches) { + if (matchedUserIds.contains(pair.userA.getUser().getId()) + || matchedUserIds.contains(pair.userB.getUser().getId())) { + continue; + } + + String groupName = team.getTeamName() + groupIndex; + SubGroup sg = SubGroup.builder() + .team(team) + .name(groupName) + .build(); + subGroupRepository.save(sg); + + saveSubGroupMember(sg, pair.userA.getUser()); + saveSubGroupMember(sg, pair.userB.getUser()); + + matchedUserIds.add(pair.userA.getUser().getId()); + matchedUserIds.add(pair.userB.getUser().getId()); + + subGroups.add(sg); + groupIndex++; + } + + // 7. 잉여 처리 + handleLeftovers(groupA, groupB, matchedUserIds, team, groupIndex, subGroups); + + // 8. 결과 DTO 변환 + return buildMatchingResponseDto(team, subGroups); + } + + // groupA의 각 멤버에게 groupB 중 top N 후보자 리스트를 만들어줌 + // 점수 높은 순서대로 5명 (동점자 포함) 까지 + private Map> createCandidateMap(List groupA, + List groupB, + Long teamId) { + Map> candidateMap = new HashMap<>(); + + for (TeamMember tmA : groupA) { + List tempList = new ArrayList<>(); + + for (TeamMember tmB : groupB) { + int mbtiScore = calculateMbtiTotalScore(tmA.getUser(), tmB.getUser()); + double similarityScore = calculateSimilarityScore(tmA.getUser(), tmB.getUser(), teamId); + // 가중치 계산으로 조정 + double totalScore = mbtiScore * MBTI_WEIGHT + similarityScore * SIMILARITY_WEIGHT; + + tempList.add(new PairMatch(tmA, tmB, totalScore)); + } + + tempList.sort(Comparator.comparingDouble(PairMatch::getTotalScore).reversed()); + + List topCandidates = new ArrayList<>(); + double lastScore = -1; + + for (int i = 0; i < tempList.size(); i++) { + PairMatch pm = tempList.get(i); + if (i < 5) { + topCandidates.add(pm); + lastScore = pm.totalScore; + } else if (pm.totalScore == lastScore) { + topCandidates.add(pm); + } else { + break; + } + } + + candidateMap.put(tmA.getUser().getId(), topCandidates); + } + + return candidateMap; + } + + // candidateMap 기반으로 모든 (A,B) 쌍에 대해 PairMatch 생성 + // 다시 MBTI 점수 + 질문 유사도 점수 계산 + private List generateAllPairMatches(Map> candidateMap, + Map memberMap) { + List pairs = new ArrayList<>(); + + for (List pairList : candidateMap.values()) { + pairs.addAll(pairList); + } + + return pairs; + } + + + // A→B, B→A MBTI 점수를 각각 구해서 합산 + // 대칭적이지 않을 수도 있다는 점 고려 (현재 데이터는 대칭적임) + private int calculateMbtiTotalScore(User a, User b) { + int scoreAtoB = mbtiScoreProvider.getScore(a.getMbti(), b.getMbti()); + int scoreBtoA = mbtiScoreProvider.getScore(b.getMbti(), a.getMbti()); + return scoreAtoB + scoreBtoA; + } + + // 팀별로 등록된 질문 리스트 조회 + // 각 질문 별로 A와 B의 점수 차이를 계산 → 유사도 환산 + // 차이가 0이면 유사도 5, 차이가 5면 유사도 0 + // 전체 유사도 점수 → 100% 환산 + private double calculateSimilarityScore(User a, User b, Long teamId) { + List answersA = answerRepository.findByUser_Id(a.getId()); + List answersB = answerRepository.findByUser_Id(b.getId()); + + // 팀별 질문 수 확보 + List questions = questionRepository.findByTeam_TeamId(teamId); + int totalQuestions = questions.size(); + + int totalSimilarity = 0; + + for (Question q : questions) { + int scoreA = answersA.stream() + .filter(ans -> ans.getQuestion().getId().equals(q.getId())) + .map(Answer::getScore) + .findFirst() + .orElse(0); + + int scoreB = answersB.stream() + .filter(ans -> ans.getQuestion().getId().equals(q.getId())) + .map(Answer::getScore) + .findFirst() + .orElse(0); + + int diff = Math.abs(scoreA - scoreB); + int similarity = 5 - diff; + totalSimilarity += similarity; + } + + double similarityRate = (double) totalSimilarity / (5 * totalQuestions); + return similarityRate * 100; + } + + // 6. Greedy 매칭 결과 저장 + private void saveSubGroupMember(SubGroup sg, User user) { + SubGroupMember sgm = SubGroupMember.builder() + .subGroup(sg) + .user(user) + .build(); + subGroupMemberRepository.save(sgm); + } + + // 7. 잉여 처리 + private void handleLeftovers(List groupA, + List groupB, + Set matchedUserIds, + Team team, + int groupIndex, + List subGroups) { + + // 그룹 이름 중복 방지 + Set existingGroupNames = subGroups.stream() + .map(SubGroup::getName) + .collect(Collectors.toSet()); + + List leftovers = new ArrayList<>(); + groupA.stream() + .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) + .forEach(leftovers::add); + groupB.stream() + .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) + .forEach(leftovers::add); + + Iterator it = leftovers.iterator(); + + while (it.hasNext()) { + TeamMember tm1 = it.next(); + if (it.hasNext()) { + TeamMember tm2 = it.next(); + + // 잉여 그룹 이름 생성 + String groupName; + do { + groupName = team.getTeamName() + groupIndex; + groupIndex++; + } while (existingGroupNames.contains(groupName)); + existingGroupNames.add(groupName); + + SubGroup sg = SubGroup.builder() + .team(team) + .name(groupName) + .build(); + subGroupRepository.save(sg); + + saveSubGroupMember(sg, tm1.getUser()); + saveSubGroupMember(sg, tm2.getUser()); + + subGroups.add(sg); + + } else { + // 홀수 남음 → 기존 그룹 중 가장 점수 높은 그룹으로 편입 + if (!subGroups.isEmpty()) { + SubGroup targetGroup = subGroups.stream() + .max(Comparator.comparing(sg -> { + List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + return members.size(); + })) + .orElse(subGroups.get(0)); + saveSubGroupMember(targetGroup, tm1.getUser()); + } else { + // 기존 그룹이 없으면 단독 그룹 생성 + String groupName; + do { + groupName = team.getTeamName() + groupIndex; + groupIndex++; + } while (existingGroupNames.contains(groupName)); + existingGroupNames.add(groupName); + + SubGroup sg = SubGroup.builder() + .team(team) + .name(groupName) + .build(); + subGroupRepository.save(sg); + + saveSubGroupMember(sg, tm1.getUser()); + subGroups.add(sg); + } + } + } + } + + // 8. 결과 DTO 생성 + private MatchingResponseDto buildMatchingResponseDto(Team team, List subGroups) { + List resultDtos = new ArrayList<>(); + + for (SubGroup sg : subGroups) { + List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + List userDtos = members.stream() + .map(sgm -> { + User u = sgm.getUser(); + return UserResponseDto.builder() + .id(u.getId()) + .name(u.getName()) + .email(u.getEmail()) + .gender(u.getGender()) + .mbti(u.getMbti()) + .build(); + }) + .collect(Collectors.toList()); + + resultDtos.add(MatchingResultDto.builder() + .subGroupId(sg.getId()) + .groupName(sg.getName()) + .members(userDtos) + .build()); + } + + return MatchingResponseDto.builder() + .teamId(team.getTeamId()) + .teamName(team.getTeamName()) + .subGroups(resultDtos) + .build(); + } + + /** + * 내부 클래스 PairMatch + */ + @Data + @AllArgsConstructor + static class PairMatch { + private TeamMember userA; + private TeamMember userB; + private double totalScore; + } + + + // 유저 ID 기준으로 기존 Answer 삭제 + // 해당 유저의 기존 Answer를 삭제하고 새 Answer를 저장 + // 새 Answer를 전부 INSERT + // MatchingService가 매칭 돌릴 때 최신 데이터를 사용 + @Transactional + public void saveAnswers(AnswerRequestDto dto) { + Long userId = dto.getUserId(); + + // 기존 Answer 전부 삭제 + List existingAnswers = answerRepository.findByUser_Id(userId); + answerRepository.deleteAll(existingAnswers); + + // 새 Answer 저장 + List newAnswers = dto.getAnswers().stream() + .map(single -> Answer.builder() + .user(User.builder().id(userId).build()) + .question(Question.builder().id(single.getQuestionId()).build()) + .score(single.getScore()) + .build()) + .collect(Collectors.toList()); + + answerRepository.saveAll(newAnswers); + } + + + // 한 번에 여러 개의 새로운 Question을 DB에 등록 + @Transactional + public void createQuestions(QuestionRequestDto dto) { + Long teamId = dto.getTeamId(); + + List questions = dto.getQuestions().stream() + .map(q -> Question.builder() + .team(Team.builder().teamId(teamId).build()) + .text(q) + .build()) + .collect(Collectors.toList()); + + questionRepository.saveAll(questions); + } + + + // 특정 Question의 질문 내용을 수정 + @Transactional + public void updateQuestion(Long questionId, QuestionUpdateDto dto) { + Question question = questionRepository.findById(questionId) + .orElseThrow(() -> new IllegalArgumentException("Question not found: " + questionId)); + + question.setText(dto.getText()); + } + + + // 특정 팀의 모든 질문 리스트를 조회 (읽기 전용 트랜잭션) + @Transactional(readOnly = true) + public List getQuestions(Long teamId) { + List questions = questionRepository.findByTeam_TeamId(teamId); + + return questions.stream() + .map(q -> QuestionResponseDto.builder() + .id(q.getId()) + .text(q.getText()) + .build()) + .collect(Collectors.toList()); + } + + + // Question에 연결된 Answer들을 먼저 삭제한 뒤, Question을 삭제 + @Transactional + public void deleteQuestion(Long questionId) { + // 삭제할 Question과 연결된 모든 Answer 레코드 조회 + List answers = answerRepository.findByQuestion_Id(questionId); + // 조회된 Answer 레코드 전부 삭제 + answerRepository.deleteAll(answers); + // 마지막으로 Question 자체 삭제 + questionRepository.deleteById(questionId); + } + + +} diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index 34c99f6..5ee781d 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -54,6 +54,10 @@ public class User { @Column(nullable = false) private Role role; + // MBTI 필드 + @Column(name = "mbti") + private String mbti; + // 회원 가입 시 자동으로 현재 시간 설정 @PrePersist public void prePersist() { From 98c3eedbfefcffe5ace0ded4c953764f90776f70 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Mon, 7 Jul 2025 13:32:56 +0900 Subject: [PATCH 012/132] Add mission_sys --- .../com/ccapp/ccgo/common/MissionStatus.java | 7 ++ .../ccgo/mission/MissionServiceImpl.java | 88 +++++++++++++++++++ .../ccapp/ccgo/mission/MissionTemplate.java | 26 ++++++ .../java/com/ccapp/ccgo/mission/Partner.java | 30 +++++++ .../ccapp/ccgo/mission/PartnerMission.java | 34 +++++++ .../ccgo/mission/PartnerServiceImpl.java | 71 +++++++++++++++ .../com/ccapp/ccgo/mission/UserMission.java | 37 ++++++++ .../repository/MissionTemplateRepository.java | 15 ++++ .../repository/PartnerMissionRepository.java | 16 ++++ .../mission/repository/PartnerRepository.java | 19 ++++ .../repository/UserMissionRepository.java | 11 +++ .../ccgo/mission/service/MissionService.java | 9 ++ .../ccgo/mission/service/PartnerService.java | 21 +++++ 13 files changed, 384 insertions(+) create mode 100644 src/main/java/com/ccapp/ccgo/common/MissionStatus.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/Partner.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/PartnerMission.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/UserMission.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionService.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java diff --git a/src/main/java/com/ccapp/ccgo/common/MissionStatus.java b/src/main/java/com/ccapp/ccgo/common/MissionStatus.java new file mode 100644 index 0000000..66777fb --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/common/MissionStatus.java @@ -0,0 +1,7 @@ +package com.ccapp.ccgo.common; + + +public enum MissionStatus { + PENDING, + COMPLETE +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java new file mode 100644 index 0000000..da4b88a --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java @@ -0,0 +1,88 @@ +package com.ccapp.ccgo.mission; + +import com.ccapp.ccgo.common.MissionStatus; +import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; +import com.ccapp.ccgo.mission.repository.PartnerMissionRepository; +import com.ccapp.ccgo.mission.repository.PartnerRepository; +import com.ccapp.ccgo.mission.repository.UserMissionRepository; +import com.ccapp.ccgo.mission.service.MissionService; +import com.ccapp.ccgo.repository.UserRepository; +import com.ccapp.ccgo.user.User; +import jakarta.transaction.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class MissionServiceImpl implements MissionService { + + private final PartnerRepository partnerRepository; + private final PartnerMissionRepository partnerMissionRepository; + private final UserMissionRepository userMissionRepository; + private final MissionTemplateRepository missionTemplateRepository; + private final UserRepository userRepository; + + @Override + public PartnerMission refreshPartnerMission(Long userId) { + // 1. 유저 정보 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저 없음")); + + // 2. 유저가 속한 Partner 찾기 (user1 또는 user2인 Partner) + Partner partner = partnerRepository.findByUser1OrUser2(user, user) + .orElseThrow(() -> new IllegalStateException("해당 유저의 짝이 없음")); + + // 3. 기존 활성 미션 비활성화 처리 + List activeMissions = partnerMissionRepository.findByPartnerAndIsActiveTrue(partner); + for (PartnerMission pm : activeMissions) { + pm.setActive(false); + partnerMissionRepository.save(pm); + } + + // 1. 마지막 미션 조회 + Optional lastMission = partnerMissionRepository.findTopByPartnerOrderByIdDesc(partner); + Long nextTemplateId = lastMission.map(pm -> pm.getTemplate().getId() + 1).orElse(1L); + if (nextTemplateId > 50L) nextTemplateId = 1L; // 50개를 순환하면 다시 1로 + + // 2. 템플릿 미션 조회 + MissionTemplate template = missionTemplateRepository.findById(nextTemplateId) + .orElseThrow(() -> new RuntimeException("템플릿 미션 없음")); + + // 3. 템플릿 기반으로 PartnerMission 생성 + PartnerMission newMission = PartnerMission.builder() + .partner(partner) + .template(template) // <-- template를 저장 + .title(template.getTitle()) + .description(template.getDescription()) + .dueDate(LocalDate.now().plusDays(7)) + .isActive(true) + .build(); + + partnerMissionRepository.save(newMission); + + // 5. 기존 UserMission은 유지하거나 삭제 가능 (옵션) + // 새 UserMission 생성 (짝의 두 유저에게) + User user1 = partner.getUser1(); + User user2 = partner.getUser2(); + + createUserMission(user1, newMission); + createUserMission(user2, newMission); + + return newMission; + } + + private void createUserMission(User user, PartnerMission mission) { + UserMission um = UserMission.builder() + .user(user) + .partnerMission(mission) + .status(MissionStatus.PENDING) + .build(); + + userMissionRepository.save(um); + } +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java b/src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java new file mode 100644 index 0000000..6b135f0 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java @@ -0,0 +1,26 @@ +package com.ccapp.ccgo.mission; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.*; + + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MissionTemplate { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + private String description; + + //1학점 3학점 5학점 10학점 + private int score; +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/Partner.java b/src/main/java/com/ccapp/ccgo/mission/Partner.java new file mode 100644 index 0000000..28a071b --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/Partner.java @@ -0,0 +1,30 @@ +package com.ccapp.ccgo.mission; + +import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.user.User; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class Partner { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "team_id") + private Team team; + + @ManyToOne + @JoinColumn(name = "user1_id") + private User user1; + + @ManyToOne + @JoinColumn(name = "user2_id") + private User user2; +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/PartnerMission.java b/src/main/java/com/ccapp/ccgo/mission/PartnerMission.java new file mode 100644 index 0000000..e85c3d4 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/PartnerMission.java @@ -0,0 +1,34 @@ +package com.ccapp.ccgo.mission; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; + +@Entity +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class PartnerMission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "template_id") + private MissionTemplate template; + + @ManyToOne + @JoinColumn(name = "partner_id") + private Partner partner; + + private String title; + + private String description; + + private LocalDate dueDate; + + private boolean isActive; +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java new file mode 100644 index 0000000..e9f0dee --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java @@ -0,0 +1,71 @@ +package com.ccapp.ccgo.mission; + +import com.ccapp.ccgo.mission.repository.PartnerRepository; +import com.ccapp.ccgo.mission.service.PartnerService; +import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.user.User; +import jakarta.transaction.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class PartnerServiceImpl implements PartnerService { + + private final PartnerRepository partnerRepository; + private final SubGroupMemberRepository subGroupMemberRepository; + + @Override + public List createPartnersFromSubGroups(List subGroups) { + List partners = new ArrayList<>(); + + for (SubGroup sg : subGroups) { + List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + + if (members.size() < 2) { + // 멤버가 2명 미만인 경우 처리 (예: 스킵하거나 예외 처리) + continue; + } + + User user1 = members.get(0).getUser(); + User user2 = members.get(1).getUser(); + Team team = sg.getTeam(); + + // 중복 Partner 체크 (팀, user1, user2 기준) + boolean exists = partnerRepository.existsByTeamAndUser1AndUser2(team, user1, user2); + if (exists) { + continue; + } + + Partner partner = Partner.builder() + .team(team) + .user1(user1) + .user2(user2) + .build(); + + partners.add(partnerRepository.save(partner)); + } + + return partners; + } + + @Override + public List findPartnersByTeamId(Long teamId) { + return partnerRepository.findByTeamId(teamId); + } + + @Override + public Optional findById(Long partnerId) { + return partnerRepository.findById(partnerId); + } + + @Override + public void deletePartner(Long partnerId) { + partnerRepository.deleteById(partnerId); + } +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/UserMission.java b/src/main/java/com/ccapp/ccgo/mission/UserMission.java new file mode 100644 index 0000000..89f9ccb --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/UserMission.java @@ -0,0 +1,37 @@ +package com.ccapp.ccgo.mission; + +import com.ccapp.ccgo.common.MissionStatus; +import com.ccapp.ccgo.user.User; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class UserMission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 미션을 수행하는 유저 + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + // 짝 미션 + @ManyToOne + @JoinColumn(name = "partner_mission_id") + private PartnerMission partnerMission; + + // 미션 상태 (예: PENDING, COMPLETE) + @Enumerated(EnumType.STRING) + private MissionStatus status; + + // 미션 제출 시간 + private LocalDateTime submittedAt; +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java new file mode 100644 index 0000000..4d6c378 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -0,0 +1,15 @@ +package com.ccapp.ccgo.mission.repository; + +import com.ccapp.ccgo.mission.MissionTemplate; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface MissionTemplateRepository extends JpaRepository { + + List findByScoreOrderById(int score); + + + Long findMaxId(); +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java new file mode 100644 index 0000000..ab839db --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java @@ -0,0 +1,16 @@ +package com.ccapp.ccgo.mission.repository; +import com.ccapp.ccgo.mission.Partner; +import com.ccapp.ccgo.mission.PartnerMission; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface PartnerMissionRepository extends JpaRepository { + + List findByPartnerId(Long partnerId); + + List findByPartnerAndIsActiveTrue(Partner partner); + + Optional findTopByPartnerOrderByIdDesc(Partner partner); +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java new file mode 100644 index 0000000..68d05a4 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java @@ -0,0 +1,19 @@ +package com.ccapp.ccgo.mission.repository; + +import com.ccapp.ccgo.mission.Partner; +import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.user.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface PartnerRepository extends JpaRepository { + // 추가로 필요한 커스텀 메서드 작성 가능 + boolean existsByTeamAndUser1AndUser2(Team team, User user1, User user2); + + List findByTeamId(Long teamId); + + Optional findByUser1OrUser2(User user1, User user2); + +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java new file mode 100644 index 0000000..c026c7d --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java @@ -0,0 +1,11 @@ +package com.ccapp.ccgo.mission.repository; + +import com.ccapp.ccgo.mission.UserMission; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserMissionRepository extends JpaRepository { + List findByUserId(Long userId); + List findByPartnerMissionId(Long partnerMissionId); +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java new file mode 100644 index 0000000..ec9a98b --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java @@ -0,0 +1,9 @@ +package com.ccapp.ccgo.mission.service; + +import com.ccapp.ccgo.mission.PartnerMission; + +public interface MissionService { + + // 유저가 새로고침 시 새로운 미션을 짝에 할당 + PartnerMission refreshPartnerMission(Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java new file mode 100644 index 0000000..dc96699 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java @@ -0,0 +1,21 @@ +package com.ccapp.ccgo.mission.service; + +import com.ccapp.ccgo.mission.Partner; + +import java.util.List; +import java.util.Optional; + +public interface PartnerService { + + // 매칭된 SubGroup 정보를 받아 Partner(짝) 생성 + List createPartnersFromSubGroups(List subGroups); + + // 팀별 Partner 목록 조회 + List findPartnersByTeamId(Long teamId); + + // Partner 조회 + Optional findById(Long partnerId); + + // Partner 삭제 + void deletePartner(Long partnerId); +} \ No newline at end of file From edb949c174d3109ab1aeff2ef15ed51b2ed4f17e Mon Sep 17 00:00:00 2001 From: bendy41 Date: Mon, 7 Jul 2025 13:57:09 +0900 Subject: [PATCH 013/132] fix --- .../com/ccapp/ccgo/mission/PartnerServiceImpl.java | 10 ++++------ .../mission/repository/MissionTemplateRepository.java | 2 +- .../ccgo/mission/repository/PartnerRepository.java | 3 ++- .../com/ccapp/ccgo/mission/service/PartnerService.java | 1 + .../ccapp/ccgo/repository/TeamMemberRepository.java | 3 ++- .../java/com/ccapp/ccgo/service/MatchingService.java | 2 +- src/main/resources/application.properties | 2 +- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java index e9f0dee..1a8cd88 100644 --- a/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java @@ -1,7 +1,10 @@ package com.ccapp.ccgo.mission; +import com.ccapp.ccgo.matching.SubGroup; +import com.ccapp.ccgo.matching.SubGroupMember; import com.ccapp.ccgo.mission.repository.PartnerRepository; import com.ccapp.ccgo.mission.service.PartnerService; +import com.ccapp.ccgo.repository.SubGroupMemberRepository; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.user.User; import jakarta.transaction.*; @@ -27,11 +30,6 @@ public List createPartnersFromSubGroups(List subGroups) { for (SubGroup sg : subGroups) { List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); - if (members.size() < 2) { - // 멤버가 2명 미만인 경우 처리 (예: 스킵하거나 예외 처리) - continue; - } - User user1 = members.get(0).getUser(); User user2 = members.get(1).getUser(); Team team = sg.getTeam(); @@ -56,7 +54,7 @@ public List createPartnersFromSubGroups(List subGroups) { @Override public List findPartnersByTeamId(Long teamId) { - return partnerRepository.findByTeamId(teamId); + return partnerRepository.findByTeam_TeamId(teamId); } @Override diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index 4d6c378..d639fb0 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -10,6 +10,6 @@ public interface MissionTemplateRepository extends JpaRepository findByScoreOrderById(int score); - + @Query("SELECT MAX(m.id) FROM MissionTemplate m") Long findMaxId(); } diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java index 68d05a4..8c20e09 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java @@ -12,7 +12,8 @@ public interface PartnerRepository extends JpaRepository { // 추가로 필요한 커스텀 메서드 작성 가능 boolean existsByTeamAndUser1AndUser2(Team team, User user1, User user2); - List findByTeamId(Long teamId); + List findByTeam_TeamId(Long teamId); + Optional findByUser1OrUser2(User user1, User user2); diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java index dc96699..99df79c 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.mission.service; +import com.ccapp.ccgo.matching.SubGroup; import com.ccapp.ccgo.mission.Partner; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java index 4d538c7..dee546e 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java @@ -31,5 +31,6 @@ public interface TeamMemberRepository extends JpaRepository { // 이미 특정 유저가 특정 팀 인지 확인 boolean existsByUserAndTeam(User user, Team team); - List findActiveMembersByTeamId(Long teamId); + List findByTeam_TeamIdAndIsActiveTrue(Long teamId); + } diff --git a/src/main/java/com/ccapp/ccgo/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/service/MatchingService.java index d5df5ce..59a6fc6 100644 --- a/src/main/java/com/ccapp/ccgo/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/service/MatchingService.java @@ -36,7 +36,7 @@ public MatchingResponseDto performMatching(Long teamId) { // 1. 팀 정보 가져오기 // 해당 팀에 소속된 팀원 전체 가져옴 // 팀원이 없다면 (비어있다면) IllegalArgumentException - List members = teamMemberRepository.findActiveMembersByTeamId(teamId); + List members = teamMemberRepository.findByTeam_TeamIdAndIsActiveTrue(teamId); Map memberMap = members.stream() .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 22f7f7e..65d55a6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = qkrwlsdn +spring.datasource.password = 06250730cc jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From fbea4ac8314c10a72182ef68f2a50651481e121c Mon Sep 17 00:00:00 2001 From: bendy41 Date: Mon, 7 Jul 2025 18:54:17 +0900 Subject: [PATCH 014/132] Done merge --- .../mission/controller/MissionController.java | 25 ++++++++ .../mission/{ => domain}/MissionTemplate.java | 2 +- .../ccgo/mission/{ => domain}/Partner.java | 2 +- .../mission/{ => domain}/PartnerMission.java | 2 +- .../mission/{ => domain}/UserMission.java | 2 +- .../ccgo/mission/dto/MissionTemplateDto.java | 24 ++++++++ .../ccapp/ccgo/mission/dto/PartnerDto.java | 24 ++++++++ .../ccgo/mission/dto/PartnerMissionDto.java | 30 ++++++++++ .../ccgo/mission/dto/UserMissionDto.java | 30 ++++++++++ .../repository/MissionTemplateRepository.java | 2 +- .../repository/PartnerMissionRepository.java | 4 +- .../mission/repository/PartnerRepository.java | 11 ++-- .../repository/UserMissionRepository.java | 2 +- .../ccgo/mission/service/MissionService.java | 8 ++- .../{ => service}/MissionServiceImpl.java | 60 +++++++++---------- .../ccgo/mission/service/PartnerService.java | 9 +-- .../{ => service}/PartnerServiceImpl.java | 51 +++++++--------- .../repository/SubGroupMemberRepository.java | 4 ++ .../ccapp/ccgo/service/MatchingService.java | 10 ++-- .../java/com/ccapp/ccgo/team/TeamMember.java | 3 - src/main/java/com/ccapp/ccgo/user/User.java | 5 -- 21 files changed, 210 insertions(+), 100 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java rename src/main/java/com/ccapp/ccgo/mission/{ => domain}/MissionTemplate.java (92%) rename src/main/java/com/ccapp/ccgo/mission/{ => domain}/Partner.java (93%) rename src/main/java/com/ccapp/ccgo/mission/{ => domain}/PartnerMission.java (93%) rename src/main/java/com/ccapp/ccgo/mission/{ => domain}/UserMission.java (95%) create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java rename src/main/java/com/ccapp/ccgo/mission/{ => service}/MissionServiceImpl.java (53%) rename src/main/java/com/ccapp/ccgo/mission/{ => service}/PartnerServiceImpl.java (56%) diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java new file mode 100644 index 0000000..b8061a8 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java @@ -0,0 +1,25 @@ +package com.ccapp.ccgo.mission.controller; + +import com.ccapp.ccgo.mission.dto.PartnerMissionDto; +import com.ccapp.ccgo.mission.service.MissionService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/missions") +@RequiredArgsConstructor +public class MissionController { + + private final MissionService missionService; + + // 팀 ID와 유저 ID를 기반으로 미션 새로고침 + @PostMapping("/refresh") + public ResponseEntity refreshPartnerMission( + @RequestParam Long teamId, + @RequestParam Long userId + ) { + PartnerMissionDto partnerMissionDto = missionService.refreshPartnerMission(teamId, userId); + return ResponseEntity.ok(partnerMissionDto); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java b/src/main/java/com/ccapp/ccgo/mission/domain/MissionTemplate.java similarity index 92% rename from src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java rename to src/main/java/com/ccapp/ccgo/mission/domain/MissionTemplate.java index 6b135f0..2e5883a 100644 --- a/src/main/java/com/ccapp/ccgo/mission/MissionTemplate.java +++ b/src/main/java/com/ccapp/ccgo/mission/domain/MissionTemplate.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.mission; +package com.ccapp.ccgo.mission.domain; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/com/ccapp/ccgo/mission/Partner.java b/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java similarity index 93% rename from src/main/java/com/ccapp/ccgo/mission/Partner.java rename to src/main/java/com/ccapp/ccgo/mission/domain/Partner.java index 28a071b..04fd3e8 100644 --- a/src/main/java/com/ccapp/ccgo/mission/Partner.java +++ b/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.mission; +package com.ccapp.ccgo.mission.domain; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.user.User; diff --git a/src/main/java/com/ccapp/ccgo/mission/PartnerMission.java b/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java similarity index 93% rename from src/main/java/com/ccapp/ccgo/mission/PartnerMission.java rename to src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java index e85c3d4..879b868 100644 --- a/src/main/java/com/ccapp/ccgo/mission/PartnerMission.java +++ b/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.mission; +package com.ccapp.ccgo.mission.domain; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/mission/UserMission.java b/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java similarity index 95% rename from src/main/java/com/ccapp/ccgo/mission/UserMission.java rename to src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java index 89f9ccb..c714176 100644 --- a/src/main/java/com/ccapp/ccgo/mission/UserMission.java +++ b/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.mission; +package com.ccapp.ccgo.mission.domain; import com.ccapp.ccgo.common.MissionStatus; import com.ccapp.ccgo.user.User; diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java new file mode 100644 index 0000000..1762a7e --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java @@ -0,0 +1,24 @@ +package com.ccapp.ccgo.mission.dto; + +import com.ccapp.ccgo.mission.domain.MissionTemplate; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class MissionTemplateDto { + + private Long id; + private String title; + private String description; + private int score; + + public static MissionTemplateDto fromEntity(MissionTemplate template) { + return MissionTemplateDto.builder() + .id(template.getId()) + .title(template.getTitle()) + .description(template.getDescription()) + .score(template.getScore()) + .build(); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java new file mode 100644 index 0000000..fe0a1d3 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java @@ -0,0 +1,24 @@ +package com.ccapp.ccgo.mission.dto; + +import com.ccapp.ccgo.mission.domain.Partner; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PartnerDto { + + private Long id; + private Long teamId; + private Long user1Id; + private Long user2Id; + + public static PartnerDto fromEntity(Partner partner) { + return PartnerDto.builder() + .id(partner.getId()) + .teamId(partner.getTeam().getTeamId()) + .user1Id(partner.getUser1().getId()) + .user2Id(partner.getUser2().getId()) + .build(); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java new file mode 100644 index 0000000..1f0773e --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java @@ -0,0 +1,30 @@ +package com.ccapp.ccgo.mission.dto; + +import com.ccapp.ccgo.mission.domain.PartnerMission; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PartnerMissionDto { + private Long id; + private String title; + private String description; + private LocalDate dueDate; + private boolean isActive; + + public static PartnerMissionDto fromEntity(PartnerMission pm) { + return new PartnerMissionDto( + pm.getId(), + pm.getTitle(), + pm.getDescription(), + pm.getDueDate(), + pm.isActive() + ); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java new file mode 100644 index 0000000..b636618 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java @@ -0,0 +1,30 @@ +package com.ccapp.ccgo.mission.dto; + +import com.ccapp.ccgo.common.MissionStatus; +import com.ccapp.ccgo.mission.domain.UserMission; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserMissionDto { + private Long id; + private Long userId; + private Long partnerMissionId; + private MissionStatus status; + private LocalDateTime submittedAt; + + public static UserMissionDto fromEntity(UserMission um) { + return new UserMissionDto( + um.getId(), + um.getUser().getId(), + um.getPartnerMission().getId(), + um.getStatus(), + um.getSubmittedAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index d639fb0..dd30aff 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.MissionTemplate; +import com.ccapp.ccgo.mission.domain.MissionTemplate; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java index ab839db..abe9ec5 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.Partner; -import com.ccapp.ccgo.mission.PartnerMission; +import com.ccapp.ccgo.mission.domain.Partner; +import com.ccapp.ccgo.mission.domain.PartnerMission; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java index 8c20e09..9250c5c 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.Partner; +import com.ccapp.ccgo.mission.domain.Partner; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.user.User; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,12 +9,11 @@ import java.util.Optional; public interface PartnerRepository extends JpaRepository { - // 추가로 필요한 커스텀 메서드 작성 가능 + boolean existsByTeamAndUser1AndUser2(Team team, User user1, User user2); List findByTeam_TeamId(Long teamId); - - Optional findByUser1OrUser2(User user1, User user2); - -} \ No newline at end of file + // ❗ 유저1 또는 유저2가 user이고, 특정 팀에 속한 Partner를 찾기 + Optional findByTeamAndUser1OrTeamAndUser2(Team team1, User user1, Team team2, User user2); +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java index c026c7d..5f4e98d 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.UserMission; +import com.ccapp.ccgo.mission.domain.UserMission; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java index ec9a98b..36443f6 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java @@ -1,9 +1,11 @@ package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.mission.PartnerMission; +import com.ccapp.ccgo.mission.domain.PartnerMission; +import com.ccapp.ccgo.mission.dto.PartnerMissionDto; +import jakarta.transaction.Transactional; public interface MissionService { - // 유저가 새로고침 시 새로운 미션을 짝에 할당 - PartnerMission refreshPartnerMission(Long userId); + // 특정 팀 내에서 유저의 파트너 미션 갱신 + PartnerMissionDto refreshPartnerMission(Long teamId, Long userId); } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java similarity index 53% rename from src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java rename to src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java index da4b88a..ea37b1f 100644 --- a/src/main/java/com/ccapp/ccgo/mission/MissionServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java @@ -1,20 +1,19 @@ -package com.ccapp.ccgo.mission; +package com.ccapp.ccgo.mission.service; import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; -import com.ccapp.ccgo.mission.repository.PartnerMissionRepository; -import com.ccapp.ccgo.mission.repository.PartnerRepository; -import com.ccapp.ccgo.mission.repository.UserMissionRepository; -import com.ccapp.ccgo.mission.service.MissionService; +import com.ccapp.ccgo.mission.domain.*; +import com.ccapp.ccgo.mission.dto.PartnerMissionDto; +import com.ccapp.ccgo.mission.repository.*; import com.ccapp.ccgo.repository.UserRepository; +import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.repository.TeamRepository; import com.ccapp.ccgo.user.User; -import jakarta.transaction.*; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.util.List; -import java.util.Optional; @Service @RequiredArgsConstructor @@ -26,37 +25,38 @@ public class MissionServiceImpl implements MissionService { private final UserMissionRepository userMissionRepository; private final MissionTemplateRepository missionTemplateRepository; private final UserRepository userRepository; + private final TeamRepository teamRepository; @Override - public PartnerMission refreshPartnerMission(Long userId) { - // 1. 유저 정보 조회 + public PartnerMissionDto refreshPartnerMission(Long teamId, Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("유저 없음")); - // 2. 유저가 속한 Partner 찾기 (user1 또는 user2인 Partner) - Partner partner = partnerRepository.findByUser1OrUser2(user, user) - .orElseThrow(() -> new IllegalStateException("해당 유저의 짝이 없음")); + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("팀 없음")); - // 3. 기존 활성 미션 비활성화 처리 + Partner partner = partnerRepository.findByTeamAndUser1OrTeamAndUser2(team, user, team, user) + .orElseThrow(() -> new IllegalStateException("해당 팀에서 짝이 없음")); + + // 기존 활성 미션 비활성화 List activeMissions = partnerMissionRepository.findByPartnerAndIsActiveTrue(partner); for (PartnerMission pm : activeMissions) { pm.setActive(false); partnerMissionRepository.save(pm); } - // 1. 마지막 미션 조회 - Optional lastMission = partnerMissionRepository.findTopByPartnerOrderByIdDesc(partner); - Long nextTemplateId = lastMission.map(pm -> pm.getTemplate().getId() + 1).orElse(1L); - if (nextTemplateId > 50L) nextTemplateId = 1L; // 50개를 순환하면 다시 1로 + // 마지막 미션 기반으로 다음 템플릿 선택 + Long nextTemplateId = partnerMissionRepository.findTopByPartnerOrderByIdDesc(partner) + .map(pm -> pm.getTemplate().getId() + 1) + .orElse(1L); + if (nextTemplateId > 50L) nextTemplateId = 1L; - // 2. 템플릿 미션 조회 MissionTemplate template = missionTemplateRepository.findById(nextTemplateId) .orElseThrow(() -> new RuntimeException("템플릿 미션 없음")); - // 3. 템플릿 기반으로 PartnerMission 생성 PartnerMission newMission = PartnerMission.builder() .partner(partner) - .template(template) // <-- template를 저장 + .template(template) .title(template.getTitle()) .description(template.getDescription()) .dueDate(LocalDate.now().plusDays(7)) @@ -65,24 +65,18 @@ public PartnerMission refreshPartnerMission(Long userId) { partnerMissionRepository.save(newMission); - // 5. 기존 UserMission은 유지하거나 삭제 가능 (옵션) - // 새 UserMission 생성 (짝의 두 유저에게) - User user1 = partner.getUser1(); - User user2 = partner.getUser2(); - - createUserMission(user1, newMission); - createUserMission(user2, newMission); + createUserMission(partner.getUser1(), newMission); + createUserMission(partner.getUser2(), newMission); - return newMission; + return PartnerMissionDto.fromEntity(newMission); } private void createUserMission(User user, PartnerMission mission) { - UserMission um = UserMission.builder() + UserMission userMission = UserMission.builder() .user(user) .partnerMission(mission) .status(MissionStatus.PENDING) .build(); - - userMissionRepository.save(um); + userMissionRepository.save(userMission); } -} \ No newline at end of file +} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java index 99df79c..c4ec809 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java @@ -1,7 +1,7 @@ package com.ccapp.ccgo.mission.service; import com.ccapp.ccgo.matching.SubGroup; -import com.ccapp.ccgo.mission.Partner; +import com.ccapp.ccgo.mission.domain.Partner; import java.util.List; import java.util.Optional; @@ -11,12 +11,5 @@ public interface PartnerService { // 매칭된 SubGroup 정보를 받아 Partner(짝) 생성 List createPartnersFromSubGroups(List subGroups); - // 팀별 Partner 목록 조회 - List findPartnersByTeamId(Long teamId); - // Partner 조회 - Optional findById(Long partnerId); - - // Partner 삭제 - void deletePartner(Long partnerId); } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java similarity index 56% rename from src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java rename to src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java index 1a8cd88..df49b16 100644 --- a/src/main/java/com/ccapp/ccgo/mission/PartnerServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java @@ -1,19 +1,21 @@ -package com.ccapp.ccgo.mission; +package com.ccapp.ccgo.mission.service; import com.ccapp.ccgo.matching.SubGroup; import com.ccapp.ccgo.matching.SubGroupMember; +import com.ccapp.ccgo.mission.domain.Partner; import com.ccapp.ccgo.mission.repository.PartnerRepository; -import com.ccapp.ccgo.mission.service.PartnerService; import com.ccapp.ccgo.repository.SubGroupMemberRepository; +import com.ccapp.ccgo.repository.TeamRepository; +import com.ccapp.ccgo.repository.UserRepository; import com.ccapp.ccgo.team.Team; import com.ccapp.ccgo.user.User; -import jakarta.transaction.*; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; -import java.util.Optional; + @Service @RequiredArgsConstructor @@ -21,49 +23,38 @@ public class PartnerServiceImpl implements PartnerService { private final PartnerRepository partnerRepository; - private final SubGroupMemberRepository subGroupMemberRepository; + private final UserRepository userRepository; + private final TeamRepository teamRepository; + private final SubGroupMemberRepository subGroupMemberRepository; // 추가 @Override public List createPartnersFromSubGroups(List subGroups) { List partners = new ArrayList<>(); - for (SubGroup sg : subGroups) { - List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + for (SubGroup subGroup : subGroups) { + Team team = subGroup.getTeam(); - User user1 = members.get(0).getUser(); - User user2 = members.get(1).getUser(); - Team team = sg.getTeam(); + // 해당 SubGroup의 멤버 2명 조회 + List members = subGroupMemberRepository.findBySubGroup(subGroup); - // 중복 Partner 체크 (팀, user1, user2 기준) - boolean exists = partnerRepository.existsByTeamAndUser1AndUser2(team, user1, user2); - if (exists) { - continue; + if (members.size() != 2) { + throw new IllegalStateException("SubGroup에 멤버가 2명이 아닙니다: " + subGroup.getId()); } + User user1 = members.get(0).getUser(); + User user2 = members.get(1).getUser(); + Partner partner = Partner.builder() .team(team) .user1(user1) .user2(user2) .build(); - partners.add(partnerRepository.save(partner)); + partnerRepository.save(partner); + partners.add(partner); } return partners; } - @Override - public List findPartnersByTeamId(Long teamId) { - return partnerRepository.findByTeam_TeamId(teamId); - } - - @Override - public Optional findById(Long partnerId) { - return partnerRepository.findById(partnerId); - } - - @Override - public void deletePartner(Long partnerId) { - partnerRepository.deleteById(partnerId); - } -} \ No newline at end of file +} diff --git a/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java index 767ba1f..8e692df 100644 --- a/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java @@ -1,9 +1,13 @@ package com.ccapp.ccgo.repository; +import com.ccapp.ccgo.matching.SubGroup; import com.ccapp.ccgo.matching.SubGroupMember; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface SubGroupMemberRepository extends JpaRepository { List findBySubGroup_Id(Long subGroupId); + + // SubGroup 기준으로 멤버들 조회 + List findBySubGroup(SubGroup subGroup); } diff --git a/src/main/java/com/ccapp/ccgo/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/service/MatchingService.java index 59a6fc6..e21ebfb 100644 --- a/src/main/java/com/ccapp/ccgo/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/service/MatchingService.java @@ -37,14 +37,16 @@ public MatchingResponseDto performMatching(Long teamId) { // 해당 팀에 소속된 팀원 전체 가져옴 // 팀원이 없다면 (비어있다면) IllegalArgumentException List members = teamMemberRepository.findByTeam_TeamIdAndIsActiveTrue(teamId); - Map memberMap = members.stream() - .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); - if (members.isEmpty()) { throw new IllegalArgumentException("해당 팀(" + teamId + ")에 유저가 없습니다."); } + Map memberMap = members.stream() + .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); + + + Team team = members.get(0).getTeam(); // 2. 남/여 그룹 나누기 @@ -222,7 +224,7 @@ private double calculateSimilarityScore(User a, User b, Long teamId) { totalSimilarity += similarity; } - double similarityRate = (double) totalSimilarity / (5 * totalQuestions); + Double similarityRate = (double) totalSimilarity / (5 * totalQuestions); return similarityRate * 100; } diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/TeamMember.java index a6a2eed..8fd4c50 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/TeamMember.java @@ -44,9 +44,6 @@ public class TeamMember { @JoinColumn(name = "user_id", nullable = false) private User user; - @Column(name = "user_name", nullable = false) - private String userName; - // 팀 내 역할 ("TEAM_LEADER" or "TEAM_MEMBER") @Enumerated(EnumType.STRING) @Column(nullable = false) diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index 5ee781d..e217400 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -49,11 +49,6 @@ public class User { @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; - //팀내 역할 (팀장/팀원) - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private Role role; - // MBTI 필드 @Column(name = "mbti") private String mbti; From d1114146d468258b1f7e95eabd77f66f043c27d8 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 8 Jul 2025 10:26:14 +0900 Subject: [PATCH 015/132] tested by postman --- .idea/dataSources.xml | 1 + .idea/sqldialects.xml | 2 +- .../com/ccapp/ccgo/jwt/SecurityConfig.java | 2 +- .../ccapp/ccgo/user/BcryptTestController.java | 25 +++++++++++++++++++ src/main/resources/application.properties | 2 +- 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/user/BcryptTestController.java diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 7a4f374..aeb9e05 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -9,6 +9,7 @@ + $ProjectFileDir$ diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index d21819a..2cb6d30 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java index 69b6ff5..b1173b4 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java @@ -54,7 +54,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/login", "/register").permitAll() // 로그인, 회원가입 허용 + .requestMatchers("/api/auth/login","/bcrypt-test", "/register").permitAll() // 로그인, 회원가입 허용, "/bcrypt-test" .anyRequest().authenticated() // 그 외는 인증 필요 ) .authenticationProvider(authenticationProvider()) diff --git a/src/main/java/com/ccapp/ccgo/user/BcryptTestController.java b/src/main/java/com/ccapp/ccgo/user/BcryptTestController.java new file mode 100644 index 0000000..e02c282 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/user/BcryptTestController.java @@ -0,0 +1,25 @@ +package com.ccapp.ccgo.user; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class BcryptTestController { + +// @GetMapping("/bcrypt-test") +// public String test() { +// String rawPw = "Test@1234"; +// String hash = "$2a$10$plQyO8OH8Sv5uBrm/fRO1OaXOCfcR5fZ0miWJ0OTZzzZEv6AfCMAO"; +// +// boolean matches = new BCryptPasswordEncoder().matches(rawPw, hash); +// return "비밀번호 일치 여부: " + matches; +// } + @GetMapping("/bcrypt-test") + public String generate() { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String rawPw = "Test@1234"; + String encodedPw = encoder.encode(rawPw); + return encodedPw; +} +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65d55a6..22f7f7e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = 06250730cc +spring.datasource.password = qkrwlsdn jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 83df87ce3205b4f6dc78ff9a0e26a9441ef765dc Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 8 Jul 2025 12:35:15 +0900 Subject: [PATCH 016/132] Add sql --- src/main/resources/MissionInsert.sql | 117 +++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main/resources/MissionInsert.sql diff --git a/src/main/resources/MissionInsert.sql b/src/main/resources/MissionInsert.sql new file mode 100644 index 0000000..3963e49 --- /dev/null +++ b/src/main/resources/MissionInsert.sql @@ -0,0 +1,117 @@ +INSERT INTO mission_template (title, description, score) +VALUES +-- 1학점 + (' 미션 1', '서로의 첫인상 공유하기', 1), + (' 미션 2', '인스타 맞팔하기', 1), + (' 미션 3', '매점가서 서로 아이스크림 사주기', 1), + (' 미션 4', '기도제목 공유하기', 1), + (' 미션 5', '셀카 같이 찍기', 1), + (' 미션 6', '노래 추천해주기', 1), + (' 미션 7', '공통 관심사 하나 찾기', 1), + (' 미션 8', '간식 선물하기', 1), + (' 미션 9', '서로 MBTI 말해주기', 1), + (' 미션 10', '닮은꼴 캐릭터 찾기', 1), + (' 미션 11', '최애 유튜버 추천하기', 1), + (' 미션 12', '좋아하는 음식 추천하기', 1), + (' 미션 13', '서로의 TMI 하나씩 공유하기', 1), + (' 미션 14', '큐티 나눔하기', 1), + (' 미션 15', '서로 별명 지어주기', 1), + (' 미션 16', '서로 생일 말해주기', 1), + (' 미션 17', '좋아하는 성경구절/명언 공유하기', 1), + (' 미션 18', '서로 이름 3행시 짓기', 1), + (' 미션 19', '옛날 사진 공유하기', 1), + (' 미션 20', '짧은 응원 메시지 보내기', 1), + (' 미션 21', '최근 꾼 꿈 공유하기', 1), + (' 미션 22', '가위 바위 보', 1), + (' 미션 23', '게시판에서 흥미로운 공지 찾아 사진 찍기', 1), + (' 미션 24', '에타 시간표 공유하기', 1), + (' 미션 25', '교내에서 처음 가보는 건물 탐방하기', 1), + (' 미션 26', '키우는 동물사진 보여주기', 1), + (' 미션 27', '자주 가는 장소 찍어오기', 1), + (' 미션 28', '같이 노래 한 곡 부르기', 1), + (' 미션 29', '팔씨름 하기', 1), + (' 미션 30', '카톡 이름 애칭으로 바꾸기', 1), + +-- 3 학점 짜리 + (' 미션 1', '초상화 그려주기', 3), + (' 미션 2', '교내 카페 음료 마시기', 3), + (' 미션 3', '매점가서 서로 아이스크림 사주기', 3), + (' 미션 4', '한한하기', 3), + (' 미션 5', '학식 같이 먹기', 3), + (' 미션 6', '모닝콜 해주기', 3), + (' 미션 7', '5분이상 통화하기', 3), + (' 미션 8', '커플 프사 하기', 3), + (' 미션 9', '평봉에서 누워 하늘 사진 찍기', 3), + (' 미션 10', '다른 CC랑 스몰토크 5분', 3), + (' 미션 11', '도서관에서 아무 책 들고 사진 찍기', 3), + (' 미션 12', '안 먹어본 음료 뽑아먹기', 3), + (' 미션 13', '같은 표정으로 셀카 3장 찍기', 3), + (' 미션 14', '손으로 하트 사진 찍기', 3), + (' 미션 15', '다른 CC랑 간식 나눠먹기', 3), + (' 미션 16', '모두 다른 포즈로 점프샷 3장 찍기', 3), + (' 미션 17', '영화 포스터 따라한 사진 찍기', 3), + (' 미션 18', '롤모델이 누구인지 물어보기', 3), + (' 미션 19', '교내 농구대에서 슛 3번 던져보기', 3), + (' 미션 20', '활주로 내려갔다가 돌아오기', 3), + (' 미션 21', '팀 구호 외치며 영상 찍기', 3), + (' 미션 22', '10초 내 자기소개 릴레이 찍기', 3), + (' 미션 23', '스터디룸 예약해서 30분 이상 사용하기', 3), + (' 미션 24', '학생식당 메뉴 중 랜덤으로 고르고 먹기', 3), + (' 미션 25', '손하트 영상 찍기', 3), + (' 미션 26', '캠퍼스 둘레길 돌아보고 풍경 영상 찍기', 3), + (' 미션 27', '교내 운동기구 사용해보기', 3), + (' 미션 28', '제일 큰 나무 찾아서 사진 찍기', 3), + (' 미션 29', '같이 공부하기', 3), + (' 미션 30', '옷 맞춰입기', 3), + +-- 5학점 + (' 미션 1', '옷 맞춰입기', 1), + (' 미션 2', '인스타 맞팔하기', 1), + (' 미션 3', '매점가서 서로 아이스크림 사주기', 1), + (' 미션 4', '기도제목 공유하기', 1), + (' 미션 5', '셀카 같이 찍기', 1), + (' 미션 6', '노래 추천해주기', 1), + (' 미션 7', '공통 관심사 하나 찾기', 1), + (' 미션 8', '간식 선물하기', 1), + (' 미션 9', '서로 MBTI 말해주기', 1), + (' 미션 10', '닮은꼴 캐릭터 찾기', 1), + (' 미션 11', '', 1), + (' 미션 12', '옷 맞춰입기', 1), + (' 미션 13', '옷 맞춰입기', 1), + (' 미션 14', '옷 맞춰입기', 1), + (' 미션 15', '옷 맞춰입기', 1), + (' 미션 16', '옷 맞춰입기', 1), + (' 미션 17', '옷 맞춰입기', 1), + (' 미션 18', '옷 맞춰입기', 1), + (' 미션 19', '옷 맞춰입기', 1), + (' 미션 20', '옷 맞춰입기', 1), + (' 미션 21', '옷 맞춰입기', 1), + (' 미션 22', '옷 맞춰입기', 1), + (' 미션 23', '옷 맞춰입기', 1), + (' 미션 24', '옷 맞춰입기', 1), + (' 미션 25', '옷 맞춰입기', 1), + (' 미션 26', '옷 맞춰입기', 1), + (' 미션 27', '옷 맞춰입기', 1), + (' 미션 28', '옷 맞춰입기', 1), + (' 미션 29', '옷 맞춰입기', 1), + (' 미션 30', '옷 맞춰입기', 1), + (' 미션 31', '옷 맞춰입기', 1), + (' 미션 32', '옷 맞춰입기', 1), + (' 미션 33', '옷 맞춰입기', 1), + (' 미션 34', '옷 맞춰입기', 1), + (' 미션 35', '옷 맞춰입기', 1), + (' 미션 36', '옷 맞춰입기', 1), + (' 미션 37', '옷 맞춰입기', 1), + (' 미션 38', '옷 맞춰입기', 1), + (' 미션 39', '옷 맞춰입기', 1), + (' 미션 40', '옷 맞춰입기', 1), + (' 미션 41', '옷 맞춰입기', 1), + (' 미션 42', '옷 맞춰입기', 1), + (' 미션 43', '옷 맞춰입기', 1), + (' 미션 44', '옷 맞춰입기', 1), + (' 미션 45', '옷 맞춰입기', 1), + (' 미션 46', '옷 맞춰입기', 1), + (' 미션 47', '옷 맞춰입기', 1), + (' 미션 48', '옷 맞춰입기', 1), + (' 미션 49', '옷 맞춰입기', 1), + (' 미션 50', '옷 맞춰입기', 1); From fb28137ab5faf3011aa935f9cc528a15875612d0 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 9 Jul 2025 12:44:16 +0900 Subject: [PATCH 017/132] Add Survey_result --- .../ccapp/ccgo/controller/TeamController.java | 21 +++- .../ccapp/ccgo/dto/SurveyCompleteRequest.java | 8 ++ .../mission/controller/MissionController.java | 5 +- .../repository/MissionTemplateRepository.java | 2 +- .../repository/PartnerMissionRepository.java | 9 +- .../ccgo/mission/service/MissionService.java | 4 +- .../mission/service/MissionServiceImpl.java | 45 +++++--- .../ccgo/repository/TeamMemberRepository.java | 3 + .../ccapp/ccgo/service/TeamMemberService.java | 24 ++++ src/main/resources/MissionInsert.sql | 104 ++++++++---------- src/main/resources/application.properties | 2 +- 11 files changed, 141 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/dto/SurveyCompleteRequest.java create mode 100644 src/main/java/com/ccapp/ccgo/service/TeamMemberService.java diff --git a/src/main/java/com/ccapp/ccgo/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/controller/TeamController.java index 10def72..4942589 100644 --- a/src/main/java/com/ccapp/ccgo/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/controller/TeamController.java @@ -1,5 +1,7 @@ package com.ccapp.ccgo.controller; +import com.ccapp.ccgo.dto.SurveyCompleteRequest; import com.ccapp.ccgo.dto.TeamResponseDto; +import com.ccapp.ccgo.service.TeamMemberService; import com.ccapp.ccgo.user.User; import com.ccapp.ccgo.jwt.LoginUserDetails; import com.ccapp.ccgo.repository.TeamMemberRepository; @@ -8,9 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; @@ -20,6 +20,7 @@ @RequestMapping("/api/team") public class TeamController { + private final TeamMemberService teamMemberService; private final TeamMemberRepository teamMemberRepository; @GetMapping("/mine") @@ -38,4 +39,18 @@ public ResponseEntity> getMyTeams( return ResponseEntity.ok(result); } + + + @PostMapping("/survey/complete") + public ResponseEntity completeSurvey(@RequestBody SurveyCompleteRequest request, + @AuthenticationPrincipal LoginUserDetails loginUserDetails) { + System.out.print("프로그램ㅅ ㅣ작"); + User currentUser = loginUserDetails.getUser(); + System.out.print("1"); + teamMemberService.markSurveyCompleted(currentUser.getId(), request.getTeamId()); + System.out.print("2"); + return ResponseEntity.ok().build(); + } + + } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/dto/SurveyCompleteRequest.java b/src/main/java/com/ccapp/ccgo/dto/SurveyCompleteRequest.java new file mode 100644 index 0000000..cb18155 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/dto/SurveyCompleteRequest.java @@ -0,0 +1,8 @@ +package com.ccapp.ccgo.dto; + +import lombok.Getter; + +@Getter +public class SurveyCompleteRequest { + private Long teamId; +} diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java index b8061a8..edf8d3d 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java @@ -17,9 +17,10 @@ public class MissionController { @PostMapping("/refresh") public ResponseEntity refreshPartnerMission( @RequestParam Long teamId, - @RequestParam Long userId + @RequestParam Long userId, + @RequestParam int score ) { - PartnerMissionDto partnerMissionDto = missionService.refreshPartnerMission(teamId, userId); + PartnerMissionDto partnerMissionDto = missionService.refreshPartnerMission(teamId, userId,score); return ResponseEntity.ok(partnerMissionDto); } } diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index dd30aff..3a196b9 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -8,7 +8,7 @@ public interface MissionTemplateRepository extends JpaRepository { - List findByScoreOrderById(int score); + List findByScore(int score); @Query("SELECT MAX(m.id) FROM MissionTemplate m") Long findMaxId(); diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java index abe9ec5..fbc6d14 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java @@ -1,4 +1,5 @@ package com.ccapp.ccgo.mission.repository; +import com.ccapp.ccgo.mission.domain.MissionTemplate; import com.ccapp.ccgo.mission.domain.Partner; import com.ccapp.ccgo.mission.domain.PartnerMission; import org.springframework.data.jpa.repository.JpaRepository; @@ -7,10 +8,10 @@ import java.util.Optional; public interface PartnerMissionRepository extends JpaRepository { + // 활성화된 점수별 미션 조회 + List findByPartnerAndIsActiveTrueAndTemplateScore(Partner partner, int score); - List findByPartnerId(Long partnerId); + // 최근 3개 미션 조회 (dueDate 기준 내림차순) + List findTop3ByPartnerOrderByDueDateDesc(Partner partner); - List findByPartnerAndIsActiveTrue(Partner partner); - - Optional findTopByPartnerOrderByIdDesc(Partner partner); } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java index 36443f6..507b824 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java @@ -6,6 +6,6 @@ public interface MissionService { - // 특정 팀 내에서 유저의 파트너 미션 갱신 - PartnerMissionDto refreshPartnerMission(Long teamId, Long userId); + PartnerMissionDto refreshPartnerMission(Long teamId, Long userId, int score); + } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java index ea37b1f..2baafa9 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java @@ -14,6 +14,7 @@ import java.time.LocalDate; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -28,7 +29,7 @@ public class MissionServiceImpl implements MissionService { private final TeamRepository teamRepository; @Override - public PartnerMissionDto refreshPartnerMission(Long teamId, Long userId) { + public PartnerMissionDto refreshPartnerMission(Long teamId, Long userId, int score) { User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("유저 없음")); @@ -38,28 +39,44 @@ public PartnerMissionDto refreshPartnerMission(Long teamId, Long userId) { Partner partner = partnerRepository.findByTeamAndUser1OrTeamAndUser2(team, user, team, user) .orElseThrow(() -> new IllegalStateException("해당 팀에서 짝이 없음")); - // 기존 활성 미션 비활성화 - List activeMissions = partnerMissionRepository.findByPartnerAndIsActiveTrue(partner); + // 해당 점수 미션 중 기존 활성 미션 비활성화 + List activeMissions = partnerMissionRepository + .findByPartnerAndIsActiveTrueAndTemplateScore(partner, score); for (PartnerMission pm : activeMissions) { pm.setActive(false); partnerMissionRepository.save(pm); } - // 마지막 미션 기반으로 다음 템플릿 선택 - Long nextTemplateId = partnerMissionRepository.findTopByPartnerOrderByIdDesc(partner) - .map(pm -> pm.getTemplate().getId() + 1) - .orElse(1L); - if (nextTemplateId > 50L) nextTemplateId = 1L; + // 최근 3개 미션 조회해서 제외할 템플릿 ID 리스트 만들기 + List recentMissions = partnerMissionRepository + .findTop3ByPartnerOrderByDueDateDesc(partner); + List excludeIds = recentMissions.stream() + .map(pm -> pm.getTemplate().getId()) + .collect(Collectors.toList()); - MissionTemplate template = missionTemplateRepository.findById(nextTemplateId) - .orElseThrow(() -> new RuntimeException("템플릿 미션 없음")); + // 점수별 전체 미션 리스트 조회 + List allTemplates = missionTemplateRepository.findByScore(score); + + // 제외 리스트에 없는 미션만 후보로 필터링 + List candidateTemplates = allTemplates.stream() + .filter(mt -> !excludeIds.contains(mt.getId())) + .collect(Collectors.toList()); + + // 후보가 없으면 전체에서 랜덤 선택 + if (candidateTemplates.isEmpty()) { + candidateTemplates = allTemplates; + } + + // 랜덤 미션 선택 + int randomIndex = (int) (Math.random() * candidateTemplates.size()); + MissionTemplate selectedTemplate = candidateTemplates.get(randomIndex); PartnerMission newMission = PartnerMission.builder() .partner(partner) - .template(template) - .title(template.getTitle()) - .description(template.getDescription()) - .dueDate(LocalDate.now().plusDays(7)) + .template(selectedTemplate) + .title(selectedTemplate.getTitle()) + .description(selectedTemplate.getDescription()) + .dueDate(LocalDate.now().plusDays(14)) .isActive(true) .build(); diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java index dee546e..7f42592 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java @@ -13,6 +13,9 @@ */ public interface TeamMemberRepository extends JpaRepository { + Optional findByUser_IdAndTeam_TeamId(Long userId, Long teamId); + + // 한 유저가 이미 어떤 팀에 속해있는지 검사 boolean existsByUser(User user); diff --git a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java new file mode 100644 index 0000000..0130511 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java @@ -0,0 +1,24 @@ +package com.ccapp.ccgo.service; + +import com.ccapp.ccgo.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.TeamMember; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TeamMemberService { + + private final TeamMemberRepository teamMemberRepository; + + @Transactional + public void markSurveyCompleted(Long userId, Long teamId) { + TeamMember teamMember = teamMemberRepository + .findByUser_IdAndTeam_TeamId(userId, teamId) + .orElseThrow(() -> new RuntimeException("팀 멤버를 찾을 수 없습니다.")); + + teamMember.setSurveyCompleted(true); // ← 여기서 DB의 isSurveyCompleted를 true로 변경 + } + +} diff --git a/src/main/resources/MissionInsert.sql b/src/main/resources/MissionInsert.sql index 3963e49..7b8b21d 100644 --- a/src/main/resources/MissionInsert.sql +++ b/src/main/resources/MissionInsert.sql @@ -1,6 +1,5 @@ -INSERT INTO mission_template (title, description, score) -VALUES --- 1학점 +USE newuser; +INSERT INTO mission_template (title, description, score) VALUES (' 미션 1', '서로의 첫인상 공유하기', 1), (' 미션 2', '인스타 맞팔하기', 1), (' 미션 3', '매점가서 서로 아이스크림 사주기', 1), @@ -22,7 +21,7 @@ VALUES (' 미션 19', '옛날 사진 공유하기', 1), (' 미션 20', '짧은 응원 메시지 보내기', 1), (' 미션 21', '최근 꾼 꿈 공유하기', 1), - (' 미션 22', '가위 바위 보', 1), + (' 미션 22', '가위 바위 보 진사람이 딱밤맞기', 1), (' 미션 23', '게시판에서 흥미로운 공지 찾아 사진 찍기', 1), (' 미션 24', '에타 시간표 공유하기', 1), (' 미션 25', '교내에서 처음 가보는 건물 탐방하기', 1), @@ -30,9 +29,7 @@ VALUES (' 미션 27', '자주 가는 장소 찍어오기', 1), (' 미션 28', '같이 노래 한 곡 부르기', 1), (' 미션 29', '팔씨름 하기', 1), - (' 미션 30', '카톡 이름 애칭으로 바꾸기', 1), - --- 3 학점 짜리 + (' 미션 30', '상대방 카톡 별명으로 바꾸기', 1), (' 미션 1', '초상화 그려주기', 3), (' 미션 2', '교내 카페 음료 마시기', 3), (' 미션 3', '매점가서 서로 아이스크림 사주기', 3), @@ -63,55 +60,44 @@ VALUES (' 미션 28', '제일 큰 나무 찾아서 사진 찍기', 3), (' 미션 29', '같이 공부하기', 3), (' 미션 30', '옷 맞춰입기', 3), - --- 5학점 - (' 미션 1', '옷 맞춰입기', 1), - (' 미션 2', '인스타 맞팔하기', 1), - (' 미션 3', '매점가서 서로 아이스크림 사주기', 1), - (' 미션 4', '기도제목 공유하기', 1), - (' 미션 5', '셀카 같이 찍기', 1), - (' 미션 6', '노래 추천해주기', 1), - (' 미션 7', '공통 관심사 하나 찾기', 1), - (' 미션 8', '간식 선물하기', 1), - (' 미션 9', '서로 MBTI 말해주기', 1), - (' 미션 10', '닮은꼴 캐릭터 찾기', 1), - (' 미션 11', '', 1), - (' 미션 12', '옷 맞춰입기', 1), - (' 미션 13', '옷 맞춰입기', 1), - (' 미션 14', '옷 맞춰입기', 1), - (' 미션 15', '옷 맞춰입기', 1), - (' 미션 16', '옷 맞춰입기', 1), - (' 미션 17', '옷 맞춰입기', 1), - (' 미션 18', '옷 맞춰입기', 1), - (' 미션 19', '옷 맞춰입기', 1), - (' 미션 20', '옷 맞춰입기', 1), - (' 미션 21', '옷 맞춰입기', 1), - (' 미션 22', '옷 맞춰입기', 1), - (' 미션 23', '옷 맞춰입기', 1), - (' 미션 24', '옷 맞춰입기', 1), - (' 미션 25', '옷 맞춰입기', 1), - (' 미션 26', '옷 맞춰입기', 1), - (' 미션 27', '옷 맞춰입기', 1), - (' 미션 28', '옷 맞춰입기', 1), - (' 미션 29', '옷 맞춰입기', 1), - (' 미션 30', '옷 맞춰입기', 1), - (' 미션 31', '옷 맞춰입기', 1), - (' 미션 32', '옷 맞춰입기', 1), - (' 미션 33', '옷 맞춰입기', 1), - (' 미션 34', '옷 맞춰입기', 1), - (' 미션 35', '옷 맞춰입기', 1), - (' 미션 36', '옷 맞춰입기', 1), - (' 미션 37', '옷 맞춰입기', 1), - (' 미션 38', '옷 맞춰입기', 1), - (' 미션 39', '옷 맞춰입기', 1), - (' 미션 40', '옷 맞춰입기', 1), - (' 미션 41', '옷 맞춰입기', 1), - (' 미션 42', '옷 맞춰입기', 1), - (' 미션 43', '옷 맞춰입기', 1), - (' 미션 44', '옷 맞춰입기', 1), - (' 미션 45', '옷 맞춰입기', 1), - (' 미션 46', '옷 맞춰입기', 1), - (' 미션 47', '옷 맞춰입기', 1), - (' 미션 48', '옷 맞춰입기', 1), - (' 미션 49', '옷 맞춰입기', 1), - (' 미션 50', '옷 맞춰입기', 1); + (' 미션 1', '둘이서 야식먹기', 5), + (' 미션 2', '손편지 써주기', 5), + (' 미션 3', '3시간 이상 같이 있기', 5), + (' 미션 4', '둘이서 천마지 가기', 5), + (' 미션 5', '그레이스스쿨 별 보러 가기', 5), + (' 미션 6', 'SNS에 태그하기', 5), + (' 미션 7', '강물 예배가기', 5), + (' 미션 8', '서로 노래 불러주기', 5), + (' 미션 9', '다른 cc팀이랑 밥먹기', 5), + (' 미션 10', '엽사 찍기', 5), + (' 미션 11', '둘만의 단톡방 만들기', 5), + (' 미션 12', '1분 동안 눈 마주치기', 5), + (' 미션 13', '하루동안 서로 반말쓰기', 5), + (' 미션 14', '서로 눈 감고 서로 그리기', 5), + (' 미션 15', '같이 보드게임 하기', 5), + (' 미션 16', '볼에 스티커 붙이고 다니기', 5), + (' 미션 17', '같이 게임 하기', 5), + (' 미션 18', '당연하지 게임하기(총 10번)', 5), + (' 미션 19', '같이 운동하기', 5), + (' 미션 20', '교내 모든 건물 앞에서 사진 찍기', 5), +(' 미션 1', '인생네컷 찍기', 10), +(' 미션 2', '전화 10분이상하기', 10), +(' 미션 3', '노래방 가기', 10), +(' 미션 4', '단둘이 교내에서 영화 보기', 10), +(' 미션 5', '교외 카페 가기', 10), +(' 미션 6', '같이 바다가서 인증샷 찍기', 10), +(' 미션 7', '같이 릴스 찍기', 10), +(' 미션 8', '모든 팀사람과 사진 찍기', 10), +(' 미션 9', '영일대 놀러가기', 10), +(' 미션 10', '더블 데이트', 10), +(' 미션 11', '같이 새벽 예배가기', 10), +(' 미션 12', '우산 1개 쓰고 한한하기', 10), +(' 미션 13', '스페이스워크에서 사진찍기', 10), +(' 미션 14', '2인 브이로그 찍기', 10), +(' 미션 15', '둘이서 밤 산책하기', 10), +(' 미션 16', '서로 프로필 사진 만들어주기', 10), +(' 미션 17', '틱톡 챌린지 따라하기', 10), +(' 미션 18', '같이 드라마 보기', 10), +(' 미션 19', '영일대 불꽃놀이 하기', 10), +(' 미션 20', '두 사람만의 엽기 포즈 찍기', 10); + \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 22f7f7e..65d55a6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = qkrwlsdn +spring.datasource.password = 06250730cc jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 252064b022ef1f8a30e303a70da4b3410d6feff3 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 9 Jul 2025 16:11:26 +0900 Subject: [PATCH 018/132] Edit mbti --- src/main/java/com/ccapp/ccgo/team/TeamMember.java | 5 +++++ src/main/java/com/ccapp/ccgo/user/User.java | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/TeamMember.java index 8fd4c50..873faf4 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/TeamMember.java @@ -60,4 +60,9 @@ public class TeamMember { @Column(nullable = false) private boolean isSurveyCompleted = false; // 설문조사 기본값 false + // MBTI 필드 + @Column(name = "mbti") + private String mbti; + + } diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index e217400..cfbcc0a 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -49,10 +49,6 @@ public class User { @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; - // MBTI 필드 - @Column(name = "mbti") - private String mbti; - // 회원 가입 시 자동으로 현재 시간 설정 @PrePersist public void prePersist() { From eeeb4ccce9975e84deba1b381c4e6c2652d6a0bd Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 9 Jul 2025 17:27:08 +0900 Subject: [PATCH 019/132] . --- .../com/ccapp/ccgo/dto/AnswerRequestDto.java | 2 + .../ccapp/ccgo/service/MatchingService.java | 72 ++++++++++++++----- src/main/java/com/ccapp/ccgo/user/User.java | 1 + 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java b/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java index d127921..a0001d9 100644 --- a/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java @@ -10,5 +10,7 @@ @Builder public class AnswerRequestDto { private Long userId; + private String mbti; + private Long teamId; private List answers; } diff --git a/src/main/java/com/ccapp/ccgo/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/service/MatchingService.java index e21ebfb..124bad7 100644 --- a/src/main/java/com/ccapp/ccgo/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/service/MatchingService.java @@ -29,6 +29,7 @@ public class MatchingService { private static final double SIMILARITY_WEIGHT = 0.5; private final MbtiScoreProvider mbtiScoreProvider; + private final UserRepository userRepository; @Transactional public MatchingResponseDto performMatching(Long teamId) { @@ -139,8 +140,8 @@ private Map> createCandidateMap(List groupA, List tempList = new ArrayList<>(); for (TeamMember tmB : groupB) { - int mbtiScore = calculateMbtiTotalScore(tmA.getUser(), tmB.getUser()); - double similarityScore = calculateSimilarityScore(tmA.getUser(), tmB.getUser(), teamId); + int mbtiScore = calculateMbtiTotalScore(tmA, tmB); + double similarityScore = calculateSimilarityScore(tmA, tmB, teamId); // 가중치 계산으로 조정 double totalScore = mbtiScore * MBTI_WEIGHT + similarityScore * SIMILARITY_WEIGHT; @@ -186,9 +187,14 @@ private List generateAllPairMatches(Map> candid // A→B, B→A MBTI 점수를 각각 구해서 합산 // 대칭적이지 않을 수도 있다는 점 고려 (현재 데이터는 대칭적임) - private int calculateMbtiTotalScore(User a, User b) { - int scoreAtoB = mbtiScoreProvider.getScore(a.getMbti(), b.getMbti()); - int scoreBtoA = mbtiScoreProvider.getScore(b.getMbti(), a.getMbti()); + private int calculateMbtiTotalScore(TeamMember a, TeamMember b) { + String mbtiA = a.getMbti(); + String mbtiB = b.getMbti(); + if (mbtiA == null || mbtiB == null) { + return 0; // mbti가 없으면 점수 0 처리하거나, 다른 정책 적용 + } + int scoreAtoB = mbtiScoreProvider.getScore(mbtiA, mbtiB); + int scoreBtoA = mbtiScoreProvider.getScore(mbtiB, mbtiA); return scoreAtoB + scoreBtoA; } @@ -196,9 +202,12 @@ private int calculateMbtiTotalScore(User a, User b) { // 각 질문 별로 A와 B의 점수 차이를 계산 → 유사도 환산 // 차이가 0이면 유사도 5, 차이가 5면 유사도 0 // 전체 유사도 점수 → 100% 환산 - private double calculateSimilarityScore(User a, User b, Long teamId) { - List answersA = answerRepository.findByUser_Id(a.getId()); - List answersB = answerRepository.findByUser_Id(b.getId()); + private double calculateSimilarityScore(TeamMember a, TeamMember b, Long teamId) { + Long userIdA = a.getUser().getId(); + Long userIdB = b.getUser().getId(); + + List answersA = answerRepository.findByUser_Id(userIdA); + List answersB = answerRepository.findByUser_Id(userIdB); // 팀별 질문 수 확보 List questions = questionRepository.findByTeam_TeamId(teamId); @@ -322,15 +331,21 @@ private MatchingResponseDto buildMatchingResponseDto(Team team, List s for (SubGroup sg : subGroups) { List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + List userDtos = members.stream() .map(sgm -> { - User u = sgm.getUser(); + User user = sgm.getUser(); + + // 유저와 팀 기반으로 TeamMember 정보 조회 + TeamMember teamMember = teamMemberRepository.findByUser_IdAndTeam_TeamId(user.getId(), team.getTeamId()) + .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); + return UserResponseDto.builder() - .id(u.getId()) - .name(u.getName()) - .email(u.getEmail()) - .gender(u.getGender()) - .mbti(u.getMbti()) + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .gender(user.getGender()) + .mbti(teamMember.getMbti()) // ✅ 여기에서 MBTI 가져오기 .build(); }) .collect(Collectors.toList()); @@ -349,6 +364,7 @@ private MatchingResponseDto buildMatchingResponseDto(Team team, List s .build(); } + /** * 내부 클래스 PairMatch */ @@ -368,21 +384,41 @@ static class PairMatch { @Transactional public void saveAnswers(AnswerRequestDto dto) { Long userId = dto.getUserId(); + Long teamId = dto.getTeamId(); - // 기존 Answer 전부 삭제 - List existingAnswers = answerRepository.findByUser_Id(userId); + // 해당 팀의 질문만 필터링해서 삭제 + List teamQuestions = questionRepository.findByTeam_TeamId(teamId); + List teamQuestionIds = teamQuestions.stream() + .map(Question::getId) + .collect(Collectors.toList()); + List existingAnswers = answerRepository.findByUser_Id(userId).stream() + .filter(ans -> teamQuestionIds.contains(ans.getQuestion().getId())) + .collect(Collectors.toList()); answerRepository.deleteAll(existingAnswers); - // 새 Answer 저장 + // 새로 저장 << 이게 무슨 뜻이징 + + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + List newAnswers = dto.getAnswers().stream() .map(single -> Answer.builder() - .user(User.builder().id(userId).build()) + .user(user) // 영속 상태의 User 엔티티 .question(Question.builder().id(single.getQuestionId()).build()) .score(single.getScore()) .build()) .collect(Collectors.toList()); answerRepository.saveAll(newAnswers); + + if (dto.getMbti() != null && !dto.getMbti().isEmpty()) { + TeamMember teamMember = teamMemberRepository + .findByUser_IdAndTeam_TeamId(userId, teamId) + .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); + + teamMember.setMbti(dto.getMbti()); + teamMemberRepository.save(teamMember); + } } diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/User.java index cfbcc0a..6b1a492 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/User.java @@ -58,6 +58,7 @@ public void prePersist() { //관심사 설문조사 완료여부 @Column(name = "test_completed", nullable = false) private boolean isInterestCompleted = false; + } From a757f13734368c219dc77a63916d1c663c9e3b37 Mon Sep 17 00:00:00 2001 From: Losecow <100589396+Losecow@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:59:27 +0900 Subject: [PATCH 020/132] refacroting --- .../{ => auth}/controller/AuthController.java | 30 +++++++--------- .../ccgo/{ => auth}/dto/LoginRequestDto.java | 2 +- .../ccgo/{ => auth}/dto/LoginResponseDto.java | 3 +- .../ccgo/{ => auth}/dto/TokenResponseDto.java | 2 +- .../jwt/JwtAuthenticationFilter.java | 3 +- .../ccgo/{ => auth}/jwt/JwtProvider.java | 2 +- .../ccapp/ccgo/{ => auth}/jwt/JwtToken.java | 2 +- .../ccgo/{ => auth}/jwt/LoginUserDetails.java | 6 ++-- .../ccgo/{ => auth}/jwt/SecurityConfig.java | 3 +- .../service}/LoginUserDetailsService.java | 7 ++-- .../exception/CustomException.java | 2 +- .../exception/GlobalExceptionHandler.java | 2 +- .../test}/BcryptTestController.java | 2 +- .../controller/InviteCodeController.java | 34 +++++++------------ .../dto/InviteCodeCreateRequestDto.java | 2 +- .../dto/InviteCodeCreateResponseDto.java | 2 +- .../dto/InviteCodeJoinRequestDto.java | 2 +- .../dto/InviteCodeJoinResponseDto.java | 2 +- .../repository/InviteCodeRepository.java | 6 ++-- .../service/InviteCodeService.java | 18 +++++----- .../controller/MatchingController.java | 12 ++++--- .../matching/{ => domain}/MbtiScoreId.java | 2 +- .../{ => domain}/MbtiScoreProvider.java | 5 +-- .../matching/{ => domain/entity}/Answer.java | 4 +-- .../{ => domain/entity}/MbtiScore.java | 3 +- .../{ => domain/entity}/Question.java | 4 +-- .../{ => domain/entity}/SubGroup.java | 4 +-- .../{ => domain/entity}/SubGroupMember.java | 4 +-- .../dto/MatchingResponseDto.java | 2 +- .../{ => matching}/dto/MatchingResultDto.java | 3 +- .../repository/MbtiScoreRepository.java | 6 ++-- .../repository/SubGroupMemberRepository.java | 6 ++-- .../repository/SubGroupRepository.java | 4 +-- .../service/MatchingService.java | 30 +++++++++++----- .../ccgo/mission/dto/MissionTemplateDto.java | 2 +- .../ccapp/ccgo/mission/dto/PartnerDto.java | 2 +- .../ccgo/mission/dto/PartnerMissionDto.java | 2 +- .../ccgo/mission/dto/UserMissionDto.java | 2 +- .../{domain => entity}/MissionTemplate.java | 2 +- .../mission/{domain => entity}/Partner.java | 6 ++-- .../{domain => entity}/PartnerMission.java | 2 +- .../{domain => entity}/UserMission.java | 4 +-- .../repository/MissionTemplateRepository.java | 2 +- .../repository/PartnerMissionRepository.java | 4 +-- .../mission/repository/PartnerRepository.java | 6 ++-- .../repository/UserMissionRepository.java | 2 +- .../ccgo/mission/service/MissionService.java | 2 -- .../mission/service/MissionServiceImpl.java | 10 +++--- .../ccgo/mission/service/PartnerService.java | 5 ++- .../mission/service/PartnerServiceImpl.java | 16 ++++----- .../{ => question}/dto/AnswerRequestDto.java | 2 +- .../dto/QuestionRequestDto.java | 2 +- .../dto/QuestionResponseDto.java | 2 +- .../{ => question}/dto/QuestionUpdateDto.java | 2 +- .../{ => question}/dto/SingleAnswerDto.java | 2 +- .../repository/AnswerRepository.java | 4 +-- .../repository/QuestionRepository.java | 4 +-- .../{ => team}/controller/TeamController.java | 10 +++--- .../{ => team}/dto/TeamMemberResponseDto.java | 2 +- .../ccgo/{ => team}/dto/TeamRequestDto.java | 2 +- .../ccgo/{ => team}/dto/TeamResponseDto.java | 2 +- .../ccgo/team/{ => entity}/InviteCode.java | 2 +- .../ccapp/ccgo/team/{ => entity}/Team.java | 2 +- .../ccgo/team/{ => entity}/TeamMember.java | 4 +-- .../repository/TeamMemberRepository.java | 8 ++--- .../{ => team}/repository/TeamRepository.java | 4 +-- .../{ => user}/controller/UserController.java | 8 ++--- .../ccgo/{ => user}/dto/UserRequestDto.java | 3 +- .../ccgo/{ => user}/dto/UserResponseDto.java | 2 +- .../ccapp/ccgo/user/{ => entity}/User.java | 3 +- .../ccgo/{dto => user/mapper}/UserMapper.java | 6 ++-- .../{ => user}/repository/UserRepository.java | 4 +-- .../ccgo/{ => user}/service/UserService.java | 16 ++++----- 73 files changed, 195 insertions(+), 191 deletions(-) rename src/main/java/com/ccapp/ccgo/{ => auth}/controller/AuthController.java (89%) rename src/main/java/com/ccapp/ccgo/{ => auth}/dto/LoginRequestDto.java (94%) rename src/main/java/com/ccapp/ccgo/{ => auth}/dto/LoginResponseDto.java (90%) rename src/main/java/com/ccapp/ccgo/{ => auth}/dto/TokenResponseDto.java (87%) rename src/main/java/com/ccapp/ccgo/{ => auth}/jwt/JwtAuthenticationFilter.java (96%) rename src/main/java/com/ccapp/ccgo/{ => auth}/jwt/JwtProvider.java (98%) rename src/main/java/com/ccapp/ccgo/{ => auth}/jwt/JwtToken.java (89%) rename src/main/java/com/ccapp/ccgo/{ => auth}/jwt/LoginUserDetails.java (88%) rename src/main/java/com/ccapp/ccgo/{ => auth}/jwt/SecurityConfig.java (97%) rename src/main/java/com/ccapp/ccgo/{jwt => auth/service}/LoginUserDetailsService.java (83%) rename src/main/java/com/ccapp/ccgo/{ => common}/exception/CustomException.java (91%) rename src/main/java/com/ccapp/ccgo/{ => common}/exception/GlobalExceptionHandler.java (98%) rename src/main/java/com/ccapp/ccgo/{user => common/test}/BcryptTestController.java (95%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/controller/InviteCodeController.java (78%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/dto/InviteCodeCreateRequestDto.java (63%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/dto/InviteCodeCreateResponseDto.java (83%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/dto/InviteCodeJoinRequestDto.java (80%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/dto/InviteCodeJoinResponseDto.java (86%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/repository/InviteCodeRepository.java (84%) rename src/main/java/com/ccapp/ccgo/{ => invitecode}/service/InviteCodeService.java (90%) rename src/main/java/com/ccapp/ccgo/{ => matching}/controller/MatchingController.java (80%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain}/MbtiScoreId.java (83%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain}/MbtiScoreProvider.java (86%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain/entity}/Answer.java (89%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain/entity}/MbtiScore.java (81%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain/entity}/Question.java (87%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain/entity}/SubGroup.java (87%) rename src/main/java/com/ccapp/ccgo/matching/{ => domain/entity}/SubGroupMember.java (87%) rename src/main/java/com/ccapp/ccgo/{ => matching}/dto/MatchingResponseDto.java (86%) rename src/main/java/com/ccapp/ccgo/{ => matching}/dto/MatchingResultDto.java (80%) rename src/main/java/com/ccapp/ccgo/{ => matching}/repository/MbtiScoreRepository.java (57%) rename src/main/java/com/ccapp/ccgo/{ => matching}/repository/SubGroupMemberRepository.java (68%) rename src/main/java/com/ccapp/ccgo/{ => matching}/repository/SubGroupRepository.java (68%) rename src/main/java/com/ccapp/ccgo/{ => matching}/service/MatchingService.java (93%) rename src/main/java/com/ccapp/ccgo/mission/{domain => entity}/MissionTemplate.java (92%) rename src/main/java/com/ccapp/ccgo/mission/{domain => entity}/Partner.java (80%) rename src/main/java/com/ccapp/ccgo/mission/{domain => entity}/PartnerMission.java (93%) rename src/main/java/com/ccapp/ccgo/mission/{domain => entity}/UserMission.java (90%) rename src/main/java/com/ccapp/ccgo/{ => question}/dto/AnswerRequestDto.java (84%) rename src/main/java/com/ccapp/ccgo/{ => question}/dto/QuestionRequestDto.java (83%) rename src/main/java/com/ccapp/ccgo/{ => question}/dto/QuestionResponseDto.java (80%) rename src/main/java/com/ccapp/ccgo/{ => question}/dto/QuestionUpdateDto.java (78%) rename src/main/java/com/ccapp/ccgo/{ => question}/dto/SingleAnswerDto.java (81%) rename src/main/java/com/ccapp/ccgo/{ => question}/repository/AnswerRepository.java (72%) rename src/main/java/com/ccapp/ccgo/{ => question}/repository/QuestionRepository.java (68%) rename src/main/java/com/ccapp/ccgo/{ => team}/controller/TeamController.java (83%) rename src/main/java/com/ccapp/ccgo/{ => team}/dto/TeamMemberResponseDto.java (84%) rename src/main/java/com/ccapp/ccgo/{ => team}/dto/TeamRequestDto.java (76%) rename src/main/java/com/ccapp/ccgo/{ => team}/dto/TeamResponseDto.java (83%) rename src/main/java/com/ccapp/ccgo/team/{ => entity}/InviteCode.java (96%) rename src/main/java/com/ccapp/ccgo/team/{ => entity}/Team.java (95%) rename src/main/java/com/ccapp/ccgo/team/{ => entity}/TeamMember.java (95%) rename src/main/java/com/ccapp/ccgo/{ => team}/repository/TeamMemberRepository.java (85%) rename src/main/java/com/ccapp/ccgo/{ => team}/repository/TeamRepository.java (85%) rename src/main/java/com/ccapp/ccgo/{ => user}/controller/UserController.java (81%) rename src/main/java/com/ccapp/ccgo/{ => user}/dto/UserRequestDto.java (86%) rename src/main/java/com/ccapp/ccgo/{ => user}/dto/UserResponseDto.java (92%) rename src/main/java/com/ccapp/ccgo/user/{ => entity}/User.java (95%) rename src/main/java/com/ccapp/ccgo/{dto => user/mapper}/UserMapper.java (83%) rename src/main/java/com/ccapp/ccgo/{ => user}/repository/UserRepository.java (72%) rename src/main/java/com/ccapp/ccgo/{ => user}/service/UserService.java (89%) diff --git a/src/main/java/com/ccapp/ccgo/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java similarity index 89% rename from src/main/java/com/ccapp/ccgo/controller/AuthController.java rename to src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java index d08f181..bec1273 100644 --- a/src/main/java/com/ccapp/ccgo/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java @@ -1,18 +1,16 @@ -package com.ccapp.ccgo.controller; - -import com.ccapp.ccgo.common.Role; -import com.ccapp.ccgo.dto.TokenResponseDto; -import com.ccapp.ccgo.jwt.LoginUserDetailsService; -import com.ccapp.ccgo.repository.TeamMemberRepository; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.team.TeamMember; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.dto.LoginRequestDto; -import com.ccapp.ccgo.dto.LoginResponseDto; -import com.ccapp.ccgo.jwt.JwtProvider; -import com.ccapp.ccgo.jwt.LoginUserDetails; +package com.ccapp.ccgo.auth.controller; + +import com.ccapp.ccgo.auth.dto.LoginRequestDto; +import com.ccapp.ccgo.auth.dto.LoginResponseDto; +import com.ccapp.ccgo.auth.dto.TokenResponseDto; +import com.ccapp.ccgo.auth.jwt.JwtProvider; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; +import com.ccapp.ccgo.auth.service.LoginUserDetailsService; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.user.entity.User; import lombok.extern.slf4j.Slf4j; -import com.ccapp.ccgo.repository.TeamRepository; +import com.ccapp.ccgo.team.repository.TeamRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseCookie; @@ -21,15 +19,11 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import org.springframework.http.HttpHeaders; -import java.time.LocalDateTime; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; @Slf4j @RestController diff --git a/src/main/java/com/ccapp/ccgo/dto/LoginRequestDto.java b/src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java similarity index 94% rename from src/main/java/com/ccapp/ccgo/dto/LoginRequestDto.java rename to src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java index 7230285..f31f832 100644 --- a/src/main/java/com/ccapp/ccgo/dto/LoginRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.auth.dto; import lombok.Getter; import jakarta.validation.constraints.Email; diff --git a/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java b/src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java similarity index 90% rename from src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java rename to src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java index 8bba79c..7658443 100644 --- a/src/main/java/com/ccapp/ccgo/dto/LoginResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java @@ -1,6 +1,5 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.auth.dto; -import jakarta.persistence.Column; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/dto/TokenResponseDto.java b/src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java similarity index 87% rename from src/main/java/com/ccapp/ccgo/dto/TokenResponseDto.java rename to src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java index fd9e5cc..0adc131 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TokenResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.auth.dto; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/ccapp/ccgo/jwt/JwtAuthenticationFilter.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java similarity index 96% rename from src/main/java/com/ccapp/ccgo/jwt/JwtAuthenticationFilter.java rename to src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java index 693d646..0ae60eb 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ -package com.ccapp.ccgo.jwt; +package com.ccapp.ccgo.auth.jwt; +import com.ccapp.ccgo.auth.service.LoginUserDetailsService; import jakarta.servlet.FilterChain; import jakarta.servlet.http.Cookie; import jakarta.servlet.ServletException; diff --git a/src/main/java/com/ccapp/ccgo/jwt/JwtProvider.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java similarity index 98% rename from src/main/java/com/ccapp/ccgo/jwt/JwtProvider.java rename to src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java index bd6b35b..ccd2abe 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/JwtProvider.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.jwt; +package com.ccapp.ccgo.auth.jwt; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; diff --git a/src/main/java/com/ccapp/ccgo/jwt/JwtToken.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtToken.java similarity index 89% rename from src/main/java/com/ccapp/ccgo/jwt/JwtToken.java rename to src/main/java/com/ccapp/ccgo/auth/jwt/JwtToken.java index 26fbaf1..1133e00 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/JwtToken.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtToken.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.jwt; +package com.ccapp.ccgo.auth.jwt; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java b/src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java similarity index 88% rename from src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java rename to src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java index 81db6c0..e75c88b 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetails.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java @@ -1,15 +1,13 @@ -package com.ccapp.ccgo.jwt; +package com.ccapp.ccgo.auth.jwt; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import java.util.Collection; import java.util.Collections; -import java.util.List; @Getter public class LoginUserDetails implements UserDetails { diff --git a/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/auth/jwt/SecurityConfig.java similarity index 97% rename from src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java rename to src/main/java/com/ccapp/ccgo/auth/jwt/SecurityConfig.java index b1173b4..5ece0f1 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/SecurityConfig.java @@ -1,5 +1,6 @@ -package com.ccapp.ccgo.jwt; +package com.ccapp.ccgo.auth.jwt; +import com.ccapp.ccgo.auth.service.LoginUserDetailsService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetailsService.java b/src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/jwt/LoginUserDetailsService.java rename to src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java index 3d5ea1d..97899f7 100644 --- a/src/main/java/com/ccapp/ccgo/jwt/LoginUserDetailsService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java @@ -1,7 +1,8 @@ -package com.ccapp.ccgo.jwt; +package com.ccapp.ccgo.auth.service; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.repository.UserRepository; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; +import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.user.repository.UserRepository; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; diff --git a/src/main/java/com/ccapp/ccgo/exception/CustomException.java b/src/main/java/com/ccapp/ccgo/common/exception/CustomException.java similarity index 91% rename from src/main/java/com/ccapp/ccgo/exception/CustomException.java rename to src/main/java/com/ccapp/ccgo/common/exception/CustomException.java index 24b2320..89e4d37 100644 --- a/src/main/java/com/ccapp/ccgo/exception/CustomException.java +++ b/src/main/java/com/ccapp/ccgo/common/exception/CustomException.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.exception; +package com.ccapp.ccgo.common.exception; import org.springframework.http.HttpStatus; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/exception/GlobalExceptionHandler.java b/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java similarity index 98% rename from src/main/java/com/ccapp/ccgo/exception/GlobalExceptionHandler.java rename to src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java index e6be98f..b3946ca 100644 --- a/src/main/java/com/ccapp/ccgo/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.exception; +package com.ccapp.ccgo.common.exception; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/ccapp/ccgo/user/BcryptTestController.java b/src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java similarity index 95% rename from src/main/java/com/ccapp/ccgo/user/BcryptTestController.java rename to src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java index e02c282..a34c230 100644 --- a/src/main/java/com/ccapp/ccgo/user/BcryptTestController.java +++ b/src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.user; +package com.ccapp.ccgo.common.test; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java similarity index 78% rename from src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java rename to src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java index efa6b12..3554ae3 100644 --- a/src/main/java/com/ccapp/ccgo/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java @@ -1,21 +1,16 @@ -package com.ccapp.ccgo.controller; - -import com.ccapp.ccgo.common.Role; -import com.ccapp.ccgo.dto.InviteCodeCreateResponseDto; -import com.ccapp.ccgo.dto.InviteCodeJoinRequestDto; -import com.ccapp.ccgo.dto.InviteCodeJoinResponseDto; -import com.ccapp.ccgo.dto.TeamRequestDto; -import com.ccapp.ccgo.exception.CustomException; -import com.ccapp.ccgo.repository.InviteCodeRepository; -import com.ccapp.ccgo.repository.TeamMemberRepository; -import com.ccapp.ccgo.repository.TeamRepository; -import com.ccapp.ccgo.repository.UserRepository; -import com.ccapp.ccgo.service.InviteCodeService; -import com.ccapp.ccgo.team.InviteCode; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.team.TeamMember; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.jwt.LoginUserDetails; +package com.ccapp.ccgo.invitecode.controller; + +import com.ccapp.ccgo.invitecode.dto.InviteCodeCreateResponseDto; +import com.ccapp.ccgo.invitecode.dto.InviteCodeJoinRequestDto; +import com.ccapp.ccgo.invitecode.dto.InviteCodeJoinResponseDto; +import com.ccapp.ccgo.team.dto.TeamRequestDto; +import com.ccapp.ccgo.invitecode.repository.InviteCodeRepository; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.repository.TeamRepository; +import com.ccapp.ccgo.invitecode.service.InviteCodeService; +import com.ccapp.ccgo.team.entity.InviteCode; +import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; import lombok.RequiredArgsConstructor; @@ -24,9 +19,6 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import java.time.LocalDateTime; -import java.util.List; - @RestController @RequestMapping("/api/invitecode") @RequiredArgsConstructor diff --git a/src/main/java/com/ccapp/ccgo/dto/InviteCodeCreateRequestDto.java b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java similarity index 63% rename from src/main/java/com/ccapp/ccgo/dto/InviteCodeCreateRequestDto.java rename to src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java index afef848..b8a864e 100644 --- a/src/main/java/com/ccapp/ccgo/dto/InviteCodeCreateRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.invitecode.dto; public class InviteCodeCreateRequestDto { private Long teamId; diff --git a/src/main/java/com/ccapp/ccgo/dto/InviteCodeCreateResponseDto.java b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateResponseDto.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/dto/InviteCodeCreateResponseDto.java rename to src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateResponseDto.java index dfea86c..5faece4 100644 --- a/src/main/java/com/ccapp/ccgo/dto/InviteCodeCreateResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.invitecode.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeJoinRequestDto.java similarity index 80% rename from src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java rename to src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeJoinRequestDto.java index 1cf527c..a57a53a 100644 --- a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeJoinRequestDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.invitecode.dto; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeJoinResponseDto.java similarity index 86% rename from src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java rename to src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeJoinResponseDto.java index 0c17413..4baafe6 100644 --- a/src/main/java/com/ccapp/ccgo/dto/InviteCodeJoinResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeJoinResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.invitecode.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java b/src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java similarity index 84% rename from src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java rename to src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java index 96d3f33..6de8a3c 100644 --- a/src/main/java/com/ccapp/ccgo/repository/InviteCodeRepository.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java @@ -1,7 +1,7 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.invitecode.repository; -import com.ccapp.ccgo.team.InviteCode; -import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.entity.InviteCode; +import com.ccapp.ccgo.team.entity.Team; import org.springframework.data.jpa.repository.JpaRepository; import java.time.LocalDateTime; diff --git a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java similarity index 90% rename from src/main/java/com/ccapp/ccgo/service/InviteCodeService.java rename to src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java index e7d01d4..a419cad 100644 --- a/src/main/java/com/ccapp/ccgo/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java @@ -1,15 +1,15 @@ -package com.ccapp.ccgo.service; +package com.ccapp.ccgo.invitecode.service; import com.ccapp.ccgo.common.Role; -import com.ccapp.ccgo.exception.CustomException; -import com.ccapp.ccgo.repository.InviteCodeRepository; -import com.ccapp.ccgo.repository.TeamMemberRepository; -import com.ccapp.ccgo.repository.TeamRepository; -import com.ccapp.ccgo.team.InviteCode; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.team.TeamMember; +import com.ccapp.ccgo.common.exception.CustomException; +import com.ccapp.ccgo.invitecode.repository.InviteCodeRepository; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.repository.TeamRepository; +import com.ccapp.ccgo.team.entity.InviteCode; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.team.entity.TeamMember; import org.springframework.scheduling.annotation.Scheduled; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java similarity index 80% rename from src/main/java/com/ccapp/ccgo/controller/MatchingController.java rename to src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index fa48689..c4b3517 100644 --- a/src/main/java/com/ccapp/ccgo/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -1,7 +1,11 @@ -package com.ccapp.ccgo.controller; - -import com.ccapp.ccgo.dto.*; -import com.ccapp.ccgo.service.MatchingService; +package com.ccapp.ccgo.matching.controller; + +import com.ccapp.ccgo.question.dto.AnswerRequestDto; +import com.ccapp.ccgo.matching.dto.MatchingResponseDto; +import com.ccapp.ccgo.question.dto.QuestionRequestDto; +import com.ccapp.ccgo.question.dto.QuestionResponseDto; +import com.ccapp.ccgo.question.dto.QuestionUpdateDto; +import com.ccapp.ccgo.matching.service.MatchingService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java b/src/main/java/com/ccapp/ccgo/matching/domain/MbtiScoreId.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java rename to src/main/java/com/ccapp/ccgo/matching/domain/MbtiScoreId.java index fd1fb0f..3071a9b 100644 --- a/src/main/java/com/ccapp/ccgo/matching/MbtiScoreId.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/MbtiScoreId.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java b/src/main/java/com/ccapp/ccgo/matching/domain/MbtiScoreProvider.java similarity index 86% rename from src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java rename to src/main/java/com/ccapp/ccgo/matching/domain/MbtiScoreProvider.java index a5e67d2..8f3d567 100644 --- a/src/main/java/com/ccapp/ccgo/matching/MbtiScoreProvider.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/MbtiScoreProvider.java @@ -1,6 +1,7 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain; -import com.ccapp.ccgo.repository.MbtiScoreRepository; +import com.ccapp.ccgo.matching.domain.entity.MbtiScore; +import com.ccapp.ccgo.matching.repository.MbtiScoreRepository; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/ccapp/ccgo/matching/Answer.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java similarity index 89% rename from src/main/java/com/ccapp/ccgo/matching/Answer.java rename to src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java index c9ce69f..5dc0eb9 100644 --- a/src/main/java/com/ccapp/ccgo/matching/Answer.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain.entity; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/matching/MbtiScore.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/MbtiScore.java similarity index 81% rename from src/main/java/com/ccapp/ccgo/matching/MbtiScore.java rename to src/main/java/com/ccapp/ccgo/matching/domain/entity/MbtiScore.java index cc9c1f1..adba3c6 100644 --- a/src/main/java/com/ccapp/ccgo/matching/MbtiScore.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/MbtiScore.java @@ -1,5 +1,6 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain.entity; +import com.ccapp.ccgo.matching.domain.MbtiScoreId; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/matching/Question.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/Question.java similarity index 87% rename from src/main/java/com/ccapp/ccgo/matching/Question.java rename to src/main/java/com/ccapp/ccgo/matching/domain/entity/Question.java index 5986dec..d7e35d8 100644 --- a/src/main/java/com/ccapp/ccgo/matching/Question.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/Question.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain.entity; -import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.entity.Team; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/matching/SubGroup.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java similarity index 87% rename from src/main/java/com/ccapp/ccgo/matching/SubGroup.java rename to src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java index 228df01..50a9a68 100644 --- a/src/main/java/com/ccapp/ccgo/matching/SubGroup.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain.entity; -import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.entity.Team; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java similarity index 87% rename from src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java rename to src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java index cd084a1..4af679a 100644 --- a/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.matching; +package com.ccapp.ccgo.matching.domain.entity; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java b/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java similarity index 86% rename from src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java rename to src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java index 4edc3e6..ba8bf85 100644 --- a/src/main/java/com/ccapp/ccgo/dto/MatchingResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.matching.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java b/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResultDto.java similarity index 80% rename from src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java rename to src/main/java/com/ccapp/ccgo/matching/dto/MatchingResultDto.java index 8250a4f..0f423b4 100644 --- a/src/main/java/com/ccapp/ccgo/dto/MatchingResultDto.java +++ b/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResultDto.java @@ -1,5 +1,6 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.matching.dto; +import com.ccapp.ccgo.user.dto.UserResponseDto; import lombok.*; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/MbtiScoreRepository.java similarity index 57% rename from src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java rename to src/main/java/com/ccapp/ccgo/matching/repository/MbtiScoreRepository.java index 96a6f88..8e13654 100644 --- a/src/main/java/com/ccapp/ccgo/repository/MbtiScoreRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/MbtiScoreRepository.java @@ -1,7 +1,7 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.matching.repository; -import com.ccapp.ccgo.matching.MbtiScore; -import com.ccapp.ccgo.matching.MbtiScoreId; +import com.ccapp.ccgo.matching.domain.entity.MbtiScore; +import com.ccapp.ccgo.matching.domain.MbtiScoreId; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java similarity index 68% rename from src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java rename to src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java index 8e692df..063812f 100644 --- a/src/main/java/com/ccapp/ccgo/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java @@ -1,7 +1,7 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.matching.repository; -import com.ccapp.ccgo.matching.SubGroup; -import com.ccapp.ccgo.matching.SubGroupMember; +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java similarity index 68% rename from src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java rename to src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java index 9c9ea59..b7ad959 100644 --- a/src/main/java/com/ccapp/ccgo/repository/SubGroupRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.matching.repository; -import com.ccapp.ccgo.matching.SubGroup; +import com.ccapp.ccgo.matching.domain.entity.SubGroup; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java similarity index 93% rename from src/main/java/com/ccapp/ccgo/service/MatchingService.java rename to src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index e21ebfb..84726c1 100644 --- a/src/main/java/com/ccapp/ccgo/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -1,11 +1,25 @@ -package com.ccapp.ccgo.service; - -import com.ccapp.ccgo.dto.*; -import com.ccapp.ccgo.matching.*; -import com.ccapp.ccgo.repository.*; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.team.TeamMember; -import com.ccapp.ccgo.user.User; +package com.ccapp.ccgo.matching.service; + +import com.ccapp.ccgo.matching.domain.MbtiScoreProvider; +import com.ccapp.ccgo.matching.domain.entity.Answer; +import com.ccapp.ccgo.matching.domain.entity.Question; +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; +import com.ccapp.ccgo.question.dto.AnswerRequestDto; +import com.ccapp.ccgo.matching.dto.MatchingResponseDto; +import com.ccapp.ccgo.matching.dto.MatchingResultDto; +import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; +import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.question.dto.QuestionRequestDto; +import com.ccapp.ccgo.question.dto.QuestionResponseDto; +import com.ccapp.ccgo.question.dto.QuestionUpdateDto; +import com.ccapp.ccgo.question.repository.AnswerRepository; +import com.ccapp.ccgo.question.repository.QuestionRepository; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.user.dto.UserResponseDto; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.user.entity.User; import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java index 1762a7e..ee06c13 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.dto; -import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.entity.MissionTemplate; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java index fe0a1d3..c6f2b9a 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.dto; -import com.ccapp.ccgo.mission.domain.Partner; +import com.ccapp.ccgo.mission.entity.Partner; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java index 1f0773e..0f35cd5 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.dto; -import com.ccapp.ccgo.mission.domain.PartnerMission; +import com.ccapp.ccgo.mission.entity.PartnerMission; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java index b636618..d872214 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java @@ -1,7 +1,7 @@ package com.ccapp.ccgo.mission.dto; import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.mission.domain.UserMission; +import com.ccapp.ccgo.mission.entity.UserMission; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/MissionTemplate.java b/src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java similarity index 92% rename from src/main/java/com/ccapp/ccgo/mission/domain/MissionTemplate.java rename to src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java index 2e5883a..0636332 100644 --- a/src/main/java/com/ccapp/ccgo/mission/domain/MissionTemplate.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.mission.domain; +package com.ccapp.ccgo.mission.entity; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java b/src/main/java/com/ccapp/ccgo/mission/entity/Partner.java similarity index 80% rename from src/main/java/com/ccapp/ccgo/mission/domain/Partner.java rename to src/main/java/com/ccapp/ccgo/mission/entity/Partner.java index 04fd3e8..e3249c1 100644 --- a/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/Partner.java @@ -1,7 +1,7 @@ -package com.ccapp.ccgo.mission.domain; +package com.ccapp.ccgo.mission.entity; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java b/src/main/java/com/ccapp/ccgo/mission/entity/PartnerMission.java similarity index 93% rename from src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java rename to src/main/java/com/ccapp/ccgo/mission/entity/PartnerMission.java index 879b868..7800ade 100644 --- a/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/PartnerMission.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.mission.domain; +package com.ccapp.ccgo.mission.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java b/src/main/java/com/ccapp/ccgo/mission/entity/UserMission.java similarity index 90% rename from src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java rename to src/main/java/com/ccapp/ccgo/mission/entity/UserMission.java index c714176..a0b80aa 100644 --- a/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/UserMission.java @@ -1,7 +1,7 @@ -package com.ccapp.ccgo.mission.domain; +package com.ccapp.ccgo.mission.entity; import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index dd30aff..ba9ab6d 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.entity.MissionTemplate; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java index abe9ec5..ec232b9 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.domain.Partner; -import com.ccapp.ccgo.mission.domain.PartnerMission; +import com.ccapp.ccgo.mission.entity.Partner; +import com.ccapp.ccgo.mission.entity.PartnerMission; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java index 9250c5c..24ddef7 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java @@ -1,8 +1,8 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.domain.Partner; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.mission.entity.Partner; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java index 5f4e98d..8488cc4 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.domain.UserMission; +import com.ccapp.ccgo.mission.entity.UserMission; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java index 36443f6..7025816 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java @@ -1,8 +1,6 @@ package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.mission.domain.PartnerMission; import com.ccapp.ccgo.mission.dto.PartnerMissionDto; -import jakarta.transaction.Transactional; public interface MissionService { diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java index ea37b1f..c8f6306 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java @@ -1,13 +1,13 @@ package com.ccapp.ccgo.mission.service; import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.mission.domain.*; +import com.ccapp.ccgo.mission.entity.*; import com.ccapp.ccgo.mission.dto.PartnerMissionDto; import com.ccapp.ccgo.mission.repository.*; -import com.ccapp.ccgo.repository.UserRepository; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.repository.TeamRepository; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.repository.UserRepository; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.team.repository.TeamRepository; +import com.ccapp.ccgo.user.entity.User; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java index c4ec809..b2bba95 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java @@ -1,10 +1,9 @@ package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.matching.SubGroup; -import com.ccapp.ccgo.mission.domain.Partner; +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.mission.entity.Partner; import java.util.List; -import java.util.Optional; public interface PartnerService { diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java index df49b16..5ef38ea 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java @@ -1,14 +1,14 @@ package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.matching.SubGroup; -import com.ccapp.ccgo.matching.SubGroupMember; -import com.ccapp.ccgo.mission.domain.Partner; +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; +import com.ccapp.ccgo.mission.entity.Partner; import com.ccapp.ccgo.mission.repository.PartnerRepository; -import com.ccapp.ccgo.repository.SubGroupMemberRepository; -import com.ccapp.ccgo.repository.TeamRepository; -import com.ccapp.ccgo.repository.UserRepository; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; +import com.ccapp.ccgo.team.repository.TeamRepository; +import com.ccapp.ccgo.user.repository.UserRepository; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.user.entity.User; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java b/src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java similarity index 84% rename from src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java rename to src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java index d127921..34c0744 100644 --- a/src/main/java/com/ccapp/ccgo/dto/AnswerRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.question.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java b/src/main/java/com/ccapp/ccgo/question/dto/QuestionRequestDto.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java rename to src/main/java/com/ccapp/ccgo/question/dto/QuestionRequestDto.java index 6b3599e..69ab7d3 100644 --- a/src/main/java/com/ccapp/ccgo/dto/QuestionRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/QuestionRequestDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.question.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java b/src/main/java/com/ccapp/ccgo/question/dto/QuestionResponseDto.java similarity index 80% rename from src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java rename to src/main/java/com/ccapp/ccgo/question/dto/QuestionResponseDto.java index 58227f7..05b04e5 100644 --- a/src/main/java/com/ccapp/ccgo/dto/QuestionResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/QuestionResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.question.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java b/src/main/java/com/ccapp/ccgo/question/dto/QuestionUpdateDto.java similarity index 78% rename from src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java rename to src/main/java/com/ccapp/ccgo/question/dto/QuestionUpdateDto.java index d615536..8a2364e 100644 --- a/src/main/java/com/ccapp/ccgo/dto/QuestionUpdateDto.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/QuestionUpdateDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.question.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java b/src/main/java/com/ccapp/ccgo/question/dto/SingleAnswerDto.java similarity index 81% rename from src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java rename to src/main/java/com/ccapp/ccgo/question/dto/SingleAnswerDto.java index 145700e..25a4003 100644 --- a/src/main/java/com/ccapp/ccgo/dto/SingleAnswerDto.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/SingleAnswerDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.question.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java b/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java similarity index 72% rename from src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java rename to src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java index 6196564..0e0a987 100644 --- a/src/main/java/com/ccapp/ccgo/repository/AnswerRepository.java +++ b/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.question.repository; -import com.ccapp.ccgo.matching.Answer; +import com.ccapp.ccgo.matching.domain.entity.Answer; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java b/src/main/java/com/ccapp/ccgo/question/repository/QuestionRepository.java similarity index 68% rename from src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java rename to src/main/java/com/ccapp/ccgo/question/repository/QuestionRepository.java index 62c0333..3e845c2 100644 --- a/src/main/java/com/ccapp/ccgo/repository/QuestionRepository.java +++ b/src/main/java/com/ccapp/ccgo/question/repository/QuestionRepository.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.question.repository; -import com.ccapp.ccgo.matching.Question; +import com.ccapp.ccgo.matching.domain.entity.Question; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/controller/TeamController.java rename to src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index 10def72..d9c9b96 100644 --- a/src/main/java/com/ccapp/ccgo/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -1,8 +1,8 @@ -package com.ccapp.ccgo.controller; -import com.ccapp.ccgo.dto.TeamResponseDto; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.jwt.LoginUserDetails; -import com.ccapp.ccgo.repository.TeamMemberRepository; +package com.ccapp.ccgo.team.controller; +import com.ccapp.ccgo.team.dto.TeamResponseDto; +import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/dto/TeamMemberResponseDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamMemberResponseDto.java similarity index 84% rename from src/main/java/com/ccapp/ccgo/dto/TeamMemberResponseDto.java rename to src/main/java/com/ccapp/ccgo/team/dto/TeamMemberResponseDto.java index 4f3ff66..22fdf3c 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TeamMemberResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamMemberResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.team.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamRequestDto.java similarity index 76% rename from src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java rename to src/main/java/com/ccapp/ccgo/team/dto/TeamRequestDto.java index 9477da0..060044f 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TeamRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamRequestDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.team.dto; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java rename to src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java index 8063ae4..aeb207e 100644 --- a/src/main/java/com/ccapp/ccgo/dto/TeamResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.team.dto; import lombok.Getter; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/ccapp/ccgo/team/InviteCode.java b/src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java similarity index 96% rename from src/main/java/com/ccapp/ccgo/team/InviteCode.java rename to src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java index 570ba2c..2ca27b6 100644 --- a/src/main/java/com/ccapp/ccgo/team/InviteCode.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.team; +package com.ccapp.ccgo.team.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/team/Team.java b/src/main/java/com/ccapp/ccgo/team/entity/Team.java similarity index 95% rename from src/main/java/com/ccapp/ccgo/team/Team.java rename to src/main/java/com/ccapp/ccgo/team/entity/Team.java index dc7f07a..9c9b029 100644 --- a/src/main/java/com/ccapp/ccgo/team/Team.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/Team.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.team; +package com.ccapp.ccgo.team.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/team/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java similarity index 95% rename from src/main/java/com/ccapp/ccgo/team/TeamMember.java rename to src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java index 8fd4c50..aecfd4b 100644 --- a/src/main/java/com/ccapp/ccgo/team/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java @@ -1,7 +1,7 @@ -package com.ccapp.ccgo.team; +package com.ccapp.ccgo.team.entity; import com.ccapp.ccgo.common.Role; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java similarity index 85% rename from src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java rename to src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java index dee546e..532207f 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java @@ -1,8 +1,8 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.team.repository; -import com.ccapp.ccgo.team.TeamMember; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.team.entity.Team; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/repository/TeamRepository.java b/src/main/java/com/ccapp/ccgo/team/repository/TeamRepository.java similarity index 85% rename from src/main/java/com/ccapp/ccgo/repository/TeamRepository.java rename to src/main/java/com/ccapp/ccgo/team/repository/TeamRepository.java index d844e81..6863b95 100644 --- a/src/main/java/com/ccapp/ccgo/repository/TeamRepository.java +++ b/src/main/java/com/ccapp/ccgo/team/repository/TeamRepository.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.team.repository; -import com.ccapp.ccgo.team.Team; +import com.ccapp.ccgo.team.entity.Team; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/controller/UserController.java b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java similarity index 81% rename from src/main/java/com/ccapp/ccgo/controller/UserController.java rename to src/main/java/com/ccapp/ccgo/user/controller/UserController.java index 4b32d25..c319ef2 100644 --- a/src/main/java/com/ccapp/ccgo/controller/UserController.java +++ b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java @@ -1,8 +1,8 @@ -package com.ccapp.ccgo.controller; +package com.ccapp.ccgo.user.controller; -import com.ccapp.ccgo.service.UserService; -import com.ccapp.ccgo.dto.UserRequestDto; -import com.ccapp.ccgo.dto.UserResponseDto; +import com.ccapp.ccgo.user.service.UserService; +import com.ccapp.ccgo.user.dto.UserRequestDto; +import com.ccapp.ccgo.user.dto.UserResponseDto; import org.springframework.http.HttpStatus; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java similarity index 86% rename from src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java rename to src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java index de0d7fb..d80c777 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java @@ -1,6 +1,5 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.user.dto; -import com.ccapp.ccgo.common.Role; import lombok.Getter; import java.time.LocalDate; import jakarta.validation.constraints.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java similarity index 92% rename from src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java rename to src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java index eecaff7..df5822f 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.user.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/user/User.java b/src/main/java/com/ccapp/ccgo/user/entity/User.java similarity index 95% rename from src/main/java/com/ccapp/ccgo/user/User.java rename to src/main/java/com/ccapp/ccgo/user/entity/User.java index e217400..a3e1964 100644 --- a/src/main/java/com/ccapp/ccgo/user/User.java +++ b/src/main/java/com/ccapp/ccgo/user/entity/User.java @@ -1,6 +1,5 @@ -package com.ccapp.ccgo.user; +package com.ccapp.ccgo.user.entity; -import com.ccapp.ccgo.common.Role; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/ccapp/ccgo/dto/UserMapper.java b/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java similarity index 83% rename from src/main/java/com/ccapp/ccgo/dto/UserMapper.java rename to src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java index f4dbd1d..a63d749 100644 --- a/src/main/java/com/ccapp/ccgo/dto/UserMapper.java +++ b/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java @@ -1,6 +1,8 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.user.mapper; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.dto.UserRequestDto; +import com.ccapp.ccgo.user.dto.UserResponseDto; +import com.ccapp.ccgo.user.entity.User; public class UserMapper { diff --git a/src/main/java/com/ccapp/ccgo/repository/UserRepository.java b/src/main/java/com/ccapp/ccgo/user/repository/UserRepository.java similarity index 72% rename from src/main/java/com/ccapp/ccgo/repository/UserRepository.java rename to src/main/java/com/ccapp/ccgo/user/repository/UserRepository.java index 5d79179..66c591f 100644 --- a/src/main/java/com/ccapp/ccgo/repository/UserRepository.java +++ b/src/main/java/com/ccapp/ccgo/user/repository/UserRepository.java @@ -1,6 +1,6 @@ -package com.ccapp.ccgo.repository; +package com.ccapp.ccgo.user.repository; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/main/java/com/ccapp/ccgo/service/UserService.java b/src/main/java/com/ccapp/ccgo/user/service/UserService.java similarity index 89% rename from src/main/java/com/ccapp/ccgo/service/UserService.java rename to src/main/java/com/ccapp/ccgo/user/service/UserService.java index d592bcc..d324df0 100644 --- a/src/main/java/com/ccapp/ccgo/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/user/service/UserService.java @@ -1,12 +1,12 @@ -package com.ccapp.ccgo.service; +package com.ccapp.ccgo.user.service; -import com.ccapp.ccgo.dto.UserRequestDto; -import com.ccapp.ccgo.dto.UserResponseDto; -import com.ccapp.ccgo.dto.UserMapper; -import com.ccapp.ccgo.exception.CustomException; -import com.ccapp.ccgo.jwt.JwtProvider; -import com.ccapp.ccgo.repository.UserRepository; -import com.ccapp.ccgo.user.User; +import com.ccapp.ccgo.user.dto.UserRequestDto; +import com.ccapp.ccgo.user.dto.UserResponseDto; +import com.ccapp.ccgo.user.mapper.UserMapper; +import com.ccapp.ccgo.common.exception.CustomException; +import com.ccapp.ccgo.auth.jwt.JwtProvider; +import com.ccapp.ccgo.user.repository.UserRepository; +import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; From 311e3d0f44ab110052e5e038e541c5d069c40436 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 10 Jul 2025 17:31:30 +0900 Subject: [PATCH 021/132] Rebuild mission --- .../ccgo/controller/MatchingController.java | 6 +- .../ccapp/ccgo/matching/SubGroupMember.java | 3 +- .../mission/controller/MissionController.java | 26 ----- .../controller/MissionTemplateController.java | 28 ++++++ .../ccapp/ccgo/mission/domain/Partner.java | 30 ------ .../ccgo/mission/domain/PartnerMission.java | 34 ------- .../ccgo/mission/domain/UserMission.java | 37 ------- .../ccgo/mission/dto/MissionTemplateDto.java | 24 ----- .../ccapp/ccgo/mission/dto/PartnerDto.java | 24 ----- .../ccgo/mission/dto/PartnerMissionDto.java | 30 ------ .../ccgo/mission/dto/UserMissionDto.java | 30 ------ .../repository/MissionTemplateRepository.java | 12 ++- .../repository/PartnerMissionRepository.java | 17 ---- .../mission/repository/PartnerRepository.java | 19 ---- .../repository/UserMissionRepository.java | 11 --- .../ccgo/mission/service/MissionService.java | 11 --- .../mission/service/MissionServiceImpl.java | 99 ------------------- .../service/MissionTemplateService.java | 11 +++ .../service/MissionTemplateServiceImpl.java | 25 +++++ .../ccgo/mission/service/PartnerService.java | 15 --- .../mission/service/PartnerServiceImpl.java | 60 ----------- 21 files changed, 79 insertions(+), 473 deletions(-) delete mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/domain/Partner.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionService.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java diff --git a/src/main/java/com/ccapp/ccgo/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/controller/MatchingController.java index fa48689..399dce1 100644 --- a/src/main/java/com/ccapp/ccgo/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/controller/MatchingController.java @@ -3,6 +3,7 @@ import com.ccapp.ccgo.dto.*; import com.ccapp.ccgo.service.MatchingService; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -18,9 +19,10 @@ public class MatchingController { * 팀장이 매칭 시작 버튼 누를 때 호출 */ // @PreAuthorize("hasRole('TEAM_LEADER')") 매칭권한 + //이런식으로 ResponseEntity를 써야 한다. @PostMapping("/start/{teamId}") - public MatchingResponseDto startMatching(@PathVariable Long teamId) { - return matchingService.performMatching(teamId); + public ResponseEntity startMatching(@PathVariable Long teamId) { + return ResponseEntity.ok(matchingService.performMatching(teamId)); } @PostMapping("/answer") diff --git a/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java b/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java index cd084a1..e248b1f 100644 --- a/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java +++ b/src/main/java/com/ccapp/ccgo/matching/SubGroupMember.java @@ -11,7 +11,8 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class SubGroupMember { +public class +SubGroupMember { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java deleted file mode 100644 index edf8d3d..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionController.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ccapp.ccgo.mission.controller; - -import com.ccapp.ccgo.mission.dto.PartnerMissionDto; -import com.ccapp.ccgo.mission.service.MissionService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/missions") -@RequiredArgsConstructor -public class MissionController { - - private final MissionService missionService; - - // 팀 ID와 유저 ID를 기반으로 미션 새로고침 - @PostMapping("/refresh") - public ResponseEntity refreshPartnerMission( - @RequestParam Long teamId, - @RequestParam Long userId, - @RequestParam int score - ) { - PartnerMissionDto partnerMissionDto = missionService.refreshPartnerMission(teamId, userId,score); - return ResponseEntity.ok(partnerMissionDto); - } -} diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java new file mode 100644 index 0000000..05569da --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java @@ -0,0 +1,28 @@ +package com.ccapp.ccgo.mission.controller; +import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.service.MissionTemplateService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/missions") +@RequiredArgsConstructor +public class MissionTemplateController { + + private final MissionTemplateService missionTemplateService; + + @GetMapping("/score/{score}/six") + public ResponseEntity> getSixMissionsByScore(@PathVariable int score) { + return ResponseEntity.ok(missionTemplateService.getSixMissionsByScore(score)); + } + + @PostMapping("/score/{score}/refresh") + public ResponseEntity refreshMission(@PathVariable int score, + @RequestBody List excludedIds) { + MissionTemplate mission = missionTemplateService.refreshMission(score, excludedIds); + return ResponseEntity.ok(mission); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java b/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java deleted file mode 100644 index 04fd3e8..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/domain/Partner.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ccapp.ccgo.mission.domain; - -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.user.User; -import jakarta.persistence.*; -import lombok.*; - -@Getter -@Entity -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -@Builder -public class Partner { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "team_id") - private Team team; - - @ManyToOne - @JoinColumn(name = "user1_id") - private User user1; - - @ManyToOne - @JoinColumn(name = "user2_id") - private User user2; -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java b/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java deleted file mode 100644 index 879b868..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/domain/PartnerMission.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ccapp.ccgo.mission.domain; -import jakarta.persistence.*; -import lombok.*; - -import java.time.LocalDate; - -@Entity -@Getter -@Setter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -@Builder -public class PartnerMission { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "template_id") - private MissionTemplate template; - - @ManyToOne - @JoinColumn(name = "partner_id") - private Partner partner; - - private String title; - - private String description; - - private LocalDate dueDate; - - private boolean isActive; -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java b/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java deleted file mode 100644 index c714176..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/domain/UserMission.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ccapp.ccgo.mission.domain; - -import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.user.User; -import jakarta.persistence.*; -import lombok.*; - -import java.time.LocalDateTime; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -@Builder -public class UserMission { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - // 미션을 수행하는 유저 - @ManyToOne - @JoinColumn(name = "user_id") - private User user; - - // 짝 미션 - @ManyToOne - @JoinColumn(name = "partner_mission_id") - private PartnerMission partnerMission; - - // 미션 상태 (예: PENDING, COMPLETE) - @Enumerated(EnumType.STRING) - private MissionStatus status; - - // 미션 제출 시간 - private LocalDateTime submittedAt; -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java deleted file mode 100644 index 1762a7e..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/dto/MissionTemplateDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ccapp.ccgo.mission.dto; - -import com.ccapp.ccgo.mission.domain.MissionTemplate; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class MissionTemplateDto { - - private Long id; - private String title; - private String description; - private int score; - - public static MissionTemplateDto fromEntity(MissionTemplate template) { - return MissionTemplateDto.builder() - .id(template.getId()) - .title(template.getTitle()) - .description(template.getDescription()) - .score(template.getScore()) - .build(); - } -} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java deleted file mode 100644 index fe0a1d3..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ccapp.ccgo.mission.dto; - -import com.ccapp.ccgo.mission.domain.Partner; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class PartnerDto { - - private Long id; - private Long teamId; - private Long user1Id; - private Long user2Id; - - public static PartnerDto fromEntity(Partner partner) { - return PartnerDto.builder() - .id(partner.getId()) - .teamId(partner.getTeam().getTeamId()) - .user1Id(partner.getUser1().getId()) - .user2Id(partner.getUser2().getId()) - .build(); - } -} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java deleted file mode 100644 index 1f0773e..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/dto/PartnerMissionDto.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ccapp.ccgo.mission.dto; - -import com.ccapp.ccgo.mission.domain.PartnerMission; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDate; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class PartnerMissionDto { - private Long id; - private String title; - private String description; - private LocalDate dueDate; - private boolean isActive; - - public static PartnerMissionDto fromEntity(PartnerMission pm) { - return new PartnerMissionDto( - pm.getId(), - pm.getTitle(), - pm.getDescription(), - pm.getDueDate(), - pm.isActive() - ); - } - -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java deleted file mode 100644 index b636618..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/dto/UserMissionDto.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ccapp.ccgo.mission.dto; - -import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.mission.domain.UserMission; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserMissionDto { - private Long id; - private Long userId; - private Long partnerMissionId; - private MissionStatus status; - private LocalDateTime submittedAt; - - public static UserMissionDto fromEntity(UserMission um) { - return new UserMissionDto( - um.getId(), - um.getUser().getId(), - um.getPartnerMission().getId(), - um.getStatus(), - um.getSubmittedAt() - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index 3a196b9..fe20f16 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -2,14 +2,20 @@ import com.ccapp.ccgo.mission.domain.MissionTemplate; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; +@Repository public interface MissionTemplateRepository extends JpaRepository { - List findByScore(int score); + @Query(value = "SELECT * FROM mission_template WHERE score = :score LIMIT 6", nativeQuery = true) + List findTop6ByScore(@Param("score") int score); + + // 새로고침용, 기존 6개 제외하고 랜덤 1개 조회 + @Query(value = "SELECT * FROM mission_template WHERE score = :score AND id NOT IN :excludedIds ORDER BY RAND() LIMIT 1", nativeQuery = true) + MissionTemplate findRandomByScoreExcludingIds(@Param("score") int score, @Param("excludedIds") List excludedIds); - @Query("SELECT MAX(m.id) FROM MissionTemplate m") - Long findMaxId(); } diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java deleted file mode 100644 index fbc6d14..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerMissionRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ccapp.ccgo.mission.repository; -import com.ccapp.ccgo.mission.domain.MissionTemplate; -import com.ccapp.ccgo.mission.domain.Partner; -import com.ccapp.ccgo.mission.domain.PartnerMission; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface PartnerMissionRepository extends JpaRepository { - // 활성화된 점수별 미션 조회 - List findByPartnerAndIsActiveTrueAndTemplateScore(Partner partner, int score); - - // 최근 3개 미션 조회 (dueDate 기준 내림차순) - List findTop3ByPartnerOrderByDueDateDesc(Partner partner); - -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java deleted file mode 100644 index 9250c5c..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/repository/PartnerRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ccapp.ccgo.mission.repository; - -import com.ccapp.ccgo.mission.domain.Partner; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.user.User; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface PartnerRepository extends JpaRepository { - - boolean existsByTeamAndUser1AndUser2(Team team, User user1, User user2); - - List findByTeam_TeamId(Long teamId); - - // ❗ 유저1 또는 유저2가 user이고, 특정 팀에 속한 Partner를 찾기 - Optional findByTeamAndUser1OrTeamAndUser2(Team team1, User user1, Team team2, User user2); -} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java deleted file mode 100644 index 5f4e98d..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/repository/UserMissionRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ccapp.ccgo.mission.repository; - -import com.ccapp.ccgo.mission.domain.UserMission; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface UserMissionRepository extends JpaRepository { - List findByUserId(Long userId); - List findByPartnerMissionId(Long partnerMissionId); -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java deleted file mode 100644 index 507b824..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionService.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ccapp.ccgo.mission.service; - -import com.ccapp.ccgo.mission.domain.PartnerMission; -import com.ccapp.ccgo.mission.dto.PartnerMissionDto; -import jakarta.transaction.Transactional; - -public interface MissionService { - - PartnerMissionDto refreshPartnerMission(Long teamId, Long userId, int score); - -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java deleted file mode 100644 index 2baafa9..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionServiceImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.ccapp.ccgo.mission.service; - -import com.ccapp.ccgo.common.MissionStatus; -import com.ccapp.ccgo.mission.domain.*; -import com.ccapp.ccgo.mission.dto.PartnerMissionDto; -import com.ccapp.ccgo.mission.repository.*; -import com.ccapp.ccgo.repository.UserRepository; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.repository.TeamRepository; -import com.ccapp.ccgo.user.User; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.time.LocalDate; -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Transactional -public class MissionServiceImpl implements MissionService { - - private final PartnerRepository partnerRepository; - private final PartnerMissionRepository partnerMissionRepository; - private final UserMissionRepository userMissionRepository; - private final MissionTemplateRepository missionTemplateRepository; - private final UserRepository userRepository; - private final TeamRepository teamRepository; - - @Override - public PartnerMissionDto refreshPartnerMission(Long teamId, Long userId, int score) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("유저 없음")); - - Team team = teamRepository.findById(teamId) - .orElseThrow(() -> new IllegalArgumentException("팀 없음")); - - Partner partner = partnerRepository.findByTeamAndUser1OrTeamAndUser2(team, user, team, user) - .orElseThrow(() -> new IllegalStateException("해당 팀에서 짝이 없음")); - - // 해당 점수 미션 중 기존 활성 미션 비활성화 - List activeMissions = partnerMissionRepository - .findByPartnerAndIsActiveTrueAndTemplateScore(partner, score); - for (PartnerMission pm : activeMissions) { - pm.setActive(false); - partnerMissionRepository.save(pm); - } - - // 최근 3개 미션 조회해서 제외할 템플릿 ID 리스트 만들기 - List recentMissions = partnerMissionRepository - .findTop3ByPartnerOrderByDueDateDesc(partner); - List excludeIds = recentMissions.stream() - .map(pm -> pm.getTemplate().getId()) - .collect(Collectors.toList()); - - // 점수별 전체 미션 리스트 조회 - List allTemplates = missionTemplateRepository.findByScore(score); - - // 제외 리스트에 없는 미션만 후보로 필터링 - List candidateTemplates = allTemplates.stream() - .filter(mt -> !excludeIds.contains(mt.getId())) - .collect(Collectors.toList()); - - // 후보가 없으면 전체에서 랜덤 선택 - if (candidateTemplates.isEmpty()) { - candidateTemplates = allTemplates; - } - - // 랜덤 미션 선택 - int randomIndex = (int) (Math.random() * candidateTemplates.size()); - MissionTemplate selectedTemplate = candidateTemplates.get(randomIndex); - - PartnerMission newMission = PartnerMission.builder() - .partner(partner) - .template(selectedTemplate) - .title(selectedTemplate.getTitle()) - .description(selectedTemplate.getDescription()) - .dueDate(LocalDate.now().plusDays(14)) - .isActive(true) - .build(); - - partnerMissionRepository.save(newMission); - - createUserMission(partner.getUser1(), newMission); - createUserMission(partner.getUser2(), newMission); - - return PartnerMissionDto.fromEntity(newMission); - } - - private void createUserMission(User user, PartnerMission mission) { - UserMission userMission = UserMission.builder() - .user(user) - .partnerMission(mission) - .status(MissionStatus.PENDING) - .build(); - userMissionRepository.save(userMission); - } -} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java new file mode 100644 index 0000000..7cbdb7d --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java @@ -0,0 +1,11 @@ +package com.ccapp.ccgo.mission.service; +import com.ccapp.ccgo.mission.domain.MissionTemplate; +import java.util.List; + + +public interface MissionTemplateService { + + List getSixMissionsByScore(int score); + + MissionTemplate refreshMission(int score, List excludedIds); +} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java new file mode 100644 index 0000000..996c4e4 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java @@ -0,0 +1,25 @@ +package com.ccapp.ccgo.mission.service; + +import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class MissionTemplateServiceImpl implements MissionTemplateService { + + private final MissionTemplateRepository missionTemplateRepository; + + @Override + public List getSixMissionsByScore(int score) { + return missionTemplateRepository.findTop6ByScore(score); + } + + @Override + public MissionTemplate refreshMission(int score, List excludedIds) { + return missionTemplateRepository.findRandomByScoreExcludingIds(score, excludedIds); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java deleted file mode 100644 index c4ec809..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/PartnerService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ccapp.ccgo.mission.service; - -import com.ccapp.ccgo.matching.SubGroup; -import com.ccapp.ccgo.mission.domain.Partner; - -import java.util.List; -import java.util.Optional; - -public interface PartnerService { - - // 매칭된 SubGroup 정보를 받아 Partner(짝) 생성 - List createPartnersFromSubGroups(List subGroups); - - -} \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java deleted file mode 100644 index df49b16..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/PartnerServiceImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.ccapp.ccgo.mission.service; - -import com.ccapp.ccgo.matching.SubGroup; -import com.ccapp.ccgo.matching.SubGroupMember; -import com.ccapp.ccgo.mission.domain.Partner; -import com.ccapp.ccgo.mission.repository.PartnerRepository; -import com.ccapp.ccgo.repository.SubGroupMemberRepository; -import com.ccapp.ccgo.repository.TeamRepository; -import com.ccapp.ccgo.repository.UserRepository; -import com.ccapp.ccgo.team.Team; -import com.ccapp.ccgo.user.User; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; - - -@Service -@RequiredArgsConstructor -@Transactional -public class PartnerServiceImpl implements PartnerService { - - private final PartnerRepository partnerRepository; - private final UserRepository userRepository; - private final TeamRepository teamRepository; - private final SubGroupMemberRepository subGroupMemberRepository; // 추가 - - @Override - public List createPartnersFromSubGroups(List subGroups) { - List partners = new ArrayList<>(); - - for (SubGroup subGroup : subGroups) { - Team team = subGroup.getTeam(); - - // 해당 SubGroup의 멤버 2명 조회 - List members = subGroupMemberRepository.findBySubGroup(subGroup); - - if (members.size() != 2) { - throw new IllegalStateException("SubGroup에 멤버가 2명이 아닙니다: " + subGroup.getId()); - } - - User user1 = members.get(0).getUser(); - User user2 = members.get(1).getUser(); - - Partner partner = Partner.builder() - .team(team) - .user1(user1) - .user2(user2) - .build(); - - partnerRepository.save(partner); - partners.add(partner); - } - - return partners; - } - -} From a20692cb1f4127473721dc854a73ee4124fd6d82 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 11 Jul 2025 10:36:28 +0900 Subject: [PATCH 022/132] Done Merge --- .../ccgo/mission/controller/MissionTemplateController.java | 2 +- .../com/ccapp/ccgo/mission/service/MissionTemplateService.java | 2 +- .../ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java index 05569da..635a083 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java @@ -1,5 +1,5 @@ package com.ccapp.ccgo.mission.controller; -import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.entity.MissionTemplate; import com.ccapp.ccgo.mission.service.MissionTemplateService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java index 7cbdb7d..04c3767 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java @@ -1,5 +1,5 @@ package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.entity.MissionTemplate; import java.util.List; diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java index 996c4e4..41ebe44 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java @@ -1,6 +1,6 @@ package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.mission.domain.MissionTemplate; +import com.ccapp.ccgo.mission.entity.MissionTemplate; import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; From 80c4dfb1d78d776acf28d3e94c0007c5cb2eee88 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 11 Jul 2025 12:21:22 +0900 Subject: [PATCH 023/132] Connect DB --- .../controller/MatchingController.java | 12 ++++++-- .../ccgo/matching/domain/entity/Answer.java | 16 +++++++--- .../matching/service/MatchingService.java | 25 +++++++++------- .../ccgo/question/dto/AnswerRequestDto.java | 1 - .../question/dto/SurveyCompleteRequest.java | 2 +- .../question/repository/AnswerRepository.java | 2 +- .../ccapp/ccgo/service/TeamMemberService.java | 15 ++++++++-- .../ccgo/team/controller/TeamController.java | 30 ++++++++++++++----- 8 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index 73f2719..fd29383 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -1,20 +1,23 @@ package com.ccapp.ccgo.matching.controller; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; import com.ccapp.ccgo.question.dto.AnswerRequestDto; import com.ccapp.ccgo.matching.dto.MatchingResponseDto; import com.ccapp.ccgo.question.dto.QuestionRequestDto; import com.ccapp.ccgo.question.dto.QuestionResponseDto; import com.ccapp.ccgo.question.dto.QuestionUpdateDto; import com.ccapp.ccgo.matching.service.MatchingService; +import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/matching") // 경로수정필요 +@RequestMapping("/api/matching") // 경로수정필요 public class MatchingController { private final MatchingService matchingService; @@ -30,8 +33,11 @@ public ResponseEntity startMatching(@PathVariable Long team } @PostMapping("/answer") - public void saveAnswers(@RequestBody AnswerRequestDto requestDto) { - matchingService.saveAnswers(requestDto); + public ResponseEntity saveAnswers(@RequestBody AnswerRequestDto dto, + @AuthenticationPrincipal LoginUserDetails loginUserDetails) { + User currentUser = loginUserDetails.getUser(); + matchingService.saveAnswers(dto, currentUser); + return ResponseEntity.ok().build(); } @PostMapping("/question") diff --git a/src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java index 5dc0eb9..90280a5 100644 --- a/src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/Answer.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.matching.domain.entity; +import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.*; @@ -25,15 +26,22 @@ public class Answer { private User user; /** - * 어떤 질문에 대한 답변인지 + * 어떤 질문에 대한 답변인지 (외래키 아님) */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "question_id", nullable = false) - private Question question; + @Column(name = "question_id", nullable = false) + private Long questionId; /** * 유저가 선택한 점수 (1~5) */ @Column(nullable = false) private Integer score; + + /** + * 어떤 팀에서 제출한 답변인지 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id", nullable = false) + private Team team; + } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 8254646..16598ad 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -16,10 +16,12 @@ import com.ccapp.ccgo.question.repository.AnswerRepository; import com.ccapp.ccgo.question.repository.QuestionRepository; import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.repository.TeamRepository; import com.ccapp.ccgo.user.dto.UserResponseDto; import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.user.repository.UserRepository; import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; @@ -38,6 +40,7 @@ public class MatchingService { private final AnswerRepository answerRepository; private final SubGroupRepository subGroupRepository; private final SubGroupMemberRepository subGroupMemberRepository; + private final TeamRepository teamRepository; // 가중치 private static final double MBTI_WEIGHT = 0.5; private static final double SIMILARITY_WEIGHT = 0.5; @@ -231,13 +234,13 @@ private double calculateSimilarityScore(TeamMember a, TeamMember b, Long teamId) for (Question q : questions) { int scoreA = answersA.stream() - .filter(ans -> ans.getQuestion().getId().equals(q.getId())) + .filter(ans -> ans.getQuestionId().equals(q.getId())) .map(Answer::getScore) .findFirst() .orElse(0); int scoreB = answersB.stream() - .filter(ans -> ans.getQuestion().getId().equals(q.getId())) + .filter(ans -> ans.getQuestionId().equals(q.getId())) .map(Answer::getScore) .findFirst() .orElse(0); @@ -396,8 +399,8 @@ static class PairMatch { // 새 Answer를 전부 INSERT // MatchingService가 매칭 돌릴 때 최신 데이터를 사용 @Transactional - public void saveAnswers(AnswerRequestDto dto) { - Long userId = dto.getUserId(); + public void saveAnswers(AnswerRequestDto dto, User user) { + Long userId = user.getId(); Long teamId = dto.getTeamId(); // 해당 팀의 질문만 필터링해서 삭제 @@ -406,20 +409,20 @@ public void saveAnswers(AnswerRequestDto dto) { .map(Question::getId) .collect(Collectors.toList()); List existingAnswers = answerRepository.findByUser_Id(userId).stream() - .filter(ans -> teamQuestionIds.contains(ans.getQuestion().getId())) + .filter(ans -> teamQuestionIds.contains(ans.getQuestionId())) .collect(Collectors.toList()); answerRepository.deleteAll(existingAnswers); // 새로 저장 << 이게 무슨 뜻이징 - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found")); - + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("Team not found")); List newAnswers = dto.getAnswers().stream() .map(single -> Answer.builder() - .user(user) // 영속 상태의 User 엔티티 - .question(Question.builder().id(single.getQuestionId()).build()) + .user(user) + .questionId(single.getQuestionId()) // question 객체 대신 questionId 직접 저장 .score(single.getScore()) + .team(team) .build()) .collect(Collectors.toList()); @@ -480,7 +483,7 @@ public List getQuestions(Long teamId) { @Transactional public void deleteQuestion(Long questionId) { // 삭제할 Question과 연결된 모든 Answer 레코드 조회 - List answers = answerRepository.findByQuestion_Id(questionId); + List answers = answerRepository.findByQuestionId(questionId); // 조회된 Answer 레코드 전부 삭제 answerRepository.deleteAll(answers); // 마지막으로 Question 자체 삭제 diff --git a/src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java b/src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java index 32466ad..288d628 100644 --- a/src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/AnswerRequestDto.java @@ -9,7 +9,6 @@ @AllArgsConstructor @Builder public class AnswerRequestDto { - private Long userId; private String mbti; private Long teamId; private List answers; diff --git a/src/main/java/com/ccapp/ccgo/question/dto/SurveyCompleteRequest.java b/src/main/java/com/ccapp/ccgo/question/dto/SurveyCompleteRequest.java index cb18155..3b7656f 100644 --- a/src/main/java/com/ccapp/ccgo/question/dto/SurveyCompleteRequest.java +++ b/src/main/java/com/ccapp/ccgo/question/dto/SurveyCompleteRequest.java @@ -1,4 +1,4 @@ -package com.ccapp.ccgo.dto; +package com.ccapp.ccgo.question.dto; import lombok.Getter; diff --git a/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java b/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java index 0e0a987..cbe6f28 100644 --- a/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java +++ b/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java @@ -8,6 +8,6 @@ public interface AnswerRepository extends JpaRepository { List findByUser_Id(Long userId); - List findByQuestion_Id(Long questionId); + List findByQuestionId(Long questionId); } diff --git a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java index 0130511..ef087aa 100644 --- a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java +++ b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java @@ -1,7 +1,8 @@ package com.ccapp.ccgo.service; -import com.ccapp.ccgo.repository.TeamMemberRepository; -import com.ccapp.ccgo.team.TeamMember; + +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,4 +22,14 @@ public void markSurveyCompleted(Long userId, Long teamId) { teamMember.setSurveyCompleted(true); // ← 여기서 DB의 isSurveyCompleted를 true로 변경 } + @Transactional + public boolean isSurveyCompleted(Long userId, Long teamId) { + TeamMember teamMember = teamMemberRepository + .findByUser_IdAndTeam_TeamId(userId, teamId) + .orElseThrow(() -> new RuntimeException("팀 멤버를 찾을 수 없습니다.")); + return teamMember.isSurveyCompleted(); // ← DB 필드 반환 + } + + + } diff --git a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index 4942589..484a50f 100644 --- a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -1,11 +1,12 @@ -package com.ccapp.ccgo.controller; -import com.ccapp.ccgo.dto.SurveyCompleteRequest; -import com.ccapp.ccgo.dto.TeamResponseDto; -import com.ccapp.ccgo.service.TeamMemberService; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.jwt.LoginUserDetails; -import com.ccapp.ccgo.repository.TeamMemberRepository; +package com.ccapp.ccgo.team.controller; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; + +import com.ccapp.ccgo.question.dto.SurveyCompleteRequest; +import com.ccapp.ccgo.service.TeamMemberService; +import com.ccapp.ccgo.team.dto.TeamResponseDto; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @RestController @@ -53,4 +55,18 @@ public ResponseEntity completeSurvey(@RequestBody SurveyCompleteRequest re } + @GetMapping("/{teamId}/survey-status") + public ResponseEntity getSurveyStatus( + @PathVariable Long teamId, + @AuthenticationPrincipal LoginUserDetails loginUserDetails) { + + User currentUser = loginUserDetails.getUser(); + + boolean isCompleted = teamMemberService.isSurveyCompleted(currentUser.getId(), teamId); + + return ResponseEntity.ok().body(Map.of("issurveycompleted", isCompleted)); //map을 써서 보낼지 dto로 보낼지 + //gpt왈 map이 더 빠르고 간단함 ㅇㅇ 흠.... + } + + } \ No newline at end of file From 8b3f2ace94c1db344a015a3aeb7dd27cba351670 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 11 Jul 2025 15:24:05 +0900 Subject: [PATCH 024/132] add Teammember Role --- .../ccgo/team/controller/TeamController.java | 17 ++++++++++++--- .../ccapp/ccgo/team/dto/SurveyStatusDto.java | 13 ++++++++++++ .../ccapp/ccgo/team/dto/TeamResponseDto.java | 2 ++ .../{ => team}/service/TeamMemberService.java | 21 +++++++++++++++++-- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/team/dto/SurveyStatusDto.java rename src/main/java/com/ccapp/ccgo/{ => team}/service/TeamMemberService.java (60%) diff --git a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index 484a50f..840b811 100644 --- a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -3,8 +3,10 @@ import com.ccapp.ccgo.question.dto.SurveyCompleteRequest; -import com.ccapp.ccgo.service.TeamMemberService; +import com.ccapp.ccgo.team.dto.SurveyStatusDto; +import com.ccapp.ccgo.team.service.TeamMemberService; import com.ccapp.ccgo.team.dto.TeamResponseDto; +import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.team.repository.TeamMemberRepository; import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; @@ -35,8 +37,10 @@ public ResponseEntity> getMyTeams( .findAllByUserAndIsActiveTrue(user) .stream() .map(tm -> new TeamResponseDto( - tm.getTeam().getTeamId(), // getTeamId()로 변경 - tm.getTeam().getTeamName())) + tm.getTeam().getTeamId(), + tm.getTeam().getTeamName(), + tm.getRole() // 팀 멤버 역할 필드 추가 + )) .collect(Collectors.toList()); return ResponseEntity.ok(result); @@ -68,5 +72,12 @@ public ResponseEntity getSurveyStatus( //gpt왈 map이 더 빠르고 간단함 ㅇㅇ 흠.... } + @GetMapping("/{teamId}/survey-status/all") + public ResponseEntity> getAllSurveyStatus(@PathVariable Long teamId) { + List result = teamMemberService.getAllSurveyStatus(teamId); + return ResponseEntity.ok(result); + } + + } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/team/dto/SurveyStatusDto.java b/src/main/java/com/ccapp/ccgo/team/dto/SurveyStatusDto.java new file mode 100644 index 0000000..b578370 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/team/dto/SurveyStatusDto.java @@ -0,0 +1,13 @@ +package com.ccapp.ccgo.team.dto; + +import com.ccapp.ccgo.common.Role; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class SurveyStatusDto { + private Long userId; + private String userName; + private boolean isSurveyCompleted; +} diff --git a/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java index aeb207e..c6bb61e 100644 --- a/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.team.dto; +import com.ccapp.ccgo.common.Role; import lombok.Getter; import lombok.AllArgsConstructor; @@ -8,4 +9,5 @@ public class TeamResponseDto { private Long teamId; private String teamName; + private Role role; } diff --git a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java b/src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java similarity index 60% rename from src/main/java/com/ccapp/ccgo/service/TeamMemberService.java rename to src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java index ef087aa..7168f40 100644 --- a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java +++ b/src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java @@ -1,12 +1,16 @@ -package com.ccapp.ccgo.service; +package com.ccapp.ccgo.team.service; +import com.ccapp.ccgo.team.dto.SurveyStatusDto; import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.team.repository.TeamMemberRepository; -import jakarta.transaction.Transactional; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class TeamMemberService { @@ -31,5 +35,18 @@ public boolean isSurveyCompleted(Long userId, Long teamId) { } + @Transactional(readOnly = true) + public List getAllSurveyStatus(Long teamId) { + List members = teamMemberRepository.findByTeam_TeamIdAndIsActiveTrue(teamId); + + return members.stream() + .map(member -> new SurveyStatusDto( + member.getUser().getId(), + member.getUser().getName(), + member.isSurveyCompleted() + )) + .collect(Collectors.toList()); + } + } From 614568316b70f22f5051b80cd7c6f8352465898d Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 11 Jul 2025 15:24:21 +0900 Subject: [PATCH 025/132] modified matching sys --- .../{auth/jwt => common}/SecurityConfig.java | 5 +- .../common/test/BcryptTestController.java | 18 ++-- .../ccgo/matching/domain/entity/SubGroup.java | 7 ++ .../repository/SubGroupMemberRepository.java | 3 + .../matching/service/MatchingService.java | 85 ++++++++++++------- .../ccapp/ccgo/service/TeamMemberService.java | 5 +- .../ccgo/team/controller/TeamController.java | 13 +-- .../ccapp/ccgo/user/service/UserService.java | 2 +- src/main/resources/MissionInsert.sql | 40 ++++----- src/main/resources/application.properties | 2 +- 10 files changed, 109 insertions(+), 71 deletions(-) rename src/main/java/com/ccapp/ccgo/{auth/jwt => common}/SecurityConfig.java (95%) diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java similarity index 95% rename from src/main/java/com/ccapp/ccgo/auth/jwt/SecurityConfig.java rename to src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index 5ece0f1..362be94 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -1,5 +1,6 @@ -package com.ccapp.ccgo.auth.jwt; +package com.ccapp.ccgo.common; +import com.ccapp.ccgo.auth.jwt.JwtAuthenticationFilter; import com.ccapp.ccgo.auth.service.LoginUserDetailsService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -55,7 +56,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/login","/bcrypt-test", "/register").permitAll() // 로그인, 회원가입 허용, "/bcrypt-test" + .requestMatchers("/api/auth/login","/bcrypt-test","/bcrypt-config", "/register").permitAll() // 로그인, 회원가입 허용, "/bcrypt-test" .anyRequest().authenticated() // 그 외는 인증 필요 ) .authenticationProvider(authenticationProvider()) diff --git a/src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java b/src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java index a34c230..743070e 100644 --- a/src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java +++ b/src/main/java/com/ccapp/ccgo/common/test/BcryptTestController.java @@ -7,19 +7,19 @@ @RestController public class BcryptTestController { -// @GetMapping("/bcrypt-test") -// public String test() { -// String rawPw = "Test@1234"; -// String hash = "$2a$10$plQyO8OH8Sv5uBrm/fRO1OaXOCfcR5fZ0miWJ0OTZzzZEv6AfCMAO"; -// -// boolean matches = new BCryptPasswordEncoder().matches(rawPw, hash); -// return "비밀번호 일치 여부: " + matches; -// } @GetMapping("/bcrypt-test") + public String test() { + String rawPw = "Test@1234"; + String hash = "$2a$10$l.Y.cfT9oRlzsiH7fSBIWearf3bijLIDR4ZSsTCnb7IsMvl8NWlnW"; + + boolean matches = new BCryptPasswordEncoder().matches(rawPw, hash); + return "비밀번호 일치 여부: " + matches; + } + @GetMapping("/bcrypt-config") public String generate() { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String rawPw = "Test@1234"; String encodedPw = encoder.encode(rawPw); return encodedPw; -} + } } diff --git a/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java index 50a9a68..3d4c3ec 100644 --- a/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroup.java @@ -30,4 +30,11 @@ public class SubGroup { */ @Column(nullable = false, length = 255) private String name; + + /** + * 그룹당 멤버 수 (고정) + */ + @Column(name = "member_count", nullable = false) + private int memberCount; + } diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java index 063812f..74dc800 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java @@ -10,4 +10,7 @@ public interface SubGroupMemberRepository extends JpaRepository findBySubGroup(SubGroup subGroup); + + long countBySubGroup_Id(Long subGroupId); + } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 8254646..36a096c 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -20,6 +20,7 @@ import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.user.repository.UserRepository; import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; @@ -120,14 +121,18 @@ public MatchingResponseDto performMatching(Long teamId) { } String groupName = team.getTeamName() + groupIndex; + List groupMembers = List.of(pair.userA.getUser(), pair.userB.getUser()); + SubGroup sg = SubGroup.builder() .team(team) .name(groupName) + .memberCount(groupMembers.size()) .build(); subGroupRepository.save(sg); - saveSubGroupMember(sg, pair.userA.getUser()); - saveSubGroupMember(sg, pair.userB.getUser()); + for (User user : groupMembers) { + saveSubGroupMember(sg, user); + } matchedUserIds.add(pair.userA.getUser().getId()); matchedUserIds.add(pair.userB.getUser().getId()); @@ -243,7 +248,7 @@ private double calculateSimilarityScore(TeamMember a, TeamMember b, Long teamId) .orElse(0); int diff = Math.abs(scoreA - scoreB); - int similarity = 5 - diff; + int similarity = 5 - diff; // 상수를 질문 수로 바꿔주면 된다. totalSimilarity += similarity; } @@ -296,44 +301,64 @@ private void handleLeftovers(List groupA, } while (existingGroupNames.contains(groupName)); existingGroupNames.add(groupName); + List groupMembers = List.of(tm1.getUser(), tm2.getUser()); + SubGroup sg = SubGroup.builder() .team(team) .name(groupName) + .memberCount(groupMembers.size()) .build(); subGroupRepository.save(sg); - saveSubGroupMember(sg, tm1.getUser()); - saveSubGroupMember(sg, tm2.getUser()); + for (User user : groupMembers) { + saveSubGroupMember(sg, user); + } subGroups.add(sg); } else { - // 홀수 남음 → 기존 그룹 중 가장 점수 높은 그룹으로 편입 + // 홀수 남음 → 기존 그룹 중 가장 점수(각각의 유사도 점수 구한 뒤의 평균) 높은 그룹으로 편입 if (!subGroups.isEmpty()) { - SubGroup targetGroup = subGroups.stream() - .max(Comparator.comparing(sg -> { - List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); - return members.size(); - })) - .orElse(subGroups.get(0)); - saveSubGroupMember(targetGroup, tm1.getUser()); + double maxAvgSimilarity = Double.NEGATIVE_INFINITY; + SubGroup bestGroup = null; + + for (SubGroup sg : subGroups) { + List members = + subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + + double totalSimilarity = 0; + int memberCount = 0; + + for (SubGroupMember sgm : members) { + TeamMember existingTm = teamMemberRepository + .findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) + .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); + + double similarityScore = + calculateSimilarityScore(tm1, existingTm, team.getTeamId()); + + totalSimilarity += similarityScore; + memberCount++; + } + + double avgSimilarity = memberCount > 0 ? totalSimilarity / memberCount : 0; + + if (avgSimilarity > maxAvgSimilarity) { + maxAvgSimilarity = avgSimilarity; + bestGroup = sg; + } + } + + if (bestGroup != null) { + saveSubGroupMember(bestGroup, tm1.getUser()); + + long newCount = + subGroupMemberRepository.countBySubGroup_Id(bestGroup.getId()); + bestGroup.setMemberCount((int) newCount); + subGroupRepository.save(bestGroup); + } } else { - // 기존 그룹이 없으면 단독 그룹 생성 - String groupName; - do { - groupName = team.getTeamName() + groupIndex; - groupIndex++; - } while (existingGroupNames.contains(groupName)); - existingGroupNames.add(groupName); - - SubGroup sg = SubGroup.builder() - .team(team) - .name(groupName) - .build(); - subGroupRepository.save(sg); - - saveSubGroupMember(sg, tm1.getUser()); - subGroups.add(sg); + System.out.println("[WARN] 홀수 남았지만 기존 그룹 없음. 유저ID: " + tm1.getUser().getId()); } } } @@ -410,7 +435,7 @@ public void saveAnswers(AnswerRequestDto dto) { .collect(Collectors.toList()); answerRepository.deleteAll(existingAnswers); - // 새로 저장 << 이게 무슨 뜻이징 + // 덮어쓰기 User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("User not found")); diff --git a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java index 0130511..af1e0c2 100644 --- a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java +++ b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java @@ -1,7 +1,8 @@ package com.ccapp.ccgo.service; -import com.ccapp.ccgo.repository.TeamMemberRepository; -import com.ccapp.ccgo.team.TeamMember; + +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index 4942589..c69356f 100644 --- a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -1,11 +1,12 @@ -package com.ccapp.ccgo.controller; +package com.ccapp.ccgo.team.controller; + +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; import com.ccapp.ccgo.dto.SurveyCompleteRequest; -import com.ccapp.ccgo.dto.TeamResponseDto; import com.ccapp.ccgo.service.TeamMemberService; -import com.ccapp.ccgo.user.User; -import com.ccapp.ccgo.jwt.LoginUserDetails; -import com.ccapp.ccgo.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.dto.TeamResponseDto; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -44,7 +45,7 @@ public ResponseEntity> getMyTeams( @PostMapping("/survey/complete") public ResponseEntity completeSurvey(@RequestBody SurveyCompleteRequest request, @AuthenticationPrincipal LoginUserDetails loginUserDetails) { - System.out.print("프로그램ㅅ ㅣ작"); + System.out.print("프로그램시작"); User currentUser = loginUserDetails.getUser(); System.out.print("1"); teamMemberService.markSurveyCompleted(currentUser.getId(), request.getTeamId()); diff --git a/src/main/java/com/ccapp/ccgo/user/service/UserService.java b/src/main/java/com/ccapp/ccgo/user/service/UserService.java index d324df0..fa54603 100644 --- a/src/main/java/com/ccapp/ccgo/user/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/user/service/UserService.java @@ -58,7 +58,7 @@ public List getAllUsers() { // 4. 사용자 상세 조회 public UserResponseDto getUserById(Long id) { - User user = userRepository.findById(id) + User user =userRepository.findById(id) .orElseThrow(() -> new CustomException("해당 ID의 사용자가 없습니다.", HttpStatus.NOT_FOUND)); return UserMapper.toDto(user); } diff --git a/src/main/resources/MissionInsert.sql b/src/main/resources/MissionInsert.sql index 7b8b21d..9b4157b 100644 --- a/src/main/resources/MissionInsert.sql +++ b/src/main/resources/MissionInsert.sql @@ -80,24 +80,24 @@ INSERT INTO mission_template (title, description, score) VALUES (' 미션 18', '당연하지 게임하기(총 10번)', 5), (' 미션 19', '같이 운동하기', 5), (' 미션 20', '교내 모든 건물 앞에서 사진 찍기', 5), -(' 미션 1', '인생네컷 찍기', 10), -(' 미션 2', '전화 10분이상하기', 10), -(' 미션 3', '노래방 가기', 10), -(' 미션 4', '단둘이 교내에서 영화 보기', 10), -(' 미션 5', '교외 카페 가기', 10), -(' 미션 6', '같이 바다가서 인증샷 찍기', 10), -(' 미션 7', '같이 릴스 찍기', 10), -(' 미션 8', '모든 팀사람과 사진 찍기', 10), -(' 미션 9', '영일대 놀러가기', 10), -(' 미션 10', '더블 데이트', 10), -(' 미션 11', '같이 새벽 예배가기', 10), -(' 미션 12', '우산 1개 쓰고 한한하기', 10), -(' 미션 13', '스페이스워크에서 사진찍기', 10), -(' 미션 14', '2인 브이로그 찍기', 10), -(' 미션 15', '둘이서 밤 산책하기', 10), -(' 미션 16', '서로 프로필 사진 만들어주기', 10), -(' 미션 17', '틱톡 챌린지 따라하기', 10), -(' 미션 18', '같이 드라마 보기', 10), -(' 미션 19', '영일대 불꽃놀이 하기', 10), -(' 미션 20', '두 사람만의 엽기 포즈 찍기', 10); + (' 미션 1', '인생네컷 찍기', 10), + (' 미션 2', '전화 10분이상하기', 10), + (' 미션 3', '노래방 가기', 10), + (' 미션 4', '단둘이 교내에서 영화 보기', 10), + (' 미션 5', '교외 카페 가기', 10), + (' 미션 6', '같이 바다가서 인증샷 찍기', 10), + (' 미션 7', '같이 릴스 찍기', 10), + (' 미션 8', '모든 팀사람과 사진 찍기', 10), + (' 미션 9', '영일대 놀러가기', 10), + (' 미션 10', '더블 데이트', 10), + (' 미션 11', '같이 새벽 예배가기', 10), + (' 미션 12', '우산 1개 쓰고 한한하기', 10), + (' 미션 13', '스페이스워크에서 사진찍기', 10), + (' 미션 14', '2인 브이로그 찍기', 10), + (' 미션 15', '둘이서 밤 산책하기', 10), + (' 미션 16', '서로 프로필 사진 만들어주기', 10), + (' 미션 17', '틱톡 챌린지 따라하기', 10), + (' 미션 18', '같이 드라마 보기', 10), + (' 미션 19', '영일대 불꽃놀이 하기', 10), + (' 미션 20', '두 사람만의 엽기 포즈 찍기', 10); \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65d55a6..22f7f7e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = 06250730cc +spring.datasource.password = qkrwlsdn jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 46bd5f7065c37502ac7881940b9a0329a737b255 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 11 Jul 2025 17:32:07 +0900 Subject: [PATCH 026/132] Connect Subgroup Name --- .../controller/MatchingController.java | 7 ++++++ .../repository/SubGroupMemberRepository.java | 8 ++++++ .../matching/service/MatchingService.java | 23 +++++++++++++++++ .../ccapp/ccgo/service/TeamMemberService.java | 25 ------------------- src/main/resources/application.properties | 2 +- 5 files changed, 39 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/com/ccapp/ccgo/service/TeamMemberService.java diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index fd29383..925aed1 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -60,4 +60,11 @@ public void updateQuestion(@PathVariable Long questionId, public void deleteQuestion(@PathVariable Long questionId) { matchingService.deleteQuestion(questionId); } + + @GetMapping("/matched-names") + public ResponseEntity> getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails) { + List matchedNames = matchingService.getMatchedUserNames(userDetails.getUser().getId()); + return ResponseEntity.ok(matchedNames); + } + } diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java index 74dc800..9a26033 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java @@ -3,6 +3,9 @@ import com.ccapp.ccgo.matching.domain.entity.SubGroup; import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + import java.util.List; public interface SubGroupMemberRepository extends JpaRepository { @@ -13,4 +16,9 @@ public interface SubGroupMemberRepository extends JpaRepository findBySameSubGroup(@Param("userId") Long userId); + } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 9d75cef..d37924b 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -514,5 +514,28 @@ public void deleteQuestion(Long questionId) { questionRepository.deleteById(questionId); } +// //팀원 이름 조회하기 기능 +// @Transactional(readOnly = true) +// public List getMatchedUserNames(Long userId) { +// List members = subGroupMemberRepository.findBySameSubGroup(userId); +// return members.stream() +// .filter(m -> !m.getUser().getId().equals(userId)) // 본인 제외 +// .map(m -> m.getUser().getName()) // 혹은 +// .collect(Collectors.toList()); +// } + + //실험 + @Transactional(readOnly = true) + public List getMatchedUserNames(Long userId) { + List members = subGroupMemberRepository.findBySameSubGroup(userId); + System.out.println("조회된 멤버 수: " + members.size()); + for(SubGroupMember m : members) { + System.out.println("멤버 ID: " + m.getUser().getId() + ", 이름: " + m.getUser().getName()); + } + return members.stream() + .filter(m -> !m.getUser().getId().equals(userId)) // 본인 제외 + .map(m -> m.getUser().getName()) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java b/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java deleted file mode 100644 index af1e0c2..0000000 --- a/src/main/java/com/ccapp/ccgo/service/TeamMemberService.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ccapp.ccgo.service; - - -import com.ccapp.ccgo.team.entity.TeamMember; -import com.ccapp.ccgo.team.repository.TeamMemberRepository; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class TeamMemberService { - - private final TeamMemberRepository teamMemberRepository; - - @Transactional - public void markSurveyCompleted(Long userId, Long teamId) { - TeamMember teamMember = teamMemberRepository - .findByUser_IdAndTeam_TeamId(userId, teamId) - .orElseThrow(() -> new RuntimeException("팀 멤버를 찾을 수 없습니다.")); - - teamMember.setSurveyCompleted(true); // ← 여기서 DB의 isSurveyCompleted를 true로 변경 - } - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 22f7f7e..65d55a6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = qkrwlsdn +spring.datasource.password = 06250730cc jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 20eb9854d65ee1e92358d34ca4b36b9bdd2a2409 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 15 Jul 2025 18:37:03 +0900 Subject: [PATCH 027/132] add Mission_sys --- .../exception/GlobalExceptionHandler.java | 10 +++ .../MatchingAlreadyCompletedException.java | 7 ++ .../matching/dto/MatchingResponseDto.java | 1 + .../matching/service/MatchingService.java | 17 +++- .../MissionAssignmentController.java | 30 +++++++ .../ccgo/mission/dto/TeamMissionDto.java | 13 ++++ .../ccgo/mission/entity/TeamMission.java | 34 ++++++++ .../repository/MissionTemplateRepository.java | 1 + .../repository/TeamMissionRepository.java | 17 ++++ .../service/MissionAssignmentService.java | 78 +++++++++++++++++++ .../ccgo/team/controller/TeamController.java | 8 +- .../ccgo/team/dto/TeamMatchingStatusDto.java | 12 +++ .../ccapp/ccgo/team/dto/TeamResponseDto.java | 1 + .../java/com/ccapp/ccgo/team/entity/Team.java | 4 + .../ccgo/team/service/TeamMemberService.java | 13 ++++ 15 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/common/exception/MatchingAlreadyCompletedException.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java create mode 100644 src/main/java/com/ccapp/ccgo/team/dto/TeamMatchingStatusDto.java diff --git a/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java b/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java index b3946ca..281564b 100644 --- a/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java @@ -7,6 +7,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.context.support.DefaultMessageSourceResolvable; + +import java.util.HashMap; import java.util.stream.Collectors; import org.springframework.security.authentication.BadCredentialsException; @@ -47,4 +49,12 @@ public ResponseEntity> handleBadCredentials(BadCredentialsEx .status(HttpStatus.UNAUTHORIZED) .body(Map.of("message", "이메일 또는 비밀번호가 잘못되었습니다.")); } + + + @ExceptionHandler(MatchingAlreadyCompletedException.class) + public ResponseEntity> handleMatchingAlreadyStarted(MatchingAlreadyCompletedException ex) { + Map errorResponse = new HashMap<>(); + errorResponse.put("error", ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } } diff --git a/src/main/java/com/ccapp/ccgo/common/exception/MatchingAlreadyCompletedException.java b/src/main/java/com/ccapp/ccgo/common/exception/MatchingAlreadyCompletedException.java new file mode 100644 index 0000000..373b14c --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/common/exception/MatchingAlreadyCompletedException.java @@ -0,0 +1,7 @@ +package com.ccapp.ccgo.common.exception; + +public class MatchingAlreadyCompletedException extends RuntimeException { + public MatchingAlreadyCompletedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java b/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java index ba8bf85..b02c865 100644 --- a/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/matching/dto/MatchingResponseDto.java @@ -11,5 +11,6 @@ public class MatchingResponseDto { private Long teamId; private String teamName; + private boolean matchingStarted; private List subGroups; } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index d37924b..97e4c1f 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.matching.service; +import com.ccapp.ccgo.common.exception.MatchingAlreadyCompletedException; import com.ccapp.ccgo.matching.domain.MbtiScoreProvider; import com.ccapp.ccgo.matching.domain.entity.Answer; import com.ccapp.ccgo.matching.domain.entity.Question; @@ -60,12 +61,15 @@ public MatchingResponseDto performMatching(Long teamId) { throw new IllegalArgumentException("해당 팀(" + teamId + ")에 유저가 없습니다."); } - Map memberMap = members.stream() - .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); + Team team = members.get(0).getTeam(); + if (team.isMatchingStarted()) { + throw new MatchingAlreadyCompletedException("이미 매칭이 완료된 팀입니다."); + } + Map memberMap = members.stream() + .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); - Team team = members.get(0).getTeam(); // 2. 남/여 그룹 나누기 @@ -146,10 +150,16 @@ public MatchingResponseDto performMatching(Long teamId) { // 7. 잉여 처리 handleLeftovers(groupA, groupB, matchedUserIds, team, groupIndex, subGroups); + // ✅ 매칭 완료를 true로 수정 + team.setMatchingStarted(true); + teamRepository.save(team); + // 8. 결과 DTO 변환 return buildMatchingResponseDto(team, subGroups); } + + // groupA의 각 멤버에게 groupB 중 top N 후보자 리스트를 만들어줌 // 점수 높은 순서대로 5명 (동점자 포함) 까지 private Map> createCandidateMap(List groupA, @@ -400,6 +410,7 @@ private MatchingResponseDto buildMatchingResponseDto(Team team, List s return MatchingResponseDto.builder() .teamId(team.getTeamId()) + .matchingStarted(team.isMatchingStarted()) .teamName(team.getTeamName()) .subGroups(resultDtos) .build(); diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java new file mode 100644 index 0000000..c891357 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java @@ -0,0 +1,30 @@ +package com.ccapp.ccgo.mission.controller; + +import com.ccapp.ccgo.mission.dto.TeamMissionDto; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import com.ccapp.ccgo.mission.service.MissionAssignmentService; + +import java.util.List; + +@RestController +@RequestMapping("/api/missions") +@RequiredArgsConstructor +public class MissionAssignmentController { + + private final MissionAssignmentService missionAssignmentService; + + @PostMapping("/assign/{teamId}") + public ResponseEntity assignMissionsToTeam(@PathVariable Long teamId) { + missionAssignmentService.assignMissionsToTeam(teamId); + return ResponseEntity.ok("미션이 팀에 성공적으로 부여되었습니다."); + } + + // 팀에 부여된 미션 리스트 조회 API + @GetMapping("/team/{teamId}") + public ResponseEntity> getTeamMissions(@PathVariable Long teamId) { + List missions = missionAssignmentService.getTeamMissions(teamId); + return ResponseEntity.ok(missions); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java new file mode 100644 index 0000000..c127e86 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java @@ -0,0 +1,13 @@ +package com.ccapp.ccgo.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class TeamMissionDto { + private Long missionId; // 미션 템플릿 ID + private String title; // 미션 제목 + private String description; // 미션 내용 + private int score; // 미션 점수 +} diff --git a/src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java b/src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java new file mode 100644 index 0000000..738b8e4 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java @@ -0,0 +1,34 @@ +package com.ccapp.ccgo.mission.entity; + +import com.ccapp.ccgo.team.entity.Team; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "team_mission") +public class TeamMission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 팀과 다대일 관계 (여러 미션이 하나 팀에 속함) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id") + private Team team; + + // 미션 템플릿과 다대일 관계 (여러 팀 미션이 하나 미션 템플릿에 기반) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_template_id") + private MissionTemplate missionTemplate; + + public TeamMission(Team team, MissionTemplate missionTemplate) { + this.team = team; + this.missionTemplate = missionTemplate; + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index 6c18745..b96844d 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -18,4 +18,5 @@ public interface MissionTemplateRepository extends JpaRepository excludedIds); + List findByScore(int score); } diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java new file mode 100644 index 0000000..c4bf99a --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java @@ -0,0 +1,17 @@ +package com.ccapp.ccgo.mission.repository; + +import com.ccapp.ccgo.mission.entity.MissionTemplate; +import com.ccapp.ccgo.mission.entity.TeamMission; +import com.ccapp.ccgo.team.entity.Team; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TeamMissionRepository extends JpaRepository { + + // 특정 팀에 할당된 미션 리스트 조회 + List findByTeam(Team team); + + + +} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java new file mode 100644 index 0000000..29ba606 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java @@ -0,0 +1,78 @@ +package com.ccapp.ccgo.mission.service; + +import com.ccapp.ccgo.mission.dto.TeamMissionDto; +import com.ccapp.ccgo.mission.entity.MissionTemplate; +import com.ccapp.ccgo.mission.entity.TeamMission; +import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; +import com.ccapp.ccgo.mission.repository.TeamMissionRepository; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.team.repository.TeamRepository; +import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MissionAssignmentService { + + private final TeamRepository teamRepository; + private final MissionTemplateRepository missionTemplateRepository; + private final TeamMissionRepository teamMissionRepository; + + @Transactional + public void assignMissionsToTeam(Long teamId) { + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); + + if (!teamMissionRepository.findByTeam(team).isEmpty()) { + throw new IllegalStateException("이미 이 팀에는 미션이 부여되었습니다."); + } + + // 각 학점별로 6개씩 미션 랜덤 선택 및 저장 + assignMissionsByScore(team, 1, 6); + assignMissionsByScore(team, 3, 6); + assignMissionsByScore(team, 5, 6); + assignMissionsByScore(team, 10, 6); + } + + private void assignMissionsByScore(Team team, int score, int count) { + List missions = missionTemplateRepository.findByScore(score); + + if (missions.size() < count) { + throw new IllegalStateException(score + "점 미션이 최소 " + count + "개 이상 필요합니다."); + } + + // 랜덤으로 섞고 count 만큼 잘라서 사용 + Collections.shuffle(missions); + List selectedMissions = missions.stream() + .limit(count) + .collect(Collectors.toList()); + + for (MissionTemplate template : selectedMissions) { + TeamMission teamMission = new TeamMission(team, template); + teamMissionRepository.save(teamMission); + } + } + + @Transactional(readOnly = true) + public List getTeamMissions(Long teamId) { + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); + + List teamMissions = teamMissionRepository.findByTeam(team); + + return teamMissions.stream() + .map(tm -> new TeamMissionDto( + tm.getMissionTemplate().getId(), + tm.getMissionTemplate().getTitle(), + tm.getMissionTemplate().getDescription(), + tm.getMissionTemplate().getScore() + )) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index 840b811..f938903 100644 --- a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -4,6 +4,8 @@ import com.ccapp.ccgo.question.dto.SurveyCompleteRequest; import com.ccapp.ccgo.team.dto.SurveyStatusDto; +import com.ccapp.ccgo.team.dto.TeamMatchingStatusDto; +import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.team.service.TeamMemberService; import com.ccapp.ccgo.team.dto.TeamResponseDto; import com.ccapp.ccgo.team.entity.TeamMember; @@ -78,6 +80,8 @@ public ResponseEntity> getAllSurveyStatus(@PathVariable Lo return ResponseEntity.ok(result); } - - + @GetMapping("/{teamId}") + public ResponseEntity getTeamInfo(@PathVariable Long teamId) { + return ResponseEntity.ok(teamMemberService.getTeamInfo(teamId)); + } } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/team/dto/TeamMatchingStatusDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamMatchingStatusDto.java new file mode 100644 index 0000000..ab75ec7 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamMatchingStatusDto.java @@ -0,0 +1,12 @@ +package com.ccapp.ccgo.team.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class TeamMatchingStatusDto { + private Long teamId; + private String teamName; + private boolean matchingStarted; +} diff --git a/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java index c6bb61e..1c607a6 100644 --- a/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java @@ -10,4 +10,5 @@ public class TeamResponseDto { private Long teamId; private String teamName; private Role role; + } diff --git a/src/main/java/com/ccapp/ccgo/team/entity/Team.java b/src/main/java/com/ccapp/ccgo/team/entity/Team.java index 9c9b029..6fb601d 100644 --- a/src/main/java/com/ccapp/ccgo/team/entity/Team.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/Team.java @@ -38,4 +38,8 @@ public class Team { @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; + // 매칭 시작 여부 추가 + @Column(name = "matching_started", nullable = false) + private boolean matchingStarted = false; + } diff --git a/src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java b/src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java index 7168f40..889b86f 100644 --- a/src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java +++ b/src/main/java/com/ccapp/ccgo/team/service/TeamMemberService.java @@ -2,8 +2,12 @@ import com.ccapp.ccgo.team.dto.SurveyStatusDto; +import com.ccapp.ccgo.team.dto.TeamMatchingStatusDto; +import com.ccapp.ccgo.team.dto.TeamResponseDto; +import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.repository.TeamRepository; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -16,6 +20,7 @@ public class TeamMemberService { private final TeamMemberRepository teamMemberRepository; + private final TeamRepository teamRepository; @Transactional public void markSurveyCompleted(Long userId, Long teamId) { @@ -49,4 +54,12 @@ public List getAllSurveyStatus(Long teamId) { } + @Transactional(readOnly = true) + public TeamMatchingStatusDto getTeamInfo(Long teamId) { + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); + return new TeamMatchingStatusDto(team.getTeamId(), team.getTeamName(), team.isMatchingStarted()); + } + + } From 0bcc84bbca754090f07eafa883f7c3629b506dab Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 16 Jul 2025 10:23:05 +0900 Subject: [PATCH 028/132] . --- .../com/ccapp/ccgo/mission/service/MissionAssignmentService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java index 29ba606..c4bf1ca 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java @@ -23,6 +23,7 @@ public class MissionAssignmentService { private final MissionTemplateRepository missionTemplateRepository; private final TeamMissionRepository teamMissionRepository; + //팀에게 미션 6개씩 부여 @Transactional public void assignMissionsToTeam(Long teamId) { Team team = teamRepository.findById(teamId) From ac84af15ab4aba2e2e6e2ff6af5290349d5688a9 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 16 Jul 2025 13:28:15 +0900 Subject: [PATCH 029/132] create assignedmission --- .gitignore | 2 +- .../entity/AssignedMission.java | 31 ++++++++++ .../controller/InviteCodeController.java | 27 +++++++-- .../dto/InviteCodeCreateRequestDto.java | 5 +- .../repository/InviteCodeRepository.java | 2 +- .../invitecode/service/InviteCodeService.java | 44 ++++++++------- .../controller/MissionTemplateController.java | 56 +++++++++---------- .../ccgo/mission/dto/TeamMissionDto.java | 2 +- .../ccgo/mission/entity/MissionTemplate.java | 2 +- .../repository/MissionTemplateRepository.java | 6 +- .../service/MissionAssignmentService.java | 2 +- .../service/MissionTemplateService.java | 4 +- .../service/MissionTemplateServiceImpl.java | 4 +- .../ccapp/ccgo/team/dto/TeamResponseDto.java | 2 + .../ccapp/ccgo/team/entity/InviteCode.java | 6 +- .../java/com/ccapp/ccgo/team/entity/Team.java | 4 ++ .../team/repository/TeamMemberRepository.java | 1 + src/main/resources/application.properties | 2 +- 18 files changed, 133 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java diff --git a/.gitignore b/.gitignore index 4143e02..62064b7 100644 --- a/.gitignore +++ b/.gitignore @@ -203,6 +203,6 @@ application-*.properties /bin/ # resources -src/main/resources/application.properties +.src/main/resources # End of https://www.toptal.com/developers/gitignore/api/windows,intellij,java,gradle \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java b/src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java new file mode 100644 index 0000000..8f3188d --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java @@ -0,0 +1,31 @@ +package com.ccapp.ccgo.assignedmission.entity; + +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.mission.entity.MissionTemplate; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AssignedMission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sub_group_id") + private SubGroup subGroup; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_template_id") + private MissionTemplate missionTemplate; + + private Boolean isCompleted; +} diff --git a/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java index 3554ae3..1635e4f 100644 --- a/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java @@ -1,10 +1,13 @@ package com.ccapp.ccgo.invitecode.controller; +import com.ccapp.ccgo.invitecode.dto.InviteCodeCreateRequestDto; import com.ccapp.ccgo.invitecode.dto.InviteCodeCreateResponseDto; import com.ccapp.ccgo.invitecode.dto.InviteCodeJoinRequestDto; import com.ccapp.ccgo.invitecode.dto.InviteCodeJoinResponseDto; import com.ccapp.ccgo.team.dto.TeamRequestDto; import com.ccapp.ccgo.invitecode.repository.InviteCodeRepository; +import com.ccapp.ccgo.team.dto.TeamResponseDto; +import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.team.repository.TeamMemberRepository; import com.ccapp.ccgo.team.repository.TeamRepository; import com.ccapp.ccgo.invitecode.service.InviteCodeService; @@ -32,7 +35,9 @@ public class InviteCodeController { //코드 만드는 부분 @PostMapping("/create") public ResponseEntity createInviteCode( - @AuthenticationPrincipal LoginUserDetails userDetails) { + @AuthenticationPrincipal LoginUserDetails userDetails, + @RequestBody InviteCodeCreateRequestDto requestDto) { + System.out.println("초대코드 생성 요청 들어옴"); System.out.println("userDetails: " + userDetails); @@ -46,7 +51,10 @@ public ResponseEntity createInviteCode( User user = userDetails.getUser(); System.out.print("코드 만듭니당"); - InviteCode inviteCode = inviteCodeService.createInviteCode(user); + Long teamId = requestDto.getTeamId(); + + InviteCode inviteCode = inviteCodeService.createInviteCode(user, teamId); + InviteCodeCreateResponseDto responseDto = InviteCodeCreateResponseDto.builder() .code(inviteCode.getCode()) .expiresAt(inviteCode.getExpiresAt()) @@ -59,20 +67,29 @@ public ResponseEntity createInviteCode( //팀 생성하기를 누르면 팀이 만들어집니당 @PostMapping("/teamname") - public ResponseEntity saveTeamName( + public ResponseEntity saveTeamName( @AuthenticationPrincipal LoginUserDetails userDetails, @RequestBody TeamRequestDto requestDto) { + if (userDetails == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } User user = userDetails.getUser(); - inviteCodeService.createTeamWithLeader(user, requestDto.getTeamName()); - return ResponseEntity.ok().build(); + Team team = inviteCodeService.createTeamWithLeader(user, requestDto.getTeamName()); + TeamResponseDto responseDto = TeamResponseDto.builder() + .teamId(team.getTeamId()) + .teamName(team.getTeamName()) + .build(); + + System.out.println("🔎 컨트롤러에서 내려보내는 responseDto = " + responseDto); + + return ResponseEntity.ok(responseDto); } + //팀원이 코드를 보냈으면 처리 @PostMapping("/join") public ResponseEntity joinByInviteCode( diff --git a/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java index b8a864e..7429fa0 100644 --- a/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/dto/InviteCodeCreateRequestDto.java @@ -1,5 +1,8 @@ package com.ccapp.ccgo.invitecode.dto; +import lombok.Data; + +@Data public class InviteCodeCreateRequestDto { private Long teamId; -} \ No newline at end of file +} diff --git a/src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java b/src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java index 6de8a3c..e197ea5 100644 --- a/src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/repository/InviteCodeRepository.java @@ -10,7 +10,7 @@ /** * InviteCode 엔티티용 Repository */ -public interface InviteCodeRepository extends JpaRepository { +public interface InviteCodeRepository extends JpaRepository { // 코드 존재 여부 boolean existsByCode(String code); diff --git a/src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java b/src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java index a419cad..1518052 100644 --- a/src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/service/InviteCodeService.java @@ -72,25 +72,25 @@ public String joinTeamByInviteCode(String code, User user) { //초대코드생성 부분 @Transactional - public InviteCode createInviteCode(User user) { - List teamMembers = teamMemberRepository.findByUserAndIsActiveTrue(user); - if (teamMembers.isEmpty()) { - throw new CustomException("팀 소속이 아닙니다.", HttpStatus.BAD_REQUEST); + public InviteCode createInviteCode(User user, Long teamId) { + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new CustomException("존재하지 않는 팀입니다.", HttpStatus.NOT_FOUND)); + + // 팀장이 이 팀의 리더인지 검증 + List teamMembers = teamMemberRepository.findByUserAndTeamAndIsActiveTrue(user, team); + boolean isLeader = teamMembers.stream() + .anyMatch(tm -> tm.getRole() == Role.LEADER); + if (!isLeader) { + throw new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN); } - TeamMember leader = teamMembers.stream() - .filter(tm -> tm.getRole() == Role.LEADER) - .findFirst() - .orElseThrow(() -> new CustomException("팀장만 초대코드를 생성할 수 있습니다.", HttpStatus.FORBIDDEN)); - - Team team = leader.getTeam(); - //기존 코드 삭제 + // 기존 초대코드 삭제 inviteCodeRepository.deleteByTeam(team); String code; do { code = generateRandomCode(); - } while (inviteCodeRepository.existsById(code)); + } while (inviteCodeRepository.existsByCode(code)); InviteCode inviteCode = InviteCode.builder() .code(code) @@ -100,6 +100,7 @@ public InviteCode createInviteCode(User user) { return inviteCodeRepository.save(inviteCode); } + //현재 시각보다 이전인 초대코드를 삭제 @Scheduled(fixedRate = 60 * 60 * 1000) // 1시간마다 실행 (ms 단위) @Transactional @@ -110,24 +111,25 @@ public void deleteExpiredInviteCodes() { @Transactional - public void createTeamWithLeader(User user, String teamName) { - // 팀 생성 + public Team createTeamWithLeader(User user, String teamName) { Team team = Team.builder() .teamName(teamName) .createdBy(user.getId()) .createdAt(LocalDateTime.now()) .build(); - teamRepository.save(team); - // 팀장 등록 - TeamMember teamMember = TeamMember.builder() - .team(team) + Team savedTeam = teamRepository.save(team); + + TeamMember teamLeader = TeamMember.builder() .user(user) - .joinedAt(LocalDateTime.now()) - .isActive(true) + .team(savedTeam) .role(Role.LEADER) + .isActive(true) .build(); - teamMemberRepository.save(teamMember); + + teamMemberRepository.save(teamLeader); + + return savedTeam; } diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java index 635a083..6bee2bd 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java @@ -1,28 +1,28 @@ -package com.ccapp.ccgo.mission.controller; -import com.ccapp.ccgo.mission.entity.MissionTemplate; -import com.ccapp.ccgo.mission.service.MissionTemplateService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/missions") -@RequiredArgsConstructor -public class MissionTemplateController { - - private final MissionTemplateService missionTemplateService; - - @GetMapping("/score/{score}/six") - public ResponseEntity> getSixMissionsByScore(@PathVariable int score) { - return ResponseEntity.ok(missionTemplateService.getSixMissionsByScore(score)); - } - - @PostMapping("/score/{score}/refresh") - public ResponseEntity refreshMission(@PathVariable int score, - @RequestBody List excludedIds) { - MissionTemplate mission = missionTemplateService.refreshMission(score, excludedIds); - return ResponseEntity.ok(mission); - } -} +//package com.ccapp.ccgo.mission.controller; +//import com.ccapp.ccgo.mission.entity.MissionTemplate; +//import com.ccapp.ccgo.mission.service.MissionTemplateService; +//import lombok.RequiredArgsConstructor; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.*; +// +//import java.util.List; +// +//@RestController +//@RequestMapping("/api/missions") +//@RequiredArgsConstructor +//public class MissionTemplateController { +// +// private final MissionTemplateService missionTemplateService; +// +// @GetMapping("/score/{score}/six") +// public ResponseEntity> getSixMissionsByScore(@PathVariable int score) { +// return ResponseEntity.ok(missionTemplateService.getSixMissionsByScore(score)); +// } +// +// @PostMapping("/score/{score}/refresh") +// public ResponseEntity refreshMission(@PathVariable int score, +// @RequestBody List excludedIds) { +// MissionTemplate mission = missionTemplateService.refreshMission(score, excludedIds); +// return ResponseEntity.ok(mission); +// } +//} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java index c127e86..38390ec 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java @@ -9,5 +9,5 @@ public class TeamMissionDto { private Long missionId; // 미션 템플릿 ID private String title; // 미션 제목 private String description; // 미션 내용 - private int score; // 미션 점수 + private Integer score; // 미션 점수 } diff --git a/src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java b/src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java index 0636332..9f2adfd 100644 --- a/src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/MissionTemplate.java @@ -22,5 +22,5 @@ public class MissionTemplate { private String description; //1학점 3학점 5학점 10학점 - private int score; + private Integer score; } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index b96844d..b06c4dd 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -12,11 +12,11 @@ public interface MissionTemplateRepository extends JpaRepository { @Query(value = "SELECT * FROM mission_template WHERE score = :score LIMIT 6", nativeQuery = true) - List findTop6ByScore(@Param("score") int score); + List findTop6ByScore(@Param("score") Integer score); // 새로고침용, 기존 6개 제외하고 랜덤 1개 조회 @Query(value = "SELECT * FROM mission_template WHERE score = :score AND id NOT IN :excludedIds ORDER BY RAND() LIMIT 1", nativeQuery = true) - MissionTemplate findRandomByScoreExcludingIds(@Param("score") int score, @Param("excludedIds") List excludedIds); + MissionTemplate findRandomByScoreExcludingIds(@Param("score") Integer score, @Param("excludedIds") List excludedIds); - List findByScore(int score); + List findByScore(Integer score); } diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java index c4bf1ca..65b65f8 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java @@ -40,7 +40,7 @@ public void assignMissionsToTeam(Long teamId) { assignMissionsByScore(team, 10, 6); } - private void assignMissionsByScore(Team team, int score, int count) { + private void assignMissionsByScore(Team team, Integer score, int count) { List missions = missionTemplateRepository.findByScore(score); if (missions.size() < count) { diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java index 04c3767..6a8ee54 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java @@ -5,7 +5,7 @@ public interface MissionTemplateService { - List getSixMissionsByScore(int score); + List getSixMissionsByScore(Integer score); - MissionTemplate refreshMission(int score, List excludedIds); + MissionTemplate refreshMission(Integer score, List excludedIds); } diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java index 41ebe44..aa3c34d 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java @@ -14,12 +14,12 @@ public class MissionTemplateServiceImpl implements MissionTemplateService { private final MissionTemplateRepository missionTemplateRepository; @Override - public List getSixMissionsByScore(int score) { + public List getSixMissionsByScore(Integer score) { return missionTemplateRepository.findTop6ByScore(score); } @Override - public MissionTemplate refreshMission(int score, List excludedIds) { + public MissionTemplate refreshMission(Integer score, List excludedIds) { return missionTemplateRepository.findRandomByScoreExcludingIds(score, excludedIds); } } diff --git a/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java index 1c607a6..00a3493 100644 --- a/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/team/dto/TeamResponseDto.java @@ -1,11 +1,13 @@ package com.ccapp.ccgo.team.dto; import com.ccapp.ccgo.common.Role; +import lombok.Builder; import lombok.Getter; import lombok.AllArgsConstructor; @Getter @AllArgsConstructor +@Builder public class TeamResponseDto { private Long teamId; private String teamName; diff --git a/src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java b/src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java index 2ca27b6..1f417ae 100644 --- a/src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/InviteCode.java @@ -20,9 +20,13 @@ @Table(name = "invite_code") public class InviteCode { + // PK - 초대 코드 (랜덤 문자열, 8자리) @Id - @Column(length = 8) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 8, nullable = false, unique = true) private String code; // FK - 팀 ID diff --git a/src/main/java/com/ccapp/ccgo/team/entity/Team.java b/src/main/java/com/ccapp/ccgo/team/entity/Team.java index 6fb601d..36a6b79 100644 --- a/src/main/java/com/ccapp/ccgo/team/entity/Team.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/Team.java @@ -1,6 +1,7 @@ package com.ccapp.ccgo.team.entity; import jakarta.persistence.*; +import jakarta.persistence.criteria.CriteriaBuilder; import lombok.*; import java.time.LocalDateTime; @@ -42,4 +43,7 @@ public class Team { @Column(name = "matching_started", nullable = false) private boolean matchingStarted = false; + @Column(name = "min_score") + private Integer minScore; + } diff --git a/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java index 1f63070..77589a0 100644 --- a/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java @@ -36,4 +36,5 @@ public interface TeamMemberRepository extends JpaRepository { List findByTeam_TeamIdAndIsActiveTrue(Long teamId); + List findByUserAndTeamAndIsActiveTrue(User user, Team team); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65d55a6..22f7f7e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = 06250730cc +spring.datasource.password = qkrwlsdn jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 69cfe0fa0d951cdbab4521959182c8ba7b944605 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 17 Jul 2025 13:26:13 +0900 Subject: [PATCH 030/132] Done mission --- .../controller/MatchingController.java | 58 +++++- .../matching/dto/MatchedNamesResponse.java | 14 ++ .../repository/SubGroupMemberRepository.java | 12 ++ .../repository/SubGroupRepository.java | 6 + .../matching/service/MatchingService.java | 46 ++++- .../MissionAssignmentController.java | 54 ++++-- .../controller/MissionTemplateController.java | 28 --- .../mission/dto/MissionCompleteRequest.java | 10 + .../ccgo/mission/dto/SubGroupMissionDto.java | 17 ++ .../ccgo/mission/dto/TeamMissionDto.java | 13 -- .../entity/SubGroupMission.java} | 20 +- .../ccgo/mission/entity/TeamMission.java | 34 ---- .../repository/MissionTemplateRepository.java | 13 +- .../repository/SubGroupMissionRepository.java | 16 ++ .../repository/TeamMissionRepository.java | 17 -- .../service/MissionAssignmentService.java | 79 -------- .../service/MissionTemplateService.java | 11 -- .../service/MissionTemplateServiceImpl.java | 25 --- .../service/SubGroupMissionService.java | 172 ++++++++++++++++++ .../team/repository/TeamMemberRepository.java | 4 + .../java/com/ccapp/ccgo/user/entity/User.java | 4 - src/main/resources/application.properties | 2 +- 22 files changed, 397 insertions(+), 258 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/matching/dto/MatchedNamesResponse.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/MissionCompleteRequest.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/SubGroupMissionDto.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java rename src/main/java/com/ccapp/ccgo/{assignedmission/entity/AssignedMission.java => mission/entity/SubGroupMission.java} (53%) delete mode 100644 src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java delete mode 100644 src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index 925aed1..8753c03 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -1,6 +1,8 @@ package com.ccapp.ccgo.matching.controller; import com.ccapp.ccgo.auth.jwt.LoginUserDetails; +import com.ccapp.ccgo.matching.dto.MatchedNamesResponse; +import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; import com.ccapp.ccgo.question.dto.AnswerRequestDto; import com.ccapp.ccgo.matching.dto.MatchingResponseDto; import com.ccapp.ccgo.question.dto.QuestionRequestDto; @@ -9,18 +11,24 @@ import com.ccapp.ccgo.matching.service.MatchingService; import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; @RestController @RequiredArgsConstructor @RequestMapping("/api/matching") // 경로수정필요 +@Slf4j public class MatchingController { private final MatchingService matchingService; + private final SubGroupMemberRepository subGroupMemberRepository; // ✅ 주입 /** * 팀장이 매칭 시작 버튼 누를 때 호출 @@ -29,6 +37,7 @@ public class MatchingController { //이런식으로 ResponseEntity를 써야 한다. @PostMapping("/start/{teamId}") public ResponseEntity startMatching(@PathVariable Long teamId) { + log.info("[Matching Start] 요청받음 | teamId: {}", teamId); return ResponseEntity.ok(matchingService.performMatching(teamId)); } @@ -61,10 +70,51 @@ public void deleteQuestion(@PathVariable Long questionId) { matchingService.deleteQuestion(questionId); } - @GetMapping("/matched-names") - public ResponseEntity> getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails) { - List matchedNames = matchingService.getMatchedUserNames(userDetails.getUser().getId()); - return ResponseEntity.ok(matchedNames); + + + //왜 안되냐 +// @GetMapping("/matched-names/{teamId}/{subGroupId}") +// public ResponseEntity getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails, +// @PathVariable Long teamId, +// @PathVariable Long subGroupId) { +// List matchedNames = matchingService.getMatchedUserNames(userDetails.getUser().getId(), teamId, subGroupId); +// MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); +// return ResponseEntity.ok(response); +// } + + //매칭된 이후, 팀id랑 subgroupid로 팀원 찾아오는놈 + @GetMapping("/matched-names/{teamId}/{subGroupId}") + public ResponseEntity getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails, + @PathVariable Long teamId, + @PathVariable Long subGroupId) { + try { + Long userId = userDetails.getUser().getId(); + System.out.println("[getMatchedNames] userId: " + userId + ", teamId: " + teamId + ", subGroupId: " + subGroupId); + + List matchedNames = matchingService.getMatchedUserNames(userId, teamId, subGroupId); + + System.out.println("[getMatchedNames] matchedNames size: " + (matchedNames != null ? matchedNames.size() : "null")); + + MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); + return ResponseEntity.ok(response); + } catch (Exception e) { + System.err.println("[getMatchedNames] 서버 에러 발생: " + e.getMessage()); + e.printStackTrace(); + return ResponseEntity.status(500).build(); + } + } + + //매칭된 직후 작동하는놈 + //현재 임시로 userid 받아오는중인데, 나중에 jwt로 수정 필요. + @GetMapping("/subgroup/{teamId}") + public ResponseEntity> getSubGroupIdByTeamId( + @PathVariable Long teamId, + @RequestParam Long userId) { + Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); + Map response = new HashMap<>(); + response.put("subGroupId", subGroupId.orElse(null)); // null 허용 가능 + return ResponseEntity.ok(response); } + } diff --git a/src/main/java/com/ccapp/ccgo/matching/dto/MatchedNamesResponse.java b/src/main/java/com/ccapp/ccgo/matching/dto/MatchedNamesResponse.java new file mode 100644 index 0000000..8c282cb --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/dto/MatchedNamesResponse.java @@ -0,0 +1,14 @@ +package com.ccapp.ccgo.matching.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +public class MatchedNamesResponse { + private Long teamId; + private Long subGroupId; + private List matchedNames; +} diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java index 9a26033..cf29a82 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java @@ -2,11 +2,13 @@ import com.ccapp.ccgo.matching.domain.entity.SubGroup; import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; +import com.ccapp.ccgo.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface SubGroupMemberRepository extends JpaRepository { List findBySubGroup_Id(Long subGroupId); @@ -21,4 +23,14 @@ public interface SubGroupMemberRepository extends JpaRepository findBySameSubGroup(@Param("userId") Long userId); + + @Query("SELECT s.user FROM SubGroupMember s WHERE s.subGroup.id = :subGroupId") + List findUsersBySubGroupId(@Param("subGroupId") Long subGroupId); + + + + @Query("SELECT sgm.subGroup.id FROM SubGroupMember sgm WHERE sgm.subGroup.team.teamId = :teamId AND sgm.user.id = :userId") + Optional findSubGroupIdByTeamIdAndUserId(@Param("teamId") Long teamId, @Param("userId") Long userId); + + } diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java index b7ad959..8a07574 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java @@ -2,8 +2,14 @@ import com.ccapp.ccgo.matching.domain.entity.SubGroup; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + import java.util.List; +import java.util.Optional; public interface SubGroupRepository extends JpaRepository { List findByTeam_TeamId(Long teamId); + + } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 97e4c1f..52b95d6 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -26,12 +26,14 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; +@Slf4j @Service @RequiredArgsConstructor public class MatchingService { @@ -536,17 +538,43 @@ public void deleteQuestion(Long questionId) { // } //실험 - @Transactional(readOnly = true) - public List getMatchedUserNames(Long userId) { - List members = subGroupMemberRepository.findBySameSubGroup(userId); - System.out.println("조회된 멤버 수: " + members.size()); - for(SubGroupMember m : members) { - System.out.println("멤버 ID: " + m.getUser().getId() + ", 이름: " + m.getUser().getName()); + public List getMatchedUserNames(Long userId, Long teamId, Long subGroupId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("사용자 없음")); + + + // 1) user가 teamId 소속인지 확인 + boolean isMember = teamMemberRepository.existsByUser_IdAndTeam_TeamIdAndIsActiveTrue(userId, teamId); + + if (!isMember) { + throw new RuntimeException("팀 소속 아님"); } - return members.stream() - .filter(m -> !m.getUser().getId().equals(userId)) // 본인 제외 - .map(m -> m.getUser().getName()) + + // 2) 서브그룹 멤버 리스트 조회 + List subGroupUsers = subGroupMemberRepository.findUsersBySubGroupId(subGroupId); + + // 3) subGroupUsers 중 같은 팀 멤버만 필터링 + List filteredUsers = subGroupUsers.stream() + .filter(u -> teamMemberRepository.existsByUser_IdAndTeam_TeamIdAndIsActiveTrue(u.getId(), teamId)) + .collect(Collectors.toList()); + + // 4) 자기 자신 제외 후 이름 리스트 반환 + return filteredUsers.stream() + .filter(u -> !u.getId().equals(userId)) + .map(User::getName) .collect(Collectors.toList()); } + + + + //매칭된 직후 발동하는놈 + public Long findSubGroupIdByTeamIdAndUserId(Long teamId, Long userId) { + // 서브그룹 조회 (예: teamId, userId로 검색) + return subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId) + .orElse(null); // 없으면 null 반환 (적절히 처리) + } + + + } diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java index c891357..f1d5c74 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java @@ -1,10 +1,12 @@ package com.ccapp.ccgo.mission.controller; -import com.ccapp.ccgo.mission.dto.TeamMissionDto; +import com.ccapp.ccgo.mission.dto.MissionCompleteRequest; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import com.ccapp.ccgo.mission.service.MissionAssignmentService; +import com.ccapp.ccgo.mission.service.SubGroupMissionService; +import com.ccapp.ccgo.mission.dto.SubGroupMissionDto; import java.util.List; @@ -13,18 +15,48 @@ @RequiredArgsConstructor public class MissionAssignmentController { - private final MissionAssignmentService missionAssignmentService; + private final SubGroupMissionService subGroupMissionService; - @PostMapping("/assign/{teamId}") - public ResponseEntity assignMissionsToTeam(@PathVariable Long teamId) { - missionAssignmentService.assignMissionsToTeam(teamId); - return ResponseEntity.ok("미션이 팀에 성공적으로 부여되었습니다."); + // 서브그룹에 미션 부여 + @PostMapping("/assign/subgroup/{subGroupId}") + public ResponseEntity assignMissionsToSubGroup(@PathVariable Long subGroupId) { + subGroupMissionService.assignMissionsToSubGroup(subGroupId); + return ResponseEntity.ok("미션이 서브그룹에 성공적으로 부여되었습니다."); } - // 팀에 부여된 미션 리스트 조회 API - @GetMapping("/team/{teamId}") - public ResponseEntity> getTeamMissions(@PathVariable Long teamId) { - List missions = missionAssignmentService.getTeamMissions(teamId); + // 서브그룹에 부여된 미션 리스트 조회 + @GetMapping("/subgroup/{subGroupId}") + public ResponseEntity> getSubGroupMissions(@PathVariable Long subGroupId) { + List missions = subGroupMissionService.getMissions(subGroupId); return ResponseEntity.ok(missions); } + + // 미션 완료 처리 + @PostMapping("/complete") + public ResponseEntity completeMission(@RequestBody MissionCompleteRequest request) { + try { + subGroupMissionService.completeMission(request.getTeamId(), request.getSubGroupId(), request.getMissionId()); + return ResponseEntity.ok("미션 완료 처리 성공"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("미션 완료 처리 실패: " + e.getMessage()); + } + } + + + //미션 새로고침 + @PostMapping("/refresh/subgroup/{subGroupId}/{subGroupMissionId}/{score}") + public ResponseEntity refreshMission( + @PathVariable Long subGroupId, + @PathVariable Long subGroupMissionId, + @PathVariable Integer score) { + try { + System.out.println("진입합니당." + subGroupId + subGroupMissionId + score); + subGroupMissionService.refreshSingleMission(subGroupId, subGroupMissionId, score); + return ResponseEntity.ok("미션 새로고침 완료"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("미션 새로고침 실패: " + e.getMessage()); + } + } + } diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java deleted file mode 100644 index 6bee2bd..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionTemplateController.java +++ /dev/null @@ -1,28 +0,0 @@ -//package com.ccapp.ccgo.mission.controller; -//import com.ccapp.ccgo.mission.entity.MissionTemplate; -//import com.ccapp.ccgo.mission.service.MissionTemplateService; -//import lombok.RequiredArgsConstructor; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.*; -// -//import java.util.List; -// -//@RestController -//@RequestMapping("/api/missions") -//@RequiredArgsConstructor -//public class MissionTemplateController { -// -// private final MissionTemplateService missionTemplateService; -// -// @GetMapping("/score/{score}/six") -// public ResponseEntity> getSixMissionsByScore(@PathVariable int score) { -// return ResponseEntity.ok(missionTemplateService.getSixMissionsByScore(score)); -// } -// -// @PostMapping("/score/{score}/refresh") -// public ResponseEntity refreshMission(@PathVariable int score, -// @RequestBody List excludedIds) { -// MissionTemplate mission = missionTemplateService.refreshMission(score, excludedIds); -// return ResponseEntity.ok(mission); -// } -//} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionCompleteRequest.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionCompleteRequest.java new file mode 100644 index 0000000..ec502b0 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/MissionCompleteRequest.java @@ -0,0 +1,10 @@ +package com.ccapp.ccgo.mission.dto; + +import lombok.Data; + +@Data +public class MissionCompleteRequest { + private Long teamId; + private Long subGroupId; // nullable + private Long missionId; +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupMissionDto.java new file mode 100644 index 0000000..23d4a63 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupMissionDto.java @@ -0,0 +1,17 @@ +package com.ccapp.ccgo.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SubGroupMissionDto { + + private Long subGroupMissionId; + private Long missionTemplateId; + private String title; + private String description; + private Integer score; + private boolean completed; + +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java deleted file mode 100644 index 38390ec..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/dto/TeamMissionDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ccapp.ccgo.mission.dto; - -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class TeamMissionDto { - private Long missionId; // 미션 템플릿 ID - private String title; // 미션 제목 - private String description; // 미션 내용 - private Integer score; // 미션 점수 -} diff --git a/src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java b/src/main/java/com/ccapp/ccgo/mission/entity/SubGroupMission.java similarity index 53% rename from src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java rename to src/main/java/com/ccapp/ccgo/mission/entity/SubGroupMission.java index 8f3188d..c43bb55 100644 --- a/src/main/java/com/ccapp/ccgo/assignedmission/entity/AssignedMission.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/SubGroupMission.java @@ -1,31 +1,31 @@ -package com.ccapp.ccgo.assignedmission.entity; +package com.ccapp.ccgo.mission.entity; import com.ccapp.ccgo.matching.domain.entity.SubGroup; -import com.ccapp.ccgo.mission.entity.MissionTemplate; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; @Entity @Getter +@Setter @NoArgsConstructor @AllArgsConstructor @Builder -public class AssignedMission { +@Table(name = "subgroup_mission") +public class SubGroupMission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "sub_group_id") + @JoinColumn(name = "subgroup_id") private SubGroup subGroup; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "mission_template_id") private MissionTemplate missionTemplate; - private Boolean isCompleted; + private boolean completed; + + } diff --git a/src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java b/src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java deleted file mode 100644 index 738b8e4..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/entity/TeamMission.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ccapp.ccgo.mission.entity; - -import com.ccapp.ccgo.team.entity.Team; -import jakarta.persistence.*; -import lombok.*; - -@Entity -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Table(name = "team_mission") -public class TeamMission { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - // 팀과 다대일 관계 (여러 미션이 하나 팀에 속함) - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "team_id") - private Team team; - - // 미션 템플릿과 다대일 관계 (여러 팀 미션이 하나 미션 템플릿에 기반) - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "mission_template_id") - private MissionTemplate missionTemplate; - - public TeamMission(Team team, MissionTemplate missionTemplate) { - this.team = team; - this.missionTemplate = missionTemplate; - } -} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java index b06c4dd..647faf2 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionTemplateRepository.java @@ -2,21 +2,10 @@ import com.ccapp.ccgo.mission.entity.MissionTemplate; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - import java.util.List; -@Repository public interface MissionTemplateRepository extends JpaRepository { - @Query(value = "SELECT * FROM mission_template WHERE score = :score LIMIT 6", nativeQuery = true) - List findTop6ByScore(@Param("score") Integer score); - - // 새로고침용, 기존 6개 제외하고 랜덤 1개 조회 - @Query(value = "SELECT * FROM mission_template WHERE score = :score AND id NOT IN :excludedIds ORDER BY RAND() LIMIT 1", nativeQuery = true) - MissionTemplate findRandomByScoreExcludingIds(@Param("score") Integer score, @Param("excludedIds") List excludedIds); - List findByScore(Integer score); + } diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java new file mode 100644 index 0000000..2b06c7b --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java @@ -0,0 +1,16 @@ +package com.ccapp.ccgo.mission.repository; + +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.mission.entity.SubGroupMission; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SubGroupMissionRepository extends JpaRepository { + + List findBySubGroup(SubGroup subGroup); + + Optional findBySubGroupAndMissionTemplateId(SubGroup subGroup, Long missionTemplateId); + +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java deleted file mode 100644 index c4bf99a..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/repository/TeamMissionRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ccapp.ccgo.mission.repository; - -import com.ccapp.ccgo.mission.entity.MissionTemplate; -import com.ccapp.ccgo.mission.entity.TeamMission; -import com.ccapp.ccgo.team.entity.Team; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface TeamMissionRepository extends JpaRepository { - - // 특정 팀에 할당된 미션 리스트 조회 - List findByTeam(Team team); - - - -} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java deleted file mode 100644 index 65b65f8..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionAssignmentService.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.ccapp.ccgo.mission.service; - -import com.ccapp.ccgo.mission.dto.TeamMissionDto; -import com.ccapp.ccgo.mission.entity.MissionTemplate; -import com.ccapp.ccgo.mission.entity.TeamMission; -import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; -import com.ccapp.ccgo.mission.repository.TeamMissionRepository; -import com.ccapp.ccgo.team.entity.Team; -import com.ccapp.ccgo.team.repository.TeamRepository; -import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class MissionAssignmentService { - - private final TeamRepository teamRepository; - private final MissionTemplateRepository missionTemplateRepository; - private final TeamMissionRepository teamMissionRepository; - - //팀에게 미션 6개씩 부여 - @Transactional - public void assignMissionsToTeam(Long teamId) { - Team team = teamRepository.findById(teamId) - .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); - - if (!teamMissionRepository.findByTeam(team).isEmpty()) { - throw new IllegalStateException("이미 이 팀에는 미션이 부여되었습니다."); - } - - // 각 학점별로 6개씩 미션 랜덤 선택 및 저장 - assignMissionsByScore(team, 1, 6); - assignMissionsByScore(team, 3, 6); - assignMissionsByScore(team, 5, 6); - assignMissionsByScore(team, 10, 6); - } - - private void assignMissionsByScore(Team team, Integer score, int count) { - List missions = missionTemplateRepository.findByScore(score); - - if (missions.size() < count) { - throw new IllegalStateException(score + "점 미션이 최소 " + count + "개 이상 필요합니다."); - } - - // 랜덤으로 섞고 count 만큼 잘라서 사용 - Collections.shuffle(missions); - List selectedMissions = missions.stream() - .limit(count) - .collect(Collectors.toList()); - - for (MissionTemplate template : selectedMissions) { - TeamMission teamMission = new TeamMission(team, template); - teamMissionRepository.save(teamMission); - } - } - - @Transactional(readOnly = true) - public List getTeamMissions(Long teamId) { - Team team = teamRepository.findById(teamId) - .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); - - List teamMissions = teamMissionRepository.findByTeam(team); - - return teamMissions.stream() - .map(tm -> new TeamMissionDto( - tm.getMissionTemplate().getId(), - tm.getMissionTemplate().getTitle(), - tm.getMissionTemplate().getDescription(), - tm.getMissionTemplate().getScore() - )) - .collect(Collectors.toList()); - } - -} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java deleted file mode 100644 index 6a8ee54..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateService.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ccapp.ccgo.mission.service; -import com.ccapp.ccgo.mission.entity.MissionTemplate; -import java.util.List; - - -public interface MissionTemplateService { - - List getSixMissionsByScore(Integer score); - - MissionTemplate refreshMission(Integer score, List excludedIds); -} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java b/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java deleted file mode 100644 index aa3c34d..0000000 --- a/src/main/java/com/ccapp/ccgo/mission/service/MissionTemplateServiceImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ccapp.ccgo.mission.service; - -import com.ccapp.ccgo.mission.entity.MissionTemplate; -import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class MissionTemplateServiceImpl implements MissionTemplateService { - - private final MissionTemplateRepository missionTemplateRepository; - - @Override - public List getSixMissionsByScore(Integer score) { - return missionTemplateRepository.findTop6ByScore(score); - } - - @Override - public MissionTemplate refreshMission(Integer score, List excludedIds) { - return missionTemplateRepository.findRandomByScoreExcludingIds(score, excludedIds); - } -} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java new file mode 100644 index 0000000..c5a9ec3 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -0,0 +1,172 @@ +package com.ccapp.ccgo.mission.service; + +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.mission.dto.SubGroupMissionDto; +import com.ccapp.ccgo.mission.entity.MissionTemplate; +import com.ccapp.ccgo.mission.entity.SubGroupMission; +import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; +import com.ccapp.ccgo.mission.repository.SubGroupMissionRepository; +import lombok.RequiredArgsConstructor; +import java.util.concurrent.ThreadLocalRandom; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class SubGroupMissionService { + + private final SubGroupRepository subGroupRepository; + private final MissionTemplateRepository missionTemplateRepository; + private final SubGroupMissionRepository subGroupMissionRepository; + + // 서브그룹에 미션 부여 + @Transactional + public void assignMissionsToSubGroup(Long subGroupId) { + SubGroup subGroup = subGroupRepository.findById(subGroupId) + .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); + + if (!subGroupMissionRepository.findBySubGroup(subGroup).isEmpty()) { + throw new IllegalStateException("이미 이 서브그룹에 미션이 부여되어 있습니다."); + } + + assignMissionsByScore(subGroup, 1, 6); + assignMissionsByScore(subGroup, 3, 6); + assignMissionsByScore(subGroup, 5, 6); + assignMissionsByScore(subGroup, 10, 6); + } + + private void assignMissionsByScore(SubGroup subGroup, Integer score, int count) { + List missions = missionTemplateRepository.findByScore(score); + if (missions.size() < count) { + throw new IllegalStateException(score + "점 미션이 최소 " + count + "개 이상 필요합니다."); + } + + Collections.shuffle(missions); + List selected = missions.subList(0, count); + + for (MissionTemplate missionTemplate : selected) { + SubGroupMission mission = SubGroupMission.builder() + .subGroup(subGroup) + .missionTemplate(missionTemplate) + .completed(false) + .build(); + subGroupMissionRepository.save(mission); + } + } + + // 미션 완료 처리 + @Transactional + public void completeMission(Long subGroupMissionId) { + SubGroupMission mission = subGroupMissionRepository.findById(subGroupMissionId) + .orElseThrow(() -> new IllegalArgumentException("미션을 찾을 수 없습니다.")); + mission.setCompleted(true); + } + + + + + // 서브그룹 미션 조회 + @Transactional(readOnly = true) + public List getMissions(Long subGroupId) { + SubGroup subGroup = subGroupRepository.findById(subGroupId) + .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); + + List missions = subGroupMissionRepository.findBySubGroup(subGroup); + + return missions.stream() + .map(m -> new SubGroupMissionDto( + m.getId(), + m.getMissionTemplate().getId(), + m.getMissionTemplate().getTitle(), + m.getMissionTemplate().getDescription(), + m.getMissionTemplate().getScore(), + m.isCompleted() + )) + .toList(); + } + + // 미션 새로고침 (완료 안 된 미션 삭제 후 새 할당) + @Transactional + public void refreshSingleMission(Long subGroupId, Long subGroupMissionId, Integer score) { + System.out.println("[refreshSingleMission] 호출됨 - subGroupId: " + subGroupId + ", subGroupMissionId: " + subGroupMissionId + ", score: " + score); + + // 1. 서브그룹 조회 + SubGroup subGroup = subGroupRepository.findById(subGroupId) + .orElseThrow(() -> { + System.out.println("[refreshSingleMission] 서브그룹을 찾을 수 없습니다."); + return new IllegalArgumentException("서브그룹을 찾을 수 없습니다."); + }); + + // 2. 교체할 기존 미션 조회 + SubGroupMission oldMission = subGroupMissionRepository.findById(subGroupMissionId) + .orElseThrow(() -> { + System.out.println("[refreshSingleMission] 교체할 미션을 찾을 수 없습니다."); + return new IllegalArgumentException("교체할 미션을 찾을 수 없습니다."); + }); + + if (!oldMission.getSubGroup().getId().equals(subGroupId)) { + System.out.println("[refreshSingleMission] 해당 미션이 서브그룹에 속하지 않습니다."); + throw new IllegalArgumentException("해당 미션이 서브그룹에 속하지 않습니다."); + } + + // 3. 현재 서브그룹에 할당된 동일 학점 미션 ID + List assignedMissionTemplateIds = subGroupMissionRepository.findBySubGroup(subGroup).stream() + .filter(m -> m.getMissionTemplate().getScore().equals(score)) + .map(m -> m.getMissionTemplate().getId()) + .toList(); + + System.out.println("[refreshSingleMission] 현재 서브그룹에 할당된 동일 학점 미션 ID 목록: " + assignedMissionTemplateIds); + + // 4. 교체 후보 미션 (현재 미션 제외) - 가변 리스트로 변환 + List candidates = missionTemplateRepository.findByScore(score).stream() + .filter(mt -> !assignedMissionTemplateIds.contains(mt.getId())) // 이미 할당된 것 제외 + .filter(mt -> !mt.getId().equals(oldMission.getMissionTemplate().getId())) // 기존 미션 제외 + .collect(Collectors.toList()); + + System.out.println("[refreshSingleMission] 교체 후보 미션 수: " + candidates.size()); + + if (candidates.isEmpty()) { + throw new IllegalStateException("교체 가능한 미션이 없습니다."); + } + + // 5. 랜덤으로 새로운 미션 선택 + int randomIndex = ThreadLocalRandom.current().nextInt(candidates.size()); + MissionTemplate newMissionTemplate = candidates.get(randomIndex); + + // 6. 교체 처리 + oldMission.setMissionTemplate(newMissionTemplate); + oldMission.setCompleted(false); + + System.out.println("[refreshSingleMission] 랜덤으로 선택된 미션 ID: " + newMissionTemplate.getId()); +} + + + + + //미션 완료 처리 + @Transactional + public void completeMission(Long teamId, Long subGroupId, Long missionTemplateId) { + // 우선 서브그룹 존재 확인 + SubGroup subGroup = subGroupRepository.findById(subGroupId) + .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); + + // subGroup이 teamId에 속하는지 확인 (필요시, SubGroup 엔티티에 teamId 필드가 있다고 가정) + if (!subGroup.getTeam().getTeamId().equals(teamId)) { + throw new IllegalArgumentException("서브그룹이 해당 팀에 속하지 않습니다."); + } + + // 해당 서브그룹 미션 찾기 (missionTemplateId 기준) + SubGroupMission mission = subGroupMissionRepository.findBySubGroupAndMissionTemplateId(subGroup, missionTemplateId) + .orElseThrow(() -> new IllegalArgumentException("해당 미션이 서브그룹에 존재하지 않습니다.")); + + // 완료 처리 + mission.setCompleted(true); + } + + +} diff --git a/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java b/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java index 77589a0..63a4c5a 100644 --- a/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/team/repository/TeamMemberRepository.java @@ -28,6 +28,7 @@ public interface TeamMemberRepository extends JpaRepository { // 유저(user)에 대해 isActive가 true인 TeamMember 리스트 반환 List findAllByUserAndIsActiveTrue(User user); + // 팀별 멤버 목록 List findAllByTeamAndIsActiveTrue(Team team); @@ -37,4 +38,7 @@ public interface TeamMemberRepository extends JpaRepository { List findByTeam_TeamIdAndIsActiveTrue(Long teamId); List findByUserAndTeamAndIsActiveTrue(User user, Team team); + + boolean existsByUser_IdAndTeam_TeamIdAndIsActiveTrue(Long userId, Long teamId); + } diff --git a/src/main/java/com/ccapp/ccgo/user/entity/User.java b/src/main/java/com/ccapp/ccgo/user/entity/User.java index 099cb62..6579306 100644 --- a/src/main/java/com/ccapp/ccgo/user/entity/User.java +++ b/src/main/java/com/ccapp/ccgo/user/entity/User.java @@ -54,10 +54,6 @@ public void prePersist() { this.createdAt = LocalDateTime.now(); } - //관심사 설문조사 완료여부 - @Column(name = "test_completed", nullable = false) - private boolean isInterestCompleted = false; - } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 22f7f7e..65d55a6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = qkrwlsdn +spring.datasource.password = 06250730cc jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 9804b96e26ad1a5bb3010a0f284b534445e03dc1 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 17 Jul 2025 15:29:07 +0900 Subject: [PATCH 031/132] Final Done --- .../ccgo/matching/repository/SubGroupMemberRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java index cf29a82..a0d8d8a 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java @@ -20,15 +20,14 @@ public interface SubGroupMemberRepository extends JpaRepository findBySameSubGroup(@Param("userId") Long userId); + "(SELECT sm.subGroup FROM SubGroupMember sm WHERE sm.user.id = :userId AND sm.subGroup.team.teamId = :teamId)") + List findBySameSubGroup(@Param("userId") Long userId, @Param("teamId") Long teamId); @Query("SELECT s.user FROM SubGroupMember s WHERE s.subGroup.id = :subGroupId") List findUsersBySubGroupId(@Param("subGroupId") Long subGroupId); - @Query("SELECT sgm.subGroup.id FROM SubGroupMember sgm WHERE sgm.subGroup.team.teamId = :teamId AND sgm.user.id = :userId") Optional findSubGroupIdByTeamIdAndUserId(@Param("teamId") Long teamId, @Param("userId") Long userId); From 0d391c5c3025e1254223b308fce609728b747a02 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 18 Jul 2025 13:13:53 +0900 Subject: [PATCH 032/132] Add Usercontext --- .../controller/MatchingController.java | 73 +++- .../domain/entity/SubGroupMember.java | 2 + .../repository/SubGroupMemberRepository.java | 25 +- .../matching/service/MatchingService.java | 366 +++++++++++++----- 4 files changed, 327 insertions(+), 139 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index 8753c03..0219dc1 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -82,26 +82,53 @@ public void deleteQuestion(@PathVariable Long questionId) { // return ResponseEntity.ok(response); // } - //매칭된 이후, 팀id랑 subgroupid로 팀원 찾아오는놈 - @GetMapping("/matched-names/{teamId}/{subGroupId}") - public ResponseEntity getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails, - @PathVariable Long teamId, - @PathVariable Long subGroupId) { - try { - Long userId = userDetails.getUser().getId(); - System.out.println("[getMatchedNames] userId: " + userId + ", teamId: " + teamId + ", subGroupId: " + subGroupId); - - List matchedNames = matchingService.getMatchedUserNames(userId, teamId, subGroupId); - - System.out.println("[getMatchedNames] matchedNames size: " + (matchedNames != null ? matchedNames.size() : "null")); - - MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); - return ResponseEntity.ok(response); - } catch (Exception e) { - System.err.println("[getMatchedNames] 서버 에러 발생: " + e.getMessage()); - e.printStackTrace(); - return ResponseEntity.status(500).build(); +// //매칭된 이후, 팀id랑 subgroupid로 팀원 찾아오는놈 +// //이거 authenticaiton 아직 구현 안되서 그런듯 +// @GetMapping("/matched-names/{teamId}/{subGroupId}") +// public ResponseEntity getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails, +// @PathVariable Long teamId, +// @PathVariable Long subGroupId) { +// try { +// Long userId = userDetails.getUser().getId(); +// System.out.println("[getMatchedNames] userId: " + userId + ", teamId: " + teamId + ", subGroupId: " + subGroupId); +// +// List matchedNames = matchingService.getMatchedUserNames(userId, teamId, subGroupId); +// +// System.out.println("[getMatchedNames] matchedNames size: " + (matchedNames != null ? matchedNames.size() : "null")); +// +// MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); +// return ResponseEntity.ok(response); +// } catch (Exception e) { +// System.err.println("[getMatchedNames] 서버 에러 발생: " + e.getMessage()); +// e.printStackTrace(); +// return ResponseEntity.status(500).build(); +// } +// } + + //매칭되고 난 뒤 쓰이는 놈일텐데.. + @GetMapping("/matched-names/{teamId}") + public ResponseEntity getMatchedNames( + @RequestParam Long userId, + @PathVariable Long teamId) { + + System.out.printf("[getMatchedNames] userId: %d, teamId: %d%n", userId, teamId); + + // 1) userId와 teamId로 subGroupId 조회 + Long subGroupId = matchingService.findSubGroupIdByTeamIdAndUserId(teamId, userId); + + if (subGroupId == null) { + // 서브그룹 미존재 (매칭 안된 상태) + return ResponseEntity.status(404).body(new MatchedNamesResponse(teamId, null, List.of())); } + + // 2) 매칭된 멤버 이름 조회 (본인 제외) + List matchedNames = matchingService.getMatchedUserNames(userId, teamId); + + System.out.printf("[getMatchedNames] matchedNames size: %d%n", matchedNames.size()); + + MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); + + return ResponseEntity.ok(response); } //매칭된 직후 작동하는놈 @@ -116,5 +143,11 @@ public ResponseEntity> getSubGroupIdByTeamId( return ResponseEntity.ok(response); } - } + + + + + + + diff --git a/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java index 0edf9f6..c3c784c 100644 --- a/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java +++ b/src/main/java/com/ccapp/ccgo/matching/domain/entity/SubGroupMember.java @@ -31,4 +31,6 @@ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + + } diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java index a0d8d8a..1e85944 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupMemberRepository.java @@ -13,23 +13,24 @@ public interface SubGroupMemberRepository extends JpaRepository { List findBySubGroup_Id(Long subGroupId); - // SubGroup 기준으로 멤버들 조회 - List findBySubGroup(SubGroup subGroup); - - long countBySubGroup_Id(Long subGroupId); - - //subgroup 자신을 제외한 팀원 이름조회 - @Query("SELECT m FROM SubGroupMember m WHERE m.subGroup = " + - "(SELECT sm.subGroup FROM SubGroupMember sm WHERE sm.user.id = :userId AND sm.subGroup.team.teamId = :teamId)") - List findBySameSubGroup(@Param("userId") Long userId, @Param("teamId") Long teamId); + @Query(""" + SELECT m.user + FROM SubGroupMember m + WHERE m.subGroup = ( + SELECT sm.subGroup + FROM SubGroupMember sm + WHERE sm.user.id = :userId AND sm.subGroup.team.teamId = :teamId + ) + AND m.user.id <> :userId + """) + List findTeamMatchedMembersExcludingUser(@Param("userId") Long userId, @Param("teamId") Long teamId); - @Query("SELECT s.user FROM SubGroupMember s WHERE s.subGroup.id = :subGroupId") - List findUsersBySubGroupId(@Param("subGroupId") Long subGroupId); - + long countBySubGroup_Id(Long subGroupId); @Query("SELECT sgm.subGroup.id FROM SubGroupMember sgm WHERE sgm.subGroup.team.teamId = :teamId AND sgm.user.id = :userId") Optional findSubGroupIdByTeamIdAndUserId(@Param("teamId") Long teamId, @Param("userId") Long userId); + } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 52b95d6..d54f518 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -270,16 +270,27 @@ private double calculateSimilarityScore(TeamMember a, TeamMember b, Long teamId) return similarityRate * 100; } - // 6. Greedy 매칭 결과 저장 - private void saveSubGroupMember(SubGroup sg, User user) { - SubGroupMember sgm = SubGroupMember.builder() - .subGroup(sg) + //원래 진우꺼. +// // 6. Greedy 매칭 결과 저장 +// private void saveSubGroupMember(SubGroup sg, User user) { +// SubGroupMember sgm = SubGroupMember.builder() +// .subGroup(sg) +// .user(user) +// .build(); +// subGroupMemberRepository.save(sgm); +// } + + //나중에 지우기 + private void saveSubGroupMember(SubGroup subGroup, User user) { + SubGroupMember member = SubGroupMember.builder() + .subGroup(subGroup) // 영속 상태 보장 .user(user) .build(); - subGroupMemberRepository.save(sgm); + subGroupMemberRepository.save(member); } - // 7. 잉여 처리 + + //새로 도전해보느놈 나중에 지우기 private void handleLeftovers(List groupA, List groupB, Set matchedUserIds, @@ -287,97 +298,245 @@ private void handleLeftovers(List groupA, int groupIndex, List subGroups) { - // 그룹 이름 중복 방지 - Set existingGroupNames = subGroups.stream() - .map(SubGroup::getName) - .collect(Collectors.toSet()); - - List leftovers = new ArrayList<>(); - groupA.stream() - .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) - .forEach(leftovers::add); - groupB.stream() - .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) - .forEach(leftovers::add); - - Iterator it = leftovers.iterator(); - - while (it.hasNext()) { - TeamMember tm1 = it.next(); - if (it.hasNext()) { - TeamMember tm2 = it.next(); - - // 잉여 그룹 이름 생성 - String groupName; - do { - groupName = team.getTeamName() + groupIndex; - groupIndex++; - } while (existingGroupNames.contains(groupName)); - existingGroupNames.add(groupName); - - List groupMembers = List.of(tm1.getUser(), tm2.getUser()); - - SubGroup sg = SubGroup.builder() - .team(team) - .name(groupName) - .memberCount(groupMembers.size()) - .build(); + // 남은 남자 / 여자 리스트 + List leftoverMales = new ArrayList<>(); + List leftoverFemales = new ArrayList<>(); + + groupA.stream().filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) + .forEach(tm -> { + if ("MALE".equalsIgnoreCase(tm.getUser().getGender())) leftoverMales.add(tm); + else leftoverFemales.add(tm); + }); + + groupB.stream().filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) + .forEach(tm -> { + if ("MALE".equalsIgnoreCase(tm.getUser().getGender())) leftoverMales.add(tm); + else leftoverFemales.add(tm); + }); + + // ✅ 남자 leftover → 기존 그룹 중 memberCount < 4인 곳에 유사도 기반 추가 + for (TeamMember male : leftoverMales) { + SubGroup bestGroup = findBestGroupForUser(male, subGroups, team, 4); // 변경: 3 → 4 + if (bestGroup != null) { + saveSubGroupMember(bestGroup, male.getUser()); + bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); + subGroupRepository.save(bestGroup); + } else { + SubGroup sg = createNewGroup(team, groupIndex++); + saveSubGroupMember(sg, male.getUser()); + sg.setMemberCount(1); subGroupRepository.save(sg); - - for (User user : groupMembers) { - saveSubGroupMember(sg, user); - } - subGroups.add(sg); + } + } + // ✅ 여자 leftover → 기존 그룹 중 memberCount < 4인 곳에 유사도 기반 추가 + for (TeamMember female : leftoverFemales) { + SubGroup bestGroup = findBestGroupForUser(female, subGroups, team, 4); + if (bestGroup != null) { + saveSubGroupMember(bestGroup, female.getUser()); + bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); + subGroupRepository.save(bestGroup); } else { - // 홀수 남음 → 기존 그룹 중 가장 점수(각각의 유사도 점수 구한 뒤의 평균) 높은 그룹으로 편입 - if (!subGroups.isEmpty()) { - double maxAvgSimilarity = Double.NEGATIVE_INFINITY; - SubGroup bestGroup = null; - - for (SubGroup sg : subGroups) { - List members = - subGroupMemberRepository.findBySubGroup_Id(sg.getId()); - - double totalSimilarity = 0; - int memberCount = 0; - - for (SubGroupMember sgm : members) { - TeamMember existingTm = teamMemberRepository - .findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) - .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); - - double similarityScore = - calculateSimilarityScore(tm1, existingTm, team.getTeamId()); - - totalSimilarity += similarityScore; - memberCount++; - } - - double avgSimilarity = memberCount > 0 ? totalSimilarity / memberCount : 0; - - if (avgSimilarity > maxAvgSimilarity) { - maxAvgSimilarity = avgSimilarity; - bestGroup = sg; - } - } - - if (bestGroup != null) { - saveSubGroupMember(bestGroup, tm1.getUser()); - - long newCount = - subGroupMemberRepository.countBySubGroup_Id(bestGroup.getId()); - bestGroup.setMemberCount((int) newCount); - subGroupRepository.save(bestGroup); - } - } else { - System.out.println("[WARN] 홀수 남았지만 기존 그룹 없음. 유저ID: " + tm1.getUser().getId()); - } + SubGroup sg = createNewGroup(team, groupIndex++); + saveSubGroupMember(sg, female.getUser()); + sg.setMemberCount(1); + subGroupRepository.save(sg); + subGroups.add(sg); } } } + + private SubGroup createNewGroup(Team team, int index) { + String groupName = team.getTeamName() + index; + SubGroup subGroup = SubGroup.builder() + .team(team) + .name(groupName) + .memberCount(0) + .build(); + return subGroupRepository.save(subGroup); // 반드시 save 후 반환 + } + + + private SubGroup findBestGroupForUser(TeamMember user, List subGroups, Team team, int maxSize) { + SubGroup bestGroup = null; + double maxAvgSimilarity = Double.NEGATIVE_INFINITY; + + for (SubGroup sg : subGroups) { + if (sg.getMemberCount() >= maxSize) continue; + + List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); + double totalSimilarity = 0; + int count = 0; + + for (SubGroupMember sgm : members) { + TeamMember existingTm = teamMemberRepository + .findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) + .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); + totalSimilarity += calculateSimilarityScore(user, existingTm, team.getTeamId()); + count++; + } + + // 평균 유사도 + double avgSim = (count > 0) ? totalSimilarity / count : 0; + + if (avgSim > maxAvgSimilarity) { + maxAvgSimilarity = avgSim; + bestGroup = sg; + } + } + + // ✅ fallback: 모든 그룹 점수가 0이거나 질문이 없어서 bestGroup이 null이면 첫 번째 그룹 반환 + if (bestGroup == null && !subGroups.isEmpty()) { + bestGroup = subGroups.get(0); + } + + return bestGroup; + } + + + +// /** +// * ✅ 특정 유저를 편입할 최적 그룹 찾기 +// * 조건: +// * - 현재 인원 < maxSize +// * - 유사도 평균이 가장 높은 그룹 +// */ +// private SubGroup findBestGroupForUser(TeamMember user, +// List subGroups, +// Team team, +// int maxSize) { +// +// SubGroup bestGroup = null; +// double maxAvgSimilarity = Double.NEGATIVE_INFINITY; +// +// for (SubGroup sg : subGroups) { +// if (sg.getMemberCount() >= maxSize) continue; +// +// List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); +// double totalSimilarity = 0; +// int count = 0; +// +// for (SubGroupMember sgm : members) { +// TeamMember existingTm = teamMemberRepository.findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) +// .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); +// double simScore = calculateSimilarityScore(user, existingTm, team.getTeamId()); +// totalSimilarity += simScore; +// count++; +// } +// +// double avgSimilarity = count > 0 ? totalSimilarity / count : 0; +// if (avgSimilarity > maxAvgSimilarity) { +// maxAvgSimilarity = avgSimilarity; +// bestGroup = sg; +// } +// } +// +// return bestGroup; +// } + + + //진우원래꺼 + // 7. 잉여 처리 +// private void handleLeftovers(List groupA, +// List groupB, +// Set matchedUserIds, +// Team team, +// int groupIndex, +// List subGroups) { +// +// // 그룹 이름 중복 방지 +// Set existingGroupNames = subGroups.stream() +// .map(SubGroup::getName) +// .collect(Collectors.toSet()); +// +// List leftovers = new ArrayList<>(); +// groupA.stream() +// .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) +// .forEach(leftovers::add); +// groupB.stream() +// .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) +// .forEach(leftovers::add); +// +// Iterator it = leftovers.iterator(); +// +// while (it.hasNext()) { +// TeamMember tm1 = it.next(); +// if (it.hasNext()) { +// TeamMember tm2 = it.next(); +// +// // 잉여 그룹 이름 생성 +// String groupName; +// do { +// groupName = team.getTeamName() + groupIndex; +// groupIndex++; +// } while (existingGroupNames.contains(groupName)); +// existingGroupNames.add(groupName); +// +// List groupMembers = List.of(tm1.getUser(), tm2.getUser()); +// +// SubGroup sg = SubGroup.builder() +// .team(team) +// .name(groupName) +// .memberCount(groupMembers.size()) +// .build(); +// subGroupRepository.save(sg); +// +// for (User user : groupMembers) { +// saveSubGroupMember(sg, user); +// } +// +// subGroups.add(sg); +// +// } else { +// // 홀수 남음 → 기존 그룹 중 가장 점수(각각의 유사도 점수 구한 뒤의 평균) 높은 그룹으로 편입 +// if (!subGroups.isEmpty()) { +// double maxAvgSimilarity = Double.NEGATIVE_INFINITY; +// SubGroup bestGroup = null; +// +// for (SubGroup sg : subGroups) { +// List members = +// subGroupMemberRepository.findBySubGroup_Id(sg.getId()); +// +// double totalSimilarity = 0; +// int memberCount = 0; +// +// for (SubGroupMember sgm : members) { +// TeamMember existingTm = teamMemberRepository +// .findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) +// .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); +// +// double similarityScore = +// calculateSimilarityScore(tm1, existingTm, team.getTeamId()); +// +// totalSimilarity += similarityScore; +// memberCount++; +// } +// +// double avgSimilarity = memberCount > 0 ? totalSimilarity / memberCount : 0; +// +// if (avgSimilarity > maxAvgSimilarity) { +// maxAvgSimilarity = avgSimilarity; +// bestGroup = sg; +// } +// } +// +// if (bestGroup != null) { +// saveSubGroupMember(bestGroup, tm1.getUser()); +// +// long newCount = +// subGroupMemberRepository.countBySubGroup_Id(bestGroup.getId()); +// bestGroup.setMemberCount((int) newCount); +// subGroupRepository.save(bestGroup); +// } +// } else { +// System.out.println("[WARN] 홀수 남았지만 기존 그룹 없음. 유저ID: " + tm1.getUser().getId()); +// } +// } +// } +// } + // 8. 결과 DTO 생성 private MatchingResponseDto buildMatchingResponseDto(Team team, List subGroups) { List resultDtos = new ArrayList<>(); @@ -539,29 +698,22 @@ public void deleteQuestion(Long questionId) { //실험 @Transactional(readOnly = true) - public List getMatchedUserNames(Long userId, Long teamId, Long subGroupId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new RuntimeException("사용자 없음")); + public List getMatchedUserNames(Long userId, Long teamId) { + // 1. 유저 존재 확인 + userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다.")); - - // 1) user가 teamId 소속인지 확인 + // 2. 팀 소속 여부 확인 (활성 멤버만) boolean isMember = teamMemberRepository.existsByUser_IdAndTeam_TeamIdAndIsActiveTrue(userId, teamId); - if (!isMember) { - throw new RuntimeException("팀 소속 아님"); + throw new RuntimeException("해당 팀에 소속되어 있지 않습니다."); } - // 2) 서브그룹 멤버 리스트 조회 - List subGroupUsers = subGroupMemberRepository.findUsersBySubGroupId(subGroupId); + // 3. 유저가 속한 SubGroup 멤버(본인 제외) 조회 + List matchedUsers = subGroupMemberRepository.findTeamMatchedMembersExcludingUser(userId, teamId); - // 3) subGroupUsers 중 같은 팀 멤버만 필터링 - List filteredUsers = subGroupUsers.stream() - .filter(u -> teamMemberRepository.existsByUser_IdAndTeam_TeamIdAndIsActiveTrue(u.getId(), teamId)) - .collect(Collectors.toList()); - - // 4) 자기 자신 제외 후 이름 리스트 반환 - return filteredUsers.stream() - .filter(u -> !u.getId().equals(userId)) + // 4. 이름만 리스트로 변환 후 반환 + return matchedUsers.stream() .map(User::getName) .collect(Collectors.toList()); } @@ -570,11 +722,11 @@ public List getMatchedUserNames(Long userId, Long teamId, Long subGroupI //매칭된 직후 발동하는놈 public Long findSubGroupIdByTeamIdAndUserId(Long teamId, Long userId) { - // 서브그룹 조회 (예: teamId, userId로 검색) return subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId) .orElse(null); // 없으면 null 반환 (적절히 처리) } + } From 28e6418d55cce196426e96f43d61d5cbe9392ab6 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 18 Jul 2025 17:10:01 +0900 Subject: [PATCH 033/132] Add dashboard --- .../repository/SubGroupRepository.java | 1 - .../controller/ScoreboardController.java | 30 ++++++++ .../mission/dto/ScoreboardResponseDto.java | 16 +++++ .../ccgo/mission/dto/SubGroupScoreDto.java | 14 ++++ .../repository/SubGroupMissionRepository.java | 1 + .../mission/service/ScoreboardService.java | 72 +++++++++++++++++++ .../service/SubGroupMissionService.java | 3 + .../ccgo/team/controller/TeamController.java | 17 +++++ .../ccgo/team/dto/SetMinScoreRequest.java | 10 +++ .../ccapp/ccgo/team/service/TeamService.java | 41 +++++++++++ 10 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java create mode 100644 src/main/java/com/ccapp/ccgo/team/dto/SetMinScoreRequest.java create mode 100644 src/main/java/com/ccapp/ccgo/team/service/TeamService.java diff --git a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java index 8a07574..a254348 100644 --- a/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java +++ b/src/main/java/com/ccapp/ccgo/matching/repository/SubGroupRepository.java @@ -11,5 +11,4 @@ public interface SubGroupRepository extends JpaRepository { List findByTeam_TeamId(Long teamId); - } diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java b/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java new file mode 100644 index 0000000..01f7bef --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java @@ -0,0 +1,30 @@ +package com.ccapp.ccgo.mission.controller; + +import com.ccapp.ccgo.mission.dto.ScoreboardResponseDto; +import com.ccapp.ccgo.mission.service.ScoreboardService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/teams/{teamId}/scoreboard") +@RequiredArgsConstructor +public class ScoreboardController { + + private final ScoreboardService scoreboardService; + + /** + * 특정 팀의 스코어보드를 조회합니다. + * @param teamId 팀 ID (경로 변수) + * @param userId 사용자 ID (쿼리 파라미터 혹은 인증 토큰에서 추출 가능) + * @return 스코어보드 정보 + */ + @GetMapping + public ResponseEntity getScoreboard( + @PathVariable Long teamId, + @RequestParam Long userId + ) { + ScoreboardResponseDto response = scoreboardService.getScoreboard(teamId, userId); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java new file mode 100644 index 0000000..1e7427b --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java @@ -0,0 +1,16 @@ +package com.ccapp.ccgo.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ScoreboardResponseDto { + private int minScore; // 팀 최소 학점 + private SubGroupScoreDto mySubGroup; // 현재 유저의 서브그룹 점수 + private List otherSubGroups; // 다른 서브그룹들 점수 +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java new file mode 100644 index 0000000..f39728f --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java @@ -0,0 +1,14 @@ +package com.ccapp.ccgo.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SubGroupScoreDto { + private Long subGroupId; + private String name; // "1조" 등 + private int score; // 현재 학점 +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java index 2b06c7b..9e6097b 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java @@ -13,4 +13,5 @@ public interface SubGroupMissionRepository extends JpaRepository findBySubGroupAndMissionTemplateId(SubGroup subGroup, Long missionTemplateId); + } diff --git a/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java b/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java new file mode 100644 index 0000000..4301a86 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java @@ -0,0 +1,72 @@ +package com.ccapp.ccgo.mission.service; + +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; +import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.mission.dto.ScoreboardResponseDto; +import com.ccapp.ccgo.mission.dto.SubGroupScoreDto; +import com.ccapp.ccgo.mission.entity.SubGroupMission; +import com.ccapp.ccgo.mission.repository.SubGroupMissionRepository; +import com.ccapp.ccgo.team.service.TeamService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ScoreboardService { + + private final SubGroupMemberRepository subGroupMemberRepository; + private final SubGroupRepository subGroupRepository; + private final SubGroupMissionRepository subGroupMissionRepository; + private final TeamService teamService; // 팀 최소 학점 조회용 + + // 현재 유저가 속한 서브그룹 점수 조회 + public SubGroupScoreDto getMySubGroupScore(Long teamId, Long userId) { + Long subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId) + .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); + + SubGroup subGroup = subGroupRepository.findById(subGroupId) + .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); + + int score = calculateSubGroupScore(subGroup); + + return new SubGroupScoreDto(subGroup.getId(), subGroup.getName(), score); + } + + // 팀 내 다른 서브그룹 점수 조회 (내 서브그룹 제외) + public List getOtherSubGroupScores(Long teamId, Long excludeSubGroupId) { + List subGroups = subGroupRepository.findByTeam_TeamId(teamId); + + return subGroups.stream() + .filter(sg -> !sg.getId().equals(excludeSubGroupId)) + .map(sg -> new SubGroupScoreDto(sg.getId(), sg.getName(), calculateSubGroupScore(sg))) + .collect(Collectors.toList()); + } + + // 서브그룹 점수 계산 (완료된 미션 점수 합) + private int calculateSubGroupScore(SubGroup subGroup) { + List missions = subGroupMissionRepository.findBySubGroup(subGroup); + + return missions.stream() + .filter(SubGroupMission::isCompleted) + .mapToInt(m -> m.getMissionTemplate().getScore()) + .sum(); + } + + // 팀 최소 학점 조회 + public int getTeamMinScore(Long teamId) { + return teamService.getMinScore(teamId); + } + + // 최종 스코어보드 반환 + public ScoreboardResponseDto getScoreboard(Long teamId, Long userId) { + int minScore = getTeamMinScore(teamId); + SubGroupScoreDto mySubGroup = getMySubGroupScore(teamId, userId); + List otherSubGroups = getOtherSubGroupScores(teamId, mySubGroup.getSubGroupId()); + + return new ScoreboardResponseDto(minScore, mySubGroup, otherSubGroups); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index c5a9ec3..8ecd5bf 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -2,7 +2,9 @@ import com.ccapp.ccgo.matching.domain.entity.SubGroup; import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.mission.dto.ScoreboardResponseDto; import com.ccapp.ccgo.mission.dto.SubGroupMissionDto; +import com.ccapp.ccgo.mission.dto.SubGroupScoreDto; import com.ccapp.ccgo.mission.entity.MissionTemplate; import com.ccapp.ccgo.mission.entity.SubGroupMission; import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; @@ -169,4 +171,5 @@ public void completeMission(Long teamId, Long subGroupId, Long missionTemplateId } + } diff --git a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index f938903..5c968b2 100644 --- a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -3,6 +3,7 @@ import com.ccapp.ccgo.question.dto.SurveyCompleteRequest; +import com.ccapp.ccgo.team.dto.SetMinScoreRequest; import com.ccapp.ccgo.team.dto.SurveyStatusDto; import com.ccapp.ccgo.team.dto.TeamMatchingStatusDto; import com.ccapp.ccgo.team.entity.Team; @@ -10,6 +11,7 @@ import com.ccapp.ccgo.team.dto.TeamResponseDto; import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import com.ccapp.ccgo.team.service.TeamService; import com.ccapp.ccgo.user.entity.User; import lombok.RequiredArgsConstructor; @@ -28,6 +30,7 @@ public class TeamController { private final TeamMemberService teamMemberService; private final TeamMemberRepository teamMemberRepository; + private final TeamService teamService; @GetMapping("/mine") public ResponseEntity> getMyTeams( @@ -84,4 +87,18 @@ public ResponseEntity> getAllSurveyStatus(@PathVariable Lo public ResponseEntity getTeamInfo(@PathVariable Long teamId) { return ResponseEntity.ok(teamMemberService.getTeamInfo(teamId)); } + + + + //최소 학점 설정하는부분 + @PostMapping("/{teamId}/min-credit") + public ResponseEntity setMinCredit( + @PathVariable Long teamId, + @RequestBody SetMinScoreRequest request, + @AuthenticationPrincipal LoginUserDetails loginUserDetails) { + + Long userId = loginUserDetails.getUser().getId(); + teamService.updateMinScore(teamId, userId, request.getMinScore()); + return ResponseEntity.ok().build(); + } } \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/team/dto/SetMinScoreRequest.java b/src/main/java/com/ccapp/ccgo/team/dto/SetMinScoreRequest.java new file mode 100644 index 0000000..f225c9e --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/team/dto/SetMinScoreRequest.java @@ -0,0 +1,10 @@ +package com.ccapp.ccgo.team.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SetMinScoreRequest { + private Integer minScore; +} diff --git a/src/main/java/com/ccapp/ccgo/team/service/TeamService.java b/src/main/java/com/ccapp/ccgo/team/service/TeamService.java new file mode 100644 index 0000000..f311392 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/team/service/TeamService.java @@ -0,0 +1,41 @@ +package com.ccapp.ccgo.team.service; + +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.team.repository.TeamRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class TeamService { + + private final TeamRepository teamRepository; + + /** + * 팀 최소 학점 업데이트 + */ + @Transactional + public void updateMinScore(Long teamId, Long userId, Integer minScore) { + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); + + // 팀장 권한 확인 + if (!team.getCreatedBy().equals(userId)) { + throw new IllegalArgumentException("팀장만 최소 학점을 설정할 수 있습니다."); + } + + team.setMinScore(minScore); + teamRepository.save(team); + } + + /** + * 팀 최소 학점 조회 + */ + @Transactional(readOnly = true) + public Integer getMinScore(Long teamId) { + Team team = teamRepository.findById(teamId) + .orElseThrow(() -> new IllegalArgumentException("팀을 찾을 수 없습니다.")); + return team.getMinScore(); + } +} From 113565269645a4387c7cd3d0b7e131d42bf67011 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 22 Jul 2025 15:30:17 +0900 Subject: [PATCH 034/132] FINAL --- .../controller/ScoreboardController.java | 2 ++ .../mission/dto/ScoreboardResponseDto.java | 1 + .../ccgo/mission/dto/SubGroupScoreDto.java | 3 +++ .../mission/service/ScoreboardService.java | 21 +++++++++++++++---- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java b/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java index 01f7bef..50f33ad 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/ScoreboardController.java @@ -27,4 +27,6 @@ public ResponseEntity getScoreboard( ScoreboardResponseDto response = scoreboardService.getScoreboard(teamId, userId); return ResponseEntity.ok(response); } + + } diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java index 1e7427b..2b8bb05 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/ScoreboardResponseDto.java @@ -13,4 +13,5 @@ public class ScoreboardResponseDto { private int minScore; // 팀 최소 학점 private SubGroupScoreDto mySubGroup; // 현재 유저의 서브그룹 점수 private List otherSubGroups; // 다른 서브그룹들 점수 + } diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java index f39728f..de59794 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/SubGroupScoreDto.java @@ -4,6 +4,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @NoArgsConstructor @AllArgsConstructor @@ -11,4 +13,5 @@ public class SubGroupScoreDto { private Long subGroupId; private String name; // "1조" 등 private int score; // 현재 학점 + private List members; // ✅ 서브그룹 소속 유저 이름 } diff --git a/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java b/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java index 4301a86..e700203 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/ScoreboardService.java @@ -23,7 +23,7 @@ public class ScoreboardService { private final SubGroupMissionRepository subGroupMissionRepository; private final TeamService teamService; // 팀 최소 학점 조회용 - // 현재 유저가 속한 서브그룹 점수 조회 + // 현재 유저가 속한 서브그룹 점수 + 멤버 이름 리스트 조회 public SubGroupScoreDto getMySubGroupScore(Long teamId, Long userId) { Long subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId) .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); @@ -32,20 +32,33 @@ public SubGroupScoreDto getMySubGroupScore(Long teamId, Long userId) { .orElseThrow(() -> new IllegalArgumentException("서브그룹을 찾을 수 없습니다.")); int score = calculateSubGroupScore(subGroup); + List members = getSubGroupMemberNames(subGroupId); // ✅ 추가 - return new SubGroupScoreDto(subGroup.getId(), subGroup.getName(), score); + return new SubGroupScoreDto(subGroup.getId(), subGroup.getName(), score, members); } - // 팀 내 다른 서브그룹 점수 조회 (내 서브그룹 제외) + // 팀 내 다른 서브그룹 점수 + 멤버 이름 리스트 조회 public List getOtherSubGroupScores(Long teamId, Long excludeSubGroupId) { List subGroups = subGroupRepository.findByTeam_TeamId(teamId); return subGroups.stream() .filter(sg -> !sg.getId().equals(excludeSubGroupId)) - .map(sg -> new SubGroupScoreDto(sg.getId(), sg.getName(), calculateSubGroupScore(sg))) + .map(sg -> { + int score = calculateSubGroupScore(sg); + List members = getSubGroupMemberNames(sg.getId()); // ✅ 추가 + return new SubGroupScoreDto(sg.getId(), sg.getName(), score, members); + }) .collect(Collectors.toList()); } + // ✅ 서브그룹 멤버 이름 리스트 조회 + private List getSubGroupMemberNames(Long subGroupId) { + return subGroupMemberRepository.findBySubGroup_Id(subGroupId).stream() + .map(member -> member.getUser().getName()) // User 엔티티에서 이름 가져오기 + .collect(Collectors.toList()); + } + + // 서브그룹 점수 계산 (완료된 미션 점수 합) private int calculateSubGroupScore(SubGroup subGroup) { List missions = subGroupMissionRepository.findBySubGroup(subGroup); From dff50bf5bfeb3cfeee507e01cbf820313f31437c Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 22 Jul 2025 15:30:33 +0900 Subject: [PATCH 035/132] catching bug --- .gitignore | 1 + .../ccapp/ccgo/matching/domain/PairMatch.java | 16 + .../matching/service/MatchingService.java | 639 ++++-------------- .../question/repository/AnswerRepository.java | 2 + src/main/resources/application.properties | 2 +- 5 files changed, 140 insertions(+), 520 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/matching/domain/PairMatch.java diff --git a/.gitignore b/.gitignore index 62064b7..0301ade 100644 --- a/.gitignore +++ b/.gitignore @@ -204,5 +204,6 @@ application-*.properties # resources .src/main/resources +src/main/resources # End of https://www.toptal.com/developers/gitignore/api/windows,intellij,java,gradle \ No newline at end of file diff --git a/src/main/java/com/ccapp/ccgo/matching/domain/PairMatch.java b/src/main/java/com/ccapp/ccgo/matching/domain/PairMatch.java new file mode 100644 index 0000000..72c8948 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/matching/domain/PairMatch.java @@ -0,0 +1,16 @@ +package com.ccapp.ccgo.matching.domain; + +import com.ccapp.ccgo.team.entity.TeamMember; +import lombok.*; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter + +public class PairMatch { + private TeamMember male; // 남자 멤버 + private TeamMember female; // 여자 멤버 + private double totalScore; // 매칭 점수(MBTI + 설문 등 종합) +} diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index d54f518..cbed04e 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -1,30 +1,24 @@ package com.ccapp.ccgo.matching.service; -import com.ccapp.ccgo.common.exception.MatchingAlreadyCompletedException; import com.ccapp.ccgo.matching.domain.MbtiScoreProvider; -import com.ccapp.ccgo.matching.domain.entity.Answer; -import com.ccapp.ccgo.matching.domain.entity.Question; -import com.ccapp.ccgo.matching.domain.entity.SubGroup; -import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; -import com.ccapp.ccgo.question.dto.AnswerRequestDto; +import com.ccapp.ccgo.matching.domain.PairMatch; +import com.ccapp.ccgo.matching.domain.entity.*; import com.ccapp.ccgo.matching.dto.MatchingResponseDto; import com.ccapp.ccgo.matching.dto.MatchingResultDto; -import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; -import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.matching.repository.*; +import com.ccapp.ccgo.question.dto.AnswerRequestDto; import com.ccapp.ccgo.question.dto.QuestionRequestDto; import com.ccapp.ccgo.question.dto.QuestionResponseDto; import com.ccapp.ccgo.question.dto.QuestionUpdateDto; import com.ccapp.ccgo.question.repository.AnswerRepository; import com.ccapp.ccgo.question.repository.QuestionRepository; +import com.ccapp.ccgo.team.entity.Team; +import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.team.repository.TeamMemberRepository; import com.ccapp.ccgo.team.repository.TeamRepository; import com.ccapp.ccgo.user.dto.UserResponseDto; -import com.ccapp.ccgo.team.entity.Team; -import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.user.entity.User; import com.ccapp.ccgo.user.repository.UserRepository; -import lombok.AllArgsConstructor; -import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -44,549 +38,169 @@ public class MatchingService { private final SubGroupRepository subGroupRepository; private final SubGroupMemberRepository subGroupMemberRepository; private final TeamRepository teamRepository; - // 가중치 + private final UserRepository userRepository; + private final MbtiScoreProvider mbtiScoreProvider; + + private static final int MAX_GROUP_SIZE = 4; private static final double MBTI_WEIGHT = 0.5; private static final double SIMILARITY_WEIGHT = 0.5; - private final MbtiScoreProvider mbtiScoreProvider; - private final UserRepository userRepository; - @Transactional public MatchingResponseDto performMatching(Long teamId) { - - // 1. 팀 정보 가져오기 - // 해당 팀에 소속된 팀원 전체 가져옴 - // 팀원이 없다면 (비어있다면) IllegalArgumentException List members = teamMemberRepository.findByTeam_TeamIdAndIsActiveTrue(teamId); - - if (members.isEmpty()) { - throw new IllegalArgumentException("해당 팀(" + teamId + ")에 유저가 없습니다."); - } - Team team = members.get(0).getTeam(); - if (team.isMatchingStarted()) { - throw new MatchingAlreadyCompletedException("이미 매칭이 완료된 팀입니다."); - } - - Map memberMap = members.stream() - .collect(Collectors.toMap(tm -> tm.getUser().getId(), tm -> tm)); - - - // 2. 남/여 그룹 나누기 - - // 남자 리스트 - List males = members.stream() - .filter(m -> "MALE".equalsIgnoreCase(m.getUser().getGender())) - .collect(Collectors.toList()); - - // 여자 리스트 - List females = members.stream() - .filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())) - .collect(Collectors.toList()); + List males = members.stream().filter(m -> "MALE".equalsIgnoreCase(m.getUser().getGender())).collect(Collectors.toList()); + List females = members.stream().filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())).collect(Collectors.toList()); - List groupA; - List groupB; - - // 기준 잡기 - // 수가 적은 쪽이 기준 / 수가 같을 경우 남자가 기준 - if (males.size() < females.size()) { - groupA = males; - groupB = females; - } else if (females.size() < males.size()) { - groupA = females; - groupB = males; - } else { - groupA = males; - groupB = females; - } + List matchCandidates = createPairMatchCandidates(males, females, teamId); + matchCandidates.sort(Comparator.comparingDouble(PairMatch::getTotalScore).reversed()); - // 3. 후보 리스트 Map 생성 - // Map을 통한 후보 리스트 조합 생성 (groupA 의 각 멤버별로 groupB 와 전부 조합) - Map> candidateMap = createCandidateMap(groupA, groupB, teamId); - - // 4. 모든 쌍 점수 계산 - // 후보 Map에 담긴 후보들로 A-B 모든 쌍 생성, 다시 MBTI + 유사도 점수 합산 - // PairMatch 객체에 저장 - List pairMatches = generateAllPairMatches(candidateMap, memberMap); - - // 5. 점수 내림차순 정렬 - pairMatches.sort(Comparator.comparingDouble(PairMatch::getTotalScore).reversed()); - - // 6. Greedy 매칭 수행 - // 이미 매칭된 유저가 포함된 쌍은 건너뜀 - // 새로 생성되는 그룹은 SubGroup - // SubGroupMember 테이블에 두 사람 매핑 - // 매칭 시 그룹명은 팀이름 + index (홍길통A1, 홍길동A2 ...) - Set matchedUserIds = new HashSet<>(); - List subGroups = new ArrayList<>(); + Set usedMaleIds = new HashSet<>(); + Set usedFemaleIds = new HashSet<>(); + List groups = new ArrayList<>(); int groupIndex = 1; - for (PairMatch pair : pairMatches) { - if (matchedUserIds.contains(pair.userA.getUser().getId()) - || matchedUserIds.contains(pair.userB.getUser().getId())) { - continue; - } + for (PairMatch pair : matchCandidates) { + Long maleId = pair.getMale().getUser().getId(); + Long femaleId = pair.getFemale().getUser().getId(); + if (usedMaleIds.contains(maleId) || usedFemaleIds.contains(femaleId)) continue; - String groupName = team.getTeamName() + groupIndex; - List groupMembers = List.of(pair.userA.getUser(), pair.userB.getUser()); + String groupName = team.getTeamName() + groupIndex++; + SubGroup group = SubGroup.builder().team(team).name(groupName).memberCount(2).build(); + subGroupRepository.save(group); - SubGroup sg = SubGroup.builder() - .team(team) - .name(groupName) - .memberCount(groupMembers.size()) - .build(); - subGroupRepository.save(sg); + saveSubGroupMember(group, pair.getMale().getUser()); + saveSubGroupMember(group, pair.getFemale().getUser()); - for (User user : groupMembers) { - saveSubGroupMember(sg, user); - } - - matchedUserIds.add(pair.userA.getUser().getId()); - matchedUserIds.add(pair.userB.getUser().getId()); - - subGroups.add(sg); - groupIndex++; + usedMaleIds.add(maleId); + usedFemaleIds.add(femaleId); + groups.add(group); } - // 7. 잉여 처리 - handleLeftovers(groupA, groupB, matchedUserIds, team, groupIndex, subGroups); + List leftoverMales = males.stream().filter(m -> !usedMaleIds.contains(m.getUser().getId())).collect(Collectors.toList()); + List leftoverFemales = females.stream().filter(f -> !usedFemaleIds.contains(f.getUser().getId())).collect(Collectors.toList()); - // ✅ 매칭 완료를 true로 수정 - team.setMatchingStarted(true); - teamRepository.save(team); + handleLeftovers(leftoverMales, leftoverFemales, groups, team, groupIndex, teamId); - // 8. 결과 DTO 변환 - return buildMatchingResponseDto(team, subGroups); + return buildMatchingResponseDto(team, groups); } - - - // groupA의 각 멤버에게 groupB 중 top N 후보자 리스트를 만들어줌 - // 점수 높은 순서대로 5명 (동점자 포함) 까지 - private Map> createCandidateMap(List groupA, - List groupB, - Long teamId) { - Map> candidateMap = new HashMap<>(); - - for (TeamMember tmA : groupA) { - List tempList = new ArrayList<>(); - - for (TeamMember tmB : groupB) { - int mbtiScore = calculateMbtiTotalScore(tmA, tmB); - double similarityScore = calculateSimilarityScore(tmA, tmB, teamId); - // 가중치 계산으로 조정 - double totalScore = mbtiScore * MBTI_WEIGHT + similarityScore * SIMILARITY_WEIGHT; - - tempList.add(new PairMatch(tmA, tmB, totalScore)); - } - - tempList.sort(Comparator.comparingDouble(PairMatch::getTotalScore).reversed()); - - List topCandidates = new ArrayList<>(); - double lastScore = -1; - - for (int i = 0; i < tempList.size(); i++) { - PairMatch pm = tempList.get(i); - if (i < 5) { - topCandidates.add(pm); - lastScore = pm.totalScore; - } else if (pm.totalScore == lastScore) { - topCandidates.add(pm); - } else { - break; - } + private List createPairMatchCandidates(List males, List females, Long teamId) { + List result = new ArrayList<>(); + for (TeamMember m : males) { + for (TeamMember f : females) { + double mbti = calculateMbtiScore(m, f); + double sim = calculateSimilarity(m, f, teamId); + result.add(new PairMatch(m, f, mbti * MBTI_WEIGHT + sim * SIMILARITY_WEIGHT)); } - - candidateMap.put(tmA.getUser().getId(), topCandidates); } - - return candidateMap; + return result; } - // candidateMap 기반으로 모든 (A,B) 쌍에 대해 PairMatch 생성 - // 다시 MBTI 점수 + 질문 유사도 점수 계산 - private List generateAllPairMatches(Map> candidateMap, - Map memberMap) { - List pairs = new ArrayList<>(); - - for (List pairList : candidateMap.values()) { - pairs.addAll(pairList); - } - - return pairs; - } - - - // A→B, B→A MBTI 점수를 각각 구해서 합산 - // 대칭적이지 않을 수도 있다는 점 고려 (현재 데이터는 대칭적임) - private int calculateMbtiTotalScore(TeamMember a, TeamMember b) { - String mbtiA = a.getMbti(); - String mbtiB = b.getMbti(); - if (mbtiA == null || mbtiB == null) { - return 0; // mbti가 없으면 점수 0 처리하거나, 다른 정책 적용 - } - int scoreAtoB = mbtiScoreProvider.getScore(mbtiA, mbtiB); - int scoreBtoA = mbtiScoreProvider.getScore(mbtiB, mbtiA); - return scoreAtoB + scoreBtoA; - } - - // 팀별로 등록된 질문 리스트 조회 - // 각 질문 별로 A와 B의 점수 차이를 계산 → 유사도 환산 - // 차이가 0이면 유사도 5, 차이가 5면 유사도 0 - // 전체 유사도 점수 → 100% 환산 - private double calculateSimilarityScore(TeamMember a, TeamMember b, Long teamId) { - Long userIdA = a.getUser().getId(); - Long userIdB = b.getUser().getId(); - - List answersA = answerRepository.findByUser_Id(userIdA); - List answersB = answerRepository.findByUser_Id(userIdB); - - // 팀별 질문 수 확보 + private double calculateSimilarity(TeamMember a, TeamMember b, Long teamId) { List questions = questionRepository.findByTeam_TeamId(teamId); - int totalQuestions = questions.size(); - - int totalSimilarity = 0; + int totalSim = 0; for (Question q : questions) { - int scoreA = answersA.stream() - .filter(ans -> ans.getQuestionId().equals(q.getId())) - .map(Answer::getScore) - .findFirst() - .orElse(0); - - int scoreB = answersB.stream() - .filter(ans -> ans.getQuestionId().equals(q.getId())) - .map(Answer::getScore) - .findFirst() - .orElse(0); - - int diff = Math.abs(scoreA - scoreB); - int similarity = 5 - diff; // 상수를 질문 수로 바꿔주면 된다. - totalSimilarity += similarity; + int sa = answerRepository.findByUser_IdAndQuestionId(a.getUser().getId(), q.getId()).map(Answer::getScore).orElse(0); + int sb = answerRepository.findByUser_IdAndQuestionId(b.getUser().getId(), q.getId()).map(Answer::getScore).orElse(0); + totalSim += Math.max(0, 5 - Math.abs(sa - sb)); } - Double similarityRate = (double) totalSimilarity / (5 * totalQuestions); - return similarityRate * 100; + return questions.isEmpty() + ? 0 + : (double) totalSim / (5 * questions.size()) * 100; } - //원래 진우꺼. -// // 6. Greedy 매칭 결과 저장 -// private void saveSubGroupMember(SubGroup sg, User user) { -// SubGroupMember sgm = SubGroupMember.builder() -// .subGroup(sg) -// .user(user) -// .build(); -// subGroupMemberRepository.save(sgm); -// } - - //나중에 지우기 - private void saveSubGroupMember(SubGroup subGroup, User user) { - SubGroupMember member = SubGroupMember.builder() - .subGroup(subGroup) // 영속 상태 보장 - .user(user) - .build(); - subGroupMemberRepository.save(member); + private double calculateMbtiScore(TeamMember a, TeamMember b) { + String mbtiA = a.getMbti(); + String mbtiB = b.getMbti(); + if (mbtiA == null || mbtiB == null) return 0; + return mbtiScoreProvider.getScore(mbtiA, mbtiB) + mbtiScoreProvider.getScore(mbtiB, mbtiA); } - - //새로 도전해보느놈 나중에 지우기 - private void handleLeftovers(List groupA, - List groupB, - Set matchedUserIds, - Team team, - int groupIndex, - List subGroups) { - - // 남은 남자 / 여자 리스트 - List leftoverMales = new ArrayList<>(); - List leftoverFemales = new ArrayList<>(); - - groupA.stream().filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) - .forEach(tm -> { - if ("MALE".equalsIgnoreCase(tm.getUser().getGender())) leftoverMales.add(tm); - else leftoverFemales.add(tm); - }); - - groupB.stream().filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) - .forEach(tm -> { - if ("MALE".equalsIgnoreCase(tm.getUser().getGender())) leftoverMales.add(tm); - else leftoverFemales.add(tm); - }); - - // ✅ 남자 leftover → 기존 그룹 중 memberCount < 4인 곳에 유사도 기반 추가 - for (TeamMember male : leftoverMales) { - SubGroup bestGroup = findBestGroupForUser(male, subGroups, team, 4); // 변경: 3 → 4 - if (bestGroup != null) { - saveSubGroupMember(bestGroup, male.getUser()); - bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); - subGroupRepository.save(bestGroup); - } else { - SubGroup sg = createNewGroup(team, groupIndex++); - saveSubGroupMember(sg, male.getUser()); - sg.setMemberCount(1); - subGroupRepository.save(sg); - subGroups.add(sg); + private void handleLeftovers(List males, List females, List groups, Team team, int groupIndex, Long teamId) { + if (males.size() == 2) { + SubGroup g = SubGroup.builder().team(team).name(team.getTeamName() + groupIndex++).memberCount(2).build(); + subGroupRepository.save(g); + saveSubGroupMember(g, males.get(0).getUser()); + saveSubGroupMember(g, males.get(1).getUser()); + groups.add(g); + } else if (males.size() == 1) { + SubGroup best = findBestGroupToInsert(males.get(0), groups, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE) { + saveSubGroupMember(best, males.get(0).getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); } } - // ✅ 여자 leftover → 기존 그룹 중 memberCount < 4인 곳에 유사도 기반 추가 - for (TeamMember female : leftoverFemales) { - SubGroup bestGroup = findBestGroupForUser(female, subGroups, team, 4); - if (bestGroup != null) { - saveSubGroupMember(bestGroup, female.getUser()); - bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); - subGroupRepository.save(bestGroup); - } else { - SubGroup sg = createNewGroup(team, groupIndex++); - saveSubGroupMember(sg, female.getUser()); - sg.setMemberCount(1); - subGroupRepository.save(sg); - subGroups.add(sg); + for (TeamMember female : females) { + for (SubGroup group : groups.stream().sorted(Comparator.comparingInt(SubGroup::getMemberCount)).collect(Collectors.toList())) { + if (group.getMemberCount() >= MAX_GROUP_SIZE) continue; + if (isFemaleInsertable(group, female, females.size())) { + saveSubGroupMember(group, female.getUser()); + group.setMemberCount(group.getMemberCount() + 1); + subGroupRepository.save(group); + break; + } } } } - - private SubGroup createNewGroup(Team team, int index) { - String groupName = team.getTeamName() + index; - SubGroup subGroup = SubGroup.builder() - .team(team) - .name(groupName) - .memberCount(0) - .build(); - return subGroupRepository.save(subGroup); // 반드시 save 후 반환 - } - - - private SubGroup findBestGroupForUser(TeamMember user, List subGroups, Team team, int maxSize) { - SubGroup bestGroup = null; - double maxAvgSimilarity = Double.NEGATIVE_INFINITY; - - for (SubGroup sg : subGroups) { - if (sg.getMemberCount() >= maxSize) continue; - - List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); - double totalSimilarity = 0; - int count = 0; - - for (SubGroupMember sgm : members) { - TeamMember existingTm = teamMemberRepository - .findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) - .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); - totalSimilarity += calculateSimilarityScore(user, existingTm, team.getTeamId()); - count++; - } - - // 평균 유사도 - double avgSim = (count > 0) ? totalSimilarity / count : 0; - - if (avgSim > maxAvgSimilarity) { - maxAvgSimilarity = avgSim; - bestGroup = sg; - } - } - - // ✅ fallback: 모든 그룹 점수가 0이거나 질문이 없어서 bestGroup이 null이면 첫 번째 그룹 반환 - if (bestGroup == null && !subGroups.isEmpty()) { - bestGroup = subGroups.get(0); + private boolean isFemaleInsertable(SubGroup group, TeamMember female, int totalFemaleLeft) { + List members = subGroupMemberRepository.findBySubGroup_Id(group.getId()); + long femaleNum = members.stream().filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())).count(); + long maleNum = members.size() - femaleNum; + if ("FEMALE".equalsIgnoreCase(female.getUser().getGender())) { + if (maleNum > 1 && femaleNum == 0 && totalFemaleLeft > 1) return false; } - - return bestGroup; + return true; } + private SubGroup findBestGroupToInsert(TeamMember tm, List groups, Long teamId) { + return groups.stream() + .filter(g -> g.getMemberCount() < MAX_GROUP_SIZE) + .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))) + .orElse(null); + } - -// /** -// * ✅ 특정 유저를 편입할 최적 그룹 찾기 -// * 조건: -// * - 현재 인원 < maxSize -// * - 유사도 평균이 가장 높은 그룹 -// */ -// private SubGroup findBestGroupForUser(TeamMember user, -// List subGroups, -// Team team, -// int maxSize) { -// -// SubGroup bestGroup = null; -// double maxAvgSimilarity = Double.NEGATIVE_INFINITY; -// -// for (SubGroup sg : subGroups) { -// if (sg.getMemberCount() >= maxSize) continue; -// -// List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); -// double totalSimilarity = 0; -// int count = 0; -// -// for (SubGroupMember sgm : members) { -// TeamMember existingTm = teamMemberRepository.findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) -// .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); -// double simScore = calculateSimilarityScore(user, existingTm, team.getTeamId()); -// totalSimilarity += simScore; -// count++; -// } -// -// double avgSimilarity = count > 0 ? totalSimilarity / count : 0; -// if (avgSimilarity > maxAvgSimilarity) { -// maxAvgSimilarity = avgSimilarity; -// bestGroup = sg; -// } -// } -// -// return bestGroup; -// } - - - //진우원래꺼 - // 7. 잉여 처리 -// private void handleLeftovers(List groupA, -// List groupB, -// Set matchedUserIds, -// Team team, -// int groupIndex, -// List subGroups) { -// -// // 그룹 이름 중복 방지 -// Set existingGroupNames = subGroups.stream() -// .map(SubGroup::getName) -// .collect(Collectors.toSet()); -// -// List leftovers = new ArrayList<>(); -// groupA.stream() -// .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) -// .forEach(leftovers::add); -// groupB.stream() -// .filter(tm -> !matchedUserIds.contains(tm.getUser().getId())) -// .forEach(leftovers::add); -// -// Iterator it = leftovers.iterator(); -// -// while (it.hasNext()) { -// TeamMember tm1 = it.next(); -// if (it.hasNext()) { -// TeamMember tm2 = it.next(); -// -// // 잉여 그룹 이름 생성 -// String groupName; -// do { -// groupName = team.getTeamName() + groupIndex; -// groupIndex++; -// } while (existingGroupNames.contains(groupName)); -// existingGroupNames.add(groupName); -// -// List groupMembers = List.of(tm1.getUser(), tm2.getUser()); -// -// SubGroup sg = SubGroup.builder() -// .team(team) -// .name(groupName) -// .memberCount(groupMembers.size()) -// .build(); -// subGroupRepository.save(sg); -// -// for (User user : groupMembers) { -// saveSubGroupMember(sg, user); -// } -// -// subGroups.add(sg); -// -// } else { -// // 홀수 남음 → 기존 그룹 중 가장 점수(각각의 유사도 점수 구한 뒤의 평균) 높은 그룹으로 편입 -// if (!subGroups.isEmpty()) { -// double maxAvgSimilarity = Double.NEGATIVE_INFINITY; -// SubGroup bestGroup = null; -// -// for (SubGroup sg : subGroups) { -// List members = -// subGroupMemberRepository.findBySubGroup_Id(sg.getId()); -// -// double totalSimilarity = 0; -// int memberCount = 0; -// -// for (SubGroupMember sgm : members) { -// TeamMember existingTm = teamMemberRepository -// .findByUser_IdAndTeam_TeamId(sgm.getUser().getId(), team.getTeamId()) -// .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); -// -// double similarityScore = -// calculateSimilarityScore(tm1, existingTm, team.getTeamId()); -// -// totalSimilarity += similarityScore; -// memberCount++; -// } -// -// double avgSimilarity = memberCount > 0 ? totalSimilarity / memberCount : 0; -// -// if (avgSimilarity > maxAvgSimilarity) { -// maxAvgSimilarity = avgSimilarity; -// bestGroup = sg; -// } -// } -// -// if (bestGroup != null) { -// saveSubGroupMember(bestGroup, tm1.getUser()); -// -// long newCount = -// subGroupMemberRepository.countBySubGroup_Id(bestGroup.getId()); -// bestGroup.setMemberCount((int) newCount); -// subGroupRepository.save(bestGroup); -// } -// } else { -// System.out.println("[WARN] 홀수 남았지만 기존 그룹 없음. 유저ID: " + tm1.getUser().getId()); -// } -// } -// } -// } - - // 8. 결과 DTO 생성 - private MatchingResponseDto buildMatchingResponseDto(Team team, List subGroups) { - List resultDtos = new ArrayList<>(); - - for (SubGroup sg : subGroups) { - List members = subGroupMemberRepository.findBySubGroup_Id(sg.getId()); - - List userDtos = members.stream() - .map(sgm -> { - User user = sgm.getUser(); - - // 유저와 팀 기반으로 TeamMember 정보 조회 - TeamMember teamMember = teamMemberRepository.findByUser_IdAndTeam_TeamId(user.getId(), team.getTeamId()) - .orElseThrow(() -> new IllegalArgumentException("TeamMember not found")); - - return UserResponseDto.builder() - .id(user.getId()) - .name(user.getName()) - .email(user.getEmail()) - .gender(user.getGender()) - .mbti(teamMember.getMbti()) // ✅ 여기에서 MBTI 가져오기 - .build(); - }) - .collect(Collectors.toList()); - - resultDtos.add(MatchingResultDto.builder() - .subGroupId(sg.getId()) - .groupName(sg.getName()) - .members(userDtos) - .build()); + private double calculateAverageSimilarity(TeamMember user, SubGroup group, Long teamId) { + List members = subGroupMemberRepository.findBySubGroup_Id(group.getId()); + double total = 0; + for (SubGroupMember m : members) { + TeamMember tm = teamMemberRepository.findByUser_IdAndTeam_TeamId(m.getUser().getId(), teamId).orElseThrow(); + total += calculateSimilarity(user, tm, teamId); } - - return MatchingResponseDto.builder() - .teamId(team.getTeamId()) - .matchingStarted(team.isMatchingStarted()) - .teamName(team.getTeamName()) - .subGroups(resultDtos) - .build(); + return members.isEmpty() ? 0 : total / members.size(); } + private void saveSubGroupMember(SubGroup group, User user) { + SubGroupMember m = SubGroupMember.builder().subGroup(group).user(user).build(); + subGroupMemberRepository.save(m); + } - /** - * 내부 클래스 PairMatch - */ - @Data - @AllArgsConstructor - static class PairMatch { - private TeamMember userA; - private TeamMember userB; - private double totalScore; + private MatchingResponseDto buildMatchingResponseDto(Team team, List groups) { + List result = groups.stream().map(g -> { + List members = subGroupMemberRepository.findBySubGroup_Id(g.getId()); + List users = members.stream().map(m -> { + User u = m.getUser(); + TeamMember tm = teamMemberRepository.findByUser_IdAndTeam_TeamId(u.getId(), team.getTeamId()).orElseThrow(); + return UserResponseDto.builder() + .id(u.getId()) + .email(u.getEmail()) + .name(u.getName()) + .gender(u.getGender()) + .birthdate(u.getBirthdate()) + .createdAt(u.getCreatedAt()) + .mbti(tm.getMbti()) + .build(); + }).collect(Collectors.toList()); + return new MatchingResultDto(g.getId(), g.getName(), users); + }).collect(Collectors.toList()); + + return new MatchingResponseDto(team.getTeamId(), team.getTeamName(), true, result); } @@ -634,7 +248,6 @@ public void saveAnswers(AnswerRequestDto dto, User user) { } } - // 한 번에 여러 개의 새로운 Question을 DB에 등록 @Transactional public void createQuestions(QuestionRequestDto dto) { @@ -686,16 +299,6 @@ public void deleteQuestion(Long questionId) { questionRepository.deleteById(questionId); } -// //팀원 이름 조회하기 기능 -// @Transactional(readOnly = true) -// public List getMatchedUserNames(Long userId) { -// List members = subGroupMemberRepository.findBySameSubGroup(userId); -// return members.stream() -// .filter(m -> !m.getUser().getId().equals(userId)) // 본인 제외 -// .map(m -> m.getUser().getName()) // 혹은 -// .collect(Collectors.toList()); -// } - //실험 @Transactional(readOnly = true) public List getMatchedUserNames(Long userId, Long teamId) { @@ -727,6 +330,4 @@ public Long findSubGroupIdByTeamIdAndUserId(Long teamId, Long userId) { } - - } diff --git a/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java b/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java index cbe6f28..914fa92 100644 --- a/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java +++ b/src/main/java/com/ccapp/ccgo/question/repository/AnswerRepository.java @@ -3,6 +3,7 @@ import com.ccapp.ccgo.matching.domain.entity.Answer; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; public interface AnswerRepository extends JpaRepository { @@ -10,4 +11,5 @@ public interface AnswerRepository extends JpaRepository { List findByQuestionId(Long questionId); + Optional findByUser_IdAndQuestionId(Long id, Long id1); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65d55a6..22f7f7e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.address=0.0.0.0 server.port=8080 spring.datasource.username = root -spring.datasource.password = 06250730cc +spring.datasource.password = qkrwlsdn jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY jwt.access-token-expiration=86400000 From 2ff1d5e3895f584ed9e8e34a5218f2663d6ef476 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 29 Jul 2025 10:26:42 +0900 Subject: [PATCH 036/132] Login_sys --- Dockerfile | 4 + build.gradle | 2 + docker-compose.yml | 48 +++++ .../ccgo/auth/controller/AuthController.java | 190 ++++++++++-------- .../ccapp/ccgo/auth/dto/LoginRequestDto.java | 12 +- .../ccapp/ccgo/auth/dto/LoginResponseDto.java | 8 +- .../ccapp/ccgo/auth/dto/TokenResponseDto.java | 8 +- .../jwt/CustomAuthenticationEntryPoint.java | 21 ++ .../auth/jwt/JwtAuthenticationFilter.java | 49 +++-- .../com/ccapp/ccgo/auth/jwt/JwtProvider.java | 88 ++++---- .../ccapp/ccgo/auth/jwt/LoginUserDetails.java | 43 ++-- .../ccapp/ccgo/auth/service/AuthService.java | 90 +++++++++ .../auth/service/LoginUserDetailsService.java | 18 +- .../auth/service/RefreshTokenService.java | 47 +++++ .../com/ccapp/ccgo/common/RedisConfig.java | 17 ++ .../com/ccapp/ccgo/common/SecurityConfig.java | 27 ++- src/main/resources/application.properties | 23 --- 17 files changed, 489 insertions(+), 206 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 src/main/java/com/ccapp/ccgo/auth/jwt/CustomAuthenticationEntryPoint.java create mode 100644 src/main/java/com/ccapp/ccgo/auth/service/AuthService.java create mode 100644 src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java create mode 100644 src/main/java/com/ccapp/ccgo/common/RedisConfig.java delete mode 100644 src/main/resources/application.properties diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d7b8d9e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM eclipse-temurin:17-jdk-jammy +COPY build/libs/*.jar app.jar +EXPOSE 8080 +CMD ["java", "-jar", "app.jar", "--spring.profiles.active=application-deploy"] diff --git a/build.gradle b/build.gradle index ff1237a..5df42af 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,8 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'com.h2database:h2' // DB 정보 저장용 디펜던시 + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.apache.commons:commons-pool2' } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..89d195a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.8' + +services: + mysql: + image: mysql:latest + container_name: mysql-container + environment: + MYSQL_ROOT_PASSWORD: 06250730cc + MYSQL_DATABASE: ccmake + MYSQL_USER: user + MYSQL_PASSWORD: 06250730cc + ports: + - "3306:3306" + volumes: + - mysql-data:/var/lib/mysql + networks: + - backend-network + + redis: + image: redis:latest + container_name: redis-container + ports: + - "6379:6379" + networks: + - backend-network + + spring-app: + build: . + container_name: spring-app-container + depends_on: + - mysql + - redis + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/your_database?useSSL=false&serverTimezone=Asia/Seoul + SPRING_DATASOURCE_USERNAME: root + SPRING_DATASOURCE_PASSWORD: 1234 + SPRING_REDIS_HOST: redis + SPRING_REDIS_PORT: 6379 + ports: + - "8080:8080" + networks: + - backend-network + +volumes: + mysql-data: + +networks: + backend-network: diff --git a/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java index bec1273..1b52842 100644 --- a/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java @@ -4,25 +4,18 @@ import com.ccapp.ccgo.auth.dto.LoginResponseDto; import com.ccapp.ccgo.auth.dto.TokenResponseDto; import com.ccapp.ccgo.auth.jwt.JwtProvider; -import com.ccapp.ccgo.auth.jwt.LoginUserDetails; -import com.ccapp.ccgo.auth.service.LoginUserDetailsService; -import com.ccapp.ccgo.team.repository.TeamMemberRepository; -import com.ccapp.ccgo.team.entity.TeamMember; -import com.ccapp.ccgo.user.entity.User; -import lombok.extern.slf4j.Slf4j; -import com.ccapp.ccgo.team.repository.TeamRepository; +import com.ccapp.ccgo.auth.service.AuthService; +import com.ccapp.ccgo.auth.service.RefreshTokenService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; -import org.springframework.http.HttpHeaders; -import java.util.List; import java.util.Map; @Slf4j @@ -31,112 +24,98 @@ @RequiredArgsConstructor public class AuthController { - private final AuthenticationManager authenticationManager; + private final AuthService authService; private final JwtProvider jwtProvider; - private final TeamMemberRepository teamMemberRepository; - private final LoginUserDetailsService loginUserDetailsService; - private final TeamRepository teamRepository; + private final RefreshTokenService refreshTokenService; + + @Value("${app.cookie.secure:true}") + private boolean cookieSecure; + + @Value("${app.cookie.same-site:Lax}") + private String cookieSameSite; + + @Value("${jwt.access-token-expiration:3600}") // 초 단위 + private long accessTokenMaxAge; + + @Value("${jwt.refresh-token-expiration:604800}") // 초 단위 + private long refreshTokenMaxAge; - //@authenticatedPrincipal UserDetails authenticatedPrincipal @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto ) { - log.info("로그인 요청 받음: {}", requestDto.getEmail()); - log.info("로그인 요청 받음: {}", requestDto.getPassword()); + public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto) { + String maskedEmail = maskEmail(requestDto.getEmail()); + log.info("로그인 요청 받음: {}", maskedEmail); try { - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken( - requestDto.getEmail(), requestDto.getPassword() - ) - ); + LoginResponseDto response = authService.login(requestDto.getEmail(), requestDto.getPassword()); - String accessToken = jwtProvider.createAccessToken(authentication); - String refreshToken = jwtProvider.createRefreshToken(authentication); - LoginUserDetails userDetails = (LoginUserDetails) authentication.getPrincipal(); - User user = userDetails.getUser(); - log.info("🔍 로그인한 사용자: {}", user.getEmail()); - - // 유저가 활성화된 팀멤버 목록 조회 - List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); - - - // 팀멤버 정보를 LoginResponseDto.TeamInfo 리스트로 변환 - List teams = teamMembers.stream() - .map(tm -> LoginResponseDto.TeamInfo.builder() - .teamId(tm.getTeam().getTeamId()) - .teamName(tm.getTeam().getTeamName()) - .role(tm.getRole().name()) - .isSurveyCompleted(tm.isSurveyCompleted()) // 팀별 설문 완료 여부 - .build()) - .toList(); - - HttpHeaders headers = createTokenCookies(accessToken, refreshToken); - - LoginResponseDto response = LoginResponseDto.builder() - .userId(user.getId()) - .email(user.getEmail()) - .name(user.getName()) - .teams(teams) - .accessToken(accessToken) - .refreshToken(refreshToken) - .build(); + HttpHeaders headers = createTokenCookies(response.getAccessToken(), response.getRefreshToken()); + + log.info("발급된 쿠키: {}", headers.get(HttpHeaders.SET_COOKIE)); + + + // 응답 바디에 토큰도 포함해서 내려줌 (Expo Go용) + Map responseBody = Map.of( + "message", "로그인 성공", + "data", LoginResponseDto.builder() + .userId(response.getUserId()) + .email(response.getEmail()) + .name(response.getName()) + .teams(response.getTeams()) + .build(), + "accessToken", response.getAccessToken(), + "refreshToken", response.getRefreshToken() + ); return ResponseEntity.ok() .headers(headers) - .body(response); + .body(responseBody); } catch (BadCredentialsException e) { + log.warn("로그인 실패 - 잘못된 이메일 또는 비밀번호: {}", maskedEmail); return ResponseEntity.status(401).body(Map.of("message", "이메일 또는 비밀번호가 잘못되었습니다.")); - } catch (RuntimeException e) { - log.error("❌ 로그인 중 런타임 예외", e); - return ResponseEntity.status(400).body(Map.of("message", e.getMessage())); } catch (Exception e) { - log.error("❌ 로그인 중 알 수 없는 예외", e); + log.error("❌ 로그인 중 오류 발생", e); return ResponseEntity.status(500).body(Map.of("message", "서버 오류가 발생했습니다.")); } } @PostMapping("/refresh") public ResponseEntity refreshToken(@CookieValue(value = "refreshToken", required = false) String refreshToken) { - if (refreshToken == null || !jwtProvider.validateToken(refreshToken)) { - return ResponseEntity.status(401).body("리프레시 토큰이 없거나 유효하지 않습니다."); - } - - String email = jwtProvider.getEmailFromToken(refreshToken); - LoginUserDetails userDetails = (LoginUserDetails) loginUserDetailsService.loadUserByUsername(email); + try { + TokenResponseDto tokenResponse = authService.refreshToken(refreshToken); + HttpHeaders headers = createTokenCookies(tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()); - Authentication authentication = new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities() - ); + log.info("새로운 쿠키: {}", headers.get(HttpHeaders.SET_COOKIE)); - String newAccessToken = jwtProvider.createAccessToken(authentication); - String newRefreshToken = jwtProvider.createRefreshToken(authentication); - HttpHeaders headers = createTokenCookies(newAccessToken, newRefreshToken); - return ResponseEntity.ok() - .headers(headers) - .body(new TokenResponseDto(newAccessToken, newRefreshToken)); + return ResponseEntity.ok() + .headers(headers) + .body(Map.of("message", "토큰 갱신 성공")); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(401).body(Map.of("message", e.getMessage())); + } catch (Exception e) { + log.error("❌ 토큰 갱신 중 오류 발생", e); + return ResponseEntity.status(500).body(Map.of("message", "서버 오류가 발생했습니다.")); + } } - /** - * accessToken, refreshToken을 HttpOnly 쿠키로 설정하는 공통 메서드 - */ + private HttpHeaders createTokenCookies(String accessToken, String refreshToken) { ResponseCookie accessTokenCookie = ResponseCookie.from("accessToken", accessToken) .httpOnly(true) .path("/") - .maxAge(60 * 60) // 1시간 - .secure(false) // 운영 배포 시 true로 - .sameSite("Lax") + .maxAge(accessTokenMaxAge) + .secure(cookieSecure) + .sameSite(cookieSameSite) .build(); ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken) .httpOnly(true) .path("/") - .maxAge(7 * 24 * 60 * 60) // 7일 - .secure(false) - .sameSite("Lax") + .maxAge(refreshTokenMaxAge) + .secure(cookieSecure) + .sameSite(cookieSameSite) .build(); HttpHeaders headers = new HttpHeaders(); @@ -144,6 +123,47 @@ private HttpHeaders createTokenCookies(String accessToken, String refreshToken) headers.add(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()); return headers; } -} + private String maskEmail(String email) { + if (email == null || !email.contains("@")) return "unknown"; + String[] parts = email.split("@"); + return parts[0].charAt(0) + "***@" + parts[1]; + } + + @PostMapping("/logout") + public ResponseEntity logout(@CookieValue(value = "refreshToken", required = false) String refreshToken) { + if (refreshToken == null || !jwtProvider.validateToken(refreshToken)) { + return ResponseEntity.status(401).body(Map.of("message", "유효하지 않은 리프레시 토큰입니다.")); + } + + // Redis에서 Refresh Token 삭제 + String email = jwtProvider.getEmailFromToken(refreshToken); + refreshTokenService.deleteRefreshToken(email); + // 쿠키 삭제 (maxAge=0) + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.SET_COOKIE, ResponseCookie.from("accessToken", "") + .path("/") + .maxAge(0) + .httpOnly(true) + .secure(cookieSecure) + .sameSite(cookieSameSite) + .build() + .toString()); + headers.add(HttpHeaders.SET_COOKIE, ResponseCookie.from("refreshToken", "") + .path("/") + .maxAge(0) + .httpOnly(true) + .secure(cookieSecure) + .sameSite(cookieSameSite) + .build() + .toString()); + + return ResponseEntity.ok() + .headers(headers) + .body(Map.of("message", "로그아웃 처리 완료")); + } + + + +} diff --git a/src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java b/src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java index f31f832..96ac2d8 100644 --- a/src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/auth/dto/LoginRequestDto.java @@ -1,18 +1,24 @@ package com.ccapp.ccgo.auth.dto; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Builder; import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class LoginRequestDto { - @NotNull(message = "이메일은 필수입니다.") + @NotBlank(message = "이메일은 필수입니다.") @Email(message = "올바른 이메일 형식이어야 합니다.") private String email; - @NotNull(message = "비밀번호는 필수입니다.") + @NotBlank(message = "비밀번호는 필수입니다.") @Size(min = 8, message = "비밀번호는 최소 8자 이상이어야 합니다.") private String password; } diff --git a/src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java b/src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java index 7658443..e0e8264 100644 --- a/src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/auth/dto/LoginResponseDto.java @@ -2,11 +2,15 @@ import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; import java.util.List; @Getter @Builder +@NoArgsConstructor +@AllArgsConstructor public class LoginResponseDto { private String grantType; // Bearer private String accessToken; @@ -14,16 +18,16 @@ public class LoginResponseDto { private Long userId; private String email; private String name; - private List teams; @Getter @Builder + @NoArgsConstructor + @AllArgsConstructor public static class TeamInfo { private Long teamId; private String teamName; private String role; // 팀 내 역할 (예: "LEADER", "MEMBER") private boolean isSurveyCompleted; } - } diff --git a/src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java b/src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java index 0adc131..eba2601 100644 --- a/src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/auth/dto/TokenResponseDto.java @@ -1,12 +1,14 @@ package com.ccapp.ccgo.auth.dto; -import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Builder; -@Data +@Getter @NoArgsConstructor @AllArgsConstructor +@Builder public class TokenResponseDto { private String accessToken; private String refreshToken; diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/CustomAuthenticationEntryPoint.java b/src/main/java/com/ccapp/ccgo/auth/jwt/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..1769f73 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/CustomAuthenticationEntryPoint.java @@ -0,0 +1,21 @@ +package com.ccapp.ccgo.auth.jwt; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; // ✅ 추가 +import jakarta.servlet.ServletException; // ✅ 추가 + +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"message\": \"인증이 필요합니다.\"}"); + } +} diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java index 0ae60eb..8348c02 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java @@ -2,8 +2,8 @@ import com.ccapp.ccgo.auth.service.LoginUserDetailsService; import jakarta.servlet.FilterChain; -import jakarta.servlet.http.Cookie; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -30,23 +30,7 @@ protected void doFilterInternal(HttpServletRequest request, FilterChain filterChain) throws ServletException, IOException { - String token = null; - - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if ("accessToken".equals(cookie.getName())) { - token = cookie.getValue(); - break; - } - } - } - - if (token == null) { - String authHeader = request.getHeader("Authorization"); - if (authHeader != null && authHeader.startsWith("Bearer ")) { - token = authHeader.substring(7); - } - } + String token = extractToken(request); try { if (token != null && jwtProvider.validateToken(token)) { @@ -61,13 +45,36 @@ protected void doFilterInternal(HttpServletRequest request, authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); + log.debug("✅ JWT 인증 성공: {}", email); } } } catch (Exception e) { - // 로그 추가 가능 (필요 시) - log.error("JWT 인증 처리 중 오류 발생", e); + log.error("❌ JWT 인증 처리 중 오류 발생", e); } filterChain.doFilter(request, response); } -} \ No newline at end of file + + /** + * Authorization 헤더 → 쿠키 순서로 토큰 추출 + */ + private String extractToken(HttpServletRequest request) { + // 1. Authorization 헤더 우선 + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + log.debug("📌 Authorization 헤더에서 토큰 추출"); + return authHeader.substring(7); + } + + // 2. 쿠키 확인 + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if ("accessToken".equals(cookie.getName())) { + log.debug("📌 쿠키에서 토큰 추출"); + return cookie.getValue(); + } + } + } + return null; + } +} diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java index ccd2abe..278cabb 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java @@ -1,39 +1,40 @@ package com.ccapp.ccgo.auth.jwt; + import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; -import org.springframework.security.core.Authentication; + import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Date; +import java.util.Map; import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; + @Slf4j @Component public class JwtProvider { private Key key; - private final String secret; - private final long accessTokenExpiration; - private final long refreshTokenExpiration; + @Value("${jwt.secret}") + private String secret; - public JwtProvider(@Value("${jwt.secret}") String secret, - @Value("${jwt.access-token-expiration}") long accessTokenExpiration, - @Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration) { - this.secret = secret; - this.accessTokenExpiration = accessTokenExpiration; - this.refreshTokenExpiration = refreshTokenExpiration; - } + @Value("${jwt.access-token-expiration}") + private long accessTokenExpiration; + + @Value("${jwt.refresh-token-expiration}") + private long refreshTokenExpiration; @PostConstruct public void init() { byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8); if (keyBytes.length < 32) { - log.warn("JWT 시크릿 키 길이가 너무 짧습니다. 최소 256비트(32바이트) 이상 권장합니다."); + log.error("JWT Secret 키가 너무 짧습니다. 32바이트 이상 권장!"); } this.key = Keys.hmacShaKeyFor(keyBytes); } @@ -43,39 +44,45 @@ public String createAccessToken(Authentication authentication) { String roles = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); - Date now = new Date(); - Date expiryDate = new Date(now.getTime() + accessTokenExpiration); - return Jwts.builder() - .setSubject(username) - .claim("roles", roles) - .setIssuedAt(now) - .setExpiration(expiryDate) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); + return buildToken(username, Map.of("roles", roles), accessTokenExpiration); } public String createRefreshToken(Authentication authentication) { + return buildToken(authentication.getName(), null, refreshTokenExpiration); + } + + private String buildToken(String subject, Map claims, long validityInMs) { Date now = new Date(); - Date expiryDate = new Date(now.getTime() + refreshTokenExpiration); - return Jwts.builder() - .setSubject(authentication.getName()) + Date expiry = new Date(now.getTime() + validityInMs); + JwtBuilder builder = Jwts.builder() + .setSubject(subject) .setIssuedAt(now) - .setExpiration(expiryDate) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); + .setExpiration(expiry) + .signWith(key, SignatureAlgorithm.HS256); + + if (claims != null) { + builder.addClaims(claims); + } + + return builder.compact(); } public boolean validateToken(String token) { try { - Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token); + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; - } catch (JwtException | IllegalArgumentException e) { - log.warn("JWT 유효성 검사 실패: {}", e.getMessage()); - return false; + } catch (ExpiredJwtException e) { + log.warn("JWT 만료: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + log.warn("지원되지 않는 JWT: {}", e.getMessage()); + } catch (MalformedJwtException e) { + log.warn("잘못된 JWT 구조: {}", e.getMessage()); + } catch (SecurityException e) { + log.warn("JWT 서명 오류: {}", e.getMessage()); + } catch (IllegalArgumentException e) { + log.warn("JWT 토큰 값 없음: {}", e.getMessage()); } + return false; } public String getEmailFromToken(String token) { @@ -86,4 +93,13 @@ public String getEmailFromToken(String token) { .getBody() .getSubject(); } -} \ No newline at end of file + + public long getAccessTokenExpiration() { + return accessTokenExpiration; + } + + public long getRefreshTokenExpiration() { + return refreshTokenExpiration; + } + +} diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java b/src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java index e75c88b..23a0e2a 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/LoginUserDetails.java @@ -1,45 +1,48 @@ package com.ccapp.ccgo.auth.jwt; +import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.user.entity.User; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; - - +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import java.util.List; import java.util.Collection; import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; @Getter public class LoginUserDetails implements UserDetails { private final User user; + private final List teamMembers; - public LoginUserDetails(User user) { + public LoginUserDetails(User user, List teamMembers) { this.user = user; + this.teamMembers = teamMembers; } - // ✅ 기본 권한 비워둠 (나중에 ROLE_ 추가 가능) @Override public Collection getAuthorities() { - return Collections.emptyList(); - } + if (teamMembers == null || teamMembers.isEmpty()) { + return Collections.emptyList(); + } - // ✅ 로그인 시 사용할 비밀번호 - @Override - public String getPassword() { - return user.getPassword(); - } + Set authorities = teamMembers.stream() + .map(TeamMember::getRole) // Role enum 반환 가정 + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())) + .collect(Collectors.toSet()); - // ✅ 로그인 시 사용할 식별자 (우린 이메일) - @Override - public String getUsername() { - return user.getEmail(); + return authorities; } - // ✅ 계정 상태 기본 활성화 (true) - @Override public boolean isAccountNonExpired() { return true; } - @Override public boolean isAccountNonLocked() { return true; } + // 이하 기존 메서드 동일 + @Override public String getPassword() { return user.getPassword(); } + @Override public String getUsername() { return user.getEmail(); } + @Override public boolean isAccountNonExpired() { return true; } + @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } - @Override public boolean isEnabled() { return true; } - + @Override public boolean isEnabled() { return true; } } + diff --git a/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java b/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java new file mode 100644 index 0000000..17d4fdd --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java @@ -0,0 +1,90 @@ +package com.ccapp.ccgo.auth.service; + +import com.ccapp.ccgo.auth.dto.LoginResponseDto; +import com.ccapp.ccgo.auth.dto.TokenResponseDto; +import com.ccapp.ccgo.auth.jwt.JwtProvider; +import com.ccapp.ccgo.auth.jwt.LoginUserDetails; +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final AuthenticationManager authenticationManager; + private final JwtProvider jwtProvider; + private final TeamMemberRepository teamMemberRepository; + private final LoginUserDetailsService loginUserDetailsService; + + // 새로 추가: Redis 연동 서비스 + private final RefreshTokenService refreshTokenService; + + public LoginResponseDto login(String email, String password) { + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(email, password) + ); + + String accessToken = jwtProvider.createAccessToken(authentication); + String refreshToken = jwtProvider.createRefreshToken(authentication); + + LoginUserDetails userDetails = (LoginUserDetails) authentication.getPrincipal(); + var user = userDetails.getUser(); + + // Redis에 Refresh Token 저장 (만료시간: jwt.refresh-token-expiration) + refreshTokenService.saveRefreshToken(user.getEmail(), refreshToken, jwtProvider.getRefreshTokenExpiration()); + + List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); + + List teams = teamMembers.stream() + .map(tm -> LoginResponseDto.TeamInfo.builder() + .teamId(tm.getTeam().getTeamId()) + .teamName(tm.getTeam().getTeamName()) + .role(tm.getRole().name()) + .isSurveyCompleted(tm.isSurveyCompleted()) + .build()) + .toList(); + + return LoginResponseDto.builder() + .userId(user.getId()) + .email(user.getEmail()) + .name(user.getName()) + .teams(teams) + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + public TokenResponseDto refreshToken(String refreshToken) { + if (!jwtProvider.validateToken(refreshToken)) { + throw new IllegalArgumentException("리프레시 토큰이 없거나 유효하지 않습니다."); + } + + String email = jwtProvider.getEmailFromToken(refreshToken); + + // Redis에서 저장된 토큰과 비교 + if (!refreshTokenService.validateRefreshToken(email, refreshToken)) { + throw new IllegalArgumentException("리프레시 토큰이 만료되었거나 로그아웃 처리된 토큰입니다."); + } + + LoginUserDetails userDetails = (LoginUserDetails) loginUserDetailsService.loadUserByUsername(email); + + Authentication authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities() + ); + + String newAccessToken = jwtProvider.createAccessToken(authentication); + String newRefreshToken = jwtProvider.createRefreshToken(authentication); + + // Redis에 새 Refresh Token 저장 (기존 토큰 덮어쓰기) + refreshTokenService.saveRefreshToken(email, newRefreshToken, jwtProvider.getRefreshTokenExpiration()); + + return new TokenResponseDto(newAccessToken, newRefreshToken); + } +} diff --git a/src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java b/src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java index 97899f7..773beea 100644 --- a/src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/LoginUserDetailsService.java @@ -1,6 +1,8 @@ package com.ccapp.ccgo.auth.service; import com.ccapp.ccgo.auth.jwt.LoginUserDetails; +import com.ccapp.ccgo.team.entity.TeamMember; +import com.ccapp.ccgo.team.repository.TeamMemberRepository; import com.ccapp.ccgo.user.entity.User; import com.ccapp.ccgo.user.repository.UserRepository; import org.springframework.security.core.userdetails.UserDetails; @@ -8,20 +10,28 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import java.util.List; + @Service public class LoginUserDetailsService implements UserDetailsService { private final UserRepository userRepository; + private final TeamMemberRepository teamMemberRepository; - public LoginUserDetailsService(UserRepository userRepository) { + // teamMemberRepository도 생성자 인자로 추가 + public LoginUserDetailsService(UserRepository userRepository, + TeamMemberRepository teamMemberRepository) { this.userRepository = userRepository; + this.teamMemberRepository = teamMemberRepository; } // ✅ 이메일로 유저 조회 → LoginUserDetails 로 감싸서 리턴 @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + public UserDetails loadUserByUsername(String email) { User user = userRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + email)); - return new LoginUserDetails(user); + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); + + return new LoginUserDetails(user, teamMembers); } } diff --git a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java new file mode 100644 index 0000000..cd94c1e --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java @@ -0,0 +1,47 @@ +package com.ccapp.ccgo.auth.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class RefreshTokenService { + + private final StringRedisTemplate redisTemplate; + + private static final String REFRESH_TOKEN_PREFIX = "refreshToken:"; + + /** + * Redis에 Refresh Token 저장 (TTL 적용) + */ + public void saveRefreshToken(String email, String refreshToken, long expirationMillis) { + String key = REFRESH_TOKEN_PREFIX + email; + redisTemplate.opsForValue().set(key, refreshToken, expirationMillis, TimeUnit.MILLISECONDS); + } + + /** + * Redis에서 Refresh Token 조회 + */ + public String getRefreshToken(String email) { + String key = REFRESH_TOKEN_PREFIX + email; + return redisTemplate.opsForValue().get(key); + } + + /** + * Redis에 저장된 Refresh Token과 비교하여 유효성 확인 + */ + public boolean validateRefreshToken(String email, String refreshToken) { + String savedToken = getRefreshToken(email); + return savedToken != null && savedToken.equals(refreshToken); + } + + /** + * 로그아웃 시 Refresh Token 삭제 + */ + public void deleteRefreshToken(String email) { + redisTemplate.delete(REFRESH_TOKEN_PREFIX + email); + } +} diff --git a/src/main/java/com/ccapp/ccgo/common/RedisConfig.java b/src/main/java/com/ccapp/ccgo/common/RedisConfig.java new file mode 100644 index 0000000..a515609 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/common/RedisConfig.java @@ -0,0 +1,17 @@ +package com.ccapp.ccgo.common; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + return redisTemplate; + } +} diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index 362be94..d6cd3e0 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.common; +import com.ccapp.ccgo.auth.jwt.CustomAuthenticationEntryPoint; import com.ccapp.ccgo.auth.jwt.JwtAuthenticationFilter; import com.ccapp.ccgo.auth.service.LoginUserDetailsService; import lombok.RequiredArgsConstructor; @@ -20,20 +21,20 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.List; + @Configuration @RequiredArgsConstructor public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final LoginUserDetailsService loginUserDetailsService; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; - // 🔐 비밀번호 인코더 등록 @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - // 🔐 로그인 시 사용할 인증 제공자 (UserDetailsService + PasswordEncoder) @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); @@ -42,13 +43,11 @@ public AuthenticationProvider authenticationProvider() { return provider; } - // 🔐 인증 매니저 (로그인 인증 처리 시 필요) @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } - // 🔐 필터 체인 설정 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http @@ -56,22 +55,32 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/login","/bcrypt-test","/bcrypt-config", "/register").permitAll() // 로그인, 회원가입 허용, "/bcrypt-test" - .anyRequest().authenticated() // 그 외는 인증 필요 + // 인증 필요 없는 엔드포인트 + .requestMatchers("/api/auth/login", "/api/auth/refresh", "/register").permitAll() + + // LEADER 전용 API (팀 관리) + .requestMatchers("/api/team/**").hasRole("LEADER") + + // MEMBER 이상 접근 가능 (예시) + .requestMatchers("/api/member/**").hasAnyRole("MEMBER", "LEADER") + + // 그 외 요청은 인증 필요 + .anyRequest().authenticated() ) .authenticationProvider(authenticationProvider()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(ex -> ex.authenticationEntryPoint(customAuthenticationEntryPoint)) .build(); } - //cors + + @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOriginPatterns(List.of("*")); // 변경된 부분 + config.setAllowedOrigins(List.of("http://192.168.29.245:8080")); // 서비스 IP주소 config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 22f7f7e..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,23 +0,0 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/newuser - -server.address=0.0.0.0 -server.port=8080 - -spring.datasource.username = root -spring.datasource.password = qkrwlsdn - -jwt.secret=????????????????????1234!@11111123451234432AVDSFUCKYOUSHITHOLY -jwt.access-token-expiration=86400000 -jwt.refresh-token-expiration=604800000 - -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver - -spring.jpa.hibernate.ddl-auto=update - -#spring.jpa.show-sql=true -#spring.jpa.properties.hibernate.format_sql=true - -spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect - -#resources ?? ?? sql ? ?? -spring.sql.init.mode=always \ No newline at end of file From 0d52311a7e5d3563c95235890fd22df1a51b0478 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 29 Jul 2025 13:14:17 +0900 Subject: [PATCH 037/132] . --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 89d195a..31519e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,9 +31,9 @@ services: - mysql - redis environment: - SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/your_database?useSSL=false&serverTimezone=Asia/Seoul - SPRING_DATASOURCE_USERNAME: root - SPRING_DATASOURCE_PASSWORD: 1234 + SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/ccmake?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true + SPRING_DATASOURCE_USERNAME: user + SPRING_DATASOURCE_PASSWORD: 06250730cc SPRING_REDIS_HOST: redis SPRING_REDIS_PORT: 6379 ports: From de4cc1a9e2189857a79caae5969a94d9697d7281 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Tue, 29 Jul 2025 16:45:32 +0900 Subject: [PATCH 038/132] sfd --- docker-compose.yml | 1 + src/test/java/com/ccapp/ccgo/CCgoApplicationTests.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 31519e7..ccccc19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,7 @@ services: SPRING_DATASOURCE_PASSWORD: 06250730cc SPRING_REDIS_HOST: redis SPRING_REDIS_PORT: 6379 + ports: - "8080:8080" networks: diff --git a/src/test/java/com/ccapp/ccgo/CCgoApplicationTests.java b/src/test/java/com/ccapp/ccgo/CCgoApplicationTests.java index 8971401..f9f1ad9 100644 --- a/src/test/java/com/ccapp/ccgo/CCgoApplicationTests.java +++ b/src/test/java/com/ccapp/ccgo/CCgoApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class CCgoApplicationTests { - @Test - void contextLoads() { - } +// @Test +// void contextLoads() { +// } } From 5bd05a216664615830bf96fafc31d0eac604196a Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 30 Jul 2025 11:02:26 +0900 Subject: [PATCH 039/132] add something --- .gitignore | 5 ++++- .idea/dataSources.xml | 4 ++-- .idea/modules/CC_Maker_BE.main.iml | 17 +++++++++++++++++ .idea/modules/CC_Maker_BE.test.iml | 17 +++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0301ade..6b9163a 100644 --- a/.gitignore +++ b/.gitignore @@ -206,4 +206,7 @@ application-*.properties .src/main/resources src/main/resources -# End of https://www.toptal.com/developers/gitignore/api/windows,intellij,java,gradle \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/windows,intellij,java,gradle + +# pem 파일 (혹시나) +*.pem \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index aeb9e05..7e60f4f 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,11 +1,11 @@ - + mysql.8 true com.mysql.cj.jdbc.Driver - jdbc:mysql://localhost:3306 + jdbc:mysql://13.209.70.253:3306 diff --git a/.idea/modules/CC_Maker_BE.main.iml b/.idea/modules/CC_Maker_BE.main.iml index ff0e05e..73cf394 100644 --- a/.idea/modules/CC_Maker_BE.main.iml +++ b/.idea/modules/CC_Maker_BE.main.iml @@ -38,6 +38,8 @@ + + @@ -52,6 +54,8 @@ + + @@ -82,6 +86,14 @@ + + + + + + + + @@ -90,6 +102,11 @@ + + + + + diff --git a/.idea/modules/CC_Maker_BE.test.iml b/.idea/modules/CC_Maker_BE.test.iml index b041ee2..5e26568 100644 --- a/.idea/modules/CC_Maker_BE.test.iml +++ b/.idea/modules/CC_Maker_BE.test.iml @@ -22,6 +22,8 @@ + + @@ -38,6 +40,8 @@ + + @@ -82,6 +86,14 @@ + + + + + + + + @@ -97,6 +109,11 @@ + + + + + From 88dcd4e5e86b7e8ec290cd52297c4eac4ed71e11 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 30 Jul 2025 13:59:24 +0900 Subject: [PATCH 040/132] add github action --- .github/workflows/deploy-backend.yml | 41 ++++++++++++++++++++++++++++ .idea/dataSources.xml | 13 +++++++++ .idea/sqldialects.xml | 1 + 3 files changed, 55 insertions(+) create mode 100644 .github/workflows/deploy-backend.yml diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml new file mode 100644 index 0000000..574752c --- /dev/null +++ b/.github/workflows/deploy-backend.yml @@ -0,0 +1,41 @@ +name: Deploy Spring Boot to EC2 + +on: + push: + branches: + - main # main 브랜치에 push될 때 실행됨 + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup PEM key + run: | + echo "${{ secrets.PEM_KEY }}" > my-key.pem + chmod 600 my-key.pem + + - name: Deploy to EC2 via SSH + run: | + ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF' + echo "📦 Spring Boot 배포 시작" + + # 1. 프로젝트 폴더로 이동 + cd ${{ secrets.DEPLOY_DIR }} + + # 2. 최신 코드 가져오기 + git pull origin main + + # 3. 빌드 (테스트 생략) + ./gradlew clean build -x test + + # 4. 기존 서버 종료 (실행 중이면) + pkill -f 'java -jar' || true + + # 5. 새 서버 실행 + nohup java -jar build/libs/*.jar > app.log 2>&1 & + echo "✅ 배포 완료" + EOF diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 7e60f4f..92b24a1 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -14,5 +14,18 @@ $ProjectFileDir$ + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306 + + + + + + + $ProjectFileDir$ + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 2cb6d30..c2d50dc 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -3,6 +3,7 @@ + \ No newline at end of file From 101d6683a71ee0972bfb06ca352d1c0f996828d5 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 30 Jul 2025 14:05:15 +0900 Subject: [PATCH 041/132] test-deploy --- .github/workflows/deploy-backend.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 574752c..7bf4148 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -3,8 +3,8 @@ name: Deploy Spring Boot to EC2 on: push: branches: - - main # main 브랜치에 push될 때 실행됨 - +# - main # main 브랜치에 push될 때 실행됨 + - test-deploy jobs: deploy: runs-on: ubuntu-latest From 97ce214596242430ec31b1185b64e54fba1ae998 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 30 Jul 2025 14:15:02 +0900 Subject: [PATCH 042/132] fix : update PEM key format --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 7bf4148..70994fd 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -5,6 +5,7 @@ on: branches: # - main # main 브랜치에 push될 때 실행됨 - test-deploy + jobs: deploy: runs-on: ubuntu-latest From b4216cc0cc6e2aa6b4c1f860d0a083513d974ffb Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:35:48 +0900 Subject: [PATCH 043/132] ci: fix ci file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gpt 병신 --- .github/workflows/deploy-backend.yml | 43 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 70994fd..0817170 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -1,10 +1,9 @@ -name: Deploy Spring Boot to EC2 +name: Deploy Spring Boot to EC2 via SCP + Docker on: push: branches: -# - main # main 브랜치에 push될 때 실행됨 - - test-deploy + - test-deploy jobs: deploy: @@ -14,29 +13,41 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PEM key + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Build Spring Boot application + run: ./gradlew clean bootJar -x test + + - name: Prepare PEM key run: | echo "${{ secrets.PEM_KEY }}" > my-key.pem chmod 600 my-key.pem - - name: Deploy to EC2 via SSH + - name: Copy JAR to EC2 + run: | + scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/*.jar \ + ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ secrets.DEPLOY_DIR }}/app.jar + + - name: Deploy Dockerized app on EC2 run: | ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF' - echo "📦 Spring Boot 배포 시작" + echo "🛠 Docker 재배포 시작" - # 1. 프로젝트 폴더로 이동 cd ${{ secrets.DEPLOY_DIR }} - # 2. 최신 코드 가져오기 - git pull origin main + # 기존 컨테이너 중지 및 제거 + docker stop spring-app || true + docker rm spring-app || true - # 3. 빌드 (테스트 생략) - ./gradlew clean build -x test + # 새 이미지 빌드 + docker build -t spring-app . - # 4. 기존 서버 종료 (실행 중이면) - pkill -f 'java -jar' || true + # 컨테이너 실행 + docker run -d --name spring-app -p 8080:8080 spring-app - # 5. 새 서버 실행 - nohup java -jar build/libs/*.jar > app.log 2>&1 & - echo "✅ 배포 완료" + echo "✅ Docker 배포 완료" EOF From b1e061a39f0033f869188fd18d9046b324e6455d Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:37:47 +0900 Subject: [PATCH 044/132] ci : test new file --- .github/workflows/deploy-backend.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 0817170..12e5c36 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -19,6 +19,9 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Give execute permission to gradlew + run: chmod +x ./gradlew + - name: Build Spring Boot application run: ./gradlew clean bootJar -x test @@ -39,11 +42,11 @@ jobs: cd ${{ secrets.DEPLOY_DIR }} - # 기존 컨테이너 중지 및 제거 + # 기존 컨테이너 종료 및 제거 docker stop spring-app || true docker rm spring-app || true - # 새 이미지 빌드 + # 새 Docker 이미지 빌드 docker build -t spring-app . # 컨테이너 실행 From cfb82e8016593e857bc71548e5686ab9a0977de8 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:39:32 +0900 Subject: [PATCH 045/132] ci: new file 22 --- .github/workflows/deploy-backend.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 12e5c36..bdb4679 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -1,14 +1,6 @@ -name: Deploy Spring Boot to EC2 via SCP + Docker - -on: - push: - branches: - - test-deploy - jobs: deploy: runs-on: ubuntu-latest - steps: - name: Checkout repository uses: actions/checkout@v3 @@ -30,6 +22,10 @@ jobs: echo "${{ secrets.PEM_KEY }}" > my-key.pem chmod 600 my-key.pem + - name: Ensure remote deploy directory exists + run: | + ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} "mkdir -p ${{ secrets.DEPLOY_DIR }}" + - name: Copy JAR to EC2 run: | scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/*.jar \ @@ -42,14 +38,11 @@ jobs: cd ${{ secrets.DEPLOY_DIR }} - # 기존 컨테이너 종료 및 제거 docker stop spring-app || true docker rm spring-app || true - # 새 Docker 이미지 빌드 docker build -t spring-app . - # 컨테이너 실행 docker run -d --name spring-app -p 8080:8080 spring-app echo "✅ Docker 배포 완료" From f926c3d885b3d213bf3002a8e022a68e1ad5de85 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:40:42 +0900 Subject: [PATCH 046/132] ci: fix file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 마 힘들다 아이가 --- .github/workflows/deploy-backend.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index bdb4679..aa700aa 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -1,3 +1,10 @@ +name: Deploy Spring Boot to EC2 via SCP + Docker + +on: + push: + branches: + - test-deploy + jobs: deploy: runs-on: ubuntu-latest From 0d4ffd253e1363c13179fcff8238bce8dc3aa6d1 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:46:25 +0900 Subject: [PATCH 047/132] ci: fix sibal --- .github/workflows/deploy-backend.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index aa700aa..8b3fa34 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -31,19 +31,18 @@ jobs: - name: Ensure remote deploy directory exists run: | - ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} "mkdir -p ${{ secrets.DEPLOY_DIR }}" + ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} "mkdir -p cc_go" - name: Copy JAR to EC2 run: | scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/*.jar \ - ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ secrets.DEPLOY_DIR }}/app.jar + ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/app.jar - name: Deploy Dockerized app on EC2 run: | ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF' echo "🛠 Docker 재배포 시작" - - cd ${{ secrets.DEPLOY_DIR }} + cd cc_go docker stop spring-app || true docker rm spring-app || true From 005f174e10067c1bd7cd68cd096de64a580ed926 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:51:10 +0900 Subject: [PATCH 048/132] ci: add scp command for docker file --- .github/workflows/deploy-backend.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 8b3fa34..c5e3d3a 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -35,9 +35,12 @@ jobs: - name: Copy JAR to EC2 run: | - scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/*.jar \ + scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/app.jar \ ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/app.jar + scp -o StrictHostKeyChecking=no -i my-key.pem Dockerfile \ + ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/Dockerfile + - name: Deploy Dockerized app on EC2 run: | ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF' From 3e75b92b3a9f3c2461be0f4b06e2e912e0bc43ca Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:53:38 +0900 Subject: [PATCH 049/132] ci: change file --- .github/workflows/deploy-backend.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index c5e3d3a..9eeed13 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -24,6 +24,11 @@ jobs: - name: Build Spring Boot application run: ./gradlew clean bootJar -x test + - name: Rename JAR for deployment + run: | + JAR_PATH=$(ls build/libs/*.jar | head -n 1) + cp "$JAR_PATH" build/libs/app.jar + - name: Prepare PEM key run: | echo "${{ secrets.PEM_KEY }}" > my-key.pem @@ -33,7 +38,7 @@ jobs: run: | ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} "mkdir -p cc_go" - - name: Copy JAR to EC2 + - name: Copy JAR and Dockerfile to EC2 run: | scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/app.jar \ ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/app.jar From 0c5fdfd774cc82a6227b08b282188b3c397767d9 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:56:37 +0900 Subject: [PATCH 050/132] ci: update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d7b8d9e..45af554 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM eclipse-temurin:17-jdk-jammy -COPY build/libs/*.jar app.jar +COPY *.jar app.jar EXPOSE 8080 CMD ["java", "-jar", "app.jar", "--spring.profiles.active=application-deploy"] From f1b6c9f0c5b82fe44adfe00501a7fa1f53f07840 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:04:44 +0900 Subject: [PATCH 051/132] ci: change to compose --- .github/workflows/deploy-backend.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 9eeed13..d307fc1 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -1,4 +1,4 @@ -name: Deploy Spring Boot to EC2 via SCP + Docker +name: Deploy Spring Boot to EC2 via SCP + Docker Compose on: push: @@ -8,6 +8,7 @@ on: jobs: deploy: runs-on: ubuntu-latest + steps: - name: Checkout repository uses: actions/checkout@v3 @@ -38,26 +39,25 @@ jobs: run: | ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} "mkdir -p cc_go" - - name: Copy JAR and Dockerfile to EC2 + - name: Copy necessary files to EC2 run: | scp -o StrictHostKeyChecking=no -i my-key.pem build/libs/app.jar \ - ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/app.jar + ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/app.jar scp -o StrictHostKeyChecking=no -i my-key.pem Dockerfile \ - ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/Dockerfile + ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/Dockerfile + + scp -o StrictHostKeyChecking=no -i my-key.pem docker-compose.yml \ + ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:cc_go/docker-compose.yml - - name: Deploy Dockerized app on EC2 + - name: Deploy using Docker Compose on EC2 run: | ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF' - echo "🛠 Docker 재배포 시작" + echo "🛠 Docker Compose 재배포 시작" cd cc_go - docker stop spring-app || true - docker rm spring-app || true - - docker build -t spring-app . - - docker run -d --name spring-app -p 8080:8080 spring-app + docker-compose down || true + docker-compose up -d --build - echo "✅ Docker 배포 완료" + echo "✅ Docker Compose 배포 완료" EOF From 3f8c04b951931e06135a86f1524ccca46ab137c3 Mon Sep 17 00:00:00 2001 From: losecow <100589396+Losecow@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:05:28 +0900 Subject: [PATCH 052/132] ci: exchange dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 45af554..d7b8d9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM eclipse-temurin:17-jdk-jammy -COPY *.jar app.jar +COPY build/libs/*.jar app.jar EXPOSE 8080 CMD ["java", "-jar", "app.jar", "--spring.profiles.active=application-deploy"] From 1ad9a6dbb8d2842dc1b51f185e2616ae1bf19ed6 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Mon, 4 Aug 2025 13:35:26 +0900 Subject: [PATCH 053/132] Fixed loginsys --- src/main/java/com/ccapp/ccgo/common/SecurityConfig.java | 2 +- src/main/resources/MissionInsert.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index d6cd3e0..1e850e0 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -59,7 +59,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/api/auth/login", "/api/auth/refresh", "/register").permitAll() // LEADER 전용 API (팀 관리) - .requestMatchers("/api/team/**").hasRole("LEADER") + .requestMatchers("/api/team/**").hasAnyRole("LEADER", "MEMBER") // MEMBER 이상 접근 가능 (예시) .requestMatchers("/api/member/**").hasAnyRole("MEMBER", "LEADER") diff --git a/src/main/resources/MissionInsert.sql b/src/main/resources/MissionInsert.sql index 9b4157b..deb6354 100644 --- a/src/main/resources/MissionInsert.sql +++ b/src/main/resources/MissionInsert.sql @@ -1,4 +1,4 @@ -USE newuser; +USE ccmake; INSERT INTO mission_template (title, description, score) VALUES (' 미션 1', '서로의 첫인상 공유하기', 1), (' 미션 2', '인스타 맞팔하기', 1), From 8a2981c9dfd3be0ac39fe0852bf9aca32eecbb11 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 4 Aug 2025 14:28:32 +0900 Subject: [PATCH 054/132] setup --- .gitignore | 5 +- .idea/dataSources.xml | 3 +- docker-compose.yml | 52 +++++++++++++++++-- .../com/ccapp/ccgo/common/SecurityConfig.java | 2 +- .../ccgo/team/controller/TeamController.java | 1 - 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 6b9163a..d9f5c60 100644 --- a/.gitignore +++ b/.gitignore @@ -209,4 +209,7 @@ src/main/resources # End of https://www.toptal.com/developers/gitignore/api/windows,intellij,java,gradle # pem 파일 (혹시나) -*.pem \ No newline at end of file +*.pem + +# .gitignore +.env diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 92b24a1..8afcafe 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -14,7 +14,7 @@ $ProjectFileDir$ - + mysql.8 true com.mysql.cj.jdbc.Driver @@ -22,7 +22,6 @@ - $ProjectFileDir$ diff --git a/docker-compose.yml b/docker-compose.yml index ccccc19..9bc3b85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,47 @@ +#version: '3.8' +# +#services: +# mysql: +# image: mysql:latest +# container_name: mysql-container +# environment: +# MYSQL_ROOT_PASSWORD: qkrwlsdn +# MYSQL_DATABASE: ccmake +# ports: +# - "3306:3306" +# volumes: +# - mysql-data:/var/lib/mysql +# networks: +# - backend-network +# +# redis: +# image: redis:latest +# container_name: redis-container +# ports: +# - "6379:6379" +# networks: +# - backend-network +# +# spring-app: +# build: . +# container_name: spring-app-container +# depends_on: +# - mysql +# - redis +# env_file: +# - .env +# ports: +# - "8080:8080" +# networks: +# - backend-network +# +#volumes: +# mysql-data: +# +#networks: +# backend-network: + + version: '3.8' services: @@ -5,10 +49,10 @@ services: image: mysql:latest container_name: mysql-container environment: - MYSQL_ROOT_PASSWORD: 06250730cc + MYSQL_ROOT_PASSWORD: qkrwlsdn MYSQL_DATABASE: ccmake MYSQL_USER: user - MYSQL_PASSWORD: 06250730cc + MYSQL_PASSWORD: qkrwlsdn ports: - "3306:3306" volumes: @@ -31,9 +75,9 @@ services: - mysql - redis environment: - SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/ccmake?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true + SPRING_DATASOURCE_URL: jdbc:mysql://mysql-container:3306/ccmake?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true SPRING_DATASOURCE_USERNAME: user - SPRING_DATASOURCE_PASSWORD: 06250730cc + SPRING_DATASOURCE_PASSWORD: qkrwlsdn SPRING_REDIS_HOST: redis SPRING_REDIS_PORT: 6379 diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index d6cd3e0..1e850e0 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -59,7 +59,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/api/auth/login", "/api/auth/refresh", "/register").permitAll() // LEADER 전용 API (팀 관리) - .requestMatchers("/api/team/**").hasRole("LEADER") + .requestMatchers("/api/team/**").hasAnyRole("LEADER", "MEMBER") // MEMBER 이상 접근 가능 (예시) .requestMatchers("/api/member/**").hasAnyRole("MEMBER", "LEADER") diff --git a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java index 5c968b2..9751a8a 100644 --- a/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java +++ b/src/main/java/com/ccapp/ccgo/team/controller/TeamController.java @@ -51,7 +51,6 @@ public ResponseEntity> getMyTeams( return ResponseEntity.ok(result); } - @PostMapping("/survey/complete") public ResponseEntity completeSurvey(@RequestBody SurveyCompleteRequest request, @AuthenticationPrincipal LoginUserDetails loginUserDetails) { From 0d5ead0c033dc1edec7a8af20f9b864ab3c3a023 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 4 Aug 2025 17:22:14 +0900 Subject: [PATCH 055/132] test1 --- .github/workflows/deploy-backend.yml | 71 +++++++++++++++++----------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 70994fd..cb8239e 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -1,42 +1,57 @@ -name: Deploy Spring Boot to EC2 +name: Deploy Backend to EC2 via Docker on: push: - branches: -# - main # main 브랜치에 push될 때 실행됨 - - test-deploy + branches: test-deploy jobs: deploy: runs-on: ubuntu-latest steps: - - name: Checkout repository + - name: Checkout code uses: actions/checkout@v3 - - name: Setup PEM key - run: | - echo "${{ secrets.PEM_KEY }}" > my-key.pem - chmod 600 my-key.pem - - - name: Deploy to EC2 via SSH - run: | - ssh -o StrictHostKeyChecking=no -i my-key.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF' - echo "📦 Spring Boot 배포 시작" - - # 1. 프로젝트 폴더로 이동 - cd ${{ secrets.DEPLOY_DIR }} + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' - # 2. 최신 코드 가져오기 - git pull origin main + - name: Build Spring Boot JAR + run: ./gradlew clean bootJar -x test - # 3. 빌드 (테스트 생략) - ./gradlew clean build -x test - - # 4. 기존 서버 종료 (실행 중이면) - pkill -f 'java -jar' || true + - name: Prepare deployment directory + run: | + mkdir -p deploy + cp build/libs/*.jar deploy/app.jar - # 5. 새 서버 실행 - nohup java -jar build/libs/*.jar > app.log 2>&1 & - echo "✅ 배포 완료" - EOF + - name: Create .env file for prod + run: | + echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env + echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env + echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env + echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env + + - name: Upload app.jar and .env to EC2 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.PEM_KEY }} + source: "deploy/app.jar,deploy/.env" + target: "/home/ec2-user/ccgo" + + - name: SSH into EC2 and restart Docker container + uses: appleboy/ssh-action@v0.1.7 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.PEM_KEY }} + script: | + cd ~/ccgo + docker stop ccgo-app || true + docker rm ccgo-app || true + docker rmi ccgo-app || true + docker compose build + docker compo From 3ec41ec8d196690eb5dcae41c2f55b56464b0c6a Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 4 Aug 2025 17:29:10 +0900 Subject: [PATCH 056/132] merge --- .github/workflows/deploy-backend.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index cb8239e..820a9fe 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -2,7 +2,7 @@ name: Deploy Backend to EC2 via Docker on: push: - branches: test-deploy + branches: [ test-deploy ] # 또는 배포 브랜치명 jobs: deploy: @@ -54,4 +54,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compo + docker compose up -d From 7cb79cf1e1a58dfc99968f24acf6cd66ffb3ff41 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 4 Aug 2025 17:36:40 +0900 Subject: [PATCH 057/132] letsgo --- .github/workflows/deploy-backend.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index d0f3e55..9983d80 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -20,6 +20,9 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Give gradlew execute permission + run: chmod +x ./gradlew + - name: Build Spring Boot JAR run: ./gradlew clean bootJar -x test From 51390a5229ee5b05abaee716bf6f77224a38bd58 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 4 Aug 2025 17:48:41 +0900 Subject: [PATCH 058/132] trigger deploy From 8ac68e9cc19b2766fc75b073152cc08621e66e79 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 4 Aug 2025 17:54:04 +0900 Subject: [PATCH 059/132] fix: adjust deploy path to fix app.jar COPY issue --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 9983d80..96031f6 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -46,6 +46,7 @@ jobs: key: ${{ secrets.PEM_KEY }} source: "deploy/app.jar,deploy/.env" target: "/home/ec2-user/ccgo" + strip_components: 1 - name: SSH into EC2 and restart Docker container uses: appleboy/ssh-action@v0.1.7 From 3e18f55473ab1a7c9e887a4a8d9a57f689911485 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 13:18:30 +0900 Subject: [PATCH 060/132] chore: update deploy script to use environment variables in .env --- .github/workflows/deploy-backend.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 96031f6..f79a965 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -3,8 +3,7 @@ name: Deploy Spring Boot to EC2 on: push: branches: -# - main # main 브랜치에 push될 때 실행됨 - - test-deploy + - test-deploy jobs: deploy: @@ -37,6 +36,8 @@ jobs: echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env + echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env + echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 From 03303949f7b0ca61db970daf0468996339906dfc Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 13:52:37 +0900 Subject: [PATCH 061/132] test --- .github/workflows/deploy-backend.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index f79a965..ecfff18 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -38,6 +38,8 @@ jobs: echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env + echo "REDIS_HOST=redis" >> deploy/.env # ★ 이거 추가 + echo "REDIS_PORT=6379" >> deploy/.env - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 @@ -62,3 +64,4 @@ jobs: docker rmi ccgo-app || true docker compose build docker compose up -d + From e058974545ad5776bdce79fdf69ca8e89db72d5d Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 14:41:40 +0900 Subject: [PATCH 062/132] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=EB=8D=95?= =?UTF-8?q?=EC=85=98=20=ED=99=98=EA=B2=BD=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?.env=20=EC=97=B0=EB=8F=99=20=EB=B0=8F=20properties=20externaliz?= =?UTF-8?q?e=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-backend.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index ecfff18..2285dc5 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -38,8 +38,8 @@ jobs: echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env - echo "REDIS_HOST=redis" >> deploy/.env # ★ 이거 추가 - echo "REDIS_PORT=6379" >> deploy/.env + echo "REDIS_HOST=redis" >> deploy/.env + echo "REDIS_PORT=6379" >> deploy/.env - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 @@ -64,4 +64,3 @@ jobs: docker rmi ccgo-app || true docker compose build docker compose up -d - From dd4f5b7ed92a6aa0e8af92b8ec4f96d1d9bb7bee Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 14:58:33 +0900 Subject: [PATCH 063/132] =?UTF-8?q?refactor:=20=ED=94=84=EB=A1=9C=EB=8D=95?= =?UTF-8?q?=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 9bc3b85..73c989e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,3 +91,4 @@ volumes: networks: backend-network: + From 63ef128ab0fb2079e72452e83e67d44ae99fbaac Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 15:03:36 +0900 Subject: [PATCH 064/132] =?UTF-8?q?refactor:=20=C3=AD=C2=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 73c989e..9bc3b85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,4 +91,3 @@ volumes: networks: backend-network: - From 57aac4090a1d9c0ee3dadf8a683668d04c8edcd5 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 15:06:57 +0900 Subject: [PATCH 065/132] refactor --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9bc3b85..03da471 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,4 +90,4 @@ volumes: mysql-data: networks: - backend-network: + backend-network: \ No newline at end of file From e3949f6f8230724fcc9f86d85cee46af1d14e270 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 15:10:18 +0900 Subject: [PATCH 066/132] refactor --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 03da471..9bc3b85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,4 +90,4 @@ volumes: mysql-data: networks: - backend-network: \ No newline at end of file + backend-network: From 37b68563aedc554e59816b89e9f0d81e06c1d465 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 15:14:09 +0900 Subject: [PATCH 067/132] refactor --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9bc3b85..03da471 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,4 +90,4 @@ volumes: mysql-data: networks: - backend-network: + backend-network: \ No newline at end of file From 55ca0efb4073e63dc253b0e6b9a620f46eeb1e30 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 15:17:57 +0900 Subject: [PATCH 068/132] refactor --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 2285dc5..86320a4 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d + docker compose up -d \ No newline at end of file From e1272fa4b50b737d51fc55e527d98b18334d88e7 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 15:38:26 +0900 Subject: [PATCH 069/132] refactor --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 86320a4..2285dc5 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d \ No newline at end of file + docker compose up -d From 2ff454d887e83853db11898ccaaf024bfea16b1f Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 16:18:25 +0900 Subject: [PATCH 070/132] refactor --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 2285dc5..b1ddda4 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -64,3 +64,4 @@ jobs: docker rmi ccgo-app || true docker compose build docker compose up -d + From 224d912143ebb25d29d2199caa65173ec0cd938b Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 16:34:32 +0900 Subject: [PATCH 071/132] refactor --- .github/workflows/deploy-backend.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index b1ddda4..86320a4 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,5 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d - + docker compose up -d \ No newline at end of file From 96c3c41b159233739ee04a0f0f3c0c9bf84398be Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 16:38:46 +0900 Subject: [PATCH 072/132] =?UTF-8?q?redis=20=EC=A3=BD=EC=9D=B4=EA=B3=A0?= =?UTF-8?q?=EC=8B=B6=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 86320a4..2285dc5 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d \ No newline at end of file + docker compose up -d From 928dbca1b093fdcc1ad7dec66a41b3b914e5db2e Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 16:52:54 +0900 Subject: [PATCH 073/132] =?UTF-8?q?redis=20=EC=A3=BD=EC=9D=B4=EA=B3=A0?= =?UTF-8?q?=EC=8B=B6=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 2285dc5..86320a4 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d + docker compose up -d \ No newline at end of file From 6bb3b68a23334c3f9ac7634ad8704bee3952f649 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 17:12:06 +0900 Subject: [PATCH 074/132] redisplease --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 86320a4..2285dc5 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d \ No newline at end of file + docker compose up -d From 9017ee061de2043b0709245d9e8d88a7221f9d9d Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 17:29:38 +0900 Subject: [PATCH 075/132] redisplease --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 2285dc5..86320a4 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d + docker compose up -d \ No newline at end of file From 9e61ada501048145db01d16eeed6639d7fea1cc0 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 17:39:26 +0900 Subject: [PATCH 076/132] redisplease --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 86320a4..2285dc5 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -63,4 +63,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d \ No newline at end of file + docker compose up -d From 9d2d32c67dca5a9758d7f8a8782688a018e1f310 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 5 Aug 2025 18:01:56 +0900 Subject: [PATCH 077/132] plz --- .idea/dataSources.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 8afcafe..1996f2c 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -5,7 +5,7 @@ mysql.8 true com.mysql.cj.jdbc.Driver - jdbc:mysql://13.209.70.253:3306 + jdbc:mysql://172.31.7.161:3306 @@ -14,14 +14,15 @@ $ProjectFileDir$ - + mysql.8 true com.mysql.cj.jdbc.Driver - jdbc:mysql://localhost:3306 + jdbc:mysql://172.31.7.161:3306 + $ProjectFileDir$ From 86f817160f253a220e1817ea6bb3d92c25d35684 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 11:09:35 +0900 Subject: [PATCH 078/132] final --- docker-compose.yml | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 03da471..f222971 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,47 +1,3 @@ -#version: '3.8' -# -#services: -# mysql: -# image: mysql:latest -# container_name: mysql-container -# environment: -# MYSQL_ROOT_PASSWORD: qkrwlsdn -# MYSQL_DATABASE: ccmake -# ports: -# - "3306:3306" -# volumes: -# - mysql-data:/var/lib/mysql -# networks: -# - backend-network -# -# redis: -# image: redis:latest -# container_name: redis-container -# ports: -# - "6379:6379" -# networks: -# - backend-network -# -# spring-app: -# build: . -# container_name: spring-app-container -# depends_on: -# - mysql -# - redis -# env_file: -# - .env -# ports: -# - "8080:8080" -# networks: -# - backend-network -# -#volumes: -# mysql-data: -# -#networks: -# backend-network: - - version: '3.8' services: From 9c4c29749aca7ec7ea6db10ee9826648edd26afd Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 11:41:37 +0900 Subject: [PATCH 079/132] FinalFinal --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 2285dc5..c125f24 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -47,7 +47,7 @@ jobs: host: ${{ secrets.EC2_HOST }} username: ${{ secrets.EC2_USER }} key: ${{ secrets.PEM_KEY }} - source: "deploy/app.jar,deploy/.env" + source: "deploy/app.jar,deploy/.env,docker-compose.yml" target: "/home/ec2-user/ccgo" strip_components: 1 From f1d431d246f5760da7b76c568a44e55b84009948 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 12:07:45 +0900 Subject: [PATCH 080/132] finalinfalinfal --- .github/workflows/deploy-backend.yml | 5 ++-- .../ccapp/ccgo/auth/service/AuthService.java | 14 --------- .../auth/service/RefreshTokenService.java | 29 ++++++------------- .../com/ccapp/ccgo/common/RedisConfig.java | 17 ----------- 4 files changed, 11 insertions(+), 54 deletions(-) delete mode 100644 src/main/java/com/ccapp/ccgo/common/RedisConfig.java diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index c125f24..5eb0341 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -3,7 +3,7 @@ name: Deploy Spring Boot to EC2 on: push: branches: - - test-deploy + - DELredis jobs: deploy: @@ -38,8 +38,7 @@ jobs: echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env - echo "REDIS_HOST=redis" >> deploy/.env - echo "REDIS_PORT=6379" >> deploy/.env + # Redis 관련 환경변수 삭제됨 - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 diff --git a/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java b/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java index 17d4fdd..066fd02 100644 --- a/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java @@ -23,9 +23,6 @@ public class AuthService { private final TeamMemberRepository teamMemberRepository; private final LoginUserDetailsService loginUserDetailsService; - // 새로 추가: Redis 연동 서비스 - private final RefreshTokenService refreshTokenService; - public LoginResponseDto login(String email, String password) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(email, password) @@ -37,9 +34,6 @@ public LoginResponseDto login(String email, String password) { LoginUserDetails userDetails = (LoginUserDetails) authentication.getPrincipal(); var user = userDetails.getUser(); - // Redis에 Refresh Token 저장 (만료시간: jwt.refresh-token-expiration) - refreshTokenService.saveRefreshToken(user.getEmail(), refreshToken, jwtProvider.getRefreshTokenExpiration()); - List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); List teams = teamMembers.stream() @@ -68,11 +62,6 @@ public TokenResponseDto refreshToken(String refreshToken) { String email = jwtProvider.getEmailFromToken(refreshToken); - // Redis에서 저장된 토큰과 비교 - if (!refreshTokenService.validateRefreshToken(email, refreshToken)) { - throw new IllegalArgumentException("리프레시 토큰이 만료되었거나 로그아웃 처리된 토큰입니다."); - } - LoginUserDetails userDetails = (LoginUserDetails) loginUserDetailsService.loadUserByUsername(email); Authentication authentication = new UsernamePasswordAuthenticationToken( @@ -82,9 +71,6 @@ public TokenResponseDto refreshToken(String refreshToken) { String newAccessToken = jwtProvider.createAccessToken(authentication); String newRefreshToken = jwtProvider.createRefreshToken(authentication); - // Redis에 새 Refresh Token 저장 (기존 토큰 덮어쓰기) - refreshTokenService.saveRefreshToken(email, newRefreshToken, jwtProvider.getRefreshTokenExpiration()); - return new TokenResponseDto(newAccessToken, newRefreshToken); } } diff --git a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java index cd94c1e..8ca3795 100644 --- a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java @@ -1,47 +1,36 @@ package com.ccapp.ccgo.auth.service; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; -import java.util.concurrent.TimeUnit; - @Service -@RequiredArgsConstructor public class RefreshTokenService { - private final StringRedisTemplate redisTemplate; - - private static final String REFRESH_TOKEN_PREFIX = "refreshToken:"; - /** - * Redis에 Refresh Token 저장 (TTL 적용) + * Stateless 방식이므로 저장 기능은 필요하지 않습니다. */ public void saveRefreshToken(String email, String refreshToken, long expirationMillis) { - String key = REFRESH_TOKEN_PREFIX + email; - redisTemplate.opsForValue().set(key, refreshToken, expirationMillis, TimeUnit.MILLISECONDS); + // 저장 기능 제거 } /** - * Redis에서 Refresh Token 조회 + * Stateless 방식이므로 조회 기능은 필요하지 않습니다. */ public String getRefreshToken(String email) { - String key = REFRESH_TOKEN_PREFIX + email; - return redisTemplate.opsForValue().get(key); + return null; } /** - * Redis에 저장된 Refresh Token과 비교하여 유효성 확인 + * JWT 토큰 자체를 검증하므로 여기서는 항상 true 리턴하거나 별도 검증을 하지 않습니다. + * 실제 토큰 검증은 JwtProvider에서 수행됩니다. */ public boolean validateRefreshToken(String email, String refreshToken) { - String savedToken = getRefreshToken(email); - return savedToken != null && savedToken.equals(refreshToken); + return true; } /** - * 로그아웃 시 Refresh Token 삭제 + * 서버에 저장된 토큰이 없으므로 삭제할 것도 없습니다. */ public void deleteRefreshToken(String email) { - redisTemplate.delete(REFRESH_TOKEN_PREFIX + email); + // 삭제 기능 제거 } } diff --git a/src/main/java/com/ccapp/ccgo/common/RedisConfig.java b/src/main/java/com/ccapp/ccgo/common/RedisConfig.java deleted file mode 100644 index a515609..0000000 --- a/src/main/java/com/ccapp/ccgo/common/RedisConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ccapp.ccgo.common; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; - -@Configuration -public class RedisConfig { - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { - RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory); - return redisTemplate; - } -} From 9005773ad5920e2bfae0a10bccdbde7c575be539 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 14:27:22 +0900 Subject: [PATCH 081/132] updated --- .idea/dataSources.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 1996f2c..e3fecfc 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -27,5 +27,17 @@ $ProjectFileDir$ + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://3.39.54.128:3306 + + + + + + $ProjectFileDir$ + \ No newline at end of file From f4abdf0805e3566a40095b8a91108a48b927ed0c Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 15:17:46 +0900 Subject: [PATCH 082/132] updated --- .github/workflows/deploy-backend.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 5eb0341..c5889aa 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -38,7 +38,6 @@ jobs: echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env - # Redis 관련 환경변수 삭제됨 - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 From 842723cc0c996c18dc81662dc727b16df9ee4276 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 15:25:28 +0900 Subject: [PATCH 083/132] updated final --- .github/workflows/deploy-backend.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index c5889aa..0d4b673 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -30,14 +30,14 @@ jobs: mkdir -p deploy cp build/libs/*.jar deploy/app.jar - - name: Create .env file for prod - run: | - echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env - echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env - echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env - echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env - echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env - echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env +# - name: Create .env file for prod +# run: | +# echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env +# echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env +# echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env +# echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env +# echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env +# echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 From 9a4b1cd87e60340a77d1dfa8cec8bcf18eed151e Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 15:28:27 +0900 Subject: [PATCH 084/132] updated final --- .github/workflows/deploy-backend.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 0d4b673..c5889aa 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -30,14 +30,14 @@ jobs: mkdir -p deploy cp build/libs/*.jar deploy/app.jar -# - name: Create .env file for prod -# run: | -# echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env -# echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env -# echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env -# echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env -# echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env -# echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env + - name: Create .env file for prod + run: | + echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env + echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env + echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env + echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env + echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env + echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 From e8a066101774e3847087dffea7b1656c02cc5747 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 15:37:28 +0900 Subject: [PATCH 085/132] web --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index c5889aa..0a89c33 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -29,6 +29,7 @@ jobs: run: | mkdir -p deploy cp build/libs/*.jar deploy/app.jar + - name: Create .env file for prod run: | From c0883c4bc99f5f0e72caedfc2f7e9b38803ce6ee Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 16:01:07 +0900 Subject: [PATCH 086/132] aaaa --- docker-compose.yml | 61 +++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f222971..a3acf45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,49 +1,34 @@ version: '3.8' services: - mysql: - image: mysql:latest - container_name: mysql-container - environment: - MYSQL_ROOT_PASSWORD: qkrwlsdn - MYSQL_DATABASE: ccmake - MYSQL_USER: user - MYSQL_PASSWORD: qkrwlsdn - ports: - - "3306:3306" - volumes: - - mysql-data:/var/lib/mysql - networks: - - backend-network - - redis: - image: redis:latest - container_name: redis-container - ports: - - "6379:6379" - networks: - - backend-network - - spring-app: + app: build: . - container_name: spring-app-container + image: ccgo-app:latest + container_name: ccgo-app + ports: + - "8080:8080" + restart: always + env_file: + - .env depends_on: - mysql - - redis environment: - SPRING_DATASOURCE_URL: jdbc:mysql://mysql-container:3306/ccmake?useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true - SPRING_DATASOURCE_USERNAME: user - SPRING_DATASOURCE_PASSWORD: qkrwlsdn - SPRING_REDIS_HOST: redis - SPRING_REDIS_PORT: 6379 + TZ: Asia/Seoul + mysql: + image: mysql:8 + container_name: mysql + restart: always ports: - - "8080:8080" - networks: - - backend-network + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASS} + MYSQL_DATABASE: ccmake + MYSQL_USER: ${DB_USER} + MYSQL_PASSWORD: ${DB_PASS} -volumes: - mysql-data: + volumes: + - mysql_data:/var/lib/mysql -networks: - backend-network: \ No newline at end of file +volumes: + mysql_data: From 826c96ff0d10f8688aa7265945a7ec19b85cd82c Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 16:50:36 +0900 Subject: [PATCH 087/132] gradle sib --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5df42af..eae9faf 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,8 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'com.h2database:h2' // DB 정보 저장용 디펜던시 + + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.apache.commons:commons-pool2' From 9b099aa60be3fb8886943c996f059b7208c8f481 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 16:54:10 +0900 Subject: [PATCH 088/132] gradle sib --- .github/workflows/deploy-backend.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 0a89c33..1f1b885 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -29,8 +29,6 @@ jobs: run: | mkdir -p deploy cp build/libs/*.jar deploy/app.jar - - - name: Create .env file for prod run: | echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env From 426f220d75a053ca94bbffc68c2ccd2573e2fa78 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 17:04:44 +0900 Subject: [PATCH 089/132] v1 --- build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index eae9faf..dbbca3b 100644 --- a/build.gradle +++ b/build.gradle @@ -38,9 +38,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' implementation 'org.springframework.boot:spring-boot-starter-validation' - - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'org.apache.commons:commons-pool2' + implementation 'com.h2database:h2' // DB 정보 저장용 디펜던시 } From ff78822123454837a5be5c556ef2ccdbaf99e38a Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 17:09:41 +0900 Subject: [PATCH 090/132] v2 --- build.gradle | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index dbbca3b..1c4d137 100644 --- a/build.gradle +++ b/build.gradle @@ -27,21 +27,22 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'com.h2database:h2' // DB 정보 저장용 디펜던시 + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + runtimeOnly 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } + tasks.named('test') { useJUnitPlatform() } From 5fbb55e1f40148b7ca82eb44077739bba8cc9263 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 17:15:02 +0900 Subject: [PATCH 091/132] v3 --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1c4d137..37c0eb3 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,8 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' From 7c243335197dc83a647055efab4c8e889b5790dc Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 17:22:02 +0900 Subject: [PATCH 092/132] v4 --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 1f1b885..f955431 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -48,6 +48,7 @@ jobs: target: "/home/ec2-user/ccgo" strip_components: 1 + - name: SSH into EC2 and restart Docker container uses: appleboy/ssh-action@v0.1.7 with: From bd84cb45508ee7406a0547be5eb96cf7bcaa7e5e Mon Sep 17 00:00:00 2001 From: bendy41 Date: Wed, 6 Aug 2025 17:28:53 +0900 Subject: [PATCH 093/132] v5 --- .idea/modules/CC_Maker_BE.main.iml | 26 +++++--------------------- .idea/modules/CC_Maker_BE.test.iml | 22 ++-------------------- 2 files changed, 7 insertions(+), 41 deletions(-) diff --git a/.idea/modules/CC_Maker_BE.main.iml b/.idea/modules/CC_Maker_BE.main.iml index 73cf394..10d37db 100644 --- a/.idea/modules/CC_Maker_BE.main.iml +++ b/.idea/modules/CC_Maker_BE.main.iml @@ -14,7 +14,9 @@ - + + + @@ -35,11 +37,9 @@ - - - - + + @@ -54,8 +54,6 @@ - - @@ -86,14 +84,6 @@ - - - - - - - - @@ -102,16 +92,10 @@ - - - - - - diff --git a/.idea/modules/CC_Maker_BE.test.iml b/.idea/modules/CC_Maker_BE.test.iml index 5e26568..ae627e9 100644 --- a/.idea/modules/CC_Maker_BE.test.iml +++ b/.idea/modules/CC_Maker_BE.test.iml @@ -19,11 +19,9 @@ - - - - + + @@ -40,8 +38,6 @@ - - @@ -86,14 +82,6 @@ - - - - - - - - @@ -109,11 +97,6 @@ - - - - - @@ -122,7 +105,6 @@ - From 7a28bbcb405b8c02faa07356f563aea8fab05fcb Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 7 Aug 2025 11:27:05 +0900 Subject: [PATCH 094/132] v6 --- .github/workflows/deploy-backend.yml | 2 ++ Dockerfile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index f955431..6488d26 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -62,3 +62,5 @@ jobs: docker rmi ccgo-app || true docker compose build docker compose up -d + + diff --git a/Dockerfile b/Dockerfile index d7b8d9e..62416c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM eclipse-temurin:17-jdk-jammy COPY build/libs/*.jar app.jar EXPOSE 8080 -CMD ["java", "-jar", "app.jar", "--spring.profiles.active=application-deploy"] +CMD ["java", "-jar", "app.jar"] From e9094a3fb5993eb0d34df0d3be5109426e67305c Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 7 Aug 2025 11:37:15 +0900 Subject: [PATCH 095/132] v7 --- .github/workflows/deploy-backend.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 6488d26..7096509 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -47,8 +47,6 @@ jobs: source: "deploy/app.jar,deploy/.env,docker-compose.yml" target: "/home/ec2-user/ccgo" strip_components: 1 - - - name: SSH into EC2 and restart Docker container uses: appleboy/ssh-action@v0.1.7 with: From 521f9448bca4ee20e75a0cc7bfddfeb69d73e4ba Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 7 Aug 2025 11:46:32 +0900 Subject: [PATCH 096/132] v8 --- .github/workflows/deploy-backend.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 7096509..f891ce3 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -29,11 +29,13 @@ jobs: run: | mkdir -p deploy cp build/libs/*.jar deploy/app.jar + - name: Create .env file for prod run: | - echo "DB_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env - echo "DB_USER=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env - echo "DB_PASS=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env + echo "SPRING_DATASOURCE_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env + echo "SPRING_DATASOURCE_USERNAME=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env + echo "SPRING_DATASOURCE_PASSWORD=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env + echo "SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver" >> deploy/.env echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env From 16cb7a2b8724acbc28a013d50659d9140a0eb3e8 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 7 Aug 2025 11:54:33 +0900 Subject: [PATCH 097/132] v9 --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index f891ce3..e1e0a23 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -30,6 +30,7 @@ jobs: mkdir -p deploy cp build/libs/*.jar deploy/app.jar + - name: Create .env file for prod run: | echo "SPRING_DATASOURCE_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env From 92bc30b5a791a7a1fb26f63cb2c9db641f06b3a2 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Thu, 7 Aug 2025 12:06:27 +0900 Subject: [PATCH 098/132] v10 --- .github/workflows/deploy-backend.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index e1e0a23..f891ce3 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -30,7 +30,6 @@ jobs: mkdir -p deploy cp build/libs/*.jar deploy/app.jar - - name: Create .env file for prod run: | echo "SPRING_DATASOURCE_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env From 3c4b1ab5ab53dab55ebdb0959a17d3b1e0ff4186 Mon Sep 17 00:00:00 2001 From: bendy41 Date: Fri, 8 Aug 2025 15:24:45 +0900 Subject: [PATCH 099/132] FINAL V --- .github/workflows/deploy-backend.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index f891ce3..e1e0a23 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -30,6 +30,7 @@ jobs: mkdir -p deploy cp build/libs/*.jar deploy/app.jar + - name: Create .env file for prod run: | echo "SPRING_DATASOURCE_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env From 848640fdfd314013fdcf37a75ea984aef7220ce7 Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 8 Aug 2025 16:06:29 +0900 Subject: [PATCH 100/132] update --- .github/workflows/deploy-backend.yml | 2 +- .../ccgo/user/controller/UserController.java | 34 ++++++++ .../ccapp/ccgo/user/mapper/UserMapper.java | 19 +++++ .../ccapp/ccgo/user/service/UserService.java | 83 +++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index e1e0a23..fe9c5f7 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -3,7 +3,7 @@ name: Deploy Spring Boot to EC2 on: push: branches: - - DELredis + - test-mysql jobs: deploy: diff --git a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java index c319ef2..eb31803 100644 --- a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java +++ b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java @@ -3,6 +3,8 @@ import com.ccapp.ccgo.user.service.UserService; import com.ccapp.ccgo.user.dto.UserRequestDto; import com.ccapp.ccgo.user.dto.UserResponseDto; +import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; +import com.ccapp.ccgo.user.dto.PasswordChangeRequestDto; import org.springframework.http.HttpStatus; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; @@ -26,4 +28,36 @@ public ResponseEntity register(@Valid @RequestBody UserRequestD UserResponseDto saved = userService.register(userRequestDto); return ResponseEntity.status(HttpStatus.CREATED).body(saved); } + + // 현재 로그인한 사용자 정보 조회 + @GetMapping("/me") + public ResponseEntity getCurrentUser() { + log.info("✅ 현재 사용자 정보 조회 요청"); + UserResponseDto user = userService.getCurrentUser(); + return ResponseEntity.ok(user); + } + + // 사용자 정보 부분 업데이트 (PATCH) + @PatchMapping("/me") + public ResponseEntity updateCurrentUser(@Valid @RequestBody UserUpdateRequestDto userUpdateRequestDto) { + log.info("✅ 사용자 정보 부분 업데이트 요청: {}", userUpdateRequestDto); + UserResponseDto updated = userService.updateCurrentUser(userUpdateRequestDto); + return ResponseEntity.ok(updated); + } + + // 사용자 정보 전체 업데이트 (PUT) + @PutMapping("/me") + public ResponseEntity updateCurrentUserFull(@Valid @RequestBody UserUpdateRequestDto userUpdateRequestDto) { + log.info("✅ 사용자 정보 전체 업데이트 요청: {}", userUpdateRequestDto); + UserResponseDto updated = userService.updateCurrentUserFull(userUpdateRequestDto); + return ResponseEntity.ok(updated); + } + + // 비밀번호 변경 + @PostMapping("/change-password") + public ResponseEntity changePassword(@Valid @RequestBody PasswordChangeRequestDto passwordChangeRequestDto) { + log.info("✅ 비밀번호 변경 요청"); + userService.changePassword(passwordChangeRequestDto); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java b/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java index a63d749..8cd0f0a 100644 --- a/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java +++ b/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java @@ -2,6 +2,7 @@ import com.ccapp.ccgo.user.dto.UserRequestDto; import com.ccapp.ccgo.user.dto.UserResponseDto; +import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; import com.ccapp.ccgo.user.entity.User; public class UserMapper { @@ -32,4 +33,22 @@ public static UserResponseDto toDto(User user) { .createdAt(user.getCreatedAt()) .build(); } + + // UpdateRequestDto -> Entity (부분 업데이트용) + public static void updateEntityFromDto(User user, UserUpdateRequestDto dto) { + if (user == null || dto == null) return; + + if (dto.getName() != null) { + user.setName(dto.getName()); + } + if (dto.getEmail() != null) { + user.setEmail(dto.getEmail()); + } + if (dto.getBirthdate() != null && !dto.getBirthdate().trim().isEmpty()) { + user.setBirthdate(dto.getBirthdateAsLocalDate()); + } + if (dto.getGender() != null) { + user.setGender(dto.getGender()); + } + } } diff --git a/src/main/java/com/ccapp/ccgo/user/service/UserService.java b/src/main/java/com/ccapp/ccgo/user/service/UserService.java index fa54603..d72a2de 100644 --- a/src/main/java/com/ccapp/ccgo/user/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/user/service/UserService.java @@ -2,6 +2,8 @@ import com.ccapp.ccgo.user.dto.UserRequestDto; import com.ccapp.ccgo.user.dto.UserResponseDto; +import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; +import com.ccapp.ccgo.user.dto.PasswordChangeRequestDto; import com.ccapp.ccgo.user.mapper.UserMapper; import com.ccapp.ccgo.common.exception.CustomException; import com.ccapp.ccgo.auth.jwt.JwtProvider; @@ -12,6 +14,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -87,4 +90,84 @@ public void deleteUser(Long id) { } userRepository.deleteById(id); } + + // 7. 현재 로그인한 사용자 정보 조회 + public UserResponseDto getCurrentUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + + return UserMapper.toDto(user); + } + + // 8. 현재 사용자 정보 부분 업데이트 (PATCH) + public UserResponseDto updateCurrentUser(UserUpdateRequestDto dto) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + + // 이메일 중복 체크 (다른 사용자가 같은 이메일을 사용하고 있는지) + if (!email.equals(dto.getEmail())) { + userRepository.findByEmail(dto.getEmail()) + .ifPresent(existingUser -> { + throw new CustomException("이미 사용 중인 이메일입니다.", HttpStatus.CONFLICT); + }); + } + + // 변경된 필드만 업데이트 + UserMapper.updateEntityFromDto(user, dto); + + userRepository.save(user); + return UserMapper.toDto(user); + } + + // 9. 현재 사용자 정보 전체 업데이트 (PUT) + public UserResponseDto updateCurrentUserFull(UserUpdateRequestDto dto) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + + // 이메일 중복 체크 + if (!email.equals(dto.getEmail())) { + userRepository.findByEmail(dto.getEmail()) + .ifPresent(existingUser -> { + throw new CustomException("이미 사용 중인 이메일입니다.", HttpStatus.CONFLICT); + }); + } + + // 모든 필드 업데이트 + user.setName(dto.getName()); + user.setEmail(dto.getEmail()); + user.setBirthdate(dto.getBirthdateAsLocalDate()); + user.setGender(dto.getGender()); + + userRepository.save(user); + return UserMapper.toDto(user); + } + + // 10. 비밀번호 변경 + public void changePassword(PasswordChangeRequestDto dto) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + + // 현재 비밀번호 확인 + if (!passwordEncoder.matches(dto.getCurrentPassword(), user.getPassword())) { + throw new CustomException("현재 비밀번호가 일치하지 않습니다.", HttpStatus.BAD_REQUEST); + } + + // 새 비밀번호로 변경 + String encodedNewPassword = passwordEncoder.encode(dto.getNewPassword()); + user.setPassword(encodedNewPassword); + + userRepository.save(user); + } } From b24192c05c10f31396074f932db2fb7d58173527 Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 8 Aug 2025 16:11:30 +0900 Subject: [PATCH 101/132] update --- .../common/test/UserApiTestController.java | 66 +++++++++++++++++++ .../user/dto/PasswordChangeRequestDto.java | 16 +++++ .../ccgo/user/dto/UserUpdateRequestDto.java | 34 ++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java create mode 100644 src/main/java/com/ccapp/ccgo/user/dto/PasswordChangeRequestDto.java create mode 100644 src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java diff --git a/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java b/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java new file mode 100644 index 0000000..774eb78 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java @@ -0,0 +1,66 @@ +package com.ccapp.ccgo.common.test; + +import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; +import com.ccapp.ccgo.user.dto.PasswordChangeRequestDto; +import com.ccapp.ccgo.user.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/api/test/user") +@RequiredArgsConstructor +public class UserApiTestController { + + private final UserService userService; + + @GetMapping("/me") + public ResponseEntity testGetCurrentUser() { + log.info("🧪 테스트: 현재 사용자 정보 조회"); + try { + var result = userService.getCurrentUser(); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("❌ 테스트 실패: {}", e.getMessage()); + return ResponseEntity.badRequest().body("테스트 실패: " + e.getMessage()); + } + } + + @PatchMapping("/me") + public ResponseEntity testUpdateCurrentUser(@RequestBody UserUpdateRequestDto dto) { + log.info("🧪 테스트: 사용자 정보 부분 업데이트 - {}", dto); + try { + var result = userService.updateCurrentUser(dto); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("❌ 테스트 실패: {}", e.getMessage()); + return ResponseEntity.badRequest().body("테스트 실패: " + e.getMessage()); + } + } + + @PutMapping("/me") + public ResponseEntity testUpdateCurrentUserFull(@RequestBody UserUpdateRequestDto dto) { + log.info("🧪 테스트: 사용자 정보 전체 업데이트 - {}", dto); + try { + var result = userService.updateCurrentUserFull(dto); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("❌ 테스트 실패: {}", e.getMessage()); + return ResponseEntity.badRequest().body("테스트 실패: " + e.getMessage()); + } + } + + @PostMapping("/change-password") + public ResponseEntity testChangePassword(@RequestBody PasswordChangeRequestDto dto) { + log.info("🧪 테스트: 비밀번호 변경"); + try { + userService.changePassword(dto); + return ResponseEntity.ok("비밀번호 변경 성공"); + } catch (Exception e) { + log.error("❌ 테스트 실패: {}", e.getMessage()); + return ResponseEntity.badRequest().body("테스트 실패: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/ccapp/ccgo/user/dto/PasswordChangeRequestDto.java b/src/main/java/com/ccapp/ccgo/user/dto/PasswordChangeRequestDto.java new file mode 100644 index 0000000..7738b62 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/user/dto/PasswordChangeRequestDto.java @@ -0,0 +1,16 @@ +package com.ccapp.ccgo.user.dto; + +import lombok.Getter; +import lombok.Setter; +import jakarta.validation.constraints.*; + +@Getter +@Setter +public class PasswordChangeRequestDto { + @NotBlank(message = "현재 비밀번호는 필수입니다.") + private String currentPassword; + + @NotBlank(message = "새 비밀번호는 필수입니다.") + @Size(min = 8, message = "새 비밀번호는 최소 8자 이상이어야 합니다.") + private String newPassword; +} diff --git a/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java new file mode 100644 index 0000000..753e9cd --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java @@ -0,0 +1,34 @@ +package com.ccapp.ccgo.user.dto; + +import lombok.Getter; +import lombok.Setter; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import jakarta.validation.constraints.*; + +@Getter +@Setter +public class UserUpdateRequestDto { + @NotBlank(message = "이름은 필수입니다.") + private String name; + + @NotBlank(message = "이메일은 필수입니다.") + @Email(message = "올바른 이메일 형식이 아닙니다.") + private String email; + + private String birthdate; // YYYY-MM-DD 형식의 문자열 + private String gender; + + // birthdate를 LocalDate로 변환하는 메서드 + public LocalDate getBirthdateAsLocalDate() { + if (birthdate == null || birthdate.trim().isEmpty()) { + return null; + } + try { + return LocalDate.parse(birthdate, DateTimeFormatter.ISO_LOCAL_DATE); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("생년월일 형식이 올바르지 않습니다. YYYY-MM-DD 형식으로 입력해주세요."); + } + } +} From 87b1ffa343593d400d7212213d4ce721774c932a Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 8 Aug 2025 16:28:19 +0900 Subject: [PATCH 102/132] update --- .../ccapp/ccgo/user/controller/UserController.java | 8 ++++++++ .../com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java | 1 + .../java/com/ccapp/ccgo/user/service/UserService.java | 11 +++++++++++ 3 files changed, 20 insertions(+) diff --git a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java index eb31803..c332206 100644 --- a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java +++ b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java @@ -60,4 +60,12 @@ public ResponseEntity changePassword(@Valid @RequestBody PasswordChangeReq userService.changePassword(passwordChangeRequestDto); return ResponseEntity.ok().build(); } + + // 계정 탈퇴 + @DeleteMapping("/me") + public ResponseEntity deleteCurrentUser() { + log.info("✅ 계정 탈퇴 요청"); + userService.deleteCurrentUser(); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java index 753e9cd..57ea0e1 100644 --- a/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java @@ -17,6 +17,7 @@ public class UserUpdateRequestDto { @Email(message = "올바른 이메일 형식이 아닙니다.") private String email; + // 선택적 필드들 (프론트엔드에서 전송하지 않을 수 있음) private String birthdate; // YYYY-MM-DD 형식의 문자열 private String gender; diff --git a/src/main/java/com/ccapp/ccgo/user/service/UserService.java b/src/main/java/com/ccapp/ccgo/user/service/UserService.java index d72a2de..80002c5 100644 --- a/src/main/java/com/ccapp/ccgo/user/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/user/service/UserService.java @@ -170,4 +170,15 @@ public void changePassword(PasswordChangeRequestDto dto) { userRepository.save(user); } + + // 11. 현재 사용자 계정 삭제 (탈퇴) + public void deleteCurrentUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String email = authentication.getName(); + + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); + + userRepository.delete(user); + } } From ec53d88a5a82427a823af17ebe7a41f82577c995 Mon Sep 17 00:00:00 2001 From: losecow Date: Fri, 8 Aug 2025 16:35:57 +0900 Subject: [PATCH 103/132] update --- .../com/ccapp/ccgo/user/controller/UserController.java | 1 + .../com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java | 4 ++-- .../java/com/ccapp/ccgo/user/service/UserService.java | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java index c332206..a7b717e 100644 --- a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java +++ b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java @@ -13,6 +13,7 @@ @Slf4j @RestController +@RequestMapping("/api/user") // @CrossOrigin 제거, SecurityConfig에서 CORS 관리 권장 public class UserController { diff --git a/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java index 57ea0e1..ed58990 100644 --- a/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserUpdateRequestDto.java @@ -18,8 +18,8 @@ public class UserUpdateRequestDto { private String email; // 선택적 필드들 (프론트엔드에서 전송하지 않을 수 있음) - private String birthdate; // YYYY-MM-DD 형식의 문자열 - private String gender; + private String birthdate; // YYYY-MM-DD 형식의 문자열 (선택사항) + private String gender; // 선택사항 // birthdate를 LocalDate로 변환하는 메서드 public LocalDate getBirthdateAsLocalDate() { diff --git a/src/main/java/com/ccapp/ccgo/user/service/UserService.java b/src/main/java/com/ccapp/ccgo/user/service/UserService.java index 80002c5..5806963 100644 --- a/src/main/java/com/ccapp/ccgo/user/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/user/service/UserService.java @@ -144,8 +144,13 @@ public UserResponseDto updateCurrentUserFull(UserUpdateRequestDto dto) { // 모든 필드 업데이트 user.setName(dto.getName()); user.setEmail(dto.getEmail()); - user.setBirthdate(dto.getBirthdateAsLocalDate()); - user.setGender(dto.getGender()); + // birthdate와 gender는 선택적 필드이므로 null 체크 + if (dto.getBirthdate() != null && !dto.getBirthdate().trim().isEmpty()) { + user.setBirthdate(dto.getBirthdateAsLocalDate()); + } + if (dto.getGender() != null && !dto.getGender().trim().isEmpty()) { + user.setGender(dto.getGender()); + } userRepository.save(user); return UserMapper.toDto(user); From 8ca5177206bc9facda1ef824134128d4d5fb2ad8 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 11 Aug 2025 15:09:24 +0900 Subject: [PATCH 104/132] update --- src/main/java/com/ccapp/ccgo/common/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index 1e850e0..7b355ee 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -56,7 +56,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth // 인증 필요 없는 엔드포인트 - .requestMatchers("/api/auth/login", "/api/auth/refresh", "/register").permitAll() + .requestMatchers("/api/auth/login", "/api/auth/refresh", "/api/user/register").permitAll() // LEADER 전용 API (팀 관리) .requestMatchers("/api/team/**").hasAnyRole("LEADER", "MEMBER") From a8c01f5bf2b8d9ee5d7e25acf6e8233ababc03f7 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 11 Aug 2025 15:39:19 +0900 Subject: [PATCH 105/132] update --- .github/workflows/deploy-backend.yml | 1 - src/main/java/com/ccapp/ccgo/common/SecurityConfig.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index fe9c5f7..036434c 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -64,4 +64,3 @@ jobs: docker compose build docker compose up -d - diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index 7b355ee..ddd268c 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -77,10 +77,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(List.of("http://192.168.29.245:8080")); // 서비스 IP주소 + config.setAllowedOrigins(List.of("*")); // 모든 origin 허용 (개발용) config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("*")); - config.setAllowCredentials(true); + config.setAllowCredentials(false); // allowCredentials가 true면 allowedOrigins에 "*" 사용 불가 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; From e2d7b16ca9f87291ae994eb182bca832ca511874 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 11 Aug 2025 15:43:11 +0900 Subject: [PATCH 106/132] update --- .../com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java | 7 +++++++ src/main/java/com/ccapp/ccgo/common/SecurityConfig.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java index 8348c02..becb6a2 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java @@ -30,6 +30,13 @@ protected void doFilterInternal(HttpServletRequest request, FilterChain filterChain) throws ServletException, IOException { + // permitAll 경로는 JWT 필터를 건너뛰기 + String requestURI = request.getRequestURI(); + if (requestURI.equals("/api/user/register") || requestURI.startsWith("/api/auth/")) { + filterChain.doFilter(request, response); + return; + } + String token = extractToken(request); try { diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index ddd268c..9f3f307 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -56,7 +56,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth // 인증 필요 없는 엔드포인트 - .requestMatchers("/api/auth/login", "/api/auth/refresh", "/api/user/register").permitAll() + .requestMatchers("/api/auth/**", "/api/user/register").permitAll() // LEADER 전용 API (팀 관리) .requestMatchers("/api/team/**").hasAnyRole("LEADER", "MEMBER") From b419e58668c349662fa41c30ca389faf61800f94 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 11 Aug 2025 16:05:46 +0900 Subject: [PATCH 107/132] update --- .gitignore | 3 +++ .idea/vcs.xml | 1 + frontend | 1 + src/main/java/com/ccapp/ccgo/common/SecurityConfig.java | 4 ++-- 4 files changed, 7 insertions(+), 2 deletions(-) create mode 160000 frontend diff --git a/.gitignore b/.gitignore index d9f5c60..97cc508 100644 --- a/.gitignore +++ b/.gitignore @@ -213,3 +213,6 @@ src/main/resources # .gitignore .env + +# +.frontend \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..9d4216c 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/frontend b/frontend new file mode 160000 index 0000000..f93a99d --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit f93a99de01007ee562a662654c192aa1dcb78237 diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index 9f3f307..e4d018e 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -77,10 +77,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(List.of("*")); // 모든 origin 허용 (개발용) + config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:3000", "http://127.0.0.1:5173")); // 개발용 origin들 config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("*")); - config.setAllowCredentials(false); // allowCredentials가 true면 allowedOrigins에 "*" 사용 불가 + config.setAllowCredentials(true); // 쿠키 허용 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; From 615a1e9150229a4bb84fe6b19045b0f5be542f13 Mon Sep 17 00:00:00 2001 From: losecow Date: Mon, 11 Aug 2025 17:01:31 +0900 Subject: [PATCH 108/132] update --- .../mission/service/SubGroupMissionService.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 8ecd5bf..01afde2 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -43,14 +43,26 @@ public void assignMissionsToSubGroup(Long subGroupId) { } private void assignMissionsByScore(SubGroup subGroup, Integer score, int count) { - List missions = missionTemplateRepository.findByScore(score); + // 1. 이미 할당된 미션 Template ID 목록 조회 + List existingMissionTemplateIds = subGroupMissionRepository.findBySubGroup(subGroup).stream() + .map(m -> m.getMissionTemplate().getId()) + .toList(); + + // 2. score 조건에 맞는 미션 템플릿 중 기존 할당 미션 제외 + List missions = missionTemplateRepository.findByScore(score).stream() + .filter(m -> !existingMissionTemplateIds.contains(m.getId())) + .toList(); + + // 3. 미션 개수 체크 if (missions.size() < count) { throw new IllegalStateException(score + "점 미션이 최소 " + count + "개 이상 필요합니다."); } + // 4. 랜덤 섞고 필요한 개수만큼 선택 Collections.shuffle(missions); List selected = missions.subList(0, count); + // 5. 새 미션 할당 for (MissionTemplate missionTemplate : selected) { SubGroupMission mission = SubGroupMission.builder() .subGroup(subGroup) @@ -61,6 +73,7 @@ private void assignMissionsByScore(SubGroup subGroup, Integer score, int count) } } + // 미션 완료 처리 @Transactional public void completeMission(Long subGroupMissionId) { From 2cb58db5eccae39024c641a3d3f124d42ea22658 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 13:34:51 +0900 Subject: [PATCH 109/132] update mission history --- .gitignore | 5 +- .idea/vcs.xml | 2 +- CC_Maker_FE | 1 + frontend | 1 - .../controller/MissionHistoryController.java | 43 +++++++ .../ccgo/mission/dto/MissionHistoryDto.java | 27 +++++ .../ccgo/mission/entity/MissionHistory.java | 50 ++++++++ .../repository/MissionHistoryRepository.java | 27 +++++ .../repository/SubGroupMissionRepository.java | 2 + .../service/SubGroupMissionService.java | 107 +++++++++++++++--- 10 files changed, 244 insertions(+), 21 deletions(-) create mode 160000 CC_Maker_FE delete mode 160000 frontend create mode 100644 src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java create mode 100644 src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java diff --git a/.gitignore b/.gitignore index 97cc508..4c06862 100644 --- a/.gitignore +++ b/.gitignore @@ -215,4 +215,7 @@ src/main/resources .env # -.frontend \ No newline at end of file +.frontend + +# 프론트엔드 파일 제외 +CC_Maker_FE/ \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 9d4216c..8cbb340 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,6 @@ - + \ No newline at end of file diff --git a/CC_Maker_FE b/CC_Maker_FE new file mode 160000 index 0000000..21ef657 --- /dev/null +++ b/CC_Maker_FE @@ -0,0 +1 @@ +Subproject commit 21ef65725bd6888c40e92e48ed1ba6f6a39329fc diff --git a/frontend b/frontend deleted file mode 160000 index f93a99d..0000000 --- a/frontend +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f93a99de01007ee562a662654c192aa1dcb78237 diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java new file mode 100644 index 0000000..1c0de31 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java @@ -0,0 +1,43 @@ +package com.ccapp.ccgo.mission.controller; + +import com.ccapp.ccgo.mission.dto.MissionHistoryDto; +import com.ccapp.ccgo.mission.service.SubGroupMissionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/mission/history") +@RequiredArgsConstructor +@Slf4j +public class MissionHistoryController { + + private final SubGroupMissionService subGroupMissionService; + + // 서브그룹의 미션 히스토리 조회 + @GetMapping("/subgroup/{subGroupId}") + public ResponseEntity> getMissionHistoryBySubGroup(@PathVariable Long subGroupId) { + log.info("서브그룹 미션 히스토리 조회 요청: subGroupId = {}", subGroupId); + List histories = subGroupMissionService.getMissionHistoryBySubGroup(subGroupId); + return ResponseEntity.ok(histories); + } + + // 사용자의 미션 히스토리 조회 + @GetMapping("/user/{userId}") + public ResponseEntity> getMissionHistoryByUser(@PathVariable Long userId) { + log.info("사용자 미션 히스토리 조회 요청: userId = {}", userId); + List histories = subGroupMissionService.getMissionHistoryByUser(userId); + return ResponseEntity.ok(histories); + } + + // 팀의 미션 히스토리 조회 + @GetMapping("/team/{teamId}") + public ResponseEntity> getMissionHistoryByTeam(@PathVariable Long teamId) { + log.info("팀 미션 히스토리 조회 요청: teamId = {}", teamId); + List histories = subGroupMissionService.getMissionHistoryByTeam(teamId); + return ResponseEntity.ok(histories); + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java new file mode 100644 index 0000000..07eb261 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java @@ -0,0 +1,27 @@ +package com.ccapp.ccgo.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MissionHistoryDto { + + private Long id; + private Long subGroupId; + private String subGroupName; // 서브그룹 이름 (필요시) + private Long userId; + private String userName; // 사용자 이름 + private Long missionTemplateId; + private String missionTitle; // 미션 제목 + private String missionDescription; // 미션 설명 + private Integer missionScore; // 미션 점수 + private LocalDateTime completedAt; + private LocalDateTime createdAt; +} diff --git a/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java b/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java new file mode 100644 index 0000000..ea09b02 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java @@ -0,0 +1,50 @@ +package com.ccapp.ccgo.mission.entity; + +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.user.entity.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "mission_history") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MissionHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sub_group_id") + private SubGroup subGroup; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_template_id") + private MissionTemplate missionTemplate; + + @Column(name = "completed_at", nullable = false) + private LocalDateTime completedAt; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + if (completedAt == null) { + completedAt = LocalDateTime.now(); + } + } +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java new file mode 100644 index 0000000..122568d --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java @@ -0,0 +1,27 @@ +package com.ccapp.ccgo.mission.repository; + +import com.ccapp.ccgo.mission.entity.MissionHistory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MissionHistoryRepository extends JpaRepository { + + // 특정 서브그룹의 미션 히스토리 조회 + List findBySubGroup_IdOrderByCompletedAtDesc(Long subGroupId); + + // 특정 사용자의 미션 히스토리 조회 + List findByUser_IdOrderByCompletedAtDesc(Long userId); + + // 특정 팀의 미션 히스토리 조회 + @Query("SELECT mh FROM MissionHistory mh WHERE mh.subGroup.team.teamId = :teamId ORDER BY mh.completedAt DESC") + List findByTeamIdOrderByCompletedAtDesc(@Param("teamId") Long teamId); + + // 특정 미션 템플릿의 완료 히스토리 조회 + List findByMissionTemplate_IdOrderByCompletedAtDesc(Long missionTemplateId); + + // 특정 서브그룹에서 특정 미션 템플릿이 완료되었는지 확인 + boolean existsBySubGroup_IdAndMissionTemplate_Id(Long subGroupId, Long missionTemplateId); +} diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java index 9e6097b..57073f7 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java @@ -13,5 +13,7 @@ public interface SubGroupMissionRepository extends JpaRepository findBySubGroupAndMissionTemplateId(SubGroup subGroup, Long missionTemplateId); + // 사용자가 이미 미션을 받았는지 체크하는 메서드 + boolean existsBySubGroup_Team_TeamIdAndSubGroup_SubGroupMembers_User_Id(Long teamId, Long userId); } diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 01afde2..921b7a4 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -1,14 +1,19 @@ package com.ccapp.ccgo.mission.service; import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; import com.ccapp.ccgo.mission.dto.ScoreboardResponseDto; import com.ccapp.ccgo.mission.dto.SubGroupMissionDto; import com.ccapp.ccgo.mission.dto.SubGroupScoreDto; +import com.ccapp.ccgo.mission.dto.MissionHistoryDto; import com.ccapp.ccgo.mission.entity.MissionTemplate; import com.ccapp.ccgo.mission.entity.SubGroupMission; +import com.ccapp.ccgo.mission.entity.MissionHistory; import com.ccapp.ccgo.mission.repository.MissionTemplateRepository; import com.ccapp.ccgo.mission.repository.SubGroupMissionRepository; +import com.ccapp.ccgo.mission.repository.MissionHistoryRepository; import lombok.RequiredArgsConstructor; import java.util.concurrent.ThreadLocalRandom; import org.springframework.stereotype.Service; @@ -17,14 +22,17 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.time.LocalDateTime; @Service @RequiredArgsConstructor public class SubGroupMissionService { private final SubGroupRepository subGroupRepository; + private final SubGroupMemberRepository subGroupMemberRepository; private final MissionTemplateRepository missionTemplateRepository; private final SubGroupMissionRepository subGroupMissionRepository; + private final MissionHistoryRepository missionHistoryRepository; // 서브그룹에 미션 부여 @Transactional @@ -36,33 +44,34 @@ public void assignMissionsToSubGroup(Long subGroupId) { throw new IllegalStateException("이미 이 서브그룹에 미션이 부여되어 있습니다."); } - assignMissionsByScore(subGroup, 1, 6); + // 그룹장 중복 미션 방지: 이미 미션을 받은 사용자가 있는지 체크 + List groupMembers = subGroupMemberRepository.findBySubGroup_Id(subGroupId); + List groupMemberIds = groupMembers.stream() + .map(member -> member.getUser().getId()) + .collect(Collectors.toList()); + + for (Long memberId : groupMemberIds) { + boolean hasExistingMission = subGroupMissionRepository.existsBySubGroup_Team_TeamIdAndSubGroup_SubGroupMembers_User_Id( + subGroup.getTeam().getTeamId(), memberId); + if (hasExistingMission) { + throw new IllegalStateException("사용자 ID " + memberId + "가 이미 다른 서브그룹에서 미션을 받았습니다."); + } + } + assignMissionsByScore(subGroup, 3, 6); assignMissionsByScore(subGroup, 5, 6); assignMissionsByScore(subGroup, 10, 6); } private void assignMissionsByScore(SubGroup subGroup, Integer score, int count) { - // 1. 이미 할당된 미션 Template ID 목록 조회 - List existingMissionTemplateIds = subGroupMissionRepository.findBySubGroup(subGroup).stream() - .map(m -> m.getMissionTemplate().getId()) - .toList(); - - // 2. score 조건에 맞는 미션 템플릿 중 기존 할당 미션 제외 - List missions = missionTemplateRepository.findByScore(score).stream() - .filter(m -> !existingMissionTemplateIds.contains(m.getId())) - .toList(); - - // 3. 미션 개수 체크 + List missions = missionTemplateRepository.findByScore(score); if (missions.size() < count) { throw new IllegalStateException(score + "점 미션이 최소 " + count + "개 이상 필요합니다."); } - // 4. 랜덤 섞고 필요한 개수만큼 선택 Collections.shuffle(missions); List selected = missions.subList(0, count); - // 5. 새 미션 할당 for (MissionTemplate missionTemplate : selected) { SubGroupMission mission = SubGroupMission.builder() .subGroup(subGroup) @@ -73,7 +82,6 @@ private void assignMissionsByScore(SubGroup subGroup, Integer score, int count) } } - // 미션 완료 처리 @Transactional public void completeMission(Long subGroupMissionId) { @@ -181,8 +189,71 @@ public void completeMission(Long teamId, Long subGroupId, Long missionTemplateId // 완료 처리 mission.setCompleted(true); + + // 미션 히스토리에 저장 + saveMissionHistory(subGroup, mission.getMissionTemplate()); + } + + // 미션 히스토리 저장 + private void saveMissionHistory(SubGroup subGroup, MissionTemplate missionTemplate) { + // 서브그룹의 모든 멤버에 대해 히스토리 저장 + List members = subGroupMemberRepository.findBySubGroup_Id(subGroup.getId()); + + for (SubGroupMember member : members) { + MissionHistory history = MissionHistory.builder() + .subGroup(subGroup) + .user(member.getUser()) + .missionTemplate(missionTemplate) + .completedAt(LocalDateTime.now()) + .build(); + + missionHistoryRepository.save(history); + } + } + + // 서브그룹의 미션 히스토리 조회 + @Transactional(readOnly = true) + public List getMissionHistoryBySubGroup(Long subGroupId) { + List histories = missionHistoryRepository.findBySubGroup_IdOrderByCompletedAtDesc(subGroupId); + + return histories.stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } + + // 사용자의 미션 히스토리 조회 + @Transactional(readOnly = true) + public List getMissionHistoryByUser(Long userId) { + List histories = missionHistoryRepository.findByUser_IdOrderByCompletedAtDesc(userId); + + return histories.stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } + + // 팀의 미션 히스토리 조회 + @Transactional(readOnly = true) + public List getMissionHistoryByTeam(Long teamId) { + List histories = missionHistoryRepository.findByTeamIdOrderByCompletedAtDesc(teamId); + + return histories.stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } + + // MissionHistory를 DTO로 변환 + private MissionHistoryDto convertToDto(MissionHistory history) { + return MissionHistoryDto.builder() + .id(history.getId()) + .subGroupId(history.getSubGroup().getId()) + .userId(history.getUser().getId()) + .userName(history.getUser().getName()) + .missionTemplateId(history.getMissionTemplate().getId()) + .missionTitle(history.getMissionTemplate().getTitle()) + .missionDescription(history.getMissionTemplate().getDescription()) + .missionScore(history.getMissionTemplate().getScore()) + .completedAt(history.getCompletedAt()) + .createdAt(history.getCreatedAt()) + .build(); } - - - } From 83e7962857aab7ace02f35d4e02ea5f30e2d65b6 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 13:40:03 +0900 Subject: [PATCH 110/132] update mission duplicate --- .../mission/repository/SubGroupMissionRepository.java | 11 +++++++++-- .../ccgo/mission/service/SubGroupMissionService.java | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java index 57073f7..4477036 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/SubGroupMissionRepository.java @@ -3,6 +3,8 @@ import com.ccapp.ccgo.matching.domain.entity.SubGroup; import com.ccapp.ccgo.mission.entity.SubGroupMission; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; @@ -13,7 +15,12 @@ public interface SubGroupMissionRepository extends JpaRepository findBySubGroupAndMissionTemplateId(SubGroup subGroup, Long missionTemplateId); - // 사용자가 이미 미션을 받았는지 체크하는 메서드 - boolean existsBySubGroup_Team_TeamIdAndSubGroup_SubGroupMembers_User_Id(Long teamId, Long userId); + // 특정 팀에서 특정 사용자가 이미 미션을 받았는지 확인 + @Query("SELECT COUNT(sgm) > 0 FROM SubGroupMission sgm " + + "JOIN sgm.subGroup sg " + + "JOIN sg.team t " + + "JOIN SubGroupMember sgm2 ON sgm2.subGroup = sg " + + "WHERE t.teamId = :teamId AND sgm2.user.id = :userId") + boolean existsByTeamIdAndUserId(@Param("teamId") Long teamId, @Param("userId") Long userId); } diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 921b7a4..82b90a6 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -51,7 +51,7 @@ public void assignMissionsToSubGroup(Long subGroupId) { .collect(Collectors.toList()); for (Long memberId : groupMemberIds) { - boolean hasExistingMission = subGroupMissionRepository.existsBySubGroup_Team_TeamIdAndSubGroup_SubGroupMembers_User_Id( + boolean hasExistingMission = subGroupMissionRepository.existsByTeamIdAndUserId( subGroup.getTeam().getTeamId(), memberId); if (hasExistingMission) { throw new IllegalStateException("사용자 ID " + memberId + "가 이미 다른 서브그룹에서 미션을 받았습니다."); From 4908b5684a237b896ac729704b3ca7ff215e1dc1 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 13:46:11 +0900 Subject: [PATCH 111/132] update mission duplicate --- create_mission_history_table.sql | 17 +++++++++++++++++ .../mission/service/SubGroupMissionService.java | 17 ++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 create_mission_history_table.sql diff --git a/create_mission_history_table.sql b/create_mission_history_table.sql new file mode 100644 index 0000000..588dd60 --- /dev/null +++ b/create_mission_history_table.sql @@ -0,0 +1,17 @@ +-- mission_history 테이블 생성 +CREATE TABLE mission_history ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + sub_group_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + mission_template_id BIGINT NOT NULL, + completed_at DATETIME NOT NULL, + created_at DATETIME NOT NULL, + FOREIGN KEY (sub_group_id) REFERENCES sub_group(id), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (mission_template_id) REFERENCES mission_template(id) +); + +-- 인덱스 추가 (성능 향상을 위해) +CREATE INDEX idx_mission_history_user_id ON mission_history(user_id); +CREATE INDEX idx_mission_history_sub_group_id ON mission_history(sub_group_id); +CREATE INDEX idx_mission_history_completed_at ON mission_history(completed_at); diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 82b90a6..feb3e60 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; +import java.util.ArrayList; import java.util.stream.Collectors; import java.time.LocalDateTime; @@ -224,11 +225,17 @@ public List getMissionHistoryBySubGroup(Long subGroupId) { // 사용자의 미션 히스토리 조회 @Transactional(readOnly = true) public List getMissionHistoryByUser(Long userId) { - List histories = missionHistoryRepository.findByUser_IdOrderByCompletedAtDesc(userId); - - return histories.stream() - .map(this::convertToDto) - .collect(Collectors.toList()); + try { + List histories = missionHistoryRepository.findByUser_IdOrderByCompletedAtDesc(userId); + + return histories.stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } catch (Exception e) { + // 테이블이 존재하지 않는 경우 등 오류 발생 시 빈 리스트 반환 + System.err.println("미션 히스토리 조회 중 오류 발생: " + e.getMessage()); + return new ArrayList<>(); + } } // 팀의 미션 히스토리 조회 From d3749df5671d8ad0cbfcb34ca86a98df3f4cc591 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 14:09:04 +0900 Subject: [PATCH 112/132] update mission duplicate --- .../ccgo/common/exception/GlobalExceptionHandler.java | 8 ++++++++ .../mission/controller/MissionHistoryController.java | 11 +++++++++-- .../ccgo/mission/service/SubGroupMissionService.java | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java b/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java index 281564b..fefffcf 100644 --- a/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ccapp/ccgo/common/exception/GlobalExceptionHandler.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.stream.Collectors; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; import java.util.Map; @Slf4j @@ -57,4 +58,11 @@ public ResponseEntity> handleMatchingAlreadyStarted(Matching errorResponse.put("error", ex.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); } + + @ExceptionHandler(InvalidDataAccessResourceUsageException.class) + public ResponseEntity> handleTableNotExists(InvalidDataAccessResourceUsageException ex) { + log.warn("테이블이 존재하지 않는 오류: {}", ex.getMessage()); + // 테이블이 존재하지 않는 경우 200 OK와 빈 리스트를 반환하도록 프론트엔드에서 처리 + return ResponseEntity.ok(Map.of("message", "데이터를 조회할 수 없습니다.")); + } } diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java index 1c0de31..6964dda 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.ArrayList; @RestController @RequestMapping("/api/mission/history") @@ -29,8 +30,14 @@ public ResponseEntity> getMissionHistoryBySubGroup(@Path @GetMapping("/user/{userId}") public ResponseEntity> getMissionHistoryByUser(@PathVariable Long userId) { log.info("사용자 미션 히스토리 조회 요청: userId = {}", userId); - List histories = subGroupMissionService.getMissionHistoryByUser(userId); - return ResponseEntity.ok(histories); + try { + List histories = subGroupMissionService.getMissionHistoryByUser(userId); + return ResponseEntity.ok(histories); + } catch (Exception e) { + log.error("미션 히스토리 조회 중 오류 발생: {}", e.getMessage()); + // 테이블이 존재하지 않는 경우 등 오류 발생 시 빈 리스트 반환 + return ResponseEntity.ok(new ArrayList<>()); + } } // 팀의 미션 히스토리 조회 diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index feb3e60..204e442 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -58,7 +58,7 @@ public void assignMissionsToSubGroup(Long subGroupId) { throw new IllegalStateException("사용자 ID " + memberId + "가 이미 다른 서브그룹에서 미션을 받았습니다."); } } - + assignMissionsByScore(subGroup, 1, 6); assignMissionsByScore(subGroup, 3, 6); assignMissionsByScore(subGroup, 5, 6); assignMissionsByScore(subGroup, 10, 6); From 360ba021aecdd132cd74c862d516cfc4443d1d79 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 14:48:42 +0900 Subject: [PATCH 113/132] update --- .github/workflows/deploy-backend.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 036434c..a559201 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -33,13 +33,25 @@ jobs: - name: Create .env file for prod run: | - echo "SPRING_DATASOURCE_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env + echo "SPRING_DATASOURCE_URL=${{ secrets.DEPLOY_DB_URL }}" >> deploy/.env echo "SPRING_DATASOURCE_USERNAME=${{ secrets.DEPLOY_DB_USER }}" >> deploy/.env echo "SPRING_DATASOURCE_PASSWORD=${{ secrets.DEPLOY_DB_PASS }}" >> deploy/.env + + echo "DB_ROOT_PASS=${{ secrets.DB_ROOT_PASS }}" >> deploy/.env + + echo "DB_URL=${{ secrets.DB_URL }}" >> deploy/.env + echo "DB_USER=${{ secrets.DB_USER }}" >> deploy/.env + echo "DB_PASS=${{ secrets.DB_PASS }}" >> deploy/.env + echo "SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver" >> deploy/.env - echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env - echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env - echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env + echo "JWT_SECRET=${{ secrets.DEPLOY_JWT_SECRET }}" >> deploy/.env + echo "JWT_ACCESS_TOKEN_EXPIRATION=86400000" >> deploy/.env + echo "JWT_REFRESH_TOKEN_EXPIRATION=604800000" >> deploy/.env + + echo "MYSQL_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASS }}" >> deploy/.env + + echo "SPRING_JPA_HIBERNATE_DDL_AUTO=update" >> deploy/.env + - name: Upload app.jar and .env to EC2 uses: appleboy/scp-action@v0.1.7 @@ -63,4 +75,3 @@ jobs: docker rmi ccgo-app || true docker compose build docker compose up -d - From 0b26e8cf1b9f19cdd14afd879e7df83da1e4767f Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 16:00:31 +0900 Subject: [PATCH 114/132] update --- .github/workflows/deploy-backend.yml | 2 +- CC_Maker_FE | 2 +- .../mission/controller/MissionHistoryController.java | 10 ++++++---- .../com/ccapp/ccgo/mission/dto/MissionHistoryDto.java | 2 ++ .../com/ccapp/ccgo/mission/entity/MissionHistory.java | 5 +++++ .../mission/repository/MissionHistoryRepository.java | 6 +++--- .../ccgo/mission/service/SubGroupMissionService.java | 9 ++++++--- 7 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index a559201..7a98889 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -50,7 +50,7 @@ jobs: echo "MYSQL_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASS }}" >> deploy/.env - echo "SPRING_JPA_HIBERNATE_DDL_AUTO=update" >> deploy/.env + echo "SPRING_JPA_HIBERNATE_DDL_AUTO=update" >> deploy/.env - name: Upload app.jar and .env to EC2 diff --git a/CC_Maker_FE b/CC_Maker_FE index 21ef657..8c72c64 160000 --- a/CC_Maker_FE +++ b/CC_Maker_FE @@ -1 +1 @@ -Subproject commit 21ef65725bd6888c40e92e48ed1ba6f6a39329fc +Subproject commit 8c72c645932f7032687c4d1bbc79966e12c2d0d6 diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java index 6964dda..45fd5e5 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionHistoryController.java @@ -26,12 +26,14 @@ public ResponseEntity> getMissionHistoryBySubGroup(@Path return ResponseEntity.ok(histories); } - // 사용자의 미션 히스토리 조회 + // 사용자의 미션 히스토리 조회 (팀별) @GetMapping("/user/{userId}") - public ResponseEntity> getMissionHistoryByUser(@PathVariable Long userId) { - log.info("사용자 미션 히스토리 조회 요청: userId = {}", userId); + public ResponseEntity> getMissionHistoryByUser( + @PathVariable Long userId, + @RequestParam Long teamId) { + log.info("사용자 미션 히스토리 조회 요청: userId = {}, teamId = {}", userId, teamId); try { - List histories = subGroupMissionService.getMissionHistoryByUser(userId); + List histories = subGroupMissionService.getMissionHistoryByUser(userId, teamId); return ResponseEntity.ok(histories); } catch (Exception e) { log.error("미션 히스토리 조회 중 오류 발생: {}", e.getMessage()); diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java index 07eb261..9eaf22b 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java @@ -16,6 +16,8 @@ public class MissionHistoryDto { private Long id; private Long subGroupId; private String subGroupName; // 서브그룹 이름 (필요시) + private Long teamId; + private String teamName; // 팀 이름 private Long userId; private String userName; // 사용자 이름 private Long missionTemplateId; diff --git a/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java b/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java index ea09b02..11ac089 100644 --- a/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java @@ -1,6 +1,7 @@ package com.ccapp.ccgo.mission.entity; import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.user.entity.User; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -30,6 +31,10 @@ public class MissionHistory { @JoinColumn(name = "user_id") private User user; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id") + private Team team; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "mission_template_id") private MissionTemplate missionTemplate; diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java index 122568d..40f0371 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java @@ -12,11 +12,11 @@ public interface MissionHistoryRepository extends JpaRepository findBySubGroup_IdOrderByCompletedAtDesc(Long subGroupId); - // 특정 사용자의 미션 히스토리 조회 - List findByUser_IdOrderByCompletedAtDesc(Long userId); + // 특정 사용자의 미션 히스토리 조회 (팀별) + List findByUser_IdAndTeam_IdOrderByCompletedAtDesc(Long userId, Long teamId); // 특정 팀의 미션 히스토리 조회 - @Query("SELECT mh FROM MissionHistory mh WHERE mh.subGroup.team.teamId = :teamId ORDER BY mh.completedAt DESC") + @Query("SELECT mh FROM MissionHistory mh WHERE mh.team.teamId = :teamId ORDER BY mh.completedAt DESC") List findByTeamIdOrderByCompletedAtDesc(@Param("teamId") Long teamId); // 특정 미션 템플릿의 완료 히스토리 조회 diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 204e442..d2fb604 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -203,6 +203,7 @@ private void saveMissionHistory(SubGroup subGroup, MissionTemplate missionTempla for (SubGroupMember member : members) { MissionHistory history = MissionHistory.builder() .subGroup(subGroup) + .team(subGroup.getTeam()) .user(member.getUser()) .missionTemplate(missionTemplate) .completedAt(LocalDateTime.now()) @@ -222,11 +223,11 @@ public List getMissionHistoryBySubGroup(Long subGroupId) { .collect(Collectors.toList()); } - // 사용자의 미션 히스토리 조회 + // 사용자의 미션 히스토리 조회 (팀별) @Transactional(readOnly = true) - public List getMissionHistoryByUser(Long userId) { + public List getMissionHistoryByUser(Long userId, Long teamId) { try { - List histories = missionHistoryRepository.findByUser_IdOrderByCompletedAtDesc(userId); + List histories = missionHistoryRepository.findByUser_IdAndTeam_IdOrderByCompletedAtDesc(userId, teamId); return histories.stream() .map(this::convertToDto) @@ -253,6 +254,8 @@ private MissionHistoryDto convertToDto(MissionHistory history) { return MissionHistoryDto.builder() .id(history.getId()) .subGroupId(history.getSubGroup().getId()) + .teamId(history.getTeam().getTeamId()) + .teamName(history.getTeam().getTeamName()) .userId(history.getUser().getId()) .userName(history.getUser().getName()) .missionTemplateId(history.getMissionTemplate().getId()) From 727b05f9c1f500f6d85c415b9ccd7095cf5aeb25 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 16:05:55 +0900 Subject: [PATCH 115/132] update --- src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java | 2 +- .../ccapp/ccgo/mission/repository/MissionHistoryRepository.java | 2 +- .../com/ccapp/ccgo/mission/service/SubGroupMissionService.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java b/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java index 11ac089..07a1005 100644 --- a/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java +++ b/src/main/java/com/ccapp/ccgo/mission/entity/MissionHistory.java @@ -32,7 +32,7 @@ public class MissionHistory { private User user; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "team_id") + @JoinColumn(name = "team_id", referencedColumnName = "teamId") private Team team; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java b/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java index 40f0371..0f34e20 100644 --- a/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java +++ b/src/main/java/com/ccapp/ccgo/mission/repository/MissionHistoryRepository.java @@ -13,7 +13,7 @@ public interface MissionHistoryRepository extends JpaRepository findBySubGroup_IdOrderByCompletedAtDesc(Long subGroupId); // 특정 사용자의 미션 히스토리 조회 (팀별) - List findByUser_IdAndTeam_IdOrderByCompletedAtDesc(Long userId, Long teamId); + List findByUser_IdAndTeam_TeamIdOrderByCompletedAtDesc(Long userId, Long teamId); // 특정 팀의 미션 히스토리 조회 @Query("SELECT mh FROM MissionHistory mh WHERE mh.team.teamId = :teamId ORDER BY mh.completedAt DESC") diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index d2fb604..3dacddc 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -227,7 +227,7 @@ public List getMissionHistoryBySubGroup(Long subGroupId) { @Transactional(readOnly = true) public List getMissionHistoryByUser(Long userId, Long teamId) { try { - List histories = missionHistoryRepository.findByUser_IdAndTeam_IdOrderByCompletedAtDesc(userId, teamId); + List histories = missionHistoryRepository.findByUser_IdAndTeam_TeamIdOrderByCompletedAtDesc(userId, teamId); return histories.stream() .map(this::convertToDto) From c34715dbbf53ebfaa2dfa982c4712bb0208d06e1 Mon Sep 17 00:00:00 2001 From: losecow Date: Tue, 12 Aug 2025 16:34:31 +0900 Subject: [PATCH 116/132] update --- .../matching/service/MatchingService.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index cbed04e..28187cb 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -121,6 +121,12 @@ private double calculateMbtiScore(TeamMember a, TeamMember b) { } private void handleLeftovers(List males, List females, List groups, Team team, int groupIndex, Long teamId) { + // 남자만 있는 경우 처리 + if (females.isEmpty() && !males.isEmpty()) { + handleMaleOnlyGroups(males, groups, team, groupIndex, teamId); + return; + } + if (males.size() == 2) { SubGroup g = SubGroup.builder().team(team).name(team.getTeamName() + groupIndex++).memberCount(2).build(); subGroupRepository.save(g); @@ -148,6 +154,31 @@ private void handleLeftovers(List males, List females, L } } } + + // 남자만 있는 경우 그룹 생성 (2명씩 커플로) + private void handleMaleOnlyGroups(List males, List groups, Team team, int groupIndex, Long teamId) { + int maleCount = males.size(); + + // 2명씩 커플로 만들기 + for (int i = 0; i < maleCount; i += 2) { + int endIndex = Math.min(i + 2, maleCount); + List groupMembers = males.subList(i, endIndex); + + if (groupMembers.size() >= 2) { // 2명 이상일 때만 그룹 생성 + SubGroup group = SubGroup.builder() + .team(team) + .name(team.getTeamName() + groupIndex++) + .memberCount(groupMembers.size()) + .build(); + subGroupRepository.save(group); + + for (TeamMember member : groupMembers) { + saveSubGroupMember(group, member.getUser()); + } + groups.add(group); + } + } + } private boolean isFemaleInsertable(SubGroup group, TeamMember female, int totalFemaleLeft) { List members = subGroupMemberRepository.findBySubGroup_Id(group.getId()); From 5e88ede324f8ae5fc1e4a971585c9e2cfb04ac5b Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 13 Aug 2025 10:35:30 +0900 Subject: [PATCH 117/132] update making subgroup --- .idea/dataSources.xml | 26 ------ .../matching/service/MatchingService.java | 87 +++++++++++++++++-- 2 files changed, 79 insertions(+), 34 deletions(-) diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index e3fecfc..9283aae 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,32 +1,6 @@ - - mysql.8 - true - com.mysql.cj.jdbc.Driver - jdbc:mysql://172.31.7.161:3306 - - - - - - - $ProjectFileDir$ - - - mysql.8 - true - com.mysql.cj.jdbc.Driver - jdbc:mysql://172.31.7.161:3306 - - - - - - - $ProjectFileDir$ - mysql.8 true diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 28187cb..f318873 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -127,6 +127,12 @@ private void handleLeftovers(List males, List females, L return; } + // 여자만 있는 경우 처리 (남자가 0명일 때) + if (males.isEmpty() && !females.isEmpty()) { + handleFemaleOnlyGroups(females, groups, team, groupIndex, teamId); + return; + } + if (males.size() == 2) { SubGroup g = SubGroup.builder().team(team).name(team.getTeamName() + groupIndex++).memberCount(2).build(); subGroupRepository.save(g); @@ -143,14 +149,11 @@ private void handleLeftovers(List males, List females, L } for (TeamMember female : females) { - for (SubGroup group : groups.stream().sorted(Comparator.comparingInt(SubGroup::getMemberCount)).collect(Collectors.toList())) { - if (group.getMemberCount() >= MAX_GROUP_SIZE) continue; - if (isFemaleInsertable(group, female, females.size())) { - saveSubGroupMember(group, female.getUser()); - group.setMemberCount(group.getMemberCount() + 1); - subGroupRepository.save(group); - break; - } + SubGroup best = findBestGroupToInsert(female, groups, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE && isFemaleInsertable(best, female, females.size())) { + saveSubGroupMember(best, female.getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); } } } @@ -179,6 +182,74 @@ private void handleMaleOnlyGroups(List males, List groups, } } } + + // 여자만 있는 경우 그룹 생성 + private void handleFemaleOnlyGroups(List females, List groups, Team team, int groupIndex, Long teamId) { + int femaleCount = females.size(); + + if (femaleCount <= 3) { + // 3명 이하면 하나의 그룹으로 + SubGroup group = SubGroup.builder() + .team(team) + .name(team.getTeamName() + groupIndex++) + .memberCount(femaleCount) + .build(); + subGroupRepository.save(group); + + for (TeamMember member : females) { + saveSubGroupMember(group, member.getUser()); + } + groups.add(group); + } else if (femaleCount == 4) { + // 4명일 때: 2/2 + createFemaleGroup(females.subList(0, 2), groups, team, groupIndex++, teamId); + createFemaleGroup(females.subList(2, 4), groups, team, groupIndex++, teamId); + } else if (femaleCount == 5) { + // 5명일 때: 2/3 + createFemaleGroup(females.subList(0, 2), groups, team, groupIndex++, teamId); + createFemaleGroup(females.subList(2, 5), groups, team, groupIndex++, teamId); + } else if (femaleCount == 6) { + // 6명일 때: 2/2/2 + createFemaleGroup(females.subList(0, 2), groups, team, groupIndex++, teamId); + createFemaleGroup(females.subList(2, 4), groups, team, groupIndex++, teamId); + createFemaleGroup(females.subList(4, 6), groups, team, groupIndex++, teamId); + } else { + // 6명 초과일 때: 2명씩 그룹으로 나누고, 남은 사람들은 기존 그룹에 추가 + int groupCount = femaleCount / 2; + for (int i = 0; i < groupCount; i++) { + int startIndex = i * 2; + int endIndex = Math.min(startIndex + 2, femaleCount); + createFemaleGroup(females.subList(startIndex, endIndex), groups, team, groupIndex++, teamId); + } + + // 남은 사람들 처리 (1명이 남은 경우) + if (femaleCount % 2 == 1) { + TeamMember remainingFemale = females.get(femaleCount - 1); + // 설문 조사 결과가 가장 일치하는 그룹에 추가 + SubGroup bestGroup = findBestGroupToInsert(remainingFemale, groups, teamId); + if (bestGroup != null && bestGroup.getMemberCount() < 3) { + saveSubGroupMember(bestGroup, remainingFemale.getUser()); + bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); + subGroupRepository.save(bestGroup); + } + } + } + } + + // 여자 그룹 생성 헬퍼 메서드 + private void createFemaleGroup(List groupMembers, List groups, Team team, int groupIndex, Long teamId) { + SubGroup group = SubGroup.builder() + .team(team) + .name(team.getTeamName() + groupIndex) + .memberCount(groupMembers.size()) + .build(); + subGroupRepository.save(group); + + for (TeamMember member : groupMembers) { + saveSubGroupMember(group, member.getUser()); + } + groups.add(group); + } private boolean isFemaleInsertable(SubGroup group, TeamMember female, int totalFemaleLeft) { List members = subGroupMemberRepository.findBySubGroup_Id(group.getId()); From e7b48efd4736f76d0e05b0d952665cf380425bce Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 13 Aug 2025 11:05:59 +0900 Subject: [PATCH 118/132] update making subgroup --- .../ccgo/mission/dto/MissionHistoryDto.java | 2 ++ .../service/SubGroupMissionService.java | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java b/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java index 9eaf22b..b068944 100644 --- a/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java +++ b/src/main/java/com/ccapp/ccgo/mission/dto/MissionHistoryDto.java @@ -6,6 +6,7 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; @Getter @Builder @@ -20,6 +21,7 @@ public class MissionHistoryDto { private String teamName; // 팀 이름 private Long userId; private String userName; // 사용자 이름 + private List matchedNames; // 매칭된 상대방들의 이름 private Long missionTemplateId; private String missionTitle; // 미션 제목 private String missionDescription; // 미션 설명 diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 3dacddc..582a152 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.ArrayList; +import java.util.Optional; import java.util.stream.Collectors; import java.time.LocalDateTime; @@ -251,13 +252,18 @@ public List getMissionHistoryByTeam(Long teamId) { // MissionHistory를 DTO로 변환 private MissionHistoryDto convertToDto(MissionHistory history) { + // 미션을 완료한 사용자와 매칭된 상대방들의 이름 조회 + List matchedNames = getMatchedUserNames(history.getUser().getId(), history.getTeam().getTeamId()); + return MissionHistoryDto.builder() .id(history.getId()) .subGroupId(history.getSubGroup().getId()) + .subGroupName(history.getSubGroup().getName()) .teamId(history.getTeam().getTeamId()) .teamName(history.getTeam().getTeamName()) .userId(history.getUser().getId()) .userName(history.getUser().getName()) + .matchedNames(matchedNames) // 매칭된 상대방들의 이름 추가 .missionTemplateId(history.getMissionTemplate().getId()) .missionTitle(history.getMissionTemplate().getTitle()) .missionDescription(history.getMissionTemplate().getDescription()) @@ -266,4 +272,31 @@ private MissionHistoryDto convertToDto(MissionHistory history) { .createdAt(history.getCreatedAt()) .build(); } + + // 사용자와 매칭된 상대방들의 이름 조회 + private List getMatchedUserNames(Long userId, Long teamId) { + try { + // 사용자가 속한 서브그룹 조회 + Optional subGroupIdOpt = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); + if (subGroupIdOpt.isEmpty()) { + return new ArrayList<>(); + } + + Long subGroupId = subGroupIdOpt.get(); + + // 같은 서브그룹의 다른 멤버들 조회 (본인 제외) + List members = subGroupMemberRepository.findBySubGroup_Id(subGroupId); + return members.stream() + .map(member -> member.getUser().getName()) + .filter(name -> !name.equals(subGroupMemberRepository.findBySubGroup_Id(subGroupId) + .stream() + .filter(m -> m.getUser().getId().equals(userId)) + .findFirst() + .map(m -> m.getUser().getName()) + .orElse(""))) + .collect(Collectors.toList()); + } catch (Exception e) { + return new ArrayList<>(); + } + } } From 0e7373c74f82eb31fc1c4c249b3ba83150b396b7 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 13 Aug 2025 11:10:55 +0900 Subject: [PATCH 119/132] update making subgroup --- .../mission/service/SubGroupMissionService.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 582a152..bbb4402 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -276,26 +276,36 @@ private MissionHistoryDto convertToDto(MissionHistory history) { // 사용자와 매칭된 상대방들의 이름 조회 private List getMatchedUserNames(Long userId, Long teamId) { try { + System.out.println("[getMatchedUserNames] userId: " + userId + ", teamId: " + teamId); + // 사용자가 속한 서브그룹 조회 Optional subGroupIdOpt = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); if (subGroupIdOpt.isEmpty()) { + System.out.println("[getMatchedUserNames] 서브그룹을 찾을 수 없음"); return new ArrayList<>(); } Long subGroupId = subGroupIdOpt.get(); + System.out.println("[getMatchedUserNames] subGroupId: " + subGroupId); // 같은 서브그룹의 다른 멤버들 조회 (본인 제외) List members = subGroupMemberRepository.findBySubGroup_Id(subGroupId); - return members.stream() + System.out.println("[getMatchedUserNames] 전체 멤버 수: " + members.size()); + + List matchedNames = members.stream() .map(member -> member.getUser().getName()) - .filter(name -> !name.equals(subGroupMemberRepository.findBySubGroup_Id(subGroupId) - .stream() + .filter(name -> !name.equals(members.stream() .filter(m -> m.getUser().getId().equals(userId)) .findFirst() .map(m -> m.getUser().getName()) .orElse(""))) .collect(Collectors.toList()); + + System.out.println("[getMatchedUserNames] 매칭된 이름들: " + matchedNames); + return matchedNames; } catch (Exception e) { + System.err.println("[getMatchedUserNames] 오류 발생: " + e.getMessage()); + e.printStackTrace(); return new ArrayList<>(); } } From b3e44b0634f3f63ce337416e2c7c68a711256ce5 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 13 Aug 2025 11:15:49 +0900 Subject: [PATCH 120/132] update making subgroup --- .../service/SubGroupMissionService.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index bbb4402..20fc48f 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -198,19 +198,25 @@ public void completeMission(Long teamId, Long subGroupId, Long missionTemplateId // 미션 히스토리 저장 private void saveMissionHistory(SubGroup subGroup, MissionTemplate missionTemplate) { - // 서브그룹의 모든 멤버에 대해 히스토리 저장 - List members = subGroupMemberRepository.findBySubGroup_Id(subGroup.getId()); + // 이미 같은 서브그룹에서 같은 미션의 히스토리가 있는지 확인 + boolean exists = missionHistoryRepository.existsBySubGroup_IdAndMissionTemplate_Id(subGroup.getId(), missionTemplate.getId()); - for (SubGroupMember member : members) { - MissionHistory history = MissionHistory.builder() - .subGroup(subGroup) - .team(subGroup.getTeam()) - .user(member.getUser()) - .missionTemplate(missionTemplate) - .completedAt(LocalDateTime.now()) - .build(); - - missionHistoryRepository.save(history); + if (!exists) { + // 서브그룹의 첫 번째 멤버만 대표로 히스토리 저장 + List members = subGroupMemberRepository.findBySubGroup_Id(subGroup.getId()); + if (!members.isEmpty()) { + SubGroupMember representativeMember = members.get(0); + + MissionHistory history = MissionHistory.builder() + .subGroup(subGroup) + .team(subGroup.getTeam()) + .user(representativeMember.getUser()) + .missionTemplate(missionTemplate) + .completedAt(LocalDateTime.now()) + .build(); + + missionHistoryRepository.save(history); + } } } From 87d76d409ffe9fb9e45e0171a583e5f5feccbea0 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 13 Aug 2025 16:41:03 +0900 Subject: [PATCH 121/132] Update toekn --- CC_Maker_FE | 2 +- .../ccgo/auth/controller/AuthController.java | 18 ++++-- .../ccapp/ccgo/auth/entity/RefreshToken.java | 35 +++++++++++ .../auth/jwt/JwtAuthenticationFilter.java | 2 +- .../com/ccapp/ccgo/auth/jwt/JwtProvider.java | 46 ++++++++++++++- .../repository/RefreshTokenRepository.java | 17 ++++++ .../ccapp/ccgo/auth/service/AuthService.java | 25 ++++++-- .../auth/service/RefreshTokenService.java | 59 +++++++++++++------ .../com/ccapp/ccgo/common/SecurityConfig.java | 15 +++++ .../service/SubGroupMissionService.java | 19 +++++- 10 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/ccapp/ccgo/auth/entity/RefreshToken.java create mode 100644 src/main/java/com/ccapp/ccgo/auth/repository/RefreshTokenRepository.java diff --git a/CC_Maker_FE b/CC_Maker_FE index 8c72c64..043286b 160000 --- a/CC_Maker_FE +++ b/CC_Maker_FE @@ -1 +1 @@ -Subproject commit 8c72c645932f7032687c4d1bbc79966e12c2d0d6 +Subproject commit 043286b0df456597e56192b61873c745fce56855 diff --git a/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java b/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java index 1b52842..ebad563 100644 --- a/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java +++ b/src/main/java/com/ccapp/ccgo/auth/controller/AuthController.java @@ -80,18 +80,26 @@ public ResponseEntity login(@Valid @RequestBody LoginRequestDto requestDto) { } @PostMapping("/refresh") - public ResponseEntity refreshToken(@CookieValue(value = "refreshToken", required = false) String refreshToken) { + public ResponseEntity refreshToken( + @CookieValue(value = "accessToken", required = false) String accessToken, + @CookieValue(value = "refreshToken", required = false) String refreshToken) { try { - TokenResponseDto tokenResponse = authService.refreshToken(refreshToken); + if (accessToken == null || refreshToken == null) { + return ResponseEntity.status(401).body(Map.of("message", "토큰이 없습니다.")); + } + + TokenResponseDto tokenResponse = authService.refreshToken(accessToken, refreshToken); HttpHeaders headers = createTokenCookies(tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()); log.info("새로운 쿠키: {}", headers.get(HttpHeaders.SET_COOKIE)); - - return ResponseEntity.ok() .headers(headers) - .body(Map.of("message", "토큰 갱신 성공")); + .body(Map.of( + "message", "토큰 갱신 성공", + "accessToken", tokenResponse.getAccessToken(), + "refreshToken", tokenResponse.getRefreshToken() + )); } catch (IllegalArgumentException e) { return ResponseEntity.status(401).body(Map.of("message", e.getMessage())); } catch (Exception e) { diff --git a/src/main/java/com/ccapp/ccgo/auth/entity/RefreshToken.java b/src/main/java/com/ccapp/ccgo/auth/entity/RefreshToken.java new file mode 100644 index 0000000..4539266 --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/auth/entity/RefreshToken.java @@ -0,0 +1,35 @@ +package com.ccapp.ccgo.auth.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "refresh_tokens") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RefreshToken { + + @Id + private String email; + + @Column(nullable = false) + private String refreshToken; + + @Column(nullable = false) + private LocalDateTime expiresAt; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java index becb6a2..c9c0d4d 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtAuthenticationFilter.java @@ -40,7 +40,7 @@ protected void doFilterInternal(HttpServletRequest request, String token = extractToken(request); try { - if (token != null && jwtProvider.validateToken(token)) { + if (token != null && jwtProvider.validateAccessToken(token)) { String email = jwtProvider.getEmailFromToken(token); if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { diff --git a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java index 278cabb..ae6634a 100644 --- a/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java +++ b/src/main/java/com/ccapp/ccgo/auth/jwt/JwtProvider.java @@ -44,11 +44,11 @@ public String createAccessToken(Authentication authentication) { String roles = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); - return buildToken(username, Map.of("roles", roles), accessTokenExpiration); + return buildToken(username, Map.of("roles", roles, "type", "ACCESS"), accessTokenExpiration); } public String createRefreshToken(Authentication authentication) { - return buildToken(authentication.getName(), null, refreshTokenExpiration); + return buildToken(authentication.getName(), Map.of("type", "REFRESH"), refreshTokenExpiration); } private String buildToken(String subject, Map claims, long validityInMs) { @@ -85,6 +85,48 @@ public boolean validateToken(String token) { return false; } + public boolean validateAccessToken(String token) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + + return "ACCESS".equals(claims.get("type")); + } catch (Exception e) { + log.warn("Access Token 검증 실패: {}", e.getMessage()); + return false; + } + } + + public boolean validateRefreshToken(String token) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + + return "REFRESH".equals(claims.get("type")); + } catch (Exception e) { + log.warn("Refresh Token 검증 실패: {}", e.getMessage()); + return false; + } + } + + public boolean validateTokenStructure(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + // 만료는 허용 (구조는 유효) + return true; + } catch (Exception e) { + return false; + } + } + public String getEmailFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(key) diff --git a/src/main/java/com/ccapp/ccgo/auth/repository/RefreshTokenRepository.java b/src/main/java/com/ccapp/ccgo/auth/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..8ec172a --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/auth/repository/RefreshTokenRepository.java @@ -0,0 +1,17 @@ +package com.ccapp.ccgo.auth.repository; + +import com.ccapp.ccgo.auth.entity.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RefreshTokenRepository extends JpaRepository { + + Optional findByEmail(String email); + + void deleteByEmail(String email); + + boolean existsByEmail(String email); +} diff --git a/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java b/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java index 066fd02..387de6a 100644 --- a/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/AuthService.java @@ -22,6 +22,7 @@ public class AuthService { private final JwtProvider jwtProvider; private final TeamMemberRepository teamMemberRepository; private final LoginUserDetailsService loginUserDetailsService; + private final RefreshTokenService refreshTokenService; public LoginResponseDto login(String email, String password) { Authentication authentication = authenticationManager.authenticate( @@ -34,6 +35,9 @@ public LoginResponseDto login(String email, String password) { LoginUserDetails userDetails = (LoginUserDetails) authentication.getPrincipal(); var user = userDetails.getUser(); + // Refresh Token DB 저장 + refreshTokenService.saveRefreshToken(email, refreshToken, jwtProvider.getRefreshTokenExpiration()); + List teamMembers = teamMemberRepository.findAllByUserAndIsActiveTrue(user); List teams = teamMembers.stream() @@ -55,15 +59,25 @@ public LoginResponseDto login(String email, String password) { .build(); } - public TokenResponseDto refreshToken(String refreshToken) { - if (!jwtProvider.validateToken(refreshToken)) { - throw new IllegalArgumentException("리프레시 토큰이 없거나 유효하지 않습니다."); + public TokenResponseDto refreshToken(String accessToken, String refreshToken) { + // 1. Access Token 구조 검증 (만료는 허용) + if (!jwtProvider.validateTokenStructure(accessToken)) { + throw new IllegalArgumentException("유효하지 않은 Access Token 구조입니다."); + } + + // 2. Refresh Token 검증 + if (!jwtProvider.validateRefreshToken(refreshToken)) { + throw new IllegalArgumentException("유효하지 않은 Refresh Token입니다."); } + // 3. DB의 Refresh Token과 비교 String email = jwtProvider.getEmailFromToken(refreshToken); + if (!refreshTokenService.validateRefreshToken(email, refreshToken)) { + throw new IllegalArgumentException("유효하지 않은 Refresh Token입니다."); + } + // 4. 새로운 토큰 생성 LoginUserDetails userDetails = (LoginUserDetails) loginUserDetailsService.loadUserByUsername(email); - Authentication authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); @@ -71,6 +85,9 @@ public TokenResponseDto refreshToken(String refreshToken) { String newAccessToken = jwtProvider.createAccessToken(authentication); String newRefreshToken = jwtProvider.createRefreshToken(authentication); + // 5. Refresh Token Rotation (기존 토큰 삭제, 새로운 토큰 저장) + refreshTokenService.updateRefreshToken(email, newRefreshToken, jwtProvider.getRefreshTokenExpiration()); + return new TokenResponseDto(newAccessToken, newRefreshToken); } } diff --git a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java index 8ca3795..59292c2 100644 --- a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java @@ -1,36 +1,61 @@ package com.ccapp.ccgo.auth.service; +import com.ccapp.ccgo.auth.entity.RefreshToken; +import com.ccapp.ccgo.auth.repository.RefreshTokenRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + +@Slf4j @Service +@RequiredArgsConstructor public class RefreshTokenService { - /** - * Stateless 방식이므로 저장 기능은 필요하지 않습니다. - */ + private final RefreshTokenRepository refreshTokenRepository; + public void saveRefreshToken(String email, String refreshToken, long expirationMillis) { - // 저장 기능 제거 + LocalDateTime expiresAt = LocalDateTime.now().plusSeconds(expirationMillis / 1000); + + RefreshToken token = RefreshToken.builder() + .email(email) + .refreshToken(refreshToken) + .expiresAt(expiresAt) + .build(); + + refreshTokenRepository.save(token); + log.info("Refresh Token 저장 완료: {}", email); } - /** - * Stateless 방식이므로 조회 기능은 필요하지 않습니다. - */ public String getRefreshToken(String email) { - return null; + return refreshTokenRepository.findByEmail(email) + .map(RefreshToken::getRefreshToken) + .orElse(null); } - /** - * JWT 토큰 자체를 검증하므로 여기서는 항상 true 리턴하거나 별도 검증을 하지 않습니다. - * 실제 토큰 검증은 JwtProvider에서 수행됩니다. - */ public boolean validateRefreshToken(String email, String refreshToken) { - return true; + return refreshTokenRepository.findByEmail(email) + .map(token -> token.getRefreshToken().equals(refreshToken) && + token.getExpiresAt().isAfter(LocalDateTime.now())) + .orElse(false); } - /** - * 서버에 저장된 토큰이 없으므로 삭제할 것도 없습니다. - */ public void deleteRefreshToken(String email) { - // 삭제 기능 제거 + refreshTokenRepository.deleteByEmail(email); + log.info("Refresh Token 삭제 완료: {}", email); + } + + public void updateRefreshToken(String email, String newRefreshToken, long expirationMillis) { + LocalDateTime expiresAt = LocalDateTime.now().plusSeconds(expirationMillis / 1000); + + RefreshToken token = RefreshToken.builder() + .email(email) + .refreshToken(newRefreshToken) + .expiresAt(expiresAt) + .build(); + + refreshTokenRepository.save(token); + log.info("Refresh Token 업데이트 완료: {}", email); } } diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index e4d018e..0f78cd1 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -19,6 +19,7 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; import java.util.List; @@ -54,6 +55,20 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .headers(headers -> headers + // X-Frame-Options: 클릭재킹 공격 방지 + .frameOptions().deny() + // X-Content-Type-Options: MIME 타입 스니핑 방지 + .contentTypeOptions().and() + // X-XSS-Protection: XSS 공격 방지 + .xssProtection().and() + // HSTS: HTTPS 강제 (2년으로 연장) + .httpStrictTransportSecurity(hstsConfig -> hstsConfig + .maxAgeInSeconds(63072000) // 2년 + ) + // Referrer Policy: 리퍼러 정보 제한 + .referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)) + ) .authorizeHttpRequests(auth -> auth // 인증 필요 없는 엔드포인트 .requestMatchers("/api/auth/**", "/api/user/register").permitAll() diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 20fc48f..2c16b60 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.ArrayList; import java.util.Optional; +import java.util.HashMap; +import java.util.Map; import java.util.stream.Collectors; import java.time.LocalDateTime; @@ -251,7 +253,22 @@ public List getMissionHistoryByUser(Long userId, Long teamId) public List getMissionHistoryByTeam(Long teamId) { List histories = missionHistoryRepository.findByTeamIdOrderByCompletedAtDesc(teamId); - return histories.stream() + // 중복 제거: 같은 서브그룹의 같은 미션은 하나만 유지 + Map uniqueMissions = new HashMap<>(); + + for (MissionHistory history : histories) { + String key = history.getSubGroup().getId() + "_" + history.getMissionTemplate().getId(); + if (!uniqueMissions.containsKey(key)) { + uniqueMissions.put(key, history); + } + } + + List uniqueHistories = new ArrayList<>(uniqueMissions.values()); + + // 완료 시간 기준으로 정렬 + uniqueHistories.sort((h1, h2) -> h2.getCompletedAt().compareTo(h1.getCompletedAt())); + + return uniqueHistories.stream() .map(this::convertToDto) .collect(Collectors.toList()); } From c469e48251e41751b2a58b214304734ecdcb6ea8 Mon Sep 17 00:00:00 2001 From: losecow Date: Thu, 14 Aug 2025 10:41:42 +0900 Subject: [PATCH 122/132] code refactoring --- .github/workflows/deploy-backend.yml | 2 +- .../controller/MatchingController.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 7a98889..99a95af 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -3,7 +3,7 @@ name: Deploy Spring Boot to EC2 on: push: branches: - - test-mysql + - REAL jobs: deploy: diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index 0219dc1..df4ea12 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -131,17 +131,17 @@ public ResponseEntity getMatchedNames( return ResponseEntity.ok(response); } - //매칭된 직후 작동하는놈 - //현재 임시로 userid 받아오는중인데, 나중에 jwt로 수정 필요. - @GetMapping("/subgroup/{teamId}") - public ResponseEntity> getSubGroupIdByTeamId( - @PathVariable Long teamId, - @RequestParam Long userId) { - Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); - Map response = new HashMap<>(); - response.put("subGroupId", subGroupId.orElse(null)); // null 허용 가능 - return ResponseEntity.ok(response); - } + // //매칭된 직후 작동하는놈 + // //현재 임시로 userid 받아오는중인데, 나중에 jwt로 수정 필요. + // @GetMapping("/subgroup/{teamId}") + // public ResponseEntity> getSubGroupIdByTeamId( + // @PathVariable Long teamId, + // @RequestParam Long userId) { + // Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); + // Map response = new HashMap<>(); + // response.put("subGroupId", subGroupId.orElse(null)); // null 허용 가능 + // return ResponseEntity.ok(response); + // } } From 9bddb90ad9dbc3cf0149eca28c435509517c79bf Mon Sep 17 00:00:00 2001 From: losecow Date: Thu, 14 Aug 2025 11:15:11 +0900 Subject: [PATCH 123/132] code refactoring --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 99a95af..5553d68 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -74,4 +74,4 @@ jobs: docker rm ccgo-app || true docker rmi ccgo-app || true docker compose build - docker compose up -d + docker compose up -d \ No newline at end of file From a05bfb9bbaef39f3b23b1a9ddf3eff5262525944 Mon Sep 17 00:00:00 2001 From: losecow Date: Thu, 14 Aug 2025 13:37:20 +0900 Subject: [PATCH 124/132] refresh token --- .../java/com/ccapp/ccgo/auth/service/RefreshTokenService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java index 59292c2..a1e56e2 100644 --- a/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java +++ b/src/main/java/com/ccapp/ccgo/auth/service/RefreshTokenService.java @@ -17,11 +17,13 @@ public class RefreshTokenService { public void saveRefreshToken(String email, String refreshToken, long expirationMillis) { LocalDateTime expiresAt = LocalDateTime.now().plusSeconds(expirationMillis / 1000); + LocalDateTime createdAt = LocalDateTime.now(); RefreshToken token = RefreshToken.builder() .email(email) .refreshToken(refreshToken) .expiresAt(expiresAt) + .createdAt(createdAt) .build(); refreshTokenRepository.save(token); @@ -48,11 +50,13 @@ public void deleteRefreshToken(String email) { public void updateRefreshToken(String email, String newRefreshToken, long expirationMillis) { LocalDateTime expiresAt = LocalDateTime.now().plusSeconds(expirationMillis / 1000); + LocalDateTime createdAt = LocalDateTime.now(); RefreshToken token = RefreshToken.builder() .email(email) .refreshToken(newRefreshToken) .expiresAt(expiresAt) + .createdAt(createdAt) .build(); refreshTokenRepository.save(token); From 4bd71835760942caeec5418a79a154b3d3a78c22 Mon Sep 17 00:00:00 2001 From: losecow Date: Thu, 14 Aug 2025 13:46:44 +0900 Subject: [PATCH 125/132] refresh token --- .../controller/MatchingController.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index df4ea12..76b3699 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -131,17 +131,17 @@ public ResponseEntity getMatchedNames( return ResponseEntity.ok(response); } - // //매칭된 직후 작동하는놈 - // //현재 임시로 userid 받아오는중인데, 나중에 jwt로 수정 필요. - // @GetMapping("/subgroup/{teamId}") - // public ResponseEntity> getSubGroupIdByTeamId( - // @PathVariable Long teamId, - // @RequestParam Long userId) { - // Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); - // Map response = new HashMap<>(); - // response.put("subGroupId", subGroupId.orElse(null)); // null 허용 가능 - // return ResponseEntity.ok(response); - // } + //매칭된 직후 작동하는놈 + //현재 임시로 userid 받아오는중인데, 나중에 jwt로 수정 필요. + @GetMapping("/subgroup/{teamId}") + public ResponseEntity> getSubGroupIdByTeamId( + @PathVariable Long teamId, + @RequestParam Long userId) { + Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); + Map response = new HashMap<>(); + response.put("subGroupId", subGroupId.orElse(null)); // null 허용 가능 + return ResponseEntity.ok(response); + } } From 39f1644f584aa9b9870d7ec3424edffe3ccd2b8d Mon Sep 17 00:00:00 2001 From: losecow Date: Thu, 14 Aug 2025 14:02:50 +0900 Subject: [PATCH 126/132] refactoring --- CC_Maker_FE | 2 +- .../controller/InviteCodeController.java | 34 +++---- .../controller/MatchingController.java | 98 ++++++++----------- .../matching/service/MatchingService.java | 22 +++-- .../MissionAssignmentController.java | 8 +- .../service/SubGroupMissionService.java | 30 +++--- 6 files changed, 98 insertions(+), 96 deletions(-) diff --git a/CC_Maker_FE b/CC_Maker_FE index 043286b..e22c51d 160000 --- a/CC_Maker_FE +++ b/CC_Maker_FE @@ -1 +1 @@ -Subproject commit 043286b0df456597e56192b61873c745fce56855 +Subproject commit e22c51de2d650b7bfeb6fd109c9d1faabbb8f00e diff --git a/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java b/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java index 1635e4f..e600698 100644 --- a/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java +++ b/src/main/java/com/ccapp/ccgo/invitecode/controller/InviteCodeController.java @@ -21,10 +21,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import lombok.extern.slf4j.Slf4j; @RestController @RequestMapping("/api/invitecode") @RequiredArgsConstructor +@Slf4j public class InviteCodeController { private final InviteCodeService inviteCodeService; @@ -32,25 +34,17 @@ public class InviteCodeController { private final TeamMemberRepository teamMemberRepository; private final TeamRepository teamRepository; - //코드 만드는 부분 + /** + * 초대코드 생성 + */ @PostMapping("/create") public ResponseEntity createInviteCode( @AuthenticationPrincipal LoginUserDetails userDetails, @RequestBody InviteCodeCreateRequestDto requestDto) { - System.out.println("초대코드 생성 요청 들어옴"); - System.out.println("userDetails: " + userDetails); - - //작동확인 주석 - if (userDetails != null) { - System.out.println("인증된 사용자 이메일: " + userDetails.getUsername()); - System.out.println("사용자 권한: " + userDetails.getAuthorities()); - } else { - System.out.println("userDetails가 null입니다. 인증 정보 없음."); - } + log.info("[InviteCode] 초대코드 생성 요청 | user: {}", userDetails.getUsername()); User user = userDetails.getUser(); - System.out.print("코드 만듭니당"); Long teamId = requestDto.getTeamId(); InviteCode inviteCode = inviteCodeService.createInviteCode(user, teamId); @@ -60,12 +54,15 @@ public ResponseEntity createInviteCode( .expiresAt(inviteCode.getExpiresAt()) .build(); - return ResponseEntity.ok(responseDto); + log.info("[InviteCode] 초대코드 생성 완료 | code: {}", inviteCode.getCode()); + return ResponseEntity.ok(responseDto); } - //팀 생성하기를 누르면 팀이 만들어집니당 + /** + * 팀 생성 + */ @PostMapping("/teamname") public ResponseEntity saveTeamName( @AuthenticationPrincipal LoginUserDetails userDetails, @@ -84,13 +81,15 @@ public ResponseEntity saveTeamName( .teamName(team.getTeamName()) .build(); - System.out.println("🔎 컨트롤러에서 내려보내는 responseDto = " + responseDto); + log.info("[InviteCode] 팀 생성 완료 | teamId: {}", team.getTeamId()); return ResponseEntity.ok(responseDto); } - //팀원이 코드를 보냈으면 처리 + /** + * 초대코드로 팀 가입 + */ @PostMapping("/join") public ResponseEntity joinByInviteCode( @RequestBody InviteCodeJoinRequestDto requestDto, @@ -101,9 +100,10 @@ public ResponseEntity joinByInviteCode( } User user = userDetails.getUser(); - System.out.println("1. 컨트롤러 진입"); + log.info("[InviteCode] 팀 가입 요청 | user: {}, code: {}", user.getEmail(), requestDto.getInviteCode()); String teamName = inviteCodeService.joinTeamByInviteCode(requestDto.getInviteCode(), user); + log.info("[InviteCode] 팀 가입 완료 | user: {}, team: {}", user.getEmail(), teamName); return ResponseEntity.ok(new InviteCodeJoinResponseDto(teamName)); } diff --git a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java index 76b3699..d0f857d 100644 --- a/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java +++ b/src/main/java/com/ccapp/ccgo/matching/controller/MatchingController.java @@ -23,95 +23,77 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/matching") // 경로수정필요 +@RequestMapping("/api/matching") @Slf4j public class MatchingController { private final MatchingService matchingService; - private final SubGroupMemberRepository subGroupMemberRepository; // ✅ 주입 + private final SubGroupMemberRepository subGroupMemberRepository; /** - * 팀장이 매칭 시작 버튼 누를 때 호출 + * 팀 매칭 시작 + * 팀장이 매칭 시작 버튼을 누를 때 호출 */ -// @PreAuthorize("hasRole('TEAM_LEADER')") 매칭권한 - //이런식으로 ResponseEntity를 써야 한다. @PostMapping("/start/{teamId}") public ResponseEntity startMatching(@PathVariable Long teamId) { - log.info("[Matching Start] 요청받음 | teamId: {}", teamId); + log.info("[Matching] 매칭 시작 요청 | teamId: {}", teamId); return ResponseEntity.ok(matchingService.performMatching(teamId)); } + /** + * 설문 답변 저장 + */ @PostMapping("/answer") - public ResponseEntity saveAnswers(@RequestBody AnswerRequestDto dto, - @AuthenticationPrincipal LoginUserDetails loginUserDetails) { + public ResponseEntity saveAnswers(@RequestBody AnswerRequestDto dto, + @AuthenticationPrincipal LoginUserDetails loginUserDetails) { User currentUser = loginUserDetails.getUser(); matchingService.saveAnswers(dto, currentUser); return ResponseEntity.ok().build(); } + /** + * 설문 질문 생성 + */ @PostMapping("/question") public void createQuestions(@RequestBody QuestionRequestDto requestDto) { matchingService.createQuestions(requestDto); } + /** + * 팀별 설문 질문 조회 + */ @GetMapping("/question") public List getQuestions(@RequestParam Long teamId) { return matchingService.getQuestions(teamId); } + /** + * 설문 질문 수정 + */ @PutMapping("/question/{questionId}") public void updateQuestion(@PathVariable Long questionId, @RequestBody QuestionUpdateDto dto) { matchingService.updateQuestion(questionId, dto); } + /** + * 설문 질문 삭제 + */ @DeleteMapping("/question/{questionId}") public void deleteQuestion(@PathVariable Long questionId) { matchingService.deleteQuestion(questionId); } - - - //왜 안되냐 -// @GetMapping("/matched-names/{teamId}/{subGroupId}") -// public ResponseEntity getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails, -// @PathVariable Long teamId, -// @PathVariable Long subGroupId) { -// List matchedNames = matchingService.getMatchedUserNames(userDetails.getUser().getId(), teamId, subGroupId); -// MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); -// return ResponseEntity.ok(response); -// } - -// //매칭된 이후, 팀id랑 subgroupid로 팀원 찾아오는놈 -// //이거 authenticaiton 아직 구현 안되서 그런듯 -// @GetMapping("/matched-names/{teamId}/{subGroupId}") -// public ResponseEntity getMatchedNames(@AuthenticationPrincipal LoginUserDetails userDetails, -// @PathVariable Long teamId, -// @PathVariable Long subGroupId) { -// try { -// Long userId = userDetails.getUser().getId(); -// System.out.println("[getMatchedNames] userId: " + userId + ", teamId: " + teamId + ", subGroupId: " + subGroupId); -// -// List matchedNames = matchingService.getMatchedUserNames(userId, teamId, subGroupId); -// -// System.out.println("[getMatchedNames] matchedNames size: " + (matchedNames != null ? matchedNames.size() : "null")); -// -// MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); -// return ResponseEntity.ok(response); -// } catch (Exception e) { -// System.err.println("[getMatchedNames] 서버 에러 발생: " + e.getMessage()); -// e.printStackTrace(); -// return ResponseEntity.status(500).build(); -// } -// } - - //매칭되고 난 뒤 쓰이는 놈일텐데.. + /** + * 매칭된 팀원 이름 조회 + * 매칭 완료 후 해당 사용자의 서브그룹 멤버들을 조회 + */ @GetMapping("/matched-names/{teamId}") public ResponseEntity getMatchedNames( @RequestParam Long userId, @PathVariable Long teamId) { - System.out.printf("[getMatchedNames] userId: %d, teamId: %d%n", userId, teamId); + log.info("[Matching] 매칭된 팀원 조회 | userId: {}, teamId: {}", userId, teamId); // 1) userId와 teamId로 subGroupId 조회 Long subGroupId = matchingService.findSubGroupIdByTeamIdAndUserId(teamId, userId); @@ -124,24 +106,26 @@ public ResponseEntity getMatchedNames( // 2) 매칭된 멤버 이름 조회 (본인 제외) List matchedNames = matchingService.getMatchedUserNames(userId, teamId); - System.out.printf("[getMatchedNames] matchedNames size: %d%n", matchedNames.size()); + log.info("[Matching] 매칭된 팀원 수: {}", matchedNames.size()); MatchedNamesResponse response = new MatchedNamesResponse(teamId, subGroupId, matchedNames); return ResponseEntity.ok(response); } - //매칭된 직후 작동하는놈 - //현재 임시로 userid 받아오는중인데, 나중에 jwt로 수정 필요. - @GetMapping("/subgroup/{teamId}") - public ResponseEntity> getSubGroupIdByTeamId( - @PathVariable Long teamId, - @RequestParam Long userId) { - Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); - Map response = new HashMap<>(); - response.put("subGroupId", subGroupId.orElse(null)); // null 허용 가능 - return ResponseEntity.ok(response); - } + /** + * 사용자의 서브그룹 ID 조회 + * 매칭 완료 후 사용자가 속한 서브그룹을 확인 + */ + @GetMapping("/subgroup/{teamId}") + public ResponseEntity> getSubGroupIdByTeamId( + @PathVariable Long teamId, + @RequestParam Long userId) { + Optional subGroupId = subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId); + Map response = new HashMap<>(); + response.put("subGroupId", subGroupId.orElse(null)); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index f318873..a9e5077 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -26,6 +26,7 @@ import java.util.*; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; @Slf4j @Service @@ -127,7 +128,7 @@ private void handleLeftovers(List males, List females, L return; } - // 여자만 있는 경우 처리 (남자가 0명일 때) + // 여자만 있는 경우 처리 if (males.isEmpty() && !females.isEmpty()) { handleFemaleOnlyGroups(females, groups, team, groupIndex, teamId); return; @@ -167,7 +168,7 @@ private void handleMaleOnlyGroups(List males, List groups, int endIndex = Math.min(i + 2, maleCount); List groupMembers = males.subList(i, endIndex); - if (groupMembers.size() >= 2) { // 2명 이상일 때만 그룹 생성 + if (groupMembers.size() >= 2) { SubGroup group = SubGroup.builder() .team(team) .name(team.getTeamName() + groupIndex++) @@ -401,9 +402,13 @@ public void deleteQuestion(Long questionId) { questionRepository.deleteById(questionId); } - //실험 + /** + * 매칭된 팀원 이름 조회 + */ @Transactional(readOnly = true) public List getMatchedUserNames(Long userId, Long teamId) { + log.info("[Matching] 매칭된 팀원 조회 | userId: {}, teamId: {}", userId, teamId); + // 1. 유저 존재 확인 userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다.")); @@ -418,17 +423,22 @@ public List getMatchedUserNames(Long userId, Long teamId) { List matchedUsers = subGroupMemberRepository.findTeamMatchedMembersExcludingUser(userId, teamId); // 4. 이름만 리스트로 변환 후 반환 - return matchedUsers.stream() + List matchedNames = matchedUsers.stream() .map(User::getName) .collect(Collectors.toList()); + + log.info("[Matching] 매칭된 팀원 수: {}", matchedNames.size()); + return matchedNames; } - //매칭된 직후 발동하는놈 + /** + * 사용자의 서브그룹 ID 조회 + */ public Long findSubGroupIdByTeamIdAndUserId(Long teamId, Long userId) { return subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId) - .orElse(null); // 없으면 null 반환 (적절히 처리) + .orElse(null); } diff --git a/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java b/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java index f1d5c74..e4c0200 100644 --- a/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java +++ b/src/main/java/com/ccapp/ccgo/mission/controller/MissionAssignmentController.java @@ -7,12 +7,14 @@ import org.springframework.web.bind.annotation.*; import com.ccapp.ccgo.mission.service.SubGroupMissionService; import com.ccapp.ccgo.mission.dto.SubGroupMissionDto; +import lombok.extern.slf4j.Slf4j; import java.util.List; @RestController @RequestMapping("/api/missions") @RequiredArgsConstructor +@Slf4j public class MissionAssignmentController { private final SubGroupMissionService subGroupMissionService; @@ -44,14 +46,16 @@ public ResponseEntity completeMission(@RequestBody MissionCompleteReques } - //미션 새로고침 + /** + * 미션 새로고침 + */ @PostMapping("/refresh/subgroup/{subGroupId}/{subGroupMissionId}/{score}") public ResponseEntity refreshMission( @PathVariable Long subGroupId, @PathVariable Long subGroupMissionId, @PathVariable Integer score) { try { - System.out.println("진입합니당." + subGroupId + subGroupMissionId + score); + log.info("[Mission] 미션 새로고침 요청 | subGroupId: {}, missionId: {}, score: {}", subGroupId, subGroupMissionId, score); subGroupMissionService.refreshSingleMission(subGroupId, subGroupMissionId, score); return ResponseEntity.ok("미션 새로고침 완료"); } catch (Exception e) { diff --git a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java index 2c16b60..d559974 100644 --- a/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java +++ b/src/main/java/com/ccapp/ccgo/mission/service/SubGroupMissionService.java @@ -18,6 +18,7 @@ import java.util.concurrent.ThreadLocalRandom; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.extern.slf4j.Slf4j; import java.util.Collections; import java.util.List; @@ -30,6 +31,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class SubGroupMissionService { private final SubGroupRepository subGroupRepository; @@ -117,27 +119,29 @@ public List getMissions(Long subGroupId) { .toList(); } - // 미션 새로고침 (완료 안 된 미션 삭제 후 새 할당) + /** + * 미션 새로고침 (완료 안 된 미션 삭제 후 새 할당) + */ @Transactional public void refreshSingleMission(Long subGroupId, Long subGroupMissionId, Integer score) { - System.out.println("[refreshSingleMission] 호출됨 - subGroupId: " + subGroupId + ", subGroupMissionId: " + subGroupMissionId + ", score: " + score); + log.info("[Mission] 미션 새로고침 시작 | subGroupId: {}, missionId: {}, score: {}", subGroupId, subGroupMissionId, score); - // 1. 서브그룹 조회 - SubGroup subGroup = subGroupRepository.findById(subGroupId) - .orElseThrow(() -> { - System.out.println("[refreshSingleMission] 서브그룹을 찾을 수 없습니다."); - return new IllegalArgumentException("서브그룹을 찾을 수 없습니다."); - }); + // 1. 서브그룹 조회 + SubGroup subGroup = subGroupRepository.findById(subGroupId) + .orElseThrow(() -> { + log.error("[Mission] 서브그룹을 찾을 수 없음 | subGroupId: {}", subGroupId); + return new IllegalArgumentException("서브그룹을 찾을 수 없습니다."); + }); // 2. 교체할 기존 미션 조회 SubGroupMission oldMission = subGroupMissionRepository.findById(subGroupMissionId) .orElseThrow(() -> { - System.out.println("[refreshSingleMission] 교체할 미션을 찾을 수 없습니다."); + log.error("[Mission] 교체할 미션을 찾을 수 없음 | missionId: {}", subGroupMissionId); return new IllegalArgumentException("교체할 미션을 찾을 수 없습니다."); }); if (!oldMission.getSubGroup().getId().equals(subGroupId)) { - System.out.println("[refreshSingleMission] 해당 미션이 서브그룹에 속하지 않습니다."); + log.error("[Mission] 해당 미션이 서브그룹에 속하지 않음 | subGroupId: {}, missionSubGroupId: {}", subGroupId, oldMission.getSubGroup().getId()); throw new IllegalArgumentException("해당 미션이 서브그룹에 속하지 않습니다."); } @@ -147,7 +151,7 @@ public void refreshSingleMission(Long subGroupId, Long subGroupMissionId, Intege .map(m -> m.getMissionTemplate().getId()) .toList(); - System.out.println("[refreshSingleMission] 현재 서브그룹에 할당된 동일 학점 미션 ID 목록: " + assignedMissionTemplateIds); + log.debug("[Mission] 현재 서브그룹에 할당된 동일 학점 미션 ID 목록: {}", assignedMissionTemplateIds); // 4. 교체 후보 미션 (현재 미션 제외) - 가변 리스트로 변환 List candidates = missionTemplateRepository.findByScore(score).stream() @@ -155,7 +159,7 @@ public void refreshSingleMission(Long subGroupId, Long subGroupMissionId, Intege .filter(mt -> !mt.getId().equals(oldMission.getMissionTemplate().getId())) // 기존 미션 제외 .collect(Collectors.toList()); - System.out.println("[refreshSingleMission] 교체 후보 미션 수: " + candidates.size()); + log.debug("[Mission] 교체 후보 미션 수: {}", candidates.size()); if (candidates.isEmpty()) { throw new IllegalStateException("교체 가능한 미션이 없습니다."); @@ -169,7 +173,7 @@ public void refreshSingleMission(Long subGroupId, Long subGroupMissionId, Intege oldMission.setMissionTemplate(newMissionTemplate); oldMission.setCompleted(false); - System.out.println("[refreshSingleMission] 랜덤으로 선택된 미션 ID: " + newMissionTemplate.getId()); + log.info("[Mission] 미션 교체 완료 | oldMissionId: {}, newMissionId: {}", oldMission.getMissionTemplate().getId(), newMissionTemplate.getId()); } From fee920d711e403c50bcfc7f624bc49d4b4b7f248 Mon Sep 17 00:00:00 2001 From: losecow Date: Thu, 14 Aug 2025 14:03:36 +0900 Subject: [PATCH 127/132] refactoring --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 5553d68..eafaa3e 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -3,7 +3,7 @@ name: Deploy Spring Boot to EC2 on: push: branches: - - REAL + - RefactoringBE jobs: deploy: From 3a875ba331c9bc22684cf571179725e56b342052 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 20 Aug 2025 11:40:01 +0900 Subject: [PATCH 128/132] update personal privacy --- CC_Maker_FE | 2 +- create_privacy_agreement_tables.sql | 58 ++++++++++++++ .../ccgo/user/controller/UserController.java | 29 ++++--- .../ccapp/ccgo/user/dto/UserRequestDto.java | 7 ++ .../ccapp/ccgo/user/dto/UserResponseDto.java | 7 ++ .../ccgo/user/entity/PrivacyAgreement.java | 52 +++++++++++++ .../java/com/ccapp/ccgo/user/entity/User.java | 16 ++++ .../ccapp/ccgo/user/mapper/UserMapper.java | 39 +++++++++- .../PrivacyAgreementRepository.java | 24 ++++++ .../ccapp/ccgo/user/service/UserService.java | 78 +++++++++---------- 10 files changed, 261 insertions(+), 51 deletions(-) create mode 100644 create_privacy_agreement_tables.sql create mode 100644 src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java create mode 100644 src/main/java/com/ccapp/ccgo/user/repository/PrivacyAgreementRepository.java diff --git a/CC_Maker_FE b/CC_Maker_FE index e22c51d..26f55c2 160000 --- a/CC_Maker_FE +++ b/CC_Maker_FE @@ -1 +1 @@ -Subproject commit e22c51de2d650b7bfeb6fd109c9d1faabbb8f00e +Subproject commit 26f55c206bd6b58e59904eeaf2e21be9bdddf0bb diff --git a/create_privacy_agreement_tables.sql b/create_privacy_agreement_tables.sql new file mode 100644 index 0000000..863687c --- /dev/null +++ b/create_privacy_agreement_tables.sql @@ -0,0 +1,58 @@ +-- 개인정보 동의서 테이블 생성 +CREATE TABLE privacy_agreements ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + version VARCHAR(20) NOT NULL UNIQUE, + content TEXT NOT NULL, + effective_date DATE NOT NULL, + created_at DATETIME NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE +); + +-- users 테이블에 개인정보 동의 관련 컬럼 추가 +ALTER TABLE users +ADD COLUMN privacy_agreement_version VARCHAR(20), +ADD COLUMN privacy_agreed BOOLEAN DEFAULT FALSE, +ADD COLUMN privacy_agreed_at DATETIME, +ADD COLUMN privacy_agreed_method VARCHAR(50), +ADD COLUMN privacy_agreed_environment VARCHAR(20); + +-- 초기 개인정보 동의서 데이터 삽입 (v1.0) +INSERT INTO privacy_agreements (version, content, effective_date, created_at, is_active) VALUES ( + 'v1.0', + '개인정보 수집 및 이용에 대한 안내 + +1. 수집하는 개인정보 항목 +- 필수항목: 이름, 생년월일, 이메일, 비밀번호, 성별 +- 선택항목: 없음 + +2. 개인정보의 수집 및 이용목적 +- 회원가입 및 서비스 이용 +- 서비스 제공 및 운영 +- 고객상담 및 문의응답 +- 서비스 개선 및 신규 서비스 개발 + +3. 개인정보의 보유 및 이용기간 +- 회원 탈퇴 시까지 (단, 관련 법령에 따라 보존이 필요한 경우 해당 기간까지) + +4. 개인정보의 파기절차 및 방법 +- 전자적 파일 형태로 저장된 개인정보는 복구 불가능한 방법으로 영구 삭제 +- 종이에 출력된 개인정보는 분쇄기로 분쇄하거나 소각을 통하여 파기 + +5. 동의 거부권 및 동의 거부에 따른 불이익 +- 개인정보 수집 및 이용에 대한 동의를 거부할 수 있습니다. +- 동의를 거부할 경우 회원가입 및 서비스 이용이 제한됩니다. + +위 내용에 동의하시면 체크박스를 선택해 주세요. + +───────────────────────────────────────── +시행일: 2025.08.20 / 버전: v1.0', + '2025-08-20', + NOW(), + TRUE +); + +-- 인덱스 생성 +CREATE INDEX idx_privacy_agreements_version ON privacy_agreements(version); +CREATE INDEX idx_privacy_agreements_active ON privacy_agreements(is_active); +CREATE INDEX idx_users_privacy_version ON users(privacy_agreement_version); +CREATE INDEX idx_users_privacy_agreed ON users(privacy_agreed); diff --git a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java index a7b717e..6d0a339 100644 --- a/src/main/java/com/ccapp/ccgo/user/controller/UserController.java +++ b/src/main/java/com/ccapp/ccgo/user/controller/UserController.java @@ -5,6 +5,7 @@ import com.ccapp.ccgo.user.dto.UserResponseDto; import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; import com.ccapp.ccgo.user.dto.PasswordChangeRequestDto; +import com.ccapp.ccgo.user.entity.PrivacyAgreement; import org.springframework.http.HttpStatus; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; @@ -48,25 +49,33 @@ public ResponseEntity updateCurrentUser(@Valid @RequestBody Use // 사용자 정보 전체 업데이트 (PUT) @PutMapping("/me") - public ResponseEntity updateCurrentUserFull(@Valid @RequestBody UserUpdateRequestDto userUpdateRequestDto) { - log.info("✅ 사용자 정보 전체 업데이트 요청: {}", userUpdateRequestDto); - UserResponseDto updated = userService.updateCurrentUserFull(userUpdateRequestDto); + public ResponseEntity updateCurrentUserFull(@Valid @RequestBody UserRequestDto userRequestDto) { + log.info("✅ 사용자 정보 전체 업데이트 요청: {}", userRequestDto); + UserResponseDto updated = userService.updateCurrentUserFull(userRequestDto); return ResponseEntity.ok(updated); } // 비밀번호 변경 - @PostMapping("/change-password") + @PatchMapping("/me/password") public ResponseEntity changePassword(@Valid @RequestBody PasswordChangeRequestDto passwordChangeRequestDto) { log.info("✅ 비밀번호 변경 요청"); userService.changePassword(passwordChangeRequestDto); return ResponseEntity.ok().build(); } - // 계정 탈퇴 - @DeleteMapping("/me") - public ResponseEntity deleteCurrentUser() { - log.info("✅ 계정 탈퇴 요청"); - userService.deleteCurrentUser(); - return ResponseEntity.ok().build(); + // 개인정보 동의서 조회 (현재 활성화된 버전) + @GetMapping("/privacy-agreement/current") + public ResponseEntity getCurrentPrivacyAgreement() { + log.info("✅ 현재 개인정보 동의서 조회 요청"); + PrivacyAgreement agreement = userService.getCurrentPrivacyAgreement(); + return ResponseEntity.ok(agreement); + } + + // 특정 버전의 개인정보 동의서 조회 + @GetMapping("/privacy-agreement/{version}") + public ResponseEntity getPrivacyAgreementByVersion(@PathVariable String version) { + log.info("✅ 개인정보 동의서 조회 요청 - 버전: {}", version); + PrivacyAgreement agreement = userService.getPrivacyAgreementByVersion(version); + return ResponseEntity.ok(agreement); } } diff --git a/src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java index d80c777..e495776 100644 --- a/src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserRequestDto.java @@ -16,4 +16,11 @@ public class UserRequestDto { private String name; private String gender; private LocalDate birthdate; + + // 개인정보 동의 관련 필드들 + private String privacyAgreementVersion; + private boolean privacyAgreed; + private String privacyAgreedAt; + private String privacyAgreedMethod; + private String privacyAgreedEnvironment; } diff --git a/src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java b/src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java index df5822f..896a1b3 100644 --- a/src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java +++ b/src/main/java/com/ccapp/ccgo/user/dto/UserResponseDto.java @@ -16,5 +16,12 @@ public class UserResponseDto { private LocalDateTime createdAt; private String role; private String mbti; // mbti 값 + + // 개인정보 동의 관련 필드들 + private String privacyAgreementVersion; + private boolean privacyAgreed; + private LocalDateTime privacyAgreedAt; + private String privacyAgreedMethod; + private String privacyAgreedEnvironment; } diff --git a/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java b/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java new file mode 100644 index 0000000..859deab --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java @@ -0,0 +1,52 @@ +package com.ccapp.ccgo.user.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 개인정보 동의서 전문을 저장하는 엔티티 + * - 버전별로 동의서 내용을 관리 + * - 사용자가 어떤 버전의 동의서에 동의했는지 추적 가능 + */ +@Entity +@Table(name = "privacy_agreements") +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class PrivacyAgreement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 동의서 버전 (예: "v1.0", "v1.1") + @Column(unique = true, nullable = false) + private String version; + + // 동의서 전문 내용 + @Column(columnDefinition = "TEXT", nullable = false) + private String content; + + // 시행일 + @Column(nullable = false) + private LocalDate effectiveDate; + + // 생성일시 + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + // 활성화 여부 (현재 사용 중인 버전인지) + @Column(nullable = false) + private boolean isActive = true; + + // 생성 시 자동으로 현재 시간 설정 + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/ccapp/ccgo/user/entity/User.java b/src/main/java/com/ccapp/ccgo/user/entity/User.java index 6579306..dca4a23 100644 --- a/src/main/java/com/ccapp/ccgo/user/entity/User.java +++ b/src/main/java/com/ccapp/ccgo/user/entity/User.java @@ -48,6 +48,22 @@ public class User { @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; + // 개인정보 동의 관련 필드들 + @Column(name = "privacy_agreement_version") + private String privacyAgreementVersion; + + @Column(name = "privacy_agreed") + private boolean privacyAgreed; + + @Column(name = "privacy_agreed_at") + private LocalDateTime privacyAgreedAt; + + @Column(name = "privacy_agreed_method") + private String privacyAgreedMethod; + + @Column(name = "privacy_agreed_environment") + private String privacyAgreedEnvironment; + // 회원 가입 시 자동으로 현재 시간 설정 @PrePersist public void prePersist() { diff --git a/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java b/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java index 8cd0f0a..42a11f9 100644 --- a/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java +++ b/src/main/java/com/ccapp/ccgo/user/mapper/UserMapper.java @@ -5,19 +5,51 @@ import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; import com.ccapp.ccgo.user.entity.User; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + public class UserMapper { // RequestDto -> Entity public static User toEntity(UserRequestDto dto, String encodedPassword) { if (dto == null) return null; - return User.builder() + User user = User.builder() .email(dto.getEmail()) .password(encodedPassword) .name(dto.getName()) .gender(dto.getGender()) .birthdate(dto.getBirthdate()) .build(); + + // 개인정보 동의 관련 필드 설정 + if (dto.getPrivacyAgreementVersion() != null) { + user.setPrivacyAgreementVersion(dto.getPrivacyAgreementVersion()); + } + user.setPrivacyAgreed(dto.isPrivacyAgreed()); + + // agreedAt 문자열을 LocalDateTime으로 변환 + if (dto.getPrivacyAgreedAt() != null && !dto.getPrivacyAgreedAt().trim().isEmpty()) { + try { + LocalDateTime agreedAt = LocalDateTime.parse(dto.getPrivacyAgreedAt(), + DateTimeFormatter.ISO_DATE_TIME); + user.setPrivacyAgreedAt(agreedAt); + } catch (Exception e) { + // 파싱 실패 시 현재 시간으로 설정 + user.setPrivacyAgreedAt(LocalDateTime.now()); + } + } else { + user.setPrivacyAgreedAt(LocalDateTime.now()); + } + + if (dto.getPrivacyAgreedMethod() != null) { + user.setPrivacyAgreedMethod(dto.getPrivacyAgreedMethod()); + } + if (dto.getPrivacyAgreedEnvironment() != null) { + user.setPrivacyAgreedEnvironment(dto.getPrivacyAgreedEnvironment()); + } + + return user; } // Entity -> ResponseDto @@ -31,6 +63,11 @@ public static UserResponseDto toDto(User user) { .gender(user.getGender()) .birthdate(user.getBirthdate()) .createdAt(user.getCreatedAt()) + .privacyAgreementVersion(user.getPrivacyAgreementVersion()) + .privacyAgreed(user.isPrivacyAgreed()) + .privacyAgreedAt(user.getPrivacyAgreedAt()) + .privacyAgreedMethod(user.getPrivacyAgreedMethod()) + .privacyAgreedEnvironment(user.getPrivacyAgreedEnvironment()) .build(); } diff --git a/src/main/java/com/ccapp/ccgo/user/repository/PrivacyAgreementRepository.java b/src/main/java/com/ccapp/ccgo/user/repository/PrivacyAgreementRepository.java new file mode 100644 index 0000000..a192b6a --- /dev/null +++ b/src/main/java/com/ccapp/ccgo/user/repository/PrivacyAgreementRepository.java @@ -0,0 +1,24 @@ +package com.ccapp.ccgo.user.repository; + +import com.ccapp.ccgo.user.entity.PrivacyAgreement; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface PrivacyAgreementRepository extends JpaRepository { + + // 버전으로 동의서 조회 + Optional findByVersion(String version); + + // 현재 활성화된 동의서 조회 + Optional findByIsActiveTrue(); + + // 특정 버전이 존재하는지 확인 + boolean existsByVersion(String version); + + // 최신 버전 조회 + @Query("SELECT pa FROM PrivacyAgreement pa WHERE pa.isActive = true ORDER BY pa.createdAt DESC") + Optional findLatestActive(); +} diff --git a/src/main/java/com/ccapp/ccgo/user/service/UserService.java b/src/main/java/com/ccapp/ccgo/user/service/UserService.java index 5806963..f62e2fc 100644 --- a/src/main/java/com/ccapp/ccgo/user/service/UserService.java +++ b/src/main/java/com/ccapp/ccgo/user/service/UserService.java @@ -8,7 +8,9 @@ import com.ccapp.ccgo.common.exception.CustomException; import com.ccapp.ccgo.auth.jwt.JwtProvider; import com.ccapp.ccgo.user.repository.UserRepository; +import com.ccapp.ccgo.user.repository.PrivacyAgreementRepository; import com.ccapp.ccgo.user.entity.User; +import com.ccapp.ccgo.user.entity.PrivacyAgreement; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; @@ -18,6 +20,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; @@ -26,6 +29,7 @@ public class UserService { private final UserRepository userRepository; + private final PrivacyAgreementRepository privacyAgreementRepository; private final PasswordEncoder passwordEncoder; private final JwtProvider jwtProvider; private final AuthenticationManager authenticationManager; @@ -36,6 +40,11 @@ public UserResponseDto register(UserRequestDto dto) { throw new CustomException("이미 가입된 이메일입니다.", HttpStatus.CONFLICT); } + // 개인정보 동의 검증 + if (dto.isPrivacyAgreed()) { + validatePrivacyAgreement(dto.getPrivacyAgreementVersion()); + } + String encodedPassword = passwordEncoder.encode(dto.getPassword()); User user = UserMapper.toEntity(dto, encodedPassword); userRepository.save(user); @@ -43,6 +52,18 @@ public UserResponseDto register(UserRequestDto dto) { return UserMapper.toDto(user); } + // 개인정보 동의서 버전 검증 + private void validatePrivacyAgreement(String version) { + if (version == null || version.trim().isEmpty()) { + throw new CustomException("개인정보 동의서 버전이 필요합니다.", HttpStatus.BAD_REQUEST); + } + + // 해당 버전의 동의서가 존재하는지 확인 + if (!privacyAgreementRepository.existsByVersion(version)) { + throw new CustomException("존재하지 않는 개인정보 동의서 버전입니다: " + version, HttpStatus.BAD_REQUEST); + } + } + // 2. 로그인: JWT 토큰 생성 반환 public String loginAndGetToken(String email, String password) { Authentication authentication = authenticationManager.authenticate( @@ -110,47 +131,27 @@ public UserResponseDto updateCurrentUser(UserUpdateRequestDto dto) { User user = userRepository.findByEmail(email) .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); - // 이메일 중복 체크 (다른 사용자가 같은 이메일을 사용하고 있는지) - if (!email.equals(dto.getEmail())) { - userRepository.findByEmail(dto.getEmail()) - .ifPresent(existingUser -> { - throw new CustomException("이미 사용 중인 이메일입니다.", HttpStatus.CONFLICT); - }); - } - - // 변경된 필드만 업데이트 UserMapper.updateEntityFromDto(user, dto); - userRepository.save(user); + return UserMapper.toDto(user); } // 9. 현재 사용자 정보 전체 업데이트 (PUT) - public UserResponseDto updateCurrentUserFull(UserUpdateRequestDto dto) { + public UserResponseDto updateCurrentUserFull(UserRequestDto dto) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String email = authentication.getName(); User user = userRepository.findByEmail(email) .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); - // 이메일 중복 체크 - if (!email.equals(dto.getEmail())) { - userRepository.findByEmail(dto.getEmail()) - .ifPresent(existingUser -> { - throw new CustomException("이미 사용 중인 이메일입니다.", HttpStatus.CONFLICT); - }); - } - - // 모든 필드 업데이트 - user.setName(dto.getName()); user.setEmail(dto.getEmail()); - // birthdate와 gender는 선택적 필드이므로 null 체크 - if (dto.getBirthdate() != null && !dto.getBirthdate().trim().isEmpty()) { - user.setBirthdate(dto.getBirthdateAsLocalDate()); - } - if (dto.getGender() != null && !dto.getGender().trim().isEmpty()) { - user.setGender(dto.getGender()); + if (dto.getPassword() != null && !dto.getPassword().isEmpty()) { + user.setPassword(passwordEncoder.encode(dto.getPassword())); } + user.setName(dto.getName()); + user.setGender(dto.getGender()); + user.setBirthdate(dto.getBirthdate()); userRepository.save(user); return UserMapper.toDto(user); @@ -170,20 +171,19 @@ public void changePassword(PasswordChangeRequestDto dto) { } // 새 비밀번호로 변경 - String encodedNewPassword = passwordEncoder.encode(dto.getNewPassword()); - user.setPassword(encodedNewPassword); - + user.setPassword(passwordEncoder.encode(dto.getNewPassword())); userRepository.save(user); } - // 11. 현재 사용자 계정 삭제 (탈퇴) - public void deleteCurrentUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String email = authentication.getName(); - - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new CustomException("로그인한 사용자를 찾을 수 없습니다.", HttpStatus.NOT_FOUND)); - - userRepository.delete(user); + // 11. 개인정보 동의서 조회 (현재 활성화된 버전) + public PrivacyAgreement getCurrentPrivacyAgreement() { + return privacyAgreementRepository.findByIsActiveTrue() + .orElseThrow(() -> new CustomException("활성화된 개인정보 동의서가 없습니다.", HttpStatus.NOT_FOUND)); + } + + // 12. 특정 버전의 개인정보 동의서 조회 + public PrivacyAgreement getPrivacyAgreementByVersion(String version) { + return privacyAgreementRepository.findByVersion(version) + .orElseThrow(() -> new CustomException("해당 버전의 개인정보 동의서가 없습니다: " + version, HttpStatus.NOT_FOUND)); } } From 4554a46e9ee4a34f9dbd3bdc802c7487c5bb6921 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 20 Aug 2025 11:43:16 +0900 Subject: [PATCH 129/132] update personal privacy --- src/main/java/com/ccapp/ccgo/common/SecurityConfig.java | 6 +++--- .../com/ccapp/ccgo/common/test/UserApiTestController.java | 3 ++- src/main/java/com/ccapp/ccgo/team/entity/Team.java | 1 + src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java | 1 + .../java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index 0f78cd1..fadc49f 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -57,11 +57,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .headers(headers -> headers // X-Frame-Options: 클릭재킹 공격 방지 - .frameOptions().deny() + .frameOptions(frameOptions -> frameOptions.deny()) // X-Content-Type-Options: MIME 타입 스니핑 방지 - .contentTypeOptions().and() + .contentTypeOptions(contentTypeOptions -> {}) // X-XSS-Protection: XSS 공격 방지 - .xssProtection().and() + .xssProtection(xssProtection -> {}) // HSTS: HTTPS 강제 (2년으로 연장) .httpStrictTransportSecurity(hstsConfig -> hstsConfig .maxAgeInSeconds(63072000) // 2년 diff --git a/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java b/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java index 774eb78..2f489f4 100644 --- a/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java +++ b/src/main/java/com/ccapp/ccgo/common/test/UserApiTestController.java @@ -1,5 +1,6 @@ package com.ccapp.ccgo.common.test; +import com.ccapp.ccgo.user.dto.UserRequestDto; import com.ccapp.ccgo.user.dto.UserUpdateRequestDto; import com.ccapp.ccgo.user.dto.PasswordChangeRequestDto; import com.ccapp.ccgo.user.service.UserService; @@ -41,7 +42,7 @@ public ResponseEntity testUpdateCurrentUser(@RequestBody UserUpdateRequestDto } @PutMapping("/me") - public ResponseEntity testUpdateCurrentUserFull(@RequestBody UserUpdateRequestDto dto) { + public ResponseEntity testUpdateCurrentUserFull(@RequestBody UserRequestDto dto) { log.info("🧪 테스트: 사용자 정보 전체 업데이트 - {}", dto); try { var result = userService.updateCurrentUserFull(dto); diff --git a/src/main/java/com/ccapp/ccgo/team/entity/Team.java b/src/main/java/com/ccapp/ccgo/team/entity/Team.java index 36a6b79..a327c30 100644 --- a/src/main/java/com/ccapp/ccgo/team/entity/Team.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/Team.java @@ -41,6 +41,7 @@ public class Team { // 매칭 시작 여부 추가 @Column(name = "matching_started", nullable = false) + @Builder.Default private boolean matchingStarted = false; @Column(name = "min_score") diff --git a/src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java b/src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java index b1b9758..f40a7bb 100644 --- a/src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java +++ b/src/main/java/com/ccapp/ccgo/team/entity/TeamMember.java @@ -58,6 +58,7 @@ public class TeamMember { //설문조사를 햇슴까 @Column(nullable = false) + @Builder.Default private boolean isSurveyCompleted = false; // 설문조사 기본값 false // MBTI 필드 diff --git a/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java b/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java index 859deab..f0265f8 100644 --- a/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java +++ b/src/main/java/com/ccapp/ccgo/user/entity/PrivacyAgreement.java @@ -42,6 +42,7 @@ public class PrivacyAgreement { // 활성화 여부 (현재 사용 중인 버전인지) @Column(nullable = false) + @Builder.Default private boolean isActive = true; // 생성 시 자동으로 현재 시간 설정 From 6d9487f2ce7cb6fd50ffc95cb95e3d9b07f42db4 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 20 Aug 2025 14:22:14 +0900 Subject: [PATCH 130/132] update personal privacy --- .../java/com/ccapp/ccgo/common/SecurityConfig.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java index fadc49f..8fbfb73 100644 --- a/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java +++ b/src/main/java/com/ccapp/ccgo/common/SecurityConfig.java @@ -71,7 +71,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .authorizeHttpRequests(auth -> auth // 인증 필요 없는 엔드포인트 - .requestMatchers("/api/auth/**", "/api/user/register").permitAll() + .requestMatchers("/api/auth/**", "/api/user/register", "/api/user/privacy-agreement/**").permitAll() // LEADER 전용 API (팀 관리) .requestMatchers("/api/team/**").hasAnyRole("LEADER", "MEMBER") @@ -92,7 +92,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:3000", "http://127.0.0.1:5173")); // 개발용 origin들 + config.setAllowedOrigins(List.of( + "http://localhost:3000", + "http://localhost:5173", + "http://127.0.0.1:3000", + "http://127.0.0.1:5173", + "http://3.39.54.128:8080" // 실제 서버 IP로 변경 필요 + )); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); // 쿠키 허용 From 90a9650d71209a097ec3903d5c3842a2933a8907 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 20 Aug 2025 15:36:59 +0900 Subject: [PATCH 131/132] update personal privacy --- .../matching/service/MatchingService.java | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index a9e5077..0bbd511 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -141,8 +141,9 @@ private void handleLeftovers(List males, List females, L saveSubGroupMember(g, males.get(1).getUser()); groups.add(g); } else if (males.size() == 1) { + // 1명이 남으면 유사도 기준으로 기존 그룹에 추가 (1명짜리 그룹 절대 생성 금지) SubGroup best = findBestGroupToInsert(males.get(0), groups, teamId); - if (best != null && best.getMemberCount() < MAX_GROUP_SIZE) { + if (best != null) { saveSubGroupMember(best, males.get(0).getUser()); best.setMemberCount(best.getMemberCount() + 1); subGroupRepository.save(best); @@ -150,8 +151,9 @@ private void handleLeftovers(List males, List females, L } for (TeamMember female : females) { + // 여자도 유사도 기준으로 기존 그룹에 추가 (1명짜리 그룹 절대 생성 금지) SubGroup best = findBestGroupToInsert(female, groups, teamId); - if (best != null && best.getMemberCount() < MAX_GROUP_SIZE && isFemaleInsertable(best, female, females.size())) { + if (best != null && isFemaleInsertable(best, female, females.size())) { saveSubGroupMember(best, female.getUser()); best.setMemberCount(best.getMemberCount() + 1); subGroupRepository.save(best); @@ -180,6 +182,14 @@ private void handleMaleOnlyGroups(List males, List groups, saveSubGroupMember(group, member.getUser()); } groups.add(group); + } else if (groupMembers.size() == 1) { + // 1명이 남으면 유사도 기준으로 기존 그룹에 추가 (1명짜리 그룹 절대 생성 금지) + SubGroup best = findBestGroupToInsert(groupMembers.get(0), groups, teamId); + if (best != null) { + saveSubGroupMember(best, groupMembers.get(0).getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); + } } } } @@ -228,7 +238,7 @@ private void handleFemaleOnlyGroups(List females, List gro TeamMember remainingFemale = females.get(femaleCount - 1); // 설문 조사 결과가 가장 일치하는 그룹에 추가 SubGroup bestGroup = findBestGroupToInsert(remainingFemale, groups, teamId); - if (bestGroup != null && bestGroup.getMemberCount() < 3) { + if (bestGroup != null) { saveSubGroupMember(bestGroup, remainingFemale.getUser()); bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); subGroupRepository.save(bestGroup); @@ -263,8 +273,26 @@ private boolean isFemaleInsertable(SubGroup group, TeamMember female, int totalF } private SubGroup findBestGroupToInsert(TeamMember tm, List groups, Long teamId) { + // 1. 4명이 아닌 그룹 중에서 3명이 아닌 그룹 (즉, 2명 그룹) 우선 선택 + Optional twoMemberGroup = groups.stream() + .filter(g -> g.getMemberCount() == 2) + .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))); + + if (twoMemberGroup.isPresent()) { + return twoMemberGroup.get(); + } + + // 2. 모든 그룹이 3명이라면 3명 그룹 중에서 유사도가 가장 높은 그룹 선택 + Optional threeMemberGroup = groups.stream() + .filter(g -> g.getMemberCount() == 3) + .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))); + + if (threeMemberGroup.isPresent()) { + return threeMemberGroup.get(); + } + + // 3. 모든 그룹이 4명이어도 유사도가 가장 높은 그룹 선택 (1명짜리 그룹 절대 생성 금지) return groups.stream() - .filter(g -> g.getMemberCount() < MAX_GROUP_SIZE) .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))) .orElse(null); } From 744d77ed9bffb85218ac90a7354d82a1062d71b4 Mon Sep 17 00:00:00 2001 From: losecow Date: Wed, 20 Aug 2025 15:55:19 +0900 Subject: [PATCH 132/132] update personal privacy --- .../matching/service/MatchingService.java | 320 +++++++++++------- 1 file changed, 201 insertions(+), 119 deletions(-) diff --git a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java index 0bbd511..78b6c13 100644 --- a/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java +++ b/src/main/java/com/ccapp/ccgo/matching/service/MatchingService.java @@ -2,16 +2,20 @@ import com.ccapp.ccgo.matching.domain.MbtiScoreProvider; import com.ccapp.ccgo.matching.domain.PairMatch; -import com.ccapp.ccgo.matching.domain.entity.*; +import com.ccapp.ccgo.matching.domain.entity.Answer; +import com.ccapp.ccgo.matching.domain.entity.Question; +import com.ccapp.ccgo.matching.domain.entity.SubGroup; +import com.ccapp.ccgo.matching.domain.entity.SubGroupMember; import com.ccapp.ccgo.matching.dto.MatchingResponseDto; import com.ccapp.ccgo.matching.dto.MatchingResultDto; -import com.ccapp.ccgo.matching.repository.*; +import com.ccapp.ccgo.matching.repository.SubGroupMemberRepository; +import com.ccapp.ccgo.matching.repository.SubGroupRepository; +import com.ccapp.ccgo.question.repository.AnswerRepository; +import com.ccapp.ccgo.question.repository.QuestionRepository; import com.ccapp.ccgo.question.dto.AnswerRequestDto; import com.ccapp.ccgo.question.dto.QuestionRequestDto; import com.ccapp.ccgo.question.dto.QuestionResponseDto; import com.ccapp.ccgo.question.dto.QuestionUpdateDto; -import com.ccapp.ccgo.question.repository.AnswerRepository; -import com.ccapp.ccgo.question.repository.QuestionRepository; import com.ccapp.ccgo.team.entity.Team; import com.ccapp.ccgo.team.entity.TeamMember; import com.ccapp.ccgo.team.repository.TeamMemberRepository; @@ -26,7 +30,6 @@ import java.util.*; import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; @Slf4j @Service @@ -49,10 +52,17 @@ public class MatchingService { @Transactional public MatchingResponseDto performMatching(Long teamId) { List members = teamMemberRepository.findByTeam_TeamIdAndIsActiveTrue(teamId); + if (members.isEmpty()) { + return new MatchingResponseDto(teamId, "", true, Collections.emptyList()); + } Team team = members.get(0).getTeam(); - List males = members.stream().filter(m -> "MALE".equalsIgnoreCase(m.getUser().getGender())).collect(Collectors.toList()); - List females = members.stream().filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())).collect(Collectors.toList()); + List males = members.stream() + .filter(m -> "MALE".equalsIgnoreCase(m.getUser().getGender())) + .collect(Collectors.toList()); + List females = members.stream() + .filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())) + .collect(Collectors.toList()); List matchCandidates = createPairMatchCandidates(males, females, teamId); matchCandidates.sort(Comparator.comparingDouble(PairMatch::getTotalScore).reversed()); @@ -62,13 +72,18 @@ public MatchingResponseDto performMatching(Long teamId) { List groups = new ArrayList<>(); int groupIndex = 1; + // 1) 커플 우선 매칭 for (PairMatch pair : matchCandidates) { Long maleId = pair.getMale().getUser().getId(); Long femaleId = pair.getFemale().getUser().getId(); if (usedMaleIds.contains(maleId) || usedFemaleIds.contains(femaleId)) continue; String groupName = team.getTeamName() + groupIndex++; - SubGroup group = SubGroup.builder().team(team).name(groupName).memberCount(2).build(); + SubGroup group = SubGroup.builder() + .team(team) + .name(groupName) + .memberCount(2) + .build(); subGroupRepository.save(group); saveSubGroupMember(group, pair.getMale().getUser()); @@ -79,10 +94,18 @@ public MatchingResponseDto performMatching(Long teamId) { groups.add(group); } - List leftoverMales = males.stream().filter(m -> !usedMaleIds.contains(m.getUser().getId())).collect(Collectors.toList()); - List leftoverFemales = females.stream().filter(f -> !usedFemaleIds.contains(f.getUser().getId())).collect(Collectors.toList()); + // 커플로 먼저 만들어진 그룹들만을 "삽입 타깃"으로 고정 + List coupleGroups = new ArrayList<>(groups); - handleLeftovers(leftoverMales, leftoverFemales, groups, team, groupIndex, teamId); + List leftoverMales = males.stream() + .filter(m -> !usedMaleIds.contains(m.getUser().getId())) + .collect(Collectors.toList()); + List leftoverFemales = females.stream() + .filter(f -> !usedFemaleIds.contains(f.getUser().getId())) + .collect(Collectors.toList()); + + // 2) 잉여 인원 처리 + handleLeftovers(leftoverMales, leftoverFemales, groups, coupleGroups, team, groupIndex, teamId); return buildMatchingResponseDto(team, groups); } @@ -104,8 +127,10 @@ private double calculateSimilarity(TeamMember a, TeamMember b, Long teamId) { int totalSim = 0; for (Question q : questions) { - int sa = answerRepository.findByUser_IdAndQuestionId(a.getUser().getId(), q.getId()).map(Answer::getScore).orElse(0); - int sb = answerRepository.findByUser_IdAndQuestionId(b.getUser().getId(), q.getId()).map(Answer::getScore).orElse(0); + int sa = answerRepository.findByUser_IdAndQuestionId(a.getUser().getId(), q.getId()) + .map(Answer::getScore).orElse(0); + int sb = answerRepository.findByUser_IdAndQuestionId(b.getUser().getId(), q.getId()) + .map(Answer::getScore).orElse(0); totalSim += Math.max(0, 5 - Math.abs(sa - sb)); } @@ -121,124 +146,200 @@ private double calculateMbtiScore(TeamMember a, TeamMember b) { return mbtiScoreProvider.getScore(mbtiA, mbtiB) + mbtiScoreProvider.getScore(mbtiB, mbtiA); } - private void handleLeftovers(List males, List females, List groups, Team team, int groupIndex, Long teamId) { - // 남자만 있는 경우 처리 + // ★ 시그니처 변경: insertTargets = 커플 그룹들 + private void handleLeftovers( + List males, + List females, + List allGroups, + List insertTargets, // 커플로 성사된 그룹만 + Team team, + int groupIndex, + Long teamId + ) { + // 여자가 없고 남자만 남은 경우 if (females.isEmpty() && !males.isEmpty()) { - handleMaleOnlyGroups(males, groups, team, groupIndex, teamId); + handleMaleOnlyGroups(males, allGroups, insertTargets, team, groupIndex, teamId); return; } - - // 여자만 있는 경우 처리 + + // 남자가 없고 여자만 남은 경우 if (males.isEmpty() && !females.isEmpty()) { - handleFemaleOnlyGroups(females, groups, team, groupIndex, teamId); + handleFemaleOnlyGroups(females, allGroups, insertTargets, team, groupIndex, teamId); return; } - + + // 남자 2명 남으면 남남 그룹 생성 (규칙) if (males.size() == 2) { - SubGroup g = SubGroup.builder().team(team).name(team.getTeamName() + groupIndex++).memberCount(2).build(); + SubGroup g = SubGroup.builder() + .team(team) + .name(team.getTeamName() + groupIndex++) + .memberCount(2) + .build(); subGroupRepository.save(g); saveSubGroupMember(g, males.get(0).getUser()); saveSubGroupMember(g, males.get(1).getUser()); - groups.add(g); + allGroups.add(g); + males = Collections.emptyList(); // 소비 } else if (males.size() == 1) { - // 1명이 남으면 유사도 기준으로 기존 그룹에 추가 (1명짜리 그룹 절대 생성 금지) - SubGroup best = findBestGroupToInsert(males.get(0), groups, teamId); - if (best != null) { + // 남자 1명 남으면 커플 그룹에 삽입 + SubGroup best = findBestGroupToInsert(males.get(0), insertTargets, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE) { saveSubGroupMember(best, males.get(0).getUser()); best.setMemberCount(best.getMemberCount() + 1); subGroupRepository.save(best); } + males = Collections.emptyList(); + } else if (males.size() > 2) { + // 2명씩 남남 그룹으로 소진, 홀수 1명 남으면 커플 그룹에 삽입 + int i = 0; + while (i + 1 < males.size()) { + SubGroup g = SubGroup.builder() + .team(team) + .name(team.getTeamName() + groupIndex++) + .memberCount(2) + .build(); + subGroupRepository.save(g); + saveSubGroupMember(g, males.get(i).getUser()); + saveSubGroupMember(g, males.get(i + 1).getUser()); + allGroups.add(g); + i += 2; + } + if (i < males.size()) { // 홀수 1명 + TeamMember last = males.get(i); + SubGroup best = findBestGroupToInsert(last, insertTargets, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE) { + saveSubGroupMember(best, last.getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); + } + } + males = Collections.emptyList(); } + // 여자 잉여: 새 그룹 만들지 말고 커플 그룹에만 삽입 for (TeamMember female : females) { - // 여자도 유사도 기준으로 기존 그룹에 추가 (1명짜리 그룹 절대 생성 금지) - SubGroup best = findBestGroupToInsert(female, groups, teamId); - if (best != null && isFemaleInsertable(best, female, females.size())) { + SubGroup best = findBestGroupToInsert(female, insertTargets, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE && isFemaleInsertable(best, female, females.size())) { saveSubGroupMember(best, female.getUser()); best.setMemberCount(best.getMemberCount() + 1); subGroupRepository.save(best); } } } - - // 남자만 있는 경우 그룹 생성 (2명씩 커플로) - private void handleMaleOnlyGroups(List males, List groups, Team team, int groupIndex, Long teamId) { + + // ★ 수정: 남자만 있는 경우도 규칙(남남 2인 그룹) 우선, 홀수는 커플 그룹에 삽입 + private void handleMaleOnlyGroups( + List males, + List allGroups, + List insertTargets, // 커플 그룹 + Team team, + int groupIndex, + Long teamId + ) { int maleCount = males.size(); - - // 2명씩 커플로 만들기 - for (int i = 0; i < maleCount; i += 2) { - int endIndex = Math.min(i + 2, maleCount); - List groupMembers = males.subList(i, endIndex); - - if (groupMembers.size() >= 2) { - SubGroup group = SubGroup.builder() - .team(team) - .name(team.getTeamName() + groupIndex++) - .memberCount(groupMembers.size()) - .build(); - subGroupRepository.save(group); - - for (TeamMember member : groupMembers) { - saveSubGroupMember(group, member.getUser()); - } - groups.add(group); - } else if (groupMembers.size() == 1) { - // 1명이 남으면 유사도 기준으로 기존 그룹에 추가 (1명짜리 그룹 절대 생성 금지) - SubGroup best = findBestGroupToInsert(groupMembers.get(0), groups, teamId); - if (best != null) { - saveSubGroupMember(best, groupMembers.get(0).getUser()); - best.setMemberCount(best.getMemberCount() + 1); - subGroupRepository.save(best); - } + if (maleCount == 1) { + SubGroup best = findBestGroupToInsert(males.get(0), insertTargets, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE) { + saveSubGroupMember(best, males.get(0).getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); + } + return; + } + // 2명씩 남남 그룹 생성 + int i = 0; + while (i + 1 < maleCount) { + SubGroup group = SubGroup.builder() + .team(team) + .name(team.getTeamName() + groupIndex++) + .memberCount(2) + .build(); + subGroupRepository.save(group); + saveSubGroupMember(group, males.get(i).getUser()); + saveSubGroupMember(group, males.get(i + 1).getUser()); + allGroups.add(group); + i += 2; + } + // 홀수 1명 남으면 커플 그룹에 삽입 + if (i < maleCount) { + TeamMember last = males.get(i); + SubGroup best = findBestGroupToInsert(last, insertTargets, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE) { + saveSubGroupMember(best, last.getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); + } + } + } + + // ★ 완전 변경: 여자만 있는 경우 기본은 커플 그룹 삽입만 허용 + // 단, 커플 그룹이 하나도 없다면(완전 무매칭) 예전 분할 방식 폴백 적용 + private void handleFemaleOnlyGroups( + List females, + List allGroups, + List insertTargets, + Team team, + int groupIndex, + Long teamId + ) { + if (insertTargets == null || insertTargets.isEmpty()) { + // 폴백: 커플 그룹이 없으면 이전 방식으로 안전 분할 + fallbackSplitFemaleOnly(females, allGroups, team, groupIndex, teamId); + return; + } + + for (TeamMember female : females) { + SubGroup best = findBestGroupToInsert(female, insertTargets, teamId); + if (best != null && best.getMemberCount() < MAX_GROUP_SIZE && isFemaleInsertable(best, female, females.size())) { + saveSubGroupMember(best, female.getUser()); + best.setMemberCount(best.getMemberCount() + 1); + subGroupRepository.save(best); } } } - - // 여자만 있는 경우 그룹 생성 - private void handleFemaleOnlyGroups(List females, List groups, Team team, int groupIndex, Long teamId) { + + // 폴백 로직: 커플 그룹이 0개인 경우에만 사용 (이전 구현 유지) + private void fallbackSplitFemaleOnly( + List females, + List groups, + Team team, + int groupIndex, + Long teamId + ) { int femaleCount = females.size(); - + if (femaleCount <= 3) { - // 3명 이하면 하나의 그룹으로 SubGroup group = SubGroup.builder() .team(team) .name(team.getTeamName() + groupIndex++) .memberCount(femaleCount) .build(); subGroupRepository.save(group); - for (TeamMember member : females) { saveSubGroupMember(group, member.getUser()); } groups.add(group); } else if (femaleCount == 4) { - // 4명일 때: 2/2 createFemaleGroup(females.subList(0, 2), groups, team, groupIndex++, teamId); createFemaleGroup(females.subList(2, 4), groups, team, groupIndex++, teamId); } else if (femaleCount == 5) { - // 5명일 때: 2/3 createFemaleGroup(females.subList(0, 2), groups, team, groupIndex++, teamId); createFemaleGroup(females.subList(2, 5), groups, team, groupIndex++, teamId); } else if (femaleCount == 6) { - // 6명일 때: 2/2/2 createFemaleGroup(females.subList(0, 2), groups, team, groupIndex++, teamId); createFemaleGroup(females.subList(2, 4), groups, team, groupIndex++, teamId); createFemaleGroup(females.subList(4, 6), groups, team, groupIndex++, teamId); } else { - // 6명 초과일 때: 2명씩 그룹으로 나누고, 남은 사람들은 기존 그룹에 추가 int groupCount = femaleCount / 2; for (int i = 0; i < groupCount; i++) { int startIndex = i * 2; int endIndex = Math.min(startIndex + 2, femaleCount); createFemaleGroup(females.subList(startIndex, endIndex), groups, team, groupIndex++, teamId); } - - // 남은 사람들 처리 (1명이 남은 경우) if (femaleCount % 2 == 1) { TeamMember remainingFemale = females.get(femaleCount - 1); - // 설문 조사 결과가 가장 일치하는 그룹에 추가 SubGroup bestGroup = findBestGroupToInsert(remainingFemale, groups, teamId); - if (bestGroup != null) { + if (bestGroup != null && bestGroup.getMemberCount() < MAX_GROUP_SIZE) { saveSubGroupMember(bestGroup, remainingFemale.getUser()); bestGroup.setMemberCount(bestGroup.getMemberCount() + 1); subGroupRepository.save(bestGroup); @@ -246,8 +347,8 @@ private void handleFemaleOnlyGroups(List females, List gro } } } - - // 여자 그룹 생성 헬퍼 메서드 + + // 여자 그룹 생성 헬퍼 (폴백에서만 사용) private void createFemaleGroup(List groupMembers, List groups, Team team, int groupIndex, Long teamId) { SubGroup group = SubGroup.builder() .team(team) @@ -255,7 +356,7 @@ private void createFemaleGroup(List groupMembers, List gro .memberCount(groupMembers.size()) .build(); subGroupRepository.save(group); - + for (TeamMember member : groupMembers) { saveSubGroupMember(group, member.getUser()); } @@ -267,32 +368,35 @@ private boolean isFemaleInsertable(SubGroup group, TeamMember female, int totalF long femaleNum = members.stream().filter(m -> "FEMALE".equalsIgnoreCase(m.getUser().getGender())).count(); long maleNum = members.size() - femaleNum; if ("FEMALE".equalsIgnoreCase(female.getUser().getGender())) { + // 남자 2+ / 여자 0 구성에, 여자가 잔여가 1명 초과면 먼저 다른 커플부터 채우도록 억제 if (maleNum > 1 && femaleNum == 0 && totalFemaleLeft > 1) return false; } return true; } - private SubGroup findBestGroupToInsert(TeamMember tm, List groups, Long teamId) { - // 1. 4명이 아닌 그룹 중에서 3명이 아닌 그룹 (즉, 2명 그룹) 우선 선택 - Optional twoMemberGroup = groups.stream() + // ★ 시그니처 변경: 삽입 후보 그룹을 파라미터로 (커플 그룹만 넘기도록) + private SubGroup findBestGroupToInsert(TeamMember tm, List candidateGroups, Long teamId) { + if (candidateGroups == null || candidateGroups.isEmpty()) return null; + + // 1) 2명 그룹(커플) 우선 + Optional twoMemberGroup = candidateGroups.stream() .filter(g -> g.getMemberCount() == 2) + .filter(g -> g.getMemberCount() < MAX_GROUP_SIZE) .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))); - - if (twoMemberGroup.isPresent()) { - return twoMemberGroup.get(); - } - - // 2. 모든 그룹이 3명이라면 3명 그룹 중에서 유사도가 가장 높은 그룹 선택 - Optional threeMemberGroup = groups.stream() + + if (twoMemberGroup.isPresent()) return twoMemberGroup.get(); + + // 2) 3명 그룹 중 최대 유사도 + Optional threeMemberGroup = candidateGroups.stream() .filter(g -> g.getMemberCount() == 3) + .filter(g -> g.getMemberCount() < MAX_GROUP_SIZE) .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))); - - if (threeMemberGroup.isPresent()) { - return threeMemberGroup.get(); - } - - // 3. 모든 그룹이 4명이어도 유사도가 가장 높은 그룹 선택 (1명짜리 그룹 절대 생성 금지) - return groups.stream() + + if (threeMemberGroup.isPresent()) return threeMemberGroup.get(); + + // 3) 여유 있는 그룹 중 최대 유사도 + return candidateGroups.stream() + .filter(g -> g.getMemberCount() < MAX_GROUP_SIZE) .max(Comparator.comparingDouble(g -> calculateAverageSimilarity(tm, g, teamId))) .orElse(null); } @@ -301,7 +405,8 @@ private double calculateAverageSimilarity(TeamMember user, SubGroup group, Long List members = subGroupMemberRepository.findBySubGroup_Id(group.getId()); double total = 0; for (SubGroupMember m : members) { - TeamMember tm = teamMemberRepository.findByUser_IdAndTeam_TeamId(m.getUser().getId(), teamId).orElseThrow(); + TeamMember tm = teamMemberRepository.findByUser_IdAndTeam_TeamId(m.getUser().getId(), teamId) + .orElseThrow(); total += calculateSimilarity(user, tm, teamId); } return members.isEmpty() ? 0 : total / members.size(); @@ -317,7 +422,8 @@ private MatchingResponseDto buildMatchingResponseDto(Team team, List g List members = subGroupMemberRepository.findBySubGroup_Id(g.getId()); List users = members.stream().map(m -> { User u = m.getUser(); - TeamMember tm = teamMemberRepository.findByUser_IdAndTeam_TeamId(u.getId(), team.getTeamId()).orElseThrow(); + TeamMember tm = teamMemberRepository.findByUser_IdAndTeam_TeamId(u.getId(), team.getTeamId()) + .orElseThrow(); return UserResponseDto.builder() .id(u.getId()) .email(u.getEmail()) @@ -334,17 +440,13 @@ private MatchingResponseDto buildMatchingResponseDto(Team team, List g return new MatchingResponseDto(team.getTeamId(), team.getTeamName(), true, result); } + // ====== 설문/질문 CRUD ====== - // 유저 ID 기준으로 기존 Answer 삭제 - // 해당 유저의 기존 Answer를 삭제하고 새 Answer를 저장 - // 새 Answer를 전부 INSERT - // MatchingService가 매칭 돌릴 때 최신 데이터를 사용 @Transactional public void saveAnswers(AnswerRequestDto dto, User user) { Long userId = user.getId(); Long teamId = dto.getTeamId(); - // 해당 팀의 질문만 필터링해서 삭제 List teamQuestions = questionRepository.findByTeam_TeamId(teamId); List teamQuestionIds = teamQuestions.stream() .map(Question::getId) @@ -354,14 +456,12 @@ public void saveAnswers(AnswerRequestDto dto, User user) { .collect(Collectors.toList()); answerRepository.deleteAll(existingAnswers); - // 덮어쓰기 - Team team = teamRepository.findById(teamId) .orElseThrow(() -> new IllegalArgumentException("Team not found")); List newAnswers = dto.getAnswers().stream() .map(single -> Answer.builder() .user(user) - .questionId(single.getQuestionId()) // question 객체 대신 questionId 직접 저장 + .questionId(single.getQuestionId()) .score(single.getScore()) .team(team) .build()) @@ -379,7 +479,6 @@ public void saveAnswers(AnswerRequestDto dto, User user) { } } - // 한 번에 여러 개의 새로운 Question을 DB에 등록 @Transactional public void createQuestions(QuestionRequestDto dto) { Long teamId = dto.getTeamId(); @@ -394,8 +493,6 @@ public void createQuestions(QuestionRequestDto dto) { questionRepository.saveAll(questions); } - - // 특정 Question의 질문 내용을 수정 @Transactional public void updateQuestion(Long questionId, QuestionUpdateDto dto) { Question question = questionRepository.findById(questionId) @@ -404,8 +501,6 @@ public void updateQuestion(Long questionId, QuestionUpdateDto dto) { question.setText(dto.getText()); } - - // 특정 팀의 모든 질문 리스트를 조회 (읽기 전용 트랜잭션) @Transactional(readOnly = true) public List getQuestions(Long teamId) { List questions = questionRepository.findByTeam_TeamId(teamId); @@ -418,15 +513,10 @@ public List getQuestions(Long teamId) { .collect(Collectors.toList()); } - - // Question에 연결된 Answer들을 먼저 삭제한 뒤, Question을 삭제 @Transactional public void deleteQuestion(Long questionId) { - // 삭제할 Question과 연결된 모든 Answer 레코드 조회 List answers = answerRepository.findByQuestionId(questionId); - // 조회된 Answer 레코드 전부 삭제 answerRepository.deleteAll(answers); - // 마지막으로 Question 자체 삭제 questionRepository.deleteById(questionId); } @@ -437,20 +527,16 @@ public void deleteQuestion(Long questionId) { public List getMatchedUserNames(Long userId, Long teamId) { log.info("[Matching] 매칭된 팀원 조회 | userId: {}, teamId: {}", userId, teamId); - // 1. 유저 존재 확인 userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다.")); - // 2. 팀 소속 여부 확인 (활성 멤버만) boolean isMember = teamMemberRepository.existsByUser_IdAndTeam_TeamIdAndIsActiveTrue(userId, teamId); if (!isMember) { throw new RuntimeException("해당 팀에 소속되어 있지 않습니다."); } - // 3. 유저가 속한 SubGroup 멤버(본인 제외) 조회 List matchedUsers = subGroupMemberRepository.findTeamMatchedMembersExcludingUser(userId, teamId); - // 4. 이름만 리스트로 변환 후 반환 List matchedNames = matchedUsers.stream() .map(User::getName) .collect(Collectors.toList()); @@ -459,8 +545,6 @@ public List getMatchedUserNames(Long userId, Long teamId) { return matchedNames; } - - /** * 사용자의 서브그룹 ID 조회 */ @@ -468,6 +552,4 @@ public Long findSubGroupIdByTeamIdAndUserId(Long teamId, Long userId) { return subGroupMemberRepository.findSubGroupIdByTeamIdAndUserId(teamId, userId) .orElse(null); } - - }