diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java new file mode 100644 index 00000000..de11a355 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java @@ -0,0 +1,128 @@ +package io.oeid.mogakgo.common.swagger.template; + +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.core.properties.swagger.error.SwaggerProfileCardLikeErrorExamples; +import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples; +import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeAPIRes; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeCreateAPIRes; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; +import io.oeid.mogakgo.exception.dto.ErrorResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "Profile Card Like", description = "프로필 카드 찔러보기 관련 API") +public interface ProfileCardLikeSwagger { + + @Operation(summary = "관심 있는 프로필 카드에 대해 찔러보기 요청 생성", description = "사용자가 관심 있는 프로필 카드에 찔러보기 요청을 보낼 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "찔러보기 요청 생성 성공", + content = @Content(schema = @Schema(implementation = UserProfileLikeCreateAPIRes.class))), + @ApiResponse(responseCode = "400", description = "요청한 데이터가 유효하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E040102", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_LIKE_ALREADY_EXIST), + @ExampleObject(name = "E040103", value = SwaggerProfileCardLikeErrorExamples.INVALID_PROFILE_CARD_LIKE_RECEIVER_INFO) + } + )), + @ApiResponse(responseCode = "403", description = "본인이 아닌 프로필 카드에 대한 찔러보기 요청 권한이 없음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E040101", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION) + )), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + )), + }) + ResponseEntity create( + @Parameter(hidden = true) Long userId, + UserProfileLikeCreateAPIReq request + ); + + @Operation(summary = "사용자가 받은 찔러보기 요청 수 조회", description = "사용자가 자신이 받은 찔러보기 요청 수를 조회할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "찔러보기 요청 수 조회 성공", + content = @Content(schema = @Schema(implementation = UserProfileLikeAPIRes.class))), + @ApiResponse(responseCode = "403", description = "본인이 아닌 프로필 카드에 대한 찔러보기 요청 권한이 없음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E040101", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION) + )), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + )), + }) + ResponseEntity getProfileLikeCountByReceiver( + @Parameter(hidden = true) Long userId, + @Parameter(description = "'찔러보기' 요청을 조회하는 사용자 ID", required = true) Long id + ); + + @Operation(summary = "사용자가 보낸 찔러보기 요청 수 조회", description = "사용자가 자신이 보낸 찔러보기 요청 수를 조회할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "찔러보기 요청 수 조회 성공", + content = @Content(schema = @Schema(implementation = UserProfileLikeAPIRes.class))), + @ApiResponse(responseCode = "403", description = "본인이 아닌 프로필 카드에 대한 찔러보기 요청 권한이 없음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E040101", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION) + )), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + )), + }) + ResponseEntity getProfileLikeCountBySender( + @Parameter(hidden = true) Long userId, + @Parameter(description = "'찔러보기' 요청을 조회하는 사용자 ID", required = true) Long id + ); + + @Operation(summary = "사용자가 보낸 찔러보기 요청 상세 리스트 조회", description = "사용자가 자신이 보낸 찔러보기 요청 상세 리스트 정보를 조회할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "찔러보기 요청 상세 리스트 조회 성공", + content = @Content(schema = @Schema(implementation = UserProfileLikeInfoAPIRes.class))), + @ApiResponse(responseCode = "403", description = "본인의 프로필 카드에 대한 찔러보기 요청만 조회할 수 있음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E040101", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION) + )), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + )), + }) + @Parameters({ + @Parameter(name = "cursorId", description = "기준이 되는 커서 ID", example = "1"), + @Parameter(name = "pageSize", description = "요청할 데이터 크기", example = "5", required = true), + @Parameter(name = "sortOrder", description = "정렬 방향", example = "ASC"), + }) + ResponseEntity> getProfileLikeInfoBySender( + @Parameter(hidden = true) Long userId, + @Parameter(description = "'찔러보기' 요청을 조회하는 사용자 ID", required = true) Long id, + @Parameter(hidden = true) CursorPaginationInfoReq pageable + ); +} diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java index 7afd418b..06961b05 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java @@ -1,6 +1,5 @@ package io.oeid.mogakgo.common.swagger.template; -import io.oeid.mogakgo.common.annotation.UserId; import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerGeoErrorExamples; @@ -22,9 +21,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; @Tag(name = "Project Card", description = "프로젝트 카드 관련 API") @SuppressWarnings("unused") @@ -163,7 +160,7 @@ ResponseEntity> getRandomOrderedProj content = @Content( mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), - examples = @ExampleObject(name = "E050201", value = SwaggerProjectErrorExamples.PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION))) + examples = @ExampleObject(name = "E030101", value = SwaggerProjectErrorExamples.PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION))) }) @Parameters({ @Parameter(name = "cursorId", description = "기준이 되는 커서 ID", example = "1"), diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java index 15489a65..9c77c5bc 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java @@ -31,6 +31,7 @@ public interface UserSwagger { schema = @Schema(implementation = ErrorResponse.class), examples = { @ExampleObject(name = "E020103", value = SwaggerUserErrorExamples.INVALID_USER_NAME), + @ExampleObject(name = "E020105", value = SwaggerUserErrorExamples.USER_WANTED_JOB_DUPLICATE) })), @ApiResponse(responseCode = "404", description = "OAuth2 정보가 존재하지 않음", content = @Content( diff --git a/src/main/java/io/oeid/mogakgo/core/configuration/SecurityConfig.java b/src/main/java/io/oeid/mogakgo/core/configuration/SecurityConfig.java index d0cd976d..fc241353 100644 --- a/src/main/java/io/oeid/mogakgo/core/configuration/SecurityConfig.java +++ b/src/main/java/io/oeid/mogakgo/core/configuration/SecurityConfig.java @@ -6,7 +6,6 @@ import io.oeid.mogakgo.domain.auth.oauth.GithubOAuth2UserService; import io.oeid.mogakgo.domain.auth.oauth.OAuth2AuthenticationSuccessHandler; import java.util.Arrays; -import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; diff --git a/src/main/java/io/oeid/mogakgo/core/configuration/WebMvcConfig.java b/src/main/java/io/oeid/mogakgo/core/configuration/WebMvcConfig.java index 72bfac05..1d83fef2 100644 --- a/src/main/java/io/oeid/mogakgo/core/configuration/WebMvcConfig.java +++ b/src/main/java/io/oeid/mogakgo/core/configuration/WebMvcConfig.java @@ -4,7 +4,6 @@ import java.util.List; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration diff --git a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java new file mode 100644 index 00000000..d02bc4eb --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java @@ -0,0 +1,12 @@ +package io.oeid.mogakgo.core.properties.swagger.error; + +public class SwaggerProfileCardLikeErrorExamples { + + public static final String PROFILE_CARD_LIKE_ALREADY_EXIST = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E040102\",\"message\":\"이미 찔러보기 요청을 전송한 프로필 카드에 찔러보기 요청을 전송할 수 없습니다.\"}"; + public static final String INVALID_PROFILE_CARD_LIKE_RECEIVER_INFO = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E040103\",\"message\":\"찔러보기 요청의 사용자가 존재하지 않습니다.\"}"; + public static final String PROFILE_CARD_LIKE_FORBIDDEN_OPERATION = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":403,\"code\":\"E040103\",\"message\":\"본인의 프로필 카드 외에 대해 찔러보기 요청 권한이 없습니다.\"}"; + + private SwaggerProfileCardLikeErrorExamples() { + } + +} diff --git a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java index 1cf7daa9..de1c9cbc 100644 --- a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java +++ b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java @@ -3,6 +3,7 @@ public class SwaggerUserErrorExamples { public static final String USER_NOT_FOUND = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":404,\"code\":\"E020301\",\"message\":\"해당 유저가 존재하지 않습니다.\"}"; + public static final String USER_WANTED_JOB_DUPLICATE = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020105\",\"message\":\"중복된 희망 직무가 있습니다.\"}"; public static final String INVALID_USER_NAME = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020103\",\"message\":\"유저 이름은 비어있을 수 없습니다.\"}"; private SwaggerUserErrorExamples() { diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java new file mode 100644 index 00000000..e8f35ce5 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java @@ -0,0 +1,90 @@ +package io.oeid.mogakgo.domain.profile.application; + +import static io.oeid.mogakgo.exception.code.ErrorCode400.PROFILE_CARD_LIKE_ALREADY_EXIST; +import static io.oeid.mogakgo.exception.code.ErrorCode403.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION; + +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; +import io.oeid.mogakgo.domain.profile.exception.ProfileCardLikeException; +import io.oeid.mogakgo.domain.profile.infrastructure.ProfileCardLikeJpaRepository; +import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; +import io.oeid.mogakgo.domain.user.application.UserCommonService; +import io.oeid.mogakgo.domain.user.application.UserProfileService; +import io.oeid.mogakgo.domain.user.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ProfileCardLikeService { + + private final ProfileCardLikeJpaRepository profileCardLikeRepository; + private final ProfileCardService profileCardService; + private final UserCommonService userCommonService; + private final UserProfileService userProfileService; + + @Transactional + public Long create(Long userId, UserProfileLikeCreateAPIReq request) { + User tokenUser = validateToken(userId); + validateSendor(tokenUser, request.getSenderId()); + validateReceiver(userId); + validateLikeAlreadyExist(request.getSenderId(), request.getReceiverId()); + + User receiver = userCommonService.getUserById(request.getReceiverId()); + ProfileCardLike profileCardLike = request.toEntity(tokenUser, receiver); + profileCardLikeRepository.save(profileCardLike); + + profileCardService.increaseTotalLikeAmount(receiver.getId()); + userProfileService.decreaseAvailableLikeCount(userId); + + return profileCardLike.getId(); + } + + // 나의 찔러보기 요청 수 조회 + public Long getReceivedLikeCountForProfileCard(Long userId, Long id) { + User tokenUser = validateToken(userId); + validateSendor(tokenUser, userId); + + return profileCardLikeRepository.getLikeCount(id); + } + + // 내가 보낸 찔러보기 요청 수 조회 + public Long getSentLikeCountForProfileCard(Long userId, Long id) { + User tokenUser = validateToken(userId); + validateSendor(tokenUser, userId); + + return profileCardLikeRepository.getLikeCountByCondition(id, null); + } + + public CursorPaginationResult getLikeInfoSenderProfile( + Long userId, Long id, CursorPaginationInfoReq pageable) { + User tokenUser = validateToken(userId); + validateSendor(tokenUser, userId); + + return profileCardLikeRepository.getLikeInfoBySender(id, pageable); + } + + private User validateToken(Long userId) { + return userCommonService.getUserById(userId); + } + + private void validateSendor(User tokenUser, Long userId) { + if (!tokenUser.getId().equals(userId)) { + throw new ProfileCardLikeException(PROFILE_CARD_LIKE_FORBIDDEN_OPERATION); + } + } + + private void validateReceiver(Long userId) { + userCommonService.getUserById(userId); + } + + private void validateLikeAlreadyExist(Long userId, Long creatorId) { + if (profileCardLikeRepository.findBySenderAndReceiver(userId, creatorId).isPresent()) { + throw new ProfileCardLikeException(PROFILE_CARD_LIKE_ALREADY_EXIST); + } + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java index b704bf84..48f19bb2 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java @@ -1,14 +1,17 @@ package io.oeid.mogakgo.domain.profile.application; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_SERVICE_REGION; +import static io.oeid.mogakgo.exception.code.ErrorCode404.USER_NOT_FOUND; import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.domain.geo.domain.enums.Region; import io.oeid.mogakgo.domain.geo.exception.GeoException; +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCard; import io.oeid.mogakgo.domain.profile.infrastructure.ProfileCardJpaRepository; import io.oeid.mogakgo.domain.user.application.UserCommonService; import io.oeid.mogakgo.domain.user.domain.User; +import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.domain.user.presentation.dto.res.UserPublicApiResponse; import java.util.Collections; import lombok.RequiredArgsConstructor; @@ -38,6 +41,16 @@ public CursorPaginationResult getRandomOrderedProfileCard return profiles; } + public void increaseTotalLikeAmount(Long userId) { + var profileCard = getProfileCard(userId); + profileCard.addLike(); + } + + private ProfileCard getProfileCard(Long userId) { + return profileCardRepository.findByUserId(userId) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + } + private User validateToken(Long userId) { return userCommonService.getUserById(userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java index 69c3b569..08cc0193 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java @@ -35,6 +35,10 @@ public class ProfileCard { @Column(name = "total_like_amount", nullable = false) private Long totalLikeAmount; + public void addLike() { + this.totalLikeAmount += 1; + } + @Builder private ProfileCard(User user) { this.user = user; diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java index 9587096f..eb4b2d57 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java @@ -30,7 +30,7 @@ public class ProfileCardLike { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "profile_card_like_id") + @Column(name = "id") private Long id; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardLikeException.java b/src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardLikeException.java new file mode 100644 index 00000000..b59c5074 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardLikeException.java @@ -0,0 +1,11 @@ +package io.oeid.mogakgo.domain.profile.exception; + +import io.oeid.mogakgo.exception.code.ErrorCode; +import io.oeid.mogakgo.exception.exception_class.CustomException; + +public class ProfileCardLikeException extends CustomException { + + public ProfileCardLikeException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java index def1a7e7..5136bab0 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java @@ -1,6 +1,7 @@ package io.oeid.mogakgo.domain.profile.infrastructure; import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCard; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,4 +9,5 @@ public interface ProfileCardJpaRepository extends JpaRepository, ProfileCardRepositoryCustom { + Optional findByUserId(Long userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java new file mode 100644 index 00000000..f4095fb7 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java @@ -0,0 +1,9 @@ +package io.oeid.mogakgo.domain.profile.infrastructure; + +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProfileCardLikeJpaRepository extends JpaRepository, + ProfileCardLikeRepositoryCustom { + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java new file mode 100644 index 00000000..ab5a4498 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java @@ -0,0 +1,16 @@ +package io.oeid.mogakgo.domain.profile.infrastructure; + +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; +import java.util.Optional; + +public interface ProfileCardLikeRepositoryCustom { + + Long getLikeCountByCondition(Long senderId, Long receiverId); + Long getLikeCount(Long userId); + CursorPaginationResult getLikeInfoBySender( + Long senderId, CursorPaginationInfoReq pageable); + Optional findBySenderAndReceiver(Long senderId, Long receiverId); +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java new file mode 100644 index 00000000..43224d6c --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java @@ -0,0 +1,99 @@ +package io.oeid.mogakgo.domain.profile.infrastructure; + +import static io.oeid.mogakgo.domain.profile.domain.entity.QProfileCardLike.profileCardLike; +import static io.oeid.mogakgo.domain.profile.domain.entity.QProfileCard.profileCard; +import static io.oeid.mogakgo.domain.user.domain.QUser.user; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; +import io.oeid.mogakgo.domain.user.domain.QUser; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ProfileCardLikeRepositoryCustomImpl implements ProfileCardLikeRepositoryCustom { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public Long getLikeCountByCondition(Long senderId, Long receiverId) { + + QUser sender = new QUser("sender"); + QUser receiver = new QUser("receiver"); + + return jpaQueryFactory + .select(profileCardLike.count()) + .from(profileCardLike) + .innerJoin(profileCardLike.sender, sender) //.on(profileCardLike.sender.id.eq(user.id)) + .innerJoin(profileCardLike.receiver, receiver) //.on(profileCardLike.receiver.id.eq(user.id)) + .where( + senderIdEq(senderId), + receiveridEq(receiverId) + ) + .fetchOne(); + } + + @Override + public Long getLikeCount(Long userId) { + return jpaQueryFactory.select(profileCard.totalLikeAmount) + .from(profileCard) + .innerJoin(profileCard.user, user) + .where(profileCard.user.id.eq(userId)) + .fetchOne(); + } + + @Override + public CursorPaginationResult getLikeInfoBySender( + Long senderId, CursorPaginationInfoReq pageable + ) { + List entities = jpaQueryFactory.selectFrom(profileCardLike) + .innerJoin(profileCardLike.sender, user).on(profileCardLike.sender.id.eq(user.id)) + .where( + cursorIdCondition(pageable.getCursorId()), + senderIdEq(senderId) + ) + .limit(pageable.getPageSize() + 1) + .fetch(); + + List result = entities.stream().map( + profileCardLike -> new UserProfileLikeInfoAPIRes( + profileCardLike.getReceiver().getUsername(), + profileCardLike.getReceiver().getAvatarUrl(), + profileCardLike.getCreatedAt() + ) + ).toList(); + + return CursorPaginationResult.fromDataWithExtraItemForNextCheck( + result, pageable.getPageSize() + ); + } + + @Override + public Optional findBySenderAndReceiver(Long senderId, Long receiverId) { + return Optional.ofNullable(jpaQueryFactory.selectFrom(profileCardLike) + .where( + senderIdEq(senderId), + receiveridEq(receiverId) + ) + .fetchOne()); + } + + private BooleanExpression senderIdEq(Long senderId) { + return senderId != null ? profileCardLike.sender.id.eq(senderId) : null; + } + + private BooleanExpression receiveridEq(Long receiverId) { + return receiverId != null ? profileCardLike.receiver.id.eq(receiverId) : null; + } + + private BooleanExpression cursorIdCondition(Long cursorId) { + return cursorId != null ? profileCardLike.id.gt(cursorId) : null; + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java new file mode 100644 index 00000000..6103753b --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java @@ -0,0 +1,65 @@ +package io.oeid.mogakgo.domain.profile.presentation; + +import io.oeid.mogakgo.common.annotation.UserId; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.common.swagger.template.ProfileCardLikeSwagger; +import io.oeid.mogakgo.domain.profile.application.ProfileCardLikeService; +import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeAPIRes; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeCreateAPIRes; +import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; +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.ModelAttribute; +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.RestController; + +@RestController +@RequestMapping("/api/v1/profiles") +@RequiredArgsConstructor +public class ProfileCardLikeController implements ProfileCardLikeSwagger { + + private final ProfileCardLikeService profileCardLikeService; + + @PostMapping("/like") + public ResponseEntity create( + @UserId Long userId, @Valid @RequestBody UserProfileLikeCreateAPIReq request + ) { + return ResponseEntity.status(201) + .body(UserProfileLikeCreateAPIRes.of(profileCardLikeService.create(userId, request))); + } + + // 사용자가 받은 '찔러보기' 요청 수 조회 API + @GetMapping("/{id}/receive/like") + public ResponseEntity getProfileLikeCountByReceiver( + @UserId Long userId, @PathVariable Long id + ) { + Long likeCount = profileCardLikeService.getReceivedLikeCountForProfileCard(userId, id); + return ResponseEntity.ok().body(UserProfileLikeAPIRes.from(id, likeCount)); + } + + // 사용자가 보낸 '찔러보기' 요청 수 조회 API + @GetMapping("/{id}/send/like") + public ResponseEntity getProfileLikeCountBySender( + @UserId Long userId, @PathVariable Long id + ) { + Long likeCount = profileCardLikeService.getSentLikeCountForProfileCard(userId, id); + return ResponseEntity.ok().body(UserProfileLikeAPIRes.from(id, likeCount)); + } + + // 사용자가 보낸 '찔러보기' 요청 상세 조회 API + @GetMapping("/list/{id}/like") + public ResponseEntity> getProfileLikeInfoBySender( + @UserId Long userId, @PathVariable Long id, + @Valid @ModelAttribute CursorPaginationInfoReq pageable + ) { + return ResponseEntity.ok() + .body(profileCardLikeService.getLikeInfoSenderProfile(userId, id, pageable)); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java new file mode 100644 index 00000000..3fe304d7 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java @@ -0,0 +1,33 @@ +package io.oeid.mogakgo.domain.profile.presentation.dto.req; + +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; +import io.oeid.mogakgo.domain.user.domain.User; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Schema(description = "관심 있는 프로필 카드에 대한 찔러보기 요청 생성 DTO") +@Getter +public class UserProfileLikeCreateAPIReq { + + @Schema(description = "찔러보기 요청을 보내는 사용자 ID") + @NotNull + private final Long senderId; + + @Schema(description = "찔러보기 요청으 받는 사용자 ID") + @NotNull + private final Long receiverId; + + private UserProfileLikeCreateAPIReq(Long senderId, Long receiverId) { + this.senderId = senderId; + this.receiverId = receiverId; + } + + public ProfileCardLike toEntity(User sender, User receiver) { + return ProfileCardLike.builder() + .sender(sender) + .receiver(receiver) + .build(); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeAPIRes.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeAPIRes.java new file mode 100644 index 00000000..cbc7669d --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeAPIRes.java @@ -0,0 +1,28 @@ +package io.oeid.mogakgo.domain.profile.presentation.dto.res; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Schema(description = "사용자가 받은 '찔러보기' 요청 응답 DTO") +@Getter +public class UserProfileLikeAPIRes { + + @Schema(description = "'찔러보기' 요청을 보내는 사용자 ID") + @NotNull + private final Long userId; + + @Schema(description = "사용자가 받은 '찔러보기' 요청 수") + @NotNull + private final Long likeCount; + + private UserProfileLikeAPIRes(Long userId, Long likeCount) { + this.userId = userId; + this.likeCount = likeCount; + } + + public static UserProfileLikeAPIRes from(Long userId, Long likeCount) { + return new UserProfileLikeAPIRes(userId, likeCount); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeCreateAPIRes.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeCreateAPIRes.java new file mode 100644 index 00000000..ac15288b --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeCreateAPIRes.java @@ -0,0 +1,22 @@ +package io.oeid.mogakgo.domain.profile.presentation.dto.res; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Schema(description = "관심 있는 프로필 카드에 찔러보기 요청 생성 응답 DTO") +@Getter +public class UserProfileLikeCreateAPIRes { + + @Schema(description = "프로필 카드에 대한 찔러보기 요청 생성 ID") + @NotNull + private final Long id; + + private UserProfileLikeCreateAPIRes(Long id) { + this.id = id; + } + + public static UserProfileLikeCreateAPIRes of(Long id) { + return new UserProfileLikeCreateAPIRes(id); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeInfoAPIRes.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeInfoAPIRes.java new file mode 100644 index 00000000..b27ac85f --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/res/UserProfileLikeInfoAPIRes.java @@ -0,0 +1,33 @@ +package io.oeid.mogakgo.domain.profile.presentation.dto.res; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.Getter; + +@Schema(description = "사용자가 보낸 찔러보기 요청 조회 응답 DTO") +@Getter +public class UserProfileLikeInfoAPIRes { + + @Schema(description = "사용자가 찔러보기 요청을 보낸 사용자 이름") + private final String username; + + @Schema(description = "사용자가 찔러보기 요청을 보낸 사용자 아바타 URL") + private final String avatarUrl; + + @Schema(description = "찔러보기 요청을 보낸 시간") + @NotNull + private final LocalDateTime createdAt; + + public UserProfileLikeInfoAPIRes(String username, String avatarUrl, LocalDateTime createdAt) { + this.username = username; + this.avatarUrl = avatarUrl; + this.createdAt = createdAt; + } + + public static UserProfileLikeInfoAPIRes from( + String username, String avatarUrl, LocalDateTime createdAt + ) { + return new UserProfileLikeInfoAPIRes(username, avatarUrl, createdAt); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java index 4233ba2d..7c4e0e3e 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java @@ -28,7 +28,6 @@ import io.oeid.mogakgo.domain.user.domain.User; import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.domain.user.infrastructure.UserJpaRepository; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import lombok.RequiredArgsConstructor; @@ -77,11 +76,6 @@ public Long create(Long userId, ProjectCreateReq request) { return project.getId(); } - private boolean isExistsNotEndProjectCard(User tokenUser) { - return !projectJpaRepository.findNotEndProjectOneByCreatorId(tokenUser.getId(), - PageRequest.of(0, 1)).isEmpty(); - } - @Transactional public void delete(Long userId, Long projectId) { // 유저 존재 여부 체크 @@ -94,6 +88,7 @@ public void delete(Long userId, Long projectId) { project.delete(user); } + @Transactional public void cancel(Long userId, Long projectId) { // 유저 존재 여부 체크 User user = getUser(userId); @@ -106,6 +101,9 @@ public void cancel(Long userId, Long projectId) { // 프로젝트 취소 project.cancel(user, projectHasReq); + + // 프로젝트가 받은 모든 요청 거절 + projectJoinRequestJpaRepository.rejectAllByProjectId(projectId); } public CursorPaginationResult getJoinRequest( @@ -160,6 +158,11 @@ public ProjectDensityRankRes getDensityRankProjects() { return new ProjectDensityRankRes(regionRankList); } + private boolean isExistsNotEndProjectCard(User tokenUser) { + return !projectJpaRepository.findNotEndProjectOneByCreatorId(tokenUser.getId(), + PageRequest.of(0, 1)).isEmpty(); + } + private void fillWithDefaultRegionsIfNecessary(List regionRankList) { if (regionRankList.size() < DENSITY_RANK_LIMIT) { // 기본 지역 순위에서 이미 리스트에 있는 지역을 제외하고 남은 지역을 추가 diff --git a/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/enums/ProjectStatus.java b/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/enums/ProjectStatus.java index 30ea3cb4..ac71bb01 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/enums/ProjectStatus.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/enums/ProjectStatus.java @@ -7,7 +7,6 @@ import io.oeid.mogakgo.domain.project.exception.ProjectException; import io.oeid.mogakgo.domain.project_join_req.exception.ProjectJoinRequestException; -import io.oeid.mogakgo.exception.code.ErrorCode; import lombok.Getter; @Getter diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java index a4418492..3469cd1d 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java @@ -1,5 +1,6 @@ package io.oeid.mogakgo.domain.project_join_req.application; +import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_CREATOR_PROJECT_JOIN_REQUEST; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_MATCHING_USER_TO_ACCEPT; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_PROJECT_JOIN_REQUEST_REGION; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_SENDER_TO_ACCEPT; @@ -21,8 +22,8 @@ import io.oeid.mogakgo.domain.project_join_req.domain.entity.ProjectJoinRequest; import io.oeid.mogakgo.domain.project_join_req.exception.ProjectJoinRequestException; import io.oeid.mogakgo.domain.project_join_req.infrastructure.ProjectJoinRequestJpaRepository; -import io.oeid.mogakgo.domain.user.application.UserCommonService; import io.oeid.mogakgo.domain.project_join_req.presentation.dto.res.ProjectJoinRequestDetailAPIRes; +import io.oeid.mogakgo.domain.user.application.UserCommonService; import io.oeid.mogakgo.domain.user.domain.User; import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.domain.user.infrastructure.UserJpaRepository; @@ -86,6 +87,11 @@ public Long accept(Long userId, Long projectRequestId) { Long matchingId = matchingService.create(projectJoinRequest); //---- + // 프로젝트에 대한 요청들 수락 되지 않은 것들 다 거절 처리 + // 비동기 가능 + projectJoinRequestRepository.rejectNoAcceptedByProjectId( + projectJoinRequest.getProject().getId(), projectJoinRequest.getId()); + // 내가 보낸 대기 중 요청이 있으면 취소 처리 // 여기서 나는 에러는 클라와 상관이 없으므로 에러가 발생해도 넘어갈 수 있게 처리. try { @@ -137,7 +143,7 @@ private Project validateProjectExist(Long projectId) { private void validateProjectCreator(Project project, Long userId) { if (project.getCreator().getId().equals(userId)) { - throw new ProjectJoinRequestException(PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION); + throw new ProjectJoinRequestException(INVALID_CREATOR_PROJECT_JOIN_REQUEST); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/dto/req/ProjectJoinCreateReq.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/dto/req/ProjectJoinCreateReq.java index 907bf345..b0458f7a 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/dto/req/ProjectJoinCreateReq.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/dto/req/ProjectJoinCreateReq.java @@ -2,7 +2,6 @@ import io.oeid.mogakgo.domain.project.domain.entity.Project; import io.oeid.mogakgo.domain.project_join_req.domain.entity.ProjectJoinRequest; -import io.oeid.mogakgo.domain.project_join_req.domain.entity.enums.RequestStatus; import io.oeid.mogakgo.domain.user.domain.User; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java index 3fed8eef..2a8ac432 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java @@ -4,6 +4,7 @@ import java.util.Optional; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; public interface ProjectJoinRequestJpaRepository extends JpaRepository, @@ -13,7 +14,6 @@ public interface ProjectJoinRequestJpaRepository extends JpaRepository findByIdWithProject(Long id); - @Query("select pjr from ProjectJoinRequest pjr where pjr.sender.id = :senderId and pjr.requestStatus = 'PENDING'") Optional findPendingBySenderId(Long senderId); @@ -23,4 +23,14 @@ public interface ProjectJoinRequestJpaRepository extends JpaRepository findAlreadyExistsAnotherJoinReq(Long userId); + @Modifying + @Query("update ProjectJoinRequest pjr set pjr.requestStatus = 'REJECTED' " + + "where pjr.project.id = :projectId and pjr.id != :acceptedRequestId and pjr.requestStatus = 'PENDING'") + int rejectNoAcceptedByProjectId(Long projectId, Long acceptedRequestId); + + @Modifying + @Query("update ProjectJoinRequest pjr set pjr.requestStatus = 'REJECTED' where pjr.project.id = :projectId and pjr.requestStatus = 'PENDING'") + int rejectAllByProjectId(Long projectId); + + } diff --git a/src/main/java/io/oeid/mogakgo/domain/user/application/UserProfileService.java b/src/main/java/io/oeid/mogakgo/domain/user/application/UserProfileService.java new file mode 100644 index 00000000..fc77bda5 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/user/application/UserProfileService.java @@ -0,0 +1,21 @@ +package io.oeid.mogakgo.domain.user.application; + +import io.oeid.mogakgo.domain.user.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserProfileService { + + private final UserCommonService userCommonService; + + @Transactional + public void decreaseAvailableLikeCount(Long userId) { + User user = userCommonService.getUserById(userId); + user.decreaseAvailableLikeCount(); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java b/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java index 1ffc56a6..dd461bee 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java @@ -5,8 +5,14 @@ import io.oeid.mogakgo.domain.user.application.dto.res.UserSignUpResponse; import io.oeid.mogakgo.domain.user.domain.User; import io.oeid.mogakgo.domain.user.domain.UserWantedJobTag; +import io.oeid.mogakgo.domain.user.domain.enums.DevelopLanguage; import io.oeid.mogakgo.domain.user.domain.enums.WantedJob; +import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.domain.user.infrastructure.UserWantedJobTagJpaRepository; +import io.oeid.mogakgo.exception.code.ErrorCode400; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,6 +29,7 @@ public class UserService { public UserSignUpResponse userSignUp(UserSignUpRequest userSignUpRequest) { User user = userCommonService.getUserById(userSignUpRequest.getUserId()); user.updateUsername(userSignUpRequest.getUsername()); + validateWantedJobDuplicate(userSignUpRequest.getWantedJobs()); for (WantedJob wantedJob : userSignUpRequest.getWantedJobs()) { userWantedJobTagRepository.save(UserWantedJobTag.builder() .user(user) @@ -33,7 +40,7 @@ public UserSignUpResponse userSignUp(UserSignUpRequest userSignUpRequest) { return UserSignUpResponse.from(user); } - public UserProfileResponse getUserProfile(Long userId){ + public UserProfileResponse getUserProfile(Long userId) { User user = userCommonService.getUserById(userId); return UserProfileResponse.from(user); } @@ -44,4 +51,18 @@ public void deleteUser(Long userId) { user.delete(); } + private void validateWantedJobDuplicate(List wantedJobs) { + Set wantedJobSet = new HashSet<>(wantedJobs); + if (wantedJobSet.size() != wantedJobs.size()) { + throw new UserException(ErrorCode400.USER_WANTED_JOB_DUPLICATE); + } + } + + private void validateDevelopLanguageDuplicate(List developLanguages) { + Set developLanguageSet = new HashSet<>(developLanguages); + if (developLanguageSet.size() != developLanguages.size()) { + throw new UserException(ErrorCode400.USER_DEVELOP_LANGUAGE_DUPLICATE); + } + } + } diff --git a/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java b/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java index 4b666757..35340410 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java @@ -1,5 +1,7 @@ package io.oeid.mogakgo.domain.user.domain; +import static io.oeid.mogakgo.exception.code.ErrorCode400.USER_AVAILABLE_LIKE_COUNT_IS_ZERO; + import io.oeid.mogakgo.domain.achievement.domain.Achievement; import io.oeid.mogakgo.domain.geo.domain.enums.Region; import io.oeid.mogakgo.domain.user.domain.enums.Role; @@ -61,7 +63,6 @@ public class User { private String githubUrl; - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @OrderBy("byteSize ASC") private final List userDevelopLanguageTags = new ArrayList<>(); @@ -146,22 +147,37 @@ public void updateGithubInfo(String githubId, String avatarUrl, String githubUrl } public void updateUsername(String username) { - if(username == null || username.isBlank()){ + if (username == null || username.isBlank()) { throw new UserException(ErrorCode400.USERNAME_SHOULD_BE_NOT_EMPTY); } this.username = username; } - public void delete(){ + public void decreaseAvailableLikeCount() { + if (this.availableLikeCount <= 0) { + throw new UserException(USER_AVAILABLE_LIKE_COUNT_IS_ZERO); + } + this.availableLikeCount -= 1; + } + + public void delete() { this.deletedAt = LocalDateTime.now(); } - public void signUpComplete(){ + public void deleteAllWantJobTags() { + this.userWantedJobTags.clear(); + } + + public void deleteAllDevelopLanguageTags() { + this.userDevelopLanguageTags.clear(); + } + + public void signUpComplete() { this.signupYn = true; } public void updateRegion(Region region) { - if(region == null){ + if (region == null) { throw new UserException(ErrorCode400.USER_REGION_SHOULD_BE_NOT_EMPTY); } this.region = region; diff --git a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java index a909c4ac..fb1121be 100644 --- a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java +++ b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java @@ -29,8 +29,14 @@ public enum ErrorCode400 implements ErrorCode { USER_WANTED_JOB_BAD_REQUEST("E020102", "희망 직무는 3개까지만 등록 가능합니다."), USERNAME_SHOULD_BE_NOT_EMPTY("E020103", "유저 이름은 비어있을 수 없습니다."), USER_REGION_SHOULD_BE_NOT_EMPTY("E020104", "유저 지역은 비어있을 수 없습니다."), + USER_WANTED_JOB_DUPLICATE("E020105", "중복된 희망 직무가 있습니다."), + USER_DEVELOP_LANGUAGE_DUPLICATE("E020106", "중복된 개발 언어가 있습니다."), + USER_AVAILABLE_LIKE_COUNT_IS_ZERO("E020105", "당일 사용할 수 있는 찔러보기 요청을 모두 소진했습니다."), USER_ID_NOT_NULL("E020001", "유저 아이디는 필수값입니다."), + PROFILE_CARD_LIKE_ALREADY_EXIST("E040102", "이미 찔러보기 요청을 전송한 프로필 카드에 찔러보기 요청을 전송할 수 없습니다."), + INVALID_PROFILE_CARD_LIKE_RECEIVER_INFO("E040103", "찔러보기 요청의 사용자가 존재하지 않습니다."), + INVALID_PROJECT_STATUS_TO_ACCEPT("E050101", "프로젝트가 대기중이 아니여서 참여 요청을 수락할 수 없습니다."), INVALID_PROJECT_REQ_STATUS_TO_ACCEPT("E050102", "프로젝트 참여 요청이 대기중이 아니여서 참여 요청을 수락할 수 없습니다."), INVALID_MATCHING_USER_TO_ACCEPT("E050103", "매칭이 진행 중인 유저는 프로젝트 참여 요청을 수락할 수 없습니다."), @@ -40,6 +46,7 @@ public enum ErrorCode400 implements ErrorCode { PROJECT_JOIN_REQUEST_ALREADY_EXIST("E090101", "이미 매칭 요청을 전송한 프로젝트에 매칭 요청을 생성할 수 없습니다."), INVALID_PROJECT_JOIN_REQUEST_REGION("E090102", "동네 인증한 구역에서만 프로젝트 매칭 요청을 생성할 수 있습니다."), PROJECT_JOIN_REQUEST_SHOULD_BE_ONLY_ONE("E090103", "프로젝트 매칭 요청은 한 번에 한 개만 전송할 수 있습니다."), + INVALID_CREATOR_PROJECT_JOIN_REQUEST("E090104", "프로젝트 생성자는 해당 프로젝트에 매칭 요청을 전송할 수 없습니다."), MATCHING_CANCEL_NOT_ALLOWED("E090101", "이미 종료되거나 취소된 매칭은 취소할 수 없습니다."), diff --git a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode403.java b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode403.java index cf4faf14..4206ca00 100644 --- a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode403.java +++ b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode403.java @@ -9,6 +9,7 @@ public enum ErrorCode403 implements ErrorCode { PROJECT_FORBIDDEN_OPERATION("E030201", "해당 프로젝트에 대한 권한이 없습니다."), PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION("E050201", "해당 프로젝트 요청에 대한 권한이 없습니다."), INVALID_CERT_INFORMATION("E070201", "동네 인증을 수행할 권한이 없습니다."), + PROFILE_CARD_LIKE_FORBIDDEN_OPERATION("E040101", "본인 프로필 카드의 좋아요 수만 조회할 수 있습니다."), MATCHING_FORBIDDEN_OPERATION("E090201", "해당 매칭에 대한 권한이 없습니다."), ;