diff --git a/admin/build.gradle b/admin/build.gradle index fda8bf5..fd1696b 100644 --- a/admin/build.gradle +++ b/admin/build.gradle @@ -10,6 +10,11 @@ repositories { } dependencies { + implementation project(':common:domain') + implementation project(':common:format') + implementation project(':common:exception') + implementation project(':common:auth') + testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' } diff --git a/admin/src/main/java/org/yapp/AdminApplication.java b/admin/src/main/java/org/yapp/AdminApplication.java new file mode 100644 index 0000000..52664ae --- /dev/null +++ b/admin/src/main/java/org/yapp/AdminApplication.java @@ -0,0 +1,12 @@ +package org.yapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AdminApplication { + + public static void main(String[] args) { + SpringApplication.run(AdminApplication.class, args); + } +} \ No newline at end of file diff --git a/admin/src/main/java/org/yapp/Main.java b/admin/src/main/java/org/yapp/Main.java deleted file mode 100644 index dce3f50..0000000 --- a/admin/src/main/java/org/yapp/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.yapp; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello world!"); - } -} \ No newline at end of file diff --git a/admin/src/main/java/org/yapp/auth/application/AdminRole.java b/admin/src/main/java/org/yapp/auth/application/AdminRole.java new file mode 100644 index 0000000..bb44eac --- /dev/null +++ b/admin/src/main/java/org/yapp/auth/application/AdminRole.java @@ -0,0 +1,6 @@ +package org.yapp.auth.application; + +public enum AdminRole { + ADMIN, + ROLE_ADMIN; +} diff --git a/admin/src/main/java/org/yapp/auth/application/AuthService.java b/admin/src/main/java/org/yapp/auth/application/AuthService.java new file mode 100644 index 0000000..b361d9c --- /dev/null +++ b/admin/src/main/java/org/yapp/auth/application/AuthService.java @@ -0,0 +1,40 @@ +package org.yapp.auth.application; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.yapp.auth.AuthToken; +import org.yapp.auth.AuthTokenGenerator; +import org.yapp.auth.application.dto.LoginDto; +import org.yapp.domain.user.User; +import org.yapp.error.code.auth.AuthErrorCode; +import org.yapp.error.exception.ApplicationException; +import org.yapp.user.application.UserService; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AuthService { + + private final UserService userService; + + private final AuthTokenGenerator authTokenGenerator; + + @Value("${admin.password}") + private String PWD; + + public AuthToken login(LoginDto loginDto) { + String oauthId = loginDto.oauthId(); + String password = loginDto.password(); + + User loginUser = userService.getUserByOauthId(oauthId); + + if (password.equals(PWD) && loginUser.getRole().equals(AdminRole.ADMIN.toString())) { + return authTokenGenerator.generate(loginUser.getId(), null, + AdminRole.ROLE_ADMIN.toString()); + } else { + throw new ApplicationException(AuthErrorCode.ACCESS_DENIED); + } + } +} diff --git a/admin/src/main/java/org/yapp/auth/application/dto/LoginDto.java b/admin/src/main/java/org/yapp/auth/application/dto/LoginDto.java new file mode 100644 index 0000000..232153a --- /dev/null +++ b/admin/src/main/java/org/yapp/auth/application/dto/LoginDto.java @@ -0,0 +1,5 @@ +package org.yapp.auth.application.dto; + +public record LoginDto(String oauthId, String password) { + +} diff --git a/admin/src/main/java/org/yapp/auth/presentation/AuthController.java b/admin/src/main/java/org/yapp/auth/presentation/AuthController.java new file mode 100644 index 0000000..84d20c6 --- /dev/null +++ b/admin/src/main/java/org/yapp/auth/presentation/AuthController.java @@ -0,0 +1,28 @@ +package org.yapp.auth.presentation; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.yapp.auth.AuthToken; +import org.yapp.auth.application.AuthService; +import org.yapp.auth.presentation.request.LoginRequest; +import org.yapp.util.CommonResponse; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/v1/auth/") +public class AuthController { + + private final AuthService authService; + + @PostMapping("/login") + public ResponseEntity> login( + @RequestBody @Valid LoginRequest loginRequest) { + AuthToken authToken = authService.login(loginRequest.toDto()); + return ResponseEntity.ok(CommonResponse.createSuccess(authToken)); + } +} diff --git a/admin/src/main/java/org/yapp/auth/presentation/request/LoginRequest.java b/admin/src/main/java/org/yapp/auth/presentation/request/LoginRequest.java new file mode 100644 index 0000000..ad8e82f --- /dev/null +++ b/admin/src/main/java/org/yapp/auth/presentation/request/LoginRequest.java @@ -0,0 +1,11 @@ +package org.yapp.auth.presentation.request; + +import jakarta.validation.constraints.NotNull; +import org.yapp.auth.application.dto.LoginDto; + +public record LoginRequest(@NotNull String loginId, @NotNull String password) { + + public LoginDto toDto() { + return new LoginDto(loginId, password); + } +} diff --git a/admin/src/main/java/org/yapp/block/application/UserBlockService.java b/admin/src/main/java/org/yapp/block/application/UserBlockService.java new file mode 100644 index 0000000..0172f2b --- /dev/null +++ b/admin/src/main/java/org/yapp/block/application/UserBlockService.java @@ -0,0 +1,53 @@ +package org.yapp.block.application; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.yapp.block.dao.UserBlockRepository; +import org.yapp.block.presentation.response.UserBlockResponse; +import org.yapp.domain.block.UserBlock; +import org.yapp.domain.user.User; +import org.yapp.util.PageResponse; + +@Service +@RequiredArgsConstructor +public class UserBlockService { + + private final UserBlockRepository userBlockRepository; + + public PageResponse getUserBlockPageResponse(int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + + Page userBlockPage = userBlockRepository.findAll(pageable); + + List content = userBlockPage.getContent().stream() + .map(userBlock -> { + User blockingUser = userBlock.getBlockingUser(); + User blockedUser = userBlock.getBlockedUser(); + + return new UserBlockResponse( + blockedUser.getId(), + blockedUser.getProfile().getProfileBasic().getNickname(), + blockedUser.getName(), + blockedUser.getProfile().getProfileBasic().getBirthdate(), + blockingUser.getId(), + blockingUser.getProfile().getProfileBasic().getNickname(), + blockingUser.getName(), + userBlock.getCreatedAt().toLocalDate()); + }).toList(); + + return new PageResponse<>( + content, + userBlockPage.getNumber(), + userBlockPage.getSize(), + userBlockPage.getTotalPages(), + userBlockPage.getTotalElements(), + userBlockPage.isFirst(), + userBlockPage.isLast() + ); + } +} diff --git a/admin/src/main/java/org/yapp/block/dao/UserBlockRepository.java b/admin/src/main/java/org/yapp/block/dao/UserBlockRepository.java new file mode 100644 index 0000000..b2dc02f --- /dev/null +++ b/admin/src/main/java/org/yapp/block/dao/UserBlockRepository.java @@ -0,0 +1,10 @@ +package org.yapp.block.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.yapp.domain.block.UserBlock; + +@Repository +public interface UserBlockRepository extends JpaRepository { + +} diff --git a/admin/src/main/java/org/yapp/block/presentation/UserBlockController.java b/admin/src/main/java/org/yapp/block/presentation/UserBlockController.java new file mode 100644 index 0000000..28e25ac --- /dev/null +++ b/admin/src/main/java/org/yapp/block/presentation/UserBlockController.java @@ -0,0 +1,31 @@ +package org.yapp.block.presentation; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.yapp.block.application.UserBlockService; +import org.yapp.block.presentation.response.UserBlockResponse; +import org.yapp.util.CommonResponse; +import org.yapp.util.PageResponse; + +@RestController() +@RequiredArgsConstructor +@RequestMapping("/admin/v1/blocks") +public class UserBlockController { + + private final UserBlockService userBlockService; + + @GetMapping("") + public ResponseEntity>> getUsers( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + PageResponse userBlockPageResponse = userBlockService.getUserBlockPageResponse( + page, size); + + return ResponseEntity.ok(CommonResponse.createSuccess(userBlockPageResponse)); + } +} diff --git a/admin/src/main/java/org/yapp/block/presentation/response/UserBlockResponse.java b/admin/src/main/java/org/yapp/block/presentation/response/UserBlockResponse.java new file mode 100644 index 0000000..d49c282 --- /dev/null +++ b/admin/src/main/java/org/yapp/block/presentation/response/UserBlockResponse.java @@ -0,0 +1,12 @@ +package org.yapp.block.presentation.response; + +import java.time.LocalDate; + +public record UserBlockResponse( + Long blockedUserId, + String BlockedUserNickname, String BlockedUserName, + LocalDate blockedUserBirthdate, Long blockingUserId, + String blockingUserNickname, + String blockingUserName, LocalDate BlockedDate) { + +} diff --git a/admin/src/main/java/org/yapp/config/SecurityConfig.java b/admin/src/main/java/org/yapp/config/SecurityConfig.java new file mode 100644 index 0000000..eaf19e0 --- /dev/null +++ b/admin/src/main/java/org/yapp/config/SecurityConfig.java @@ -0,0 +1,57 @@ +package org.yapp.config; + +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; + +import java.util.Collections; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatchers; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.yapp.jwt.JwtFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtFilter jwtFilter; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.csrf(AbstractHttpConfigurer::disable) + .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) + .httpBasic(AbstractHttpConfigurer::disable) + .sessionManagement(configurer -> configurer.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(registry -> registry + .requestMatchers(getMatcherForAnyone()) + .permitAll() + .anyRequest() + .hasAnyRole("ADMIN")) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } + + private CorsConfigurationSource corsConfigurationSource() { + return request -> { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedHeaders(Collections.singletonList("*")); + config.setAllowedMethods(Collections.singletonList("*")); + config.setAllowedOriginPatterns(Collections.singletonList("*")); + return config; + }; + } + + private RequestMatcher getMatcherForAnyone() { + return RequestMatchers.anyOf(antMatcher("/admin/v1/auth/login/**")); + } +} \ No newline at end of file diff --git a/admin/src/main/java/org/yapp/profile/application/AdminProfileService.java b/admin/src/main/java/org/yapp/profile/application/AdminProfileService.java new file mode 100644 index 0000000..0d98dee --- /dev/null +++ b/admin/src/main/java/org/yapp/profile/application/AdminProfileService.java @@ -0,0 +1,54 @@ +package org.yapp.profile.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.yapp.domain.profile.Profile; +import org.yapp.domain.profile.ProfileRejectHistory; +import org.yapp.domain.profile.ProfileStatus; +import org.yapp.domain.user.RoleStatus; +import org.yapp.domain.user.User; +import org.yapp.error.dto.ProfileErrorCode; +import org.yapp.error.exception.ApplicationException; +import org.yapp.profile.dao.ProfileRejectHistoryRepository; +import org.yapp.user.application.UserService; + +@Service +@RequiredArgsConstructor +public class AdminProfileService { + + private final ProfileRejectHistoryRepository profileRejectHistoryRepository; + private final UserService userService; + + @Transactional + public void updateProfileStatus(Long userId, boolean reasonImage, boolean reasonDescription) { + User user = userService.getUserById(userId); + Profile profile = user.getProfile(); + + if (profile == null) { + throw new ApplicationException(ProfileErrorCode.NOTFOUND_PROFILE); + } + + if (reasonImage || reasonDescription) { + rejectProfile(user.getProfile(), reasonImage, reasonDescription); + } else { + passProfile(profile); + } + } + + private void rejectProfile(Profile profile, boolean reasonImage, boolean reasonDescription) { + profileRejectHistoryRepository.save(ProfileRejectHistory.builder() + .user(profile.getUser()) + .reasonImage(reasonImage) + .reasonDescription(reasonDescription) + .build()); + + profile.updateProfileStatus(ProfileStatus.REJECTED); + profile.getUser().updateUserRole(RoleStatus.PENDING.getStatus()); + } + + private void passProfile(Profile profile) { + profile.updateProfileStatus(ProfileStatus.APPROVED); + profile.getUser().updateUserRole(RoleStatus.USER.getStatus()); + } +} diff --git a/admin/src/main/java/org/yapp/profile/dao/ProfileRejectHistoryRepository.java b/admin/src/main/java/org/yapp/profile/dao/ProfileRejectHistoryRepository.java new file mode 100644 index 0000000..9c9d8c5 --- /dev/null +++ b/admin/src/main/java/org/yapp/profile/dao/ProfileRejectHistoryRepository.java @@ -0,0 +1,12 @@ +package org.yapp.profile.dao; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.yapp.domain.profile.ProfileRejectHistory; + +@Repository +public interface ProfileRejectHistoryRepository extends JpaRepository { + + Optional findTopByUserIdOrderByCreatedAtDesc(Long userId); +} diff --git a/admin/src/main/java/org/yapp/profile/dao/ProfileValueTalkRepository.java b/admin/src/main/java/org/yapp/profile/dao/ProfileValueTalkRepository.java new file mode 100644 index 0000000..6fd4b13 --- /dev/null +++ b/admin/src/main/java/org/yapp/profile/dao/ProfileValueTalkRepository.java @@ -0,0 +1,18 @@ +package org.yapp.profile.dao; + +import io.lettuce.core.dynamic.annotation.Param; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import org.yapp.domain.profile.ProfileValueTalk; + +@Repository +public interface ProfileValueTalkRepository extends JpaRepository { + + @Query("SELECT pvt FROM ProfileValueTalk pvt " + + "JOIN FETCH pvt.valueTalk vt " + + "WHERE vt.isActive = true AND pvt.profile.id = :profileId") + List findActiveProfileValueTalksByProfileId( + @Param("profileId") Long profileId); +} \ No newline at end of file diff --git a/admin/src/main/java/org/yapp/report/application/ReportService.java b/admin/src/main/java/org/yapp/report/application/ReportService.java new file mode 100644 index 0000000..3568fce --- /dev/null +++ b/admin/src/main/java/org/yapp/report/application/ReportService.java @@ -0,0 +1,78 @@ +package org.yapp.report.application; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.yapp.domain.report.Report; +import org.yapp.report.application.dto.ReportedUserWithReasonDto; +import org.yapp.report.dao.ReportRepository; +import org.yapp.report.presentation.response.ReportDetailResponse; +import org.yapp.report.presentation.response.ReportUserResponse; +import org.yapp.util.PageResponse; + +@Service +@RequiredArgsConstructor +public class ReportService { + + private final ReportRepository reportRepository; + + public PageResponse getReportedUsersWithCount(int page, int size) { + Pageable pageable = PageRequest.of(page, size); + + Page reportedUserPage = reportRepository.findReportedUsersWithLatestReason( + pageable); + + Page reportUserResponsePage = reportedUserPage.map(dto -> + new ReportUserResponse( + dto.getUser().getId(), + dto.getUser().getProfile().getProfileBasic().getNickname(), + dto.getUser().getName(), + dto.getUser().getProfile().getProfileBasic().getBirthdate(), + dto.getReportCount(), + dto.getLatestReason() + ) + ); + + return new PageResponse<>( + reportUserResponsePage.getContent(), + reportUserResponsePage.getNumber(), + reportUserResponsePage.getSize(), + reportUserResponsePage.getTotalPages(), + reportUserResponsePage.getTotalElements(), + reportUserResponsePage.isFirst(), + reportUserResponsePage.isLast() + ); + } + + public PageResponse getReportsByReportedUserId(Long reportedUserId, + int page, int size) { + Pageable pageable = PageRequest.of(page, size); + + int order = 1; + Page reportPage = reportRepository.findAllByReportedUserIdOrderByCreatedAt( + reportedUserId, pageable); + List content = new ArrayList<>(); + + for (Report report : reportPage.getContent()) { + content.add(new ReportDetailResponse( + order++, + report.getReason(), + report.getCreatedAt().toLocalDate() + )); + } + + return new PageResponse<>( + content, + reportPage.getNumber(), + reportPage.getSize(), + reportPage.getTotalPages(), + reportPage.getTotalElements(), + reportPage.isFirst(), + reportPage.isLast() + ); + } +} diff --git a/admin/src/main/java/org/yapp/report/application/dto/ReportedUserWithReasonDto.java b/admin/src/main/java/org/yapp/report/application/dto/ReportedUserWithReasonDto.java new file mode 100644 index 0000000..8a3fc55 --- /dev/null +++ b/admin/src/main/java/org/yapp/report/application/dto/ReportedUserWithReasonDto.java @@ -0,0 +1,19 @@ +package org.yapp.report.application.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.yapp.domain.user.User; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Setter +public class ReportedUserWithReasonDto { + + private User user; + private Long reportCount; + private String latestReason; +} \ No newline at end of file diff --git a/admin/src/main/java/org/yapp/report/dao/ReportRepository.java b/admin/src/main/java/org/yapp/report/dao/ReportRepository.java new file mode 100644 index 0000000..57c54d3 --- /dev/null +++ b/admin/src/main/java/org/yapp/report/dao/ReportRepository.java @@ -0,0 +1,33 @@ +package org.yapp.report.dao; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.yapp.domain.report.Report; +import org.yapp.report.application.dto.ReportedUserWithReasonDto; + +@Repository +public interface ReportRepository extends JpaRepository { + + + @Query("SELECT new org.yapp.report.application.dto.ReportedUserWithReasonDto(" + + "r.reportedUser, COUNT(r), " + + "(SELECT r2.reason FROM Report r2 " + + " WHERE r2.reportedUser = r.reportedUser " + + " ORDER BY r2.createdAt DESC LIMIT 1)) " + + "FROM Report r " + + "GROUP BY r.reportedUser " + + "HAVING COUNT(r) >= 1 " + + "ORDER BY MAX(r.createdAt) DESC") + Page findReportedUsersWithLatestReason(Pageable pageable); + + + @Query("SELECT r FROM Report r " + + "WHERE r.reportedUser.id = :reportedUserId " + + "ORDER BY r.createdAt ASC") + Page findAllByReportedUserIdOrderByCreatedAt( + @Param("reportedUserId") Long reportedUserId, Pageable pageable); +} diff --git a/admin/src/main/java/org/yapp/report/presentation/ReportController.java b/admin/src/main/java/org/yapp/report/presentation/ReportController.java new file mode 100644 index 0000000..e493b94 --- /dev/null +++ b/admin/src/main/java/org/yapp/report/presentation/ReportController.java @@ -0,0 +1,46 @@ +package org.yapp.report.presentation; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.yapp.report.application.ReportService; +import org.yapp.report.presentation.response.ReportDetailResponse; +import org.yapp.report.presentation.response.ReportUserResponse; +import org.yapp.util.CommonResponse; +import org.yapp.util.PageResponse; + +@RestController() +@RequiredArgsConstructor +@RequestMapping("/admin/v1/reports") +public class ReportController { + + private final ReportService reportService; + + @GetMapping("") + public ResponseEntity>> getUsers( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + PageResponse reportedUsersWithCount = reportService.getReportedUsersWithCount( + page, size); + + return ResponseEntity.ok(CommonResponse.createSuccess(reportedUsersWithCount)); + } + + @GetMapping("/users/{userId}") + public ResponseEntity>> getReportDetails( + @PathVariable Long userId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "5") int size) { + + PageResponse reportDetailResponses = reportService.getReportsByReportedUserId( + userId, + page, size); + + return ResponseEntity.ok(CommonResponse.createSuccess(reportDetailResponses)); + } +} diff --git a/admin/src/main/java/org/yapp/report/presentation/response/ReportDetailResponse.java b/admin/src/main/java/org/yapp/report/presentation/response/ReportDetailResponse.java new file mode 100644 index 0000000..5b3bf91 --- /dev/null +++ b/admin/src/main/java/org/yapp/report/presentation/response/ReportDetailResponse.java @@ -0,0 +1,7 @@ +package org.yapp.report.presentation.response; + +import java.time.LocalDate; + +public record ReportDetailResponse(Integer cnt, String reason, LocalDate reportedDate) { + +} diff --git a/admin/src/main/java/org/yapp/report/presentation/response/ReportUserResponse.java b/admin/src/main/java/org/yapp/report/presentation/response/ReportUserResponse.java new file mode 100644 index 0000000..1ffebf6 --- /dev/null +++ b/admin/src/main/java/org/yapp/report/presentation/response/ReportUserResponse.java @@ -0,0 +1,8 @@ +package org.yapp.report.presentation.response; + +import java.time.LocalDate; + +public record ReportUserResponse(Long userId, String nickName, String name, LocalDate birthdate, + Long totalReportedCnt, String latestReportedReason) { + +} diff --git a/admin/src/main/java/org/yapp/user/application/UserService.java b/admin/src/main/java/org/yapp/user/application/UserService.java new file mode 100644 index 0000000..2e479f2 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/application/UserService.java @@ -0,0 +1,92 @@ +package org.yapp.user.application; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.yapp.domain.profile.Profile; +import org.yapp.domain.profile.ProfileRejectHistory; +import org.yapp.domain.profile.ProfileValueTalk; +import org.yapp.domain.user.User; +import org.yapp.error.dto.ProfileErrorCode; +import org.yapp.error.dto.UserErrorCode; +import org.yapp.error.exception.ApplicationException; +import org.yapp.profile.dao.ProfileRejectHistoryRepository; +import org.yapp.profile.dao.ProfileValueTalkRepository; +import org.yapp.user.dao.UserRepository; +import org.yapp.user.presentation.response.UserProfileDetailResponses; +import org.yapp.user.presentation.response.UserProfileValidationResponse; +import org.yapp.util.PageResponse; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + private final ProfileRejectHistoryRepository profileRejectHistoryRepository; + private final ProfileValueTalkRepository profileValueTalkRepository; + + public User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new ApplicationException(UserErrorCode.NOTFOUND_USER)); + } + + public User getUserByOauthId(String oauthId) { + return userRepository.findByOauthId(oauthId) + .orElseThrow(() -> new ApplicationException(UserErrorCode.NOTFOUND_USER)); + } + + public PageResponse getUserProfilesWithPagination(int page, + int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + + Page userPage = userRepository.findAll(pageable); + + List content = userPage.getContent().stream() + .map(user -> { + Optional optionalProfileRejectHistory = + profileRejectHistoryRepository.findTopByUserIdOrderByCreatedAtDesc( + user.getId()); + + boolean reasonImage = optionalProfileRejectHistory.map( + ProfileRejectHistory::isReasonImage).orElse(false); + boolean reasonDescription = optionalProfileRejectHistory.map( + ProfileRejectHistory::isReasonDescription).orElse(false); + + return UserProfileValidationResponse.from(user, reasonImage, reasonDescription); + }) + .toList(); + + return new PageResponse<>( + content, + userPage.getNumber(), + userPage.getSize(), + userPage.getTotalPages(), + userPage.getTotalElements(), + userPage.isFirst(), + userPage.isLast() + ); + } + + @Transactional(readOnly = true) + public UserProfileDetailResponses getUserProfileDetails(Long userId) { + User user = getUserById(userId); + Profile profile = user.getProfile(); + + if (profile == null) { + throw new ApplicationException(ProfileErrorCode.NOTFOUND_PROFILE); + } + + List activeProfileValueTalks = profileValueTalkRepository.findActiveProfileValueTalksByProfileId( + profile.getId()); + + return UserProfileDetailResponses.from(profile.getProfileBasic().getNickname(), + profile.getProfileBasic().getImageUrl(), + activeProfileValueTalks); + } +} diff --git a/admin/src/main/java/org/yapp/user/dao/UserRepository.java b/admin/src/main/java/org/yapp/user/dao/UserRepository.java new file mode 100644 index 0000000..e1d19a8 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/dao/UserRepository.java @@ -0,0 +1,14 @@ +package org.yapp.user.dao; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.yapp.domain.user.User; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findById(Long userId); + + Optional findByOauthId(String oauthId); +} diff --git a/admin/src/main/java/org/yapp/user/presentation/UpdateProfileStatusRequest.java b/admin/src/main/java/org/yapp/user/presentation/UpdateProfileStatusRequest.java new file mode 100644 index 0000000..c74d1c2 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/presentation/UpdateProfileStatusRequest.java @@ -0,0 +1,8 @@ +package org.yapp.user.presentation; + +import jakarta.validation.constraints.NotNull; + +public record UpdateProfileStatusRequest(@NotNull boolean rejectImage, + @NotNull boolean rejectDescription) { + +} diff --git a/admin/src/main/java/org/yapp/user/presentation/UserController.java b/admin/src/main/java/org/yapp/user/presentation/UserController.java new file mode 100644 index 0000000..ad35f04 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/presentation/UserController.java @@ -0,0 +1,57 @@ +package org.yapp.user.presentation; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.yapp.profile.application.AdminProfileService; +import org.yapp.user.application.UserService; +import org.yapp.user.presentation.response.UserProfileDetailResponses; +import org.yapp.user.presentation.response.UserProfileValidationResponse; +import org.yapp.util.CommonResponse; +import org.yapp.util.PageResponse; + +@RestController() +@RequiredArgsConstructor +@RequestMapping("/admin/v1/users") +public class UserController { + + private final UserService userService; + private final AdminProfileService adminProfileService; + + @GetMapping("") + public ResponseEntity>> getUsers( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + PageResponse userProfilesWithPagination = userService.getUserProfilesWithPagination( + page, size); + + return ResponseEntity.ok(CommonResponse.createSuccess(userProfilesWithPagination)); + } + + @GetMapping("/{userId}") + public ResponseEntity> getUserProfile( + @PathVariable Long userId) { + + UserProfileDetailResponses userProfileDetails = userService.getUserProfileDetails(userId); + return ResponseEntity.ok(CommonResponse.createSuccess(userProfileDetails)); + } + + @PostMapping("/{userId}/profile") + public ResponseEntity> updateUserProfileStatus( + @PathVariable Long userId, + @RequestBody @Valid UpdateProfileStatusRequest request) { + + adminProfileService.updateProfileStatus(userId, request.rejectImage(), + request.rejectDescription()); + + return ResponseEntity.ok(CommonResponse.createSuccess(null)); + } +} diff --git a/admin/src/main/java/org/yapp/user/presentation/response/UserProfileDetailResponse.java b/admin/src/main/java/org/yapp/user/presentation/response/UserProfileDetailResponse.java new file mode 100644 index 0000000..dfe5a58 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/presentation/response/UserProfileDetailResponse.java @@ -0,0 +1,18 @@ +package org.yapp.user.presentation.response; + +import org.yapp.domain.profile.ProfileValueTalk; + +public record UserProfileDetailResponse( + String category, + String title, + String answer +) { + + public UserProfileDetailResponse(ProfileValueTalk profileValueTalk) { + this( + profileValueTalk.getValueTalk().getTitle(), + profileValueTalk.getValueTalk().getCategory(), + profileValueTalk.getAnswer() + ); + } +} diff --git a/admin/src/main/java/org/yapp/user/presentation/response/UserProfileDetailResponses.java b/admin/src/main/java/org/yapp/user/presentation/response/UserProfileDetailResponses.java new file mode 100644 index 0000000..6611053 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/presentation/response/UserProfileDetailResponses.java @@ -0,0 +1,16 @@ +package org.yapp.user.presentation.response; + +import java.util.List; +import org.yapp.domain.profile.ProfileValueTalk; + +public record UserProfileDetailResponses(String nickname, String imageUrl, + List responses) { + + public static UserProfileDetailResponses from(String nickname, String imageUrl, + List profileValueTalks) { + List profileValueTalkResponses = profileValueTalks.stream() + .map(UserProfileDetailResponse::new).toList(); + + return new UserProfileDetailResponses(nickname, imageUrl, profileValueTalkResponses); + } +} diff --git a/admin/src/main/java/org/yapp/user/presentation/response/UserProfileValidationResponse.java b/admin/src/main/java/org/yapp/user/presentation/response/UserProfileValidationResponse.java new file mode 100644 index 0000000..a02e948 --- /dev/null +++ b/admin/src/main/java/org/yapp/user/presentation/response/UserProfileValidationResponse.java @@ -0,0 +1,33 @@ +package org.yapp.user.presentation.response; + +import java.time.LocalDate; +import lombok.Builder; +import org.yapp.domain.profile.Profile; +import org.yapp.domain.user.User; + +@Builder +public record UserProfileValidationResponse(Long userId, String description, + String nickname, + String name, LocalDate birthdate, String phoneNumber, + LocalDate joinDate, + String profileStatus, boolean rejectImage, + boolean rejectDescription) { + + public static UserProfileValidationResponse from(User user, boolean rejectImage, + boolean rejectDescription) { + Profile profile = user.getProfile(); + + return UserProfileValidationResponse.builder() + .userId(user.getId()) + .description(profile != null ? profile.getProfileBasic().getDescription() : null) + .nickname(profile != null ? profile.getProfileBasic().getNickname() : null) + .name(user.getName()) + .birthdate(profile != null ? profile.getProfileBasic().getBirthdate() : null) + .phoneNumber(user.getPhoneNumber() != null ? user.getPhoneNumber() : null) + .joinDate(user.getCreatedAt() != null ? user.getCreatedAt().toLocalDate() : null) + .profileStatus(profile != null ? profile.getProfileStatus().getDisplayName() : null) + .rejectImage(rejectImage) + .rejectDescription(rejectDescription) + .build(); + } +} diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml index 620ba3b..fb06f99 100644 --- a/admin/src/main/resources/application.yml +++ b/admin/src/main/resources/application.yml @@ -1,3 +1,3 @@ spring: profiles: - include: db \ No newline at end of file + include: db, secret \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index 67731b8..9c52b61 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -11,6 +11,7 @@ repositories { dependencies { implementation project(':common') + implementation project(':common:domain') testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/api/src/main/java/org/yapp/domain/auth/application/oauth/service/OauthService.java b/api/src/main/java/org/yapp/domain/auth/application/oauth/service/OauthService.java index dec7aed..0c0277d 100644 --- a/api/src/main/java/org/yapp/domain/auth/application/oauth/service/OauthService.java +++ b/api/src/main/java/org/yapp/domain/auth/application/oauth/service/OauthService.java @@ -6,9 +6,9 @@ import org.yapp.domain.auth.application.jwt.JwtUtil; import org.yapp.domain.auth.application.oauth.OauthProvider; import org.yapp.domain.auth.application.oauth.OauthProviderResolver; -import org.yapp.domain.auth.presentation.dto.enums.RoleStatus; import org.yapp.domain.auth.presentation.dto.request.OauthLoginRequest; import org.yapp.domain.auth.presentation.dto.response.OauthLoginResponse; +import org.yapp.domain.user.RoleStatus; import org.yapp.domain.user.User; import org.yapp.domain.user.dao.UserRepository; diff --git a/api/src/main/java/org/yapp/domain/block/application/BlockContactService.java b/api/src/main/java/org/yapp/domain/block/application/BlockContactService.java new file mode 100644 index 0000000..48bd1c6 --- /dev/null +++ b/api/src/main/java/org/yapp/domain/block/application/BlockContactService.java @@ -0,0 +1,49 @@ +package org.yapp.domain.block.application; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.yapp.domain.block.BlockContact; +import org.yapp.domain.block.application.dto.BlockContactCreateDto; +import org.yapp.domain.block.dao.BlockContactRepository; +import org.yapp.domain.user.User; + +@Service +@RequiredArgsConstructor +public class BlockContactService { + + private final BlockContactRepository blockContactRepository; + + @Transactional() + public void blockPhoneNumbers(BlockContactCreateDto blockContactCreateDto) { + Long userId = blockContactCreateDto.userId(); + List phoneNumbers = blockContactCreateDto.phoneNumbers(); + List newBlockContacts = new ArrayList<>(); + + Set blockedPhoneNumbers = blockContactRepository.findBlocksByUserId(userId) + .stream() + .map(BlockContact::getPhoneNumber) + .collect(Collectors.toSet()); + + phoneNumbers.stream() + .filter(phoneNumber -> !blockedPhoneNumbers.contains(phoneNumber)) + .forEach(phoneNumber -> { + BlockContact blockContact = BlockContact.builder() + .user(User.builder().id(userId).build()) + .phoneNumber(phoneNumber) + .build(); + newBlockContacts.add(blockContact); + }); + + blockContactRepository.saveAll(newBlockContacts); + } + + @Transactional(readOnly = false) + public List findBlocksByUserId(Long userId) { + return blockContactRepository.findBlocksByUserId(userId); + } +} diff --git a/api/src/main/java/org/yapp/domain/block/application/BlockService.java b/api/src/main/java/org/yapp/domain/block/application/BlockService.java deleted file mode 100644 index 54fffe0..0000000 --- a/api/src/main/java/org/yapp/domain/block/application/BlockService.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.yapp.domain.block.application; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.yapp.domain.block.Block; -import org.yapp.domain.block.application.dto.BlockCreateDto; -import org.yapp.domain.block.dao.BlockRepository; -import org.yapp.domain.user.User; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class BlockService { - private final BlockRepository blockRepository; - - @Transactional() - public void blockPhoneNumbers(BlockCreateDto blockCreateDto) { - Long userId = blockCreateDto.userId(); - List phoneNumbers = blockCreateDto.phoneNumbers(); - List newBlocks = new ArrayList<>(); - - Set blockedPhoneNumbers = - blockRepository.findBlocksByUserId(userId).stream().map(Block::getPhoneNumber).collect(Collectors.toSet()); - - phoneNumbers.stream().filter(phoneNumber -> !blockedPhoneNumbers.contains(phoneNumber)).forEach(phoneNumber -> { - Block block = Block.builder().user(User.builder().id(userId).build()).phoneNumber(phoneNumber).build(); - newBlocks.add(block); - }); - - blockRepository.saveAll(newBlocks); - } - - @Transactional(readOnly = true) - public Boolean checkIfUserBlockedPhoneNumber(Long userId, String phoneNumber) { - return blockRepository.existsByUserIdAndPhoneNumber(userId, phoneNumber); - } - - @Transactional(readOnly = false) - public List findBlocksByUserId(Long userId) { - return blockRepository.findBlocksByUserId(userId); - } -} diff --git a/api/src/main/java/org/yapp/domain/block/application/dto/BlockContactCreateDto.java b/api/src/main/java/org/yapp/domain/block/application/dto/BlockContactCreateDto.java new file mode 100644 index 0000000..81f8363 --- /dev/null +++ b/api/src/main/java/org/yapp/domain/block/application/dto/BlockContactCreateDto.java @@ -0,0 +1,13 @@ +package org.yapp.domain.block.application.dto; + +import java.util.List; + +public record BlockContactCreateDto(Long userId, List phoneNumbers) { + + public BlockContactCreateDto(Long userId, List phoneNumbers) { + this.userId = userId; + this.phoneNumbers = phoneNumbers.stream() + .map(phone -> phone.replaceAll("-", "")) + .toList(); + } +} diff --git a/api/src/main/java/org/yapp/domain/block/application/dto/BlockCreateDto.java b/api/src/main/java/org/yapp/domain/block/application/dto/BlockCreateDto.java deleted file mode 100644 index f769965..0000000 --- a/api/src/main/java/org/yapp/domain/block/application/dto/BlockCreateDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.yapp.domain.block.application.dto; - -import java.util.List; - -public record BlockCreateDto(Long userId, List phoneNumbers) { - public BlockCreateDto(Long userId, List phoneNumbers) { - this.userId = userId; - this.phoneNumbers = phoneNumbers.stream() - .map(phone -> phone.replaceAll("-", "")) - .toList(); - } -} diff --git a/api/src/main/java/org/yapp/domain/block/dao/BlockContactRepository.java b/api/src/main/java/org/yapp/domain/block/dao/BlockContactRepository.java new file mode 100644 index 0000000..aeecd19 --- /dev/null +++ b/api/src/main/java/org/yapp/domain/block/dao/BlockContactRepository.java @@ -0,0 +1,12 @@ +package org.yapp.domain.block.dao; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.yapp.domain.block.BlockContact; + +@Repository +public interface BlockContactRepository extends JpaRepository { + + List findBlocksByUserId(Long userId); +} \ No newline at end of file diff --git a/api/src/main/java/org/yapp/domain/block/dao/BlockRepository.java b/api/src/main/java/org/yapp/domain/block/dao/BlockRepository.java deleted file mode 100644 index 2d248a1..0000000 --- a/api/src/main/java/org/yapp/domain/block/dao/BlockRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.yapp.domain.block.dao; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import org.yapp.domain.block.Block; - -import java.util.List; - -@Repository -public interface BlockRepository extends JpaRepository { - boolean existsByUserIdAndPhoneNumber(Long userId, String phoneNumber); - - List findBlocksByUserId(Long userId); -} diff --git a/api/src/main/java/org/yapp/domain/block/presentation/BlockContactController.java b/api/src/main/java/org/yapp/domain/block/presentation/BlockContactController.java new file mode 100644 index 0000000..acf8e38 --- /dev/null +++ b/api/src/main/java/org/yapp/domain/block/presentation/BlockContactController.java @@ -0,0 +1,52 @@ +package org.yapp.domain.block.presentation; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.util.List; +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.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.yapp.domain.block.BlockContact; +import org.yapp.domain.block.application.BlockContactService; +import org.yapp.domain.block.application.dto.BlockContactCreateDto; +import org.yapp.domain.block.presentation.dto.request.BlockPhoneNumbersRequest; +import org.yapp.domain.block.presentation.dto.response.UserBlockContactResponses; +import org.yapp.util.CommonResponse; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/blockContacts") +public class BlockContactController { + + private final BlockContactService blockContactService; + + @PostMapping("") + @Operation(summary = "핸드폰 번호 차단", description = "핸드폰 번호 리스트를 전달받고, 전달받은 핸드폰 번호 차단을 수행합니다.", tags = { + "차단"}) + @ApiResponse(responseCode = "200", description = "핸드폰 차단 성공") + public ResponseEntity> blockPhoneNumbers( + @AuthenticationPrincipal Long userId, @RequestBody BlockPhoneNumbersRequest request) { + blockContactService.blockPhoneNumbers( + new BlockContactCreateDto(userId, request.phoneNumbers())); + return ResponseEntity.status(HttpStatus.OK) + .body(CommonResponse.createSuccessWithNoContent("핸드폰 번호 차단 성공")); + } + + @GetMapping("") + @Operation(summary = "핸드폰 번호 차단", description = "핸드폰 번호 리스트를 전달받고, 전달받은 핸드폰 번호 차단을 수행합니다.", tags = { + "차단"}) + @ApiResponse(responseCode = "200", description = "핸드폰 차단 성공") + public ResponseEntity> blockPhoneNumbers( + @AuthenticationPrincipal Long userId) { + List blockContacts = blockContactService.findBlocksByUserId(userId); + return ResponseEntity.status(HttpStatus.OK) + .body(CommonResponse.createSuccess(UserBlockContactResponses.from(userId, + blockContacts))); + } +} diff --git a/api/src/main/java/org/yapp/domain/block/presentation/BlockController.java b/api/src/main/java/org/yapp/domain/block/presentation/BlockController.java deleted file mode 100644 index 8440371..0000000 --- a/api/src/main/java/org/yapp/domain/block/presentation/BlockController.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.yapp.domain.block.presentation; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -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 org.yapp.domain.block.Block; -import org.yapp.domain.block.application.BlockService; -import org.yapp.domain.block.application.dto.BlockCreateDto; -import org.yapp.domain.block.presentation.dto.request.BlockPhoneNumbersRequest; -import org.yapp.domain.block.presentation.dto.response.UserBlockResponses; -import org.yapp.util.CommonResponse; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/blocks") -public class BlockController { - private final BlockService blockService; - - @PostMapping("") - @Operation(summary = "핸드폰 번호 차단", description = "핸드폰 번호 리스트를 전달받고, 전달받은 핸드폰 번호 차단을 수행합니다.", tags = {"차단"}) - @ApiResponse(responseCode = "200", description = "핸드폰 차단 성공") - public ResponseEntity> blockPhoneNumbers(@AuthenticationPrincipal Long userId, @RequestBody BlockPhoneNumbersRequest request) { - blockService.blockPhoneNumbers(new BlockCreateDto(userId, request.phoneNumbers())); - return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.createSuccessWithNoContent("핸드폰 번호 차단 성공")); - } - - @GetMapping("") - @Operation(summary = "핸드폰 번호 차단", description = "핸드폰 번호 리스트를 전달받고, 전달받은 핸드폰 번호 차단을 수행합니다.", tags = {"차단"}) - @ApiResponse(responseCode = "200", description = "핸드폰 차단 성공") - public ResponseEntity> blockPhoneNumbers(@AuthenticationPrincipal Long userId) { - List blocks = blockService.findBlocksByUserId(userId); - return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.createSuccess(UserBlockResponses.from(userId, blocks))); - } -} diff --git a/api/src/main/java/org/yapp/domain/block/presentation/dto/response/BlockResponse.java b/api/src/main/java/org/yapp/domain/block/presentation/dto/response/BlockContactResponse.java similarity index 52% rename from api/src/main/java/org/yapp/domain/block/presentation/dto/response/BlockResponse.java rename to api/src/main/java/org/yapp/domain/block/presentation/dto/response/BlockContactResponse.java index 468de9d..32a0af2 100644 --- a/api/src/main/java/org/yapp/domain/block/presentation/dto/response/BlockResponse.java +++ b/api/src/main/java/org/yapp/domain/block/presentation/dto/response/BlockContactResponse.java @@ -2,5 +2,6 @@ import java.time.LocalDateTime; -public record BlockResponse(String phoneNumber, LocalDateTime blockedAt) { +public record BlockContactResponse(String phoneNumber, LocalDateTime blockedAt) { + } diff --git a/api/src/main/java/org/yapp/domain/block/presentation/dto/response/UserBlockContactResponses.java b/api/src/main/java/org/yapp/domain/block/presentation/dto/response/UserBlockContactResponses.java new file mode 100644 index 0000000..534a839 --- /dev/null +++ b/api/src/main/java/org/yapp/domain/block/presentation/dto/response/UserBlockContactResponses.java @@ -0,0 +1,20 @@ +package org.yapp.domain.block.presentation.dto.response; + +import java.util.List; +import org.yapp.domain.block.BlockContact; + +public record UserBlockContactResponses(Long userId, + List blockContactResponses) { + + public static UserBlockContactResponses from(Long userId, List blockContacts) { + List blockContactResponses = blockContacts.stream().map( + blockContact -> new BlockContactResponse(blockContact.getPhoneNumber(), + blockContact.getCreatedAt())).toList(); + + return new UserBlockContactResponses( + userId, + blockContactResponses + ); + } + +} diff --git a/api/src/main/java/org/yapp/domain/block/presentation/dto/response/UserBlockResponses.java b/api/src/main/java/org/yapp/domain/block/presentation/dto/response/UserBlockResponses.java deleted file mode 100644 index 05bde3a..0000000 --- a/api/src/main/java/org/yapp/domain/block/presentation/dto/response/UserBlockResponses.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.yapp.domain.block.presentation.dto.response; - -import org.yapp.domain.block.Block; - -import java.util.List; - -public record UserBlockResponses (Long userId, List blockResponses) { - public static UserBlockResponses from (Long userId, List blocks) { - List blockResponses = blocks.stream().map(block -> new BlockResponse(block.getPhoneNumber(), block.getCreatedAt())).toList(); - - return new UserBlockResponses( - userId, - blockResponses - ); - } - -} diff --git a/api/src/main/java/org/yapp/domain/profile/application/ProfileService.java b/api/src/main/java/org/yapp/domain/profile/application/ProfileService.java index 5b6de76..70ce1ab 100644 --- a/api/src/main/java/org/yapp/domain/profile/application/ProfileService.java +++ b/api/src/main/java/org/yapp/domain/profile/application/ProfileService.java @@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.yapp.domain.auth.presentation.dto.enums.RoleStatus; import org.yapp.domain.profile.Profile; import org.yapp.domain.profile.ProfileBasic; import org.yapp.domain.profile.ProfileValuePick; @@ -19,6 +18,7 @@ import org.yapp.domain.profile.presentation.request.ProfileValuePickUpdateRequest.ProfileValuePickPair; import org.yapp.domain.profile.presentation.request.ProfileValueTalkUpdateRequest; import org.yapp.domain.profile.presentation.request.ProfileValueTalkUpdateRequest.ProfileValueTalkPair; +import org.yapp.domain.user.RoleStatus; import org.yapp.domain.user.User; import org.yapp.domain.user.application.UserService; import org.yapp.error.dto.ProfileErrorCode; diff --git a/api/src/main/java/org/yapp/domain/user/application/UserService.java b/api/src/main/java/org/yapp/domain/user/application/UserService.java index cc7b6ef..f6153d4 100644 --- a/api/src/main/java/org/yapp/domain/user/application/UserService.java +++ b/api/src/main/java/org/yapp/domain/user/application/UserService.java @@ -5,9 +5,9 @@ import org.springframework.transaction.annotation.Transactional; import org.yapp.application.AuthenticationService; import org.yapp.domain.auth.application.jwt.JwtUtil; -import org.yapp.domain.auth.presentation.dto.enums.RoleStatus; import org.yapp.domain.auth.presentation.dto.response.OauthLoginResponse; import org.yapp.domain.profile.Profile; +import org.yapp.domain.user.RoleStatus; import org.yapp.domain.user.User; import org.yapp.domain.user.dao.UserRepository; import org.yapp.error.dto.UserErrorCode; diff --git a/api/src/main/java/org/yapp/domain/user/dao/UserRepository.java b/api/src/main/java/org/yapp/domain/user/dao/UserRepository.java index 7cc581b..52741d6 100644 --- a/api/src/main/java/org/yapp/domain/user/dao/UserRepository.java +++ b/api/src/main/java/org/yapp/domain/user/dao/UserRepository.java @@ -6,5 +6,5 @@ import java.util.Optional; public interface UserRepository extends JpaRepository { - Optional findByOauthId(String oauthId); + Optional findByOauthId(String oauthId); } diff --git a/api/src/main/java/org/yapp/global/application/RedisService.java b/api/src/main/java/org/yapp/global/application/RedisService.java index 0da05cb..a33ff12 100644 --- a/api/src/main/java/org/yapp/global/application/RedisService.java +++ b/api/src/main/java/org/yapp/global/application/RedisService.java @@ -1,27 +1,25 @@ package org.yapp.global.application; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Service; - import java.time.Duration; - import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class RedisService { - private final StringRedisTemplate redisTemplate; + private final StringRedisTemplate redisTemplate; - public void deleteKey(String key) { - redisTemplate.delete(key); - } + public void deleteKey(String key) { + redisTemplate.delete(key); + } - public String getValue(String key) { - return redisTemplate.opsForValue().get(key); - } + public String getValue(String key) { + return redisTemplate.opsForValue().get(key); + } - public void setKeyWithExpiration(String key, String value, long expiredTime) { - redisTemplate.opsForValue().set(key, value, Duration.ofMillis(expiredTime)); - } + public void setKeyWithExpiration(String key, String value, long expiredTime) { + redisTemplate.opsForValue().set(key, value, Duration.ofMillis(expiredTime)); + } } diff --git a/api/src/main/resources/init.sql b/api/src/main/resources/init.sql index a2d16c4..362f2d4 100644 --- a/api/src/main/resources/init.sql +++ b/api/src/main/resources/init.sql @@ -1,16 +1,82 @@ -- ProfileBasic 더미 데이터 삽입 -INSERT INTO profile (profile_id, nickname, birthdate, height, job, location, smoking_status, - religion, - sns_activity_level, - image_url, contacts) -VALUES (1, 'JohnDoe', '1990-01-01', 180, 'Engineer', 'Seoul', 'Non-smoker', 'None', 'Medium', - 'https://example.com/johndoe.jpg', '{ - "email": "nomad8@naver.com" - }'); - --- User 더미 데이터 삽입 -INSERT INTO user_table (user_id, oauth_id, name, profile_id, role) -VALUES (1, 'oauth123', 'John Doe', 1, 'USER'); +INSERT INTO profile (profile_id, profile_status, nickname, description, birthdate, height, job, + location, smoking_status, weight, sns_activity_level, contacts, image_url, + created_at, updated_at) +VALUES (1, 'APPROVED', 'johndoe', '소프트웨어 개발자', '1990-05-10', 180, '엔지니어', '서울', '비흡연자', 75, '활동적', + '{ + "email": "john.doe@example.com" + }', 'https://example.com/john.jpg', NOW(), NOW()), + (2, 'APPROVED', 'janesmith', '마케팅 매니저', '1992-08-15', 165, '마케터', '인천', '비흡연자', 55, '보통', '{ + "email": "jane.smith@example.com" + }', 'https://example.com/jane.jpg', NOW(), NOW()), + (3, 'APPROVED', 'robbrown', '그래픽 디자이너', '1988-03-22', 175, '디자이너', '부산', '흡연자', 70, '낮음', '{ + "email": "robert.brown@example.com" + }', 'https://example.com/robert.jpg', NOW(), NOW()), + (4, 'APPROVED', 'emilydavis', 'HR 전문가', '1995-09-10', 170, 'HR', '대구', '비흡연자', 60, '활동적', '{ + "email": "emily.davis@example.com" + }', 'https://example.com/emily.jpg', NOW(), NOW()), + (5, 'APPROVED', 'mikejohnson', '데이터 과학자', '1993-12-01', 185, '과학자', '서울', '비흡연자', 80, + '매우 활동적', '{ + "email": "michael.johnson@example.com" + }', 'https://example.com/mike.jpg', NOW(), NOW()), + (6, 'APPROVED', 'sarahwilson', '학교 교사', '1985-04-17', 160, '교사', '광주', '비흡연자', 50, '보통', '{ + "email": "sarah.wilson@example.com" + }', 'https://example.com/sarah.jpg', NOW(), NOW()), + (7, 'APPROVED', 'davidmartinez', '프로젝트 매니저', '1991-11-11', 172, '매니저', '수원', '비흡연자', 68, + '활동적', '{ + "email": "david.martinez@example.com" + }', 'https://example.com/david.jpg', NOW(), NOW()), + (8, 'APPROVED', 'laurawhite', '비즈니스 분석가', '1987-06-25', 167, '분석가', '대전', '비흡연자', 58, '낮음', + '{ + "email": "laura.white@example.com" + }', 'https://example.com/laura.jpg', NOW(), NOW()), + (9, 'APPROVED', 'jamesanderson', '기업가', '1990-01-19', 178, '기업가', '서울', '흡연자', 72, '매우 활동적', + '{ + "email": "james.anderson@example.com" + }', 'https://example.com/james.jpg', NOW(), NOW()), + (10, 'APPROVED', 'oliviataylor', '제품 디자이너', '1994-07-30', 162, '디자이너', '울산', '비흡연자', 54, + '보통', '{ + "email": "olivia.taylor@example.com" + }', 'https://example.com/olivia.jpg', NOW(), NOW()); + + +INSERT INTO user_table (user_id, oauth_id, name, phone, role, profile_id, created_at, updated_at) +VALUES (1, 'oauth_1', '홍길동', '010-1234-5678', 'USER', 1, NOW(), NOW()), + (2, 'oauth_2', '김철수', '010-2345-6789', 'USER', 2, NOW(), NOW()), + (3, 'oauth_3', '이영희', '010-3456-7890', 'USER', 3, NOW(), NOW()), + (4, 'oauth_4', '박민수', '010-4567-8901', 'USER', 4, NOW(), NOW()), + (5, 'oauth_5', '최지우', '010-5678-9012', 'USER', 5, NOW(), NOW()), + (6, 'oauth_6', '강하늘', '010-6789-0123', 'USER', 6, NOW(), NOW()), + (7, 'oauth_7', '오세훈', '010-7890-1234', 'USER', 7, NOW(), NOW()), + (8, 'oauth_8', '장미란', '010-8901-2345', 'USER', 8, NOW(), NOW()), + (9, 'oauth_9', '정다은', '010-9012-3456', 'USER', 9, NOW(), NOW()), + (10, 'oauth_10', '김유진', '010-0123-4567', 'USER', 10, NOW(), NOW()); + +INSERT INTO report (report_id, reporter_user_id, reported_user_id, reason, created_at, updated_at) +VALUES (1, 1, 2, '부적절한 언어 사용', NOW(), NOW()), + (2, 3, 4, '스팸 메시지 전송', NOW(), NOW()), + (3, 5, 6, '허위 정보 제공', NOW(), NOW()), + (4, 7, 8, '욕설 및 비방', NOW(), NOW()), + (5, 9, 10, '기타 사유', NOW(), NOW()), + (6, 2, 1, '사기 의심', NOW(), NOW()), + (7, 4, 3, '허위 정보 제공', NOW(), NOW()), + (8, 6, 5, '욕설 및 비방', NOW(), NOW()), + (9, 8, 7, '부적절한 언어 사용', NOW(), NOW()), + (10, 10, 9, '스팸 메시지 전송', NOW(), NOW()); + +INSERT INTO report (report_id, reporter_user_id, reported_user_id, reason, created_at, updated_at) +VALUES (11, 3, 2, '부적절한 언어 사용 2222', NOW(), NOW()); + + +INSERT INTO report (report_id, reporter_user_id, reported_user_id, reason, created_at, updated_at) +VALUES (12, 1, 2, '부적절한 언어 사용 3333', NOW(), NOW()); + +INSERT INTO user_block (user_block_id, blocking_user_id, blocked_user_id, created_at, updated_at) +VALUES (1, 1, 2, NOW(), NOW()), + (2, 2, 3, NOW(), NOW()), + (3, 3, 4, NOW(), NOW()), + (4, 4, 1, NOW(), NOW()), + (5, 1, 3, NOW(), NOW()); -- ValueItem 더미 데이터 삽입 INSERT INTO value_pick (value_pick_id, category, question, answers, is_active) @@ -32,7 +98,7 @@ VALUES ('꿈과 목표', '어떤 일을 하며 무엇을 목표로 살아가나 INSERT INTO profile_value_pick (profile_value_pick_id, profile_id, value_pick_id, selected_answer) VALUES (1, 1, 1, 2), - (2, 1, 2, 3); + (2, 1, 2, 1); INSERT INTO profile_value_talk (profile_value_talk_id, profile_id, value_talk_id, answer) VALUES (1, 1, 1, '문장입니다 1'), diff --git a/common/auth/build.gradle b/common/auth/build.gradle new file mode 100644 index 0000000..fda8bf5 --- /dev/null +++ b/common/auth/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group = 'org.yapp' +version = '0.0.1-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/common/auth/src/main/java/org/yapp/auth/AuthToken.java b/common/auth/src/main/java/org/yapp/auth/AuthToken.java new file mode 100644 index 0000000..8ec9d57 --- /dev/null +++ b/common/auth/src/main/java/org/yapp/auth/AuthToken.java @@ -0,0 +1,5 @@ +package org.yapp.auth; + +public record AuthToken(String accessToken, String refreshToken) { + +} diff --git a/common/auth/src/main/java/org/yapp/auth/AuthTokenGenerator.java b/common/auth/src/main/java/org/yapp/auth/AuthTokenGenerator.java new file mode 100644 index 0000000..fd6778e --- /dev/null +++ b/common/auth/src/main/java/org/yapp/auth/AuthTokenGenerator.java @@ -0,0 +1,30 @@ +package org.yapp.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.yapp.jwt.JwtUtil; + +@Component +@RequiredArgsConstructor +public class AuthTokenGenerator { + + private final JwtUtil jwtUtil; + + @Value("${jwt.accessToken.expiration}") + private Long accessTokenExpiration; + + @Value("${jwt.refreshToken.expiration}") + private Long refreshTokenExpiration; + + + public AuthToken generate(Long userId, String oauthId, String role) { + String accessToken = jwtUtil.createJwt("access_token", userId, oauthId, role, + accessTokenExpiration); + + String refreshToken = jwtUtil.createJwt("refesh_token", userId, oauthId, role, + refreshTokenExpiration); + + return new AuthToken(accessToken, refreshToken); + } +} diff --git a/common/auth/src/main/java/org/yapp/jwt/JwtFilter.java b/common/auth/src/main/java/org/yapp/jwt/JwtFilter.java new file mode 100644 index 0000000..4d0ac2b --- /dev/null +++ b/common/auth/src/main/java/org/yapp/jwt/JwtFilter.java @@ -0,0 +1,68 @@ +package org.yapp.jwt; + +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +@Component +public class JwtFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String accessToken = request.getHeader("Authorization"); + if (accessToken == null || !accessToken.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + accessToken = accessToken.substring(7); + + try { + jwtUtil.isExpired(accessToken); + } catch (ExpiredJwtException e) { + + PrintWriter writer = response.getWriter(); + writer.print("access token expired"); + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + String category = jwtUtil.getCategory(accessToken); + if (!category.equals("access_token")) { + PrintWriter writer = response.getWriter(); + writer.print("invalid access token"); + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + Long userId = jwtUtil.getUserId(accessToken); + String role = jwtUtil.getRole(accessToken); + + Authentication authToken = + new UsernamePasswordAuthenticationToken(userId, null, Collections.singleton( + (GrantedAuthority) () -> role)); + + SecurityContextHolder.getContext().setAuthentication(authToken); + + filterChain.doFilter(request, response); + } +} diff --git a/common/auth/src/main/java/org/yapp/jwt/JwtUtil.java b/common/auth/src/main/java/org/yapp/jwt/JwtUtil.java new file mode 100644 index 0000000..1db9c7f --- /dev/null +++ b/common/auth/src/main/java/org/yapp/jwt/JwtUtil.java @@ -0,0 +1,89 @@ +package org.yapp.jwt; + +import io.jsonwebtoken.Jwts; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtUtil { + + private final SecretKey secretKey; + + public JwtUtil(@Value("${jwt.secret}") String secret) { + secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), + Jwts.SIG.HS256.key().build().getAlgorithm()); + } + + public String getOauthId(String token) { + String oauthId; + try { + oauthId = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token) + .getPayload().get("oauthId", String.class); + } catch (Exception e) { + throw new RuntimeException(); + } + return oauthId; + } + + public String getRole(String token) { + String role; + try { + role = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload() + .get("role", String.class); + } catch (Exception e) { + throw new RuntimeException(); + } + return role; + } + + public Boolean isExpired(String token) { + boolean before; + try { + before = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token) + .getPayload().getExpiration().before(new Date()); + } catch (Exception e) { + throw new RuntimeException(); + } + return before; + } + + public String createJwt(String category, Long userId, String oauthId, String role, + Long expiredMs) { + return Jwts.builder() + .claim("category", category) + .claim("userId", userId) + .claim("oauthId", oauthId) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } + + public Long getUserId(String token) { + Long userId; + try { + userId = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token) + .getPayload().get("userId", Long.class); + } catch (Exception e) { + throw new RuntimeException(); + } + return userId; + } + + public String getCategory(String token) { + String category; + try { + category = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token) + .getPayload().get("category", String.class); + } catch (Exception e) { + throw new RuntimeException(); + } + return category; + } +} + diff --git a/common/domain/build.gradle b/common/domain/build.gradle new file mode 100644 index 0000000..288bf73 --- /dev/null +++ b/common/domain/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' +} + +group = 'org.yapp' +version = '0.0.1-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation('io.hypersistence:hypersistence-utils-hibernate-62:3.7.0') + + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/common/domain/src/main/java/org/yapp/config/JpaConfig.java b/common/domain/src/main/java/org/yapp/config/JpaConfig.java new file mode 100644 index 0000000..7f7f0e6 --- /dev/null +++ b/common/domain/src/main/java/org/yapp/config/JpaConfig.java @@ -0,0 +1,9 @@ +package org.yapp.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { +} \ No newline at end of file diff --git a/common/src/main/java/org/yapp/domain/BaseEntity.java b/common/domain/src/main/java/org/yapp/domain/BaseEntity.java similarity index 99% rename from common/src/main/java/org/yapp/domain/BaseEntity.java rename to common/domain/src/main/java/org/yapp/domain/BaseEntity.java index 4752f1f..1bf7cfa 100644 --- a/common/src/main/java/org/yapp/domain/BaseEntity.java +++ b/common/domain/src/main/java/org/yapp/domain/BaseEntity.java @@ -4,13 +4,12 @@ import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; - @Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) diff --git a/common/domain/src/main/java/org/yapp/domain/block/BlockContact.java b/common/domain/src/main/java/org/yapp/domain/block/BlockContact.java new file mode 100644 index 0000000..c7cf114 --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/block/BlockContact.java @@ -0,0 +1,46 @@ +package org.yapp.domain.block; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.yapp.domain.BaseEntity; +import org.yapp.domain.user.User; + +@Entity +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "blocked_contacts", + uniqueConstraints = { + @UniqueConstraint(name = "unq_user_phone", columnNames = {"user_id", "phoneNumber"}) + }, + indexes = { + @Index(name = "idx_phone_number", columnList = "phoneNumber"), + } +) +public class BlockContact extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(nullable = false) + private String phoneNumber; +} \ No newline at end of file diff --git a/common/domain/src/main/java/org/yapp/domain/block/UserBlock.java b/common/domain/src/main/java/org/yapp/domain/block/UserBlock.java new file mode 100644 index 0000000..df1d1ea --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/block/UserBlock.java @@ -0,0 +1,37 @@ +package org.yapp.domain.block; + + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.yapp.domain.BaseEntity; +import org.yapp.domain.user.User; + +@Entity +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class UserBlock extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_block_id") + private Long id; + + @ManyToOne + @JoinColumn(name = "blocking_user_id", nullable = false) + private User BlockingUser; + + @ManyToOne + @JoinColumn(name = "blocked_user_id", nullable = false) + private User BlockedUser; +} diff --git a/common/domain/src/main/java/org/yapp/domain/match/MatchInfo.java b/common/domain/src/main/java/org/yapp/domain/match/MatchInfo.java new file mode 100644 index 0000000..c3a4a33 --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/match/MatchInfo.java @@ -0,0 +1,70 @@ +package org.yapp.domain.match; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDate; +import lombok.Getter; +import org.yapp.domain.user.User; + +@Entity +@Getter +public class MatchInfo { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "date") + private LocalDate date; + + @ManyToOne + @JoinColumn(name = "user_1") + private User user1; + + @Column(name = "user_1_piece_checked") + private Boolean user1PieceChecked = false; + + @Column(name = "user_1_accept") + private Boolean user1Accepted = false; + + @ManyToOne + @JoinColumn(name = "user_2") + private User user2; + + @Column(name = "user_2_piece_checked") + private Boolean user2PieceChecked = false; + + @Column(name = "user_2_accept") + private Boolean user2Accepted = false; + + public MatchInfo(LocalDate date, User user1, User user2) { + this.date = date; + this.user1 = user1; + this.user2 = user2; + } + + public static MatchInfo createMatchInfo(User user1, User user2) { + return new MatchInfo(LocalDate.now(), user1, user2); + } + + public void checkPiece(Long userId) { + if (user1.getId().equals(userId)) { + user1PieceChecked = true; + } else { + user2PieceChecked = true; + } + } + + public void acceptPiece(Long userId) { + if (user1.getId().equals(userId)) { + user1Accepted = true; + } else { + user2Accepted = true; + } + } +} diff --git a/common/src/main/java/org/yapp/domain/profile/Profile.java b/common/domain/src/main/java/org/yapp/domain/profile/Profile.java similarity index 81% rename from common/src/main/java/org/yapp/domain/profile/Profile.java rename to common/domain/src/main/java/org/yapp/domain/profile/Profile.java index 4584f45..2c0d53f 100644 --- a/common/src/main/java/org/yapp/domain/profile/Profile.java +++ b/common/domain/src/main/java/org/yapp/domain/profile/Profile.java @@ -4,6 +4,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -15,6 +17,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.yapp.domain.BaseEntity; import org.yapp.domain.user.User; @Table(name = "profile") @@ -23,7 +26,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class Profile { +public class Profile extends BaseEntity { @Id @Column(name = "profile_id") @@ -33,6 +36,10 @@ public class Profile { @OneToOne(mappedBy = "profile") private User user; + @Enumerated(EnumType.STRING) + @Builder.Default + private ProfileStatus profileStatus = ProfileStatus.INCOMPLETE; + @Embedded private ProfileBasic profileBasic; @@ -61,4 +68,8 @@ public void updateProfileValuePicks(List profileValuePicks) { public void updateProfileValueTalks(List profileValueTalks) { this.profileValueTalks = profileValueTalks; } + + public void updateProfileStatus(ProfileStatus profileStatus) { + this.profileStatus = profileStatus; + } } diff --git a/common/src/main/java/org/yapp/domain/profile/ProfileBasic.java b/common/domain/src/main/java/org/yapp/domain/profile/ProfileBasic.java similarity index 100% rename from common/src/main/java/org/yapp/domain/profile/ProfileBasic.java rename to common/domain/src/main/java/org/yapp/domain/profile/ProfileBasic.java diff --git a/common/src/main/java/org/yapp/domain/profile/ProfileBio.java b/common/domain/src/main/java/org/yapp/domain/profile/ProfileBio.java similarity index 100% rename from common/src/main/java/org/yapp/domain/profile/ProfileBio.java rename to common/domain/src/main/java/org/yapp/domain/profile/ProfileBio.java diff --git a/common/domain/src/main/java/org/yapp/domain/profile/ProfileRejectHistory.java b/common/domain/src/main/java/org/yapp/domain/profile/ProfileRejectHistory.java new file mode 100644 index 0000000..c182028 --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/profile/ProfileRejectHistory.java @@ -0,0 +1,37 @@ +package org.yapp.domain.profile; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.yapp.domain.BaseEntity; +import org.yapp.domain.user.User; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +public class ProfileRejectHistory extends BaseEntity { + + @Id + @Column(name = "profile_reject_history_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + boolean reasonImage; + + boolean reasonDescription; +} diff --git a/common/domain/src/main/java/org/yapp/domain/profile/ProfileStatus.java b/common/domain/src/main/java/org/yapp/domain/profile/ProfileStatus.java new file mode 100644 index 0000000..1cfe51f --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/profile/ProfileStatus.java @@ -0,0 +1,29 @@ +package org.yapp.domain.profile; + +public enum ProfileStatus { + + INCOMPLETE("미완료"), + REJECTED("보류"), + REVISED("수정 제출"), + APPROVED("통과"); + + private final String displayName; + + + ProfileStatus(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public static ProfileStatus fromDisplayName(String displayName) { + for (ProfileStatus status : ProfileStatus.values()) { + if (status.displayName.equals(displayName)) { + return status; + } + } + throw new IllegalArgumentException("Invalid display name: " + displayName); + } +} diff --git a/common/domain/src/main/java/org/yapp/domain/profile/ProfileValuePick.java b/common/domain/src/main/java/org/yapp/domain/profile/ProfileValuePick.java new file mode 100644 index 0000000..894da4a --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/profile/ProfileValuePick.java @@ -0,0 +1,53 @@ +package org.yapp.domain.profile; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.yapp.domain.value.ValuePick; + +@Entity +@Table(name = "profile_value_pick") +@Getter +@NoArgsConstructor +public class ProfileValuePick { + + @Id + @Column(name = "profile_value_pick_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", nullable = false) + private Profile profile; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "value_pick_id", nullable = false) + private ValuePick valuePick; + + @Column + private Integer selectedAnswer; + + @Builder + public ProfileValuePick(Profile profile, ValuePick valuePick, Integer selectedAnswer) { + this.profile = profile; + this.valuePick = valuePick; + this.selectedAnswer = selectedAnswer; + } + + public void updatedSelectedAnswer(Integer newSelectedAnswer) { + if (newSelectedAnswer == null || newSelectedAnswer <= 0) { + throw new IllegalArgumentException("선택된 항목은 1 이상이어야 합니다."); + } + + this.selectedAnswer = newSelectedAnswer; + } +} diff --git a/common/src/main/java/org/yapp/domain/profile/ProfileValueTalk.java b/common/domain/src/main/java/org/yapp/domain/profile/ProfileValueTalk.java similarity index 100% rename from common/src/main/java/org/yapp/domain/profile/ProfileValueTalk.java rename to common/domain/src/main/java/org/yapp/domain/profile/ProfileValueTalk.java diff --git a/common/domain/src/main/java/org/yapp/domain/report/Report.java b/common/domain/src/main/java/org/yapp/domain/report/Report.java new file mode 100644 index 0000000..fc8dd10 --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/report/Report.java @@ -0,0 +1,39 @@ +package org.yapp.domain.report; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.yapp.domain.BaseEntity; +import org.yapp.domain.user.User; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +public class Report extends BaseEntity { + + @Id + @Column(name = "report_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "reporter_user_id", nullable = false) + private User reporter; + + @ManyToOne + @JoinColumn(name = "reported_user_id", nullable = false) + private User reportedUser; + + @Column(length = 500) + private String reason; +} diff --git a/common/src/main/java/org/yapp/domain/term/Term.java b/common/domain/src/main/java/org/yapp/domain/term/Term.java similarity index 79% rename from common/src/main/java/org/yapp/domain/term/Term.java rename to common/domain/src/main/java/org/yapp/domain/term/Term.java index e0ac2a1..62092ee 100644 --- a/common/src/main/java/org/yapp/domain/term/Term.java +++ b/common/domain/src/main/java/org/yapp/domain/term/Term.java @@ -1,20 +1,24 @@ package org.yapp.domain.term; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor @AllArgsConstructor @Builder public class Term { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "term_id") diff --git a/common/src/main/java/org/yapp/domain/term/TermAgreement.java b/common/domain/src/main/java/org/yapp/domain/term/TermAgreement.java similarity index 70% rename from common/src/main/java/org/yapp/domain/term/TermAgreement.java rename to common/domain/src/main/java/org/yapp/domain/term/TermAgreement.java index 2ae9f7c..1e443db 100644 --- a/common/src/main/java/org/yapp/domain/term/TermAgreement.java +++ b/common/domain/src/main/java/org/yapp/domain/term/TermAgreement.java @@ -1,20 +1,27 @@ package org.yapp.domain.term; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.yapp.domain.user.User; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor @AllArgsConstructor @Builder public class TermAgreement { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "agreement_id") diff --git a/api/src/main/java/org/yapp/domain/auth/presentation/dto/enums/RoleStatus.java b/common/domain/src/main/java/org/yapp/domain/user/RoleStatus.java similarity index 85% rename from api/src/main/java/org/yapp/domain/auth/presentation/dto/enums/RoleStatus.java rename to common/domain/src/main/java/org/yapp/domain/user/RoleStatus.java index 192f4c0..35d1b71 100644 --- a/api/src/main/java/org/yapp/domain/auth/presentation/dto/enums/RoleStatus.java +++ b/common/domain/src/main/java/org/yapp/domain/user/RoleStatus.java @@ -1,4 +1,4 @@ -package org.yapp.domain.auth.presentation.dto.enums; +package org.yapp.domain.user; import com.fasterxml.jackson.annotation.JsonValue; import lombok.AllArgsConstructor; diff --git a/common/domain/src/main/java/org/yapp/domain/user/User.java b/common/domain/src/main/java/org/yapp/domain/user/User.java new file mode 100644 index 0000000..04ebaf2 --- /dev/null +++ b/common/domain/src/main/java/org/yapp/domain/user/User.java @@ -0,0 +1,59 @@ +package org.yapp.domain.user; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.yapp.domain.BaseEntity; +import org.yapp.domain.profile.Profile; + +@Table(name = "user_table") +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class User extends BaseEntity { + + @Id + @Column(name = "user_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "oauth_id") + private String oauthId; + + @Column(name = "name") + private String name; + + @Column(name = "phone") + private String phoneNumber; + + @Column(name = "role") + private String role; + + @OneToOne + @JoinColumn(name = "profile_id", unique = true) // User가 profile_id를 FK로 가짐 + private Profile profile; + + public void initializePhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + + public void updateUserRole(String role) { + this.role = role; + } +} + diff --git a/common/src/main/java/org/yapp/domain/value/ValuePick.java b/common/domain/src/main/java/org/yapp/domain/value/ValuePick.java similarity index 100% rename from common/src/main/java/org/yapp/domain/value/ValuePick.java rename to common/domain/src/main/java/org/yapp/domain/value/ValuePick.java diff --git a/common/src/main/java/org/yapp/domain/value/ValueTalk.java b/common/domain/src/main/java/org/yapp/domain/value/ValueTalk.java similarity index 100% rename from common/src/main/java/org/yapp/domain/value/ValueTalk.java rename to common/domain/src/main/java/org/yapp/domain/value/ValueTalk.java diff --git a/common/exception/build.gradle b/common/exception/build.gradle new file mode 100644 index 0000000..fda8bf5 --- /dev/null +++ b/common/exception/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group = 'org.yapp' +version = '0.0.1-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/common/exception/src/main/java/org/yapp/error/GlobalExceptionHandler.java b/common/exception/src/main/java/org/yapp/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..6161dda --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/GlobalExceptionHandler.java @@ -0,0 +1,100 @@ +package org.yapp.error; + +import java.util.List; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.yapp.error.dto.CommonErrorCode; +import org.yapp.error.dto.ErrorCode; +import org.yapp.error.dto.ErrorResponse; +import org.yapp.error.exception.ApplicationException; + + +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(ApplicationException.class) + public ResponseEntity handleQuizException(final ApplicationException e) { + final ErrorCode errorCode = e.getErrorCode(); + return handleExceptionInternal(errorCode); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(final IllegalArgumentException e) { + log.warn("handleIllegalArgument", e); + final ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; + return handleExceptionInternal(errorCode, e.getMessage()); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid( + final MethodArgumentNotValidException e, + final HttpHeaders headers, + final HttpStatusCode status, + final WebRequest request) { + log.warn("handleIllegalArgument", e); + final ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; + return handleExceptionInternal(e, errorCode); + } + + @ExceptionHandler({Exception.class}) + public ResponseEntity handleAllException(final Exception ex) { + log.warn("handleAllException", ex); + final ErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR; + return handleExceptionInternal(errorCode); + } + + private ResponseEntity handleExceptionInternal(final ErrorCode errorCode) { + return ResponseEntity.status(errorCode.getHttpStatus()) + .body(makeErrorResponse(errorCode)); + } + + private ResponseEntity handleExceptionInternal(final ErrorCode errorCode, + final String message) { + return ResponseEntity.status(errorCode.getHttpStatus()) + .body(makeErrorResponse(errorCode, message)); + } + + private ResponseEntity handleExceptionInternal(final BindException e, + final ErrorCode errorCode) { + return ResponseEntity.status(errorCode.getHttpStatus()) + .body(makeErrorResponse(e, errorCode)); + } + + private ErrorResponse makeErrorResponse(final ErrorCode errorCode) { + return ErrorResponse.builder() + .code(errorCode.name()) + .message(errorCode.getMessage()) + .build(); + } + + private ErrorResponse makeErrorResponse(final ErrorCode errorCode, final String message) { + return ErrorResponse.builder() + .code(errorCode.name()) + .message(message) + .build(); + } + + private ErrorResponse makeErrorResponse(final BindException e, final ErrorCode errorCode) { + final List validationErrorList = e.getBindingResult() + .getFieldErrors() + .stream() + .map(ErrorResponse.ValidationError::of) + .collect(Collectors.toList()); + + return ErrorResponse.builder() + .code(errorCode.name()) + .message(errorCode.getMessage()) + .errors(validationErrorList) + .build(); + } +} \ No newline at end of file diff --git a/common/exception/src/main/java/org/yapp/error/code/auth/AuthErrorCode.java b/common/exception/src/main/java/org/yapp/error/code/auth/AuthErrorCode.java new file mode 100644 index 0000000..3e90524 --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/code/auth/AuthErrorCode.java @@ -0,0 +1,18 @@ +package org.yapp.error.code.auth; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.yapp.error.dto.ErrorCode; + +@Getter +@RequiredArgsConstructor +public enum AuthErrorCode implements ErrorCode { + OAUTH_ERROR(HttpStatus.FORBIDDEN, "Oauth Error"), + ACCESS_DENIED(HttpStatus.FORBIDDEN, "권한이 없습니다."), + ; + + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/code/auth/SmsAuthErrorCode.java b/common/exception/src/main/java/org/yapp/error/code/auth/SmsAuthErrorCode.java new file mode 100644 index 0000000..122f81f --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/code/auth/SmsAuthErrorCode.java @@ -0,0 +1,15 @@ +package org.yapp.error.code.auth; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.yapp.error.dto.ErrorCode; + +@Getter +@RequiredArgsConstructor +public enum SmsAuthErrorCode implements ErrorCode { + CODE_NOT_EXIST(HttpStatus.BAD_REQUEST, "code not exist"), + CODE_NOT_CORRECT(HttpStatus.BAD_REQUEST, "code not correct"); + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/CommonErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/CommonErrorCode.java new file mode 100644 index 0000000..1f8a1ee --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/CommonErrorCode.java @@ -0,0 +1,23 @@ +package org.yapp.error.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum CommonErrorCode implements ErrorCode{ + // 400 BAD_REQUEST + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid parameter included"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "Authentication required"), + FORBIDDEN(HttpStatus.FORBIDDEN, "You do not have permission to access this resource"), + + // 404 NOT_FOUND + RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "Resource not found"), + + // 500 INTERNAL_SERVER_ERROR + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error"); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/ErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/ErrorCode.java new file mode 100644 index 0000000..0ec5cf7 --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/ErrorCode.java @@ -0,0 +1,9 @@ +package org.yapp.error.dto; + +import org.springframework.http.HttpStatus; + +public interface ErrorCode { + String name(); + HttpStatus getHttpStatus(); + String getMessage(); +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/ErrorResponse.java b/common/exception/src/main/java/org/yapp/error/dto/ErrorResponse.java new file mode 100644 index 0000000..87099b9 --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/ErrorResponse.java @@ -0,0 +1,36 @@ +package org.yapp.error.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.FieldError; + +@Getter +@Builder +@RequiredArgsConstructor +public class ErrorResponse { + + private final String code; + private final String message; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private final List errors; + + @Getter + @Builder + @RequiredArgsConstructor + public static class ValidationError { + + private final String field; + private final String message; + + public static ValidationError of(final FieldError fieldError) { + return ValidationError.builder() + .field(fieldError.getField()) + .message(fieldError.getDefaultMessage()) + .build(); + } + } +} \ No newline at end of file diff --git a/common/exception/src/main/java/org/yapp/error/dto/MatchErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/MatchErrorCode.java new file mode 100644 index 0000000..ca14e0b --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/MatchErrorCode.java @@ -0,0 +1,15 @@ +package org.yapp.error.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum MatchErrorCode implements ErrorCode { + NOTFOUND_MATCH(HttpStatus.NOT_FOUND, "Match not found"), + ; + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/ProfileErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/ProfileErrorCode.java new file mode 100644 index 0000000..4e73c11 --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/ProfileErrorCode.java @@ -0,0 +1,16 @@ +package org.yapp.error.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ProfileErrorCode implements ErrorCode { + INACTIVE_PROFILE(HttpStatus.FORBIDDEN, "비활성화 프로필입니다."), + NOTFOUND_PROFILE(HttpStatus.NOT_FOUND, "존재하지 않는 프로필입니다."), + ; + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/SecurityErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/SecurityErrorCode.java new file mode 100644 index 0000000..e842d1d --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/SecurityErrorCode.java @@ -0,0 +1,30 @@ +package org.yapp.error.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum SecurityErrorCode implements ErrorCode { + // Authentication Errors + MISSING_ACCESS_TOKEN(HttpStatus.BAD_REQUEST, "Access token is missing."), + INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "Invalid access token."), + EXPIRED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "Access token has expired."), + + MISSING_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "Refresh token is missing."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "Invalid refresh token."), + EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "Refresh token has expired."), + + INVALID_AUTHORIZATION_HEADER(HttpStatus.BAD_REQUEST, "Authorization header is invalid."), + UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "Unsupported token format."), + INVALID_JWT_SIGNATURE(HttpStatus.UNAUTHORIZED, "Invalid JWT signature."), + + // Authorization Errors + ACCESS_DENIED(HttpStatus.FORBIDDEN, "Access is denied."), + ROLE_NOT_ALLOWED(HttpStatus.FORBIDDEN, "User does not have the required role to access this resource."), + ; + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/TermErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/TermErrorCode.java new file mode 100644 index 0000000..dd46507 --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/TermErrorCode.java @@ -0,0 +1,17 @@ +package org.yapp.error.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum TermErrorCode implements ErrorCode { + INACTIVE_TERM(HttpStatus.FORBIDDEN, "접근이 금지된 약관입니다."), + NOTFOUND_TERM(HttpStatus.NOT_FOUND, "유효하지 않은 약관입니다."), + NOT_REQUIRED_TERM(HttpStatus.BAD_REQUEST, "모든 필수 약관을 동의해야합니다.") + ; + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/dto/UserErrorCode.java b/common/exception/src/main/java/org/yapp/error/dto/UserErrorCode.java new file mode 100644 index 0000000..e2304b0 --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/dto/UserErrorCode.java @@ -0,0 +1,16 @@ +package org.yapp.error.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum UserErrorCode implements ErrorCode { + INACTIVE_USER(HttpStatus.FORBIDDEN, "User is inactive"), + NOTFOUND_USER(HttpStatus.NOT_FOUND, "User not found"), + ; + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/common/exception/src/main/java/org/yapp/error/exception/ApplicationException.java b/common/exception/src/main/java/org/yapp/error/exception/ApplicationException.java new file mode 100644 index 0000000..7fab0ee --- /dev/null +++ b/common/exception/src/main/java/org/yapp/error/exception/ApplicationException.java @@ -0,0 +1,12 @@ +package org.yapp.error.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.yapp.error.dto.ErrorCode; + +@Getter +@RequiredArgsConstructor +public class ApplicationException extends RuntimeException { + + private final ErrorCode errorCode; +} diff --git a/common/format/build.gradle b/common/format/build.gradle new file mode 100644 index 0000000..fda8bf5 --- /dev/null +++ b/common/format/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group = 'org.yapp' +version = '0.0.1-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/common/format/src/main/java/org/yapp/util/CommonResponse.java b/common/format/src/main/java/org/yapp/util/CommonResponse.java new file mode 100644 index 0000000..3c29533 --- /dev/null +++ b/common/format/src/main/java/org/yapp/util/CommonResponse.java @@ -0,0 +1,60 @@ +package org.yapp.util; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CommonResponse { + + private static final String SUCCESS_STATUS = "success"; + private static final String FAIL_STATUS = "fail"; + private static final String ERROR_STATUS = "error"; + + private String status; + private String message; + private T data; + + private CommonResponse(String status, T data, String message) { + this.status = status; + this.data = data; + this.message = message; + } + + public static CommonResponse createError(String message) { + return new CommonResponse<>(ERROR_STATUS, null, message); + } + + public static CommonResponse createFail(BindingResult bindingResult) { + Map errors = new HashMap<>(); + + List allErrors = bindingResult.getAllErrors(); + for (ObjectError error : allErrors) { + if (error instanceof FieldError) { + errors.put(((FieldError) error).getField(), error.getDefaultMessage()); + } else { + errors.put(error.getObjectName(), error.getDefaultMessage()); + } + } + return new CommonResponse<>(FAIL_STATUS, errors, null); + } + + public static CommonResponse createSuccess(T data) { + return new CommonResponse<>(SUCCESS_STATUS, data, "요청이 성공적으로 처리되었습니다."); + } + + public static CommonResponse createSuccessWithNoContent() { + return new CommonResponse<>(SUCCESS_STATUS, null, null); + } + + public static CommonResponse createSuccessWithNoContent(String message) { + return new CommonResponse<>(SUCCESS_STATUS, null, message); + } +} \ No newline at end of file diff --git a/common/format/src/main/java/org/yapp/util/PageResponse.java b/common/format/src/main/java/org/yapp/util/PageResponse.java new file mode 100644 index 0000000..b3d51d3 --- /dev/null +++ b/common/format/src/main/java/org/yapp/util/PageResponse.java @@ -0,0 +1,18 @@ +package org.yapp.util; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class PageResponse { + + private List content; + private int currentPage; + private int pageSize; + private int totalPages; + private long totalElements; + private boolean isFirstPage; + private boolean isLastPage; +} \ No newline at end of file diff --git a/common/src/main/java/org/yapp/domain/block/Block.java b/common/src/main/java/org/yapp/domain/block/Block.java deleted file mode 100644 index e91670a..0000000 --- a/common/src/main/java/org/yapp/domain/block/Block.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.yapp.domain.block; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.yapp.domain.BaseEntity; -import org.yapp.domain.user.User; - -@Entity -@Builder -@Getter -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "blocked_contacts", - uniqueConstraints = { - @UniqueConstraint(name = "unq_user_phone", columnNames = {"user_id", "phoneNumber"}) - }, - indexes = { - @Index(name = "idx_phone_number", columnList = "phoneNumber"), - } -) -public class Block extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column(nullable = false) - private String phoneNumber; -} \ No newline at end of file diff --git a/common/src/main/java/org/yapp/domain/match/MatchInfo.java b/common/src/main/java/org/yapp/domain/match/MatchInfo.java index 2594ce0..2b69cc0 100644 --- a/common/src/main/java/org/yapp/domain/match/MatchInfo.java +++ b/common/src/main/java/org/yapp/domain/match/MatchInfo.java @@ -69,4 +69,4 @@ public void acceptPiece(Long userId) { user2Accepted = true; } } -} +} \ No newline at end of file diff --git a/common/src/main/java/org/yapp/domain/profile/ProfileValuePick.java b/common/src/main/java/org/yapp/domain/profile/ProfileValuePick.java deleted file mode 100644 index 037340c..0000000 --- a/common/src/main/java/org/yapp/domain/profile/ProfileValuePick.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.yapp.domain.profile; - -import org.yapp.domain.value.ValuePick; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "profile_value_pick") -@Getter -@NoArgsConstructor -public class ProfileValuePick { - @Id - @Column(name = "profile_value_pick_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "profile_id", nullable = false) - private Profile profile; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "value_pick_id", nullable = false) - private ValuePick valuePick; - - @Column - private Integer selectedAnswer; - - @Builder - public ProfileValuePick(Profile profile, ValuePick valuePick, Integer selectedAnswer) { - this.profile = profile; - this.valuePick = valuePick; - this.selectedAnswer = selectedAnswer; - } - - public void updatedSelectedAnswer(Integer newSelectedAnswer) { - if (newSelectedAnswer == null || newSelectedAnswer <= 0) - throw new IllegalArgumentException("선택된 항목은 1 이상이어야 합니다."); - - this.selectedAnswer = newSelectedAnswer; - } -} diff --git a/common/src/main/java/org/yapp/domain/user/User.java b/common/src/main/java/org/yapp/domain/user/User.java deleted file mode 100644 index d712e6f..0000000 --- a/common/src/main/java/org/yapp/domain/user/User.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.yapp.domain.user; - -import org.yapp.domain.profile.Profile; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; -import jakarta.persistence.Table; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Table(name = "user_table") -@Entity -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class User { - - @Id - @Column(name = "user_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "oauth_id") - private String oauthId; - - @Column(name = "name") - private String name; - - @Column(name = "phone") - private String phoneNumber; - - @Column(name = "role") - private String role; - - @OneToOne - @JoinColumn(name = "profile_id", unique = true) // User가 profile_id를 FK로 가짐 - private Profile profile; - - public void initializePhoneNumber(String phoneNumber) { - this.phoneNumber = phoneNumber; - } - - public void setProfile(Profile profile) { - this.profile = profile; - } - - public void updateUserRole(String role) { - this.role = role; - } -} - diff --git a/settings.gradle b/settings.gradle index 6d789cb..6b03748 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,12 @@ rootProject.name = 'dating-match' include 'api' include 'admin' include 'common' +include 'common:domain' +findProject(':common:domain')?.name = 'domain' +include 'common:exception' +findProject(':common:exception')?.name = 'exception' +include 'common:format' +findProject(':common:format')?.name = 'format' +include 'common:auth' +findProject(':common:auth')?.name = 'auth'