diff --git a/build.gradle.kts b/build.gradle.kts index 6b528cbf..cc9b1410 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ plugins { kotlin("jvm") version "2.1.0" kotlin("plugin.spring") version "2.1.0" kotlin("plugin.jpa") version "2.1.0" + kotlin("plugin.lombok") version "2.1.0" id("org.jlleitschuh.gradle.ktlint") version "12.1.2" } diff --git a/src/main/java/com/weeth/domain/penalty/application/dto/PenaltyDTO.java b/src/main/java/com/weeth/domain/penalty/application/dto/PenaltyDTO.java deleted file mode 100644 index 0284ceb9..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/dto/PenaltyDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.weeth.domain.penalty.application.dto; - -import jakarta.validation.constraints.NotNull; -import com.weeth.domain.penalty.domain.entity.enums.PenaltyType; -import lombok.Builder; - -import java.time.LocalDateTime; -import java.util.List; - -public class PenaltyDTO { - - @Builder - public record Save( - @NotNull Long userId, - @NotNull PenaltyType penaltyType, - String penaltyDescription - ){} - - @Builder - public record Update( - @NotNull Long penaltyId, - String penaltyDescription - ){} - - @Builder - public record ResponseAll( - Integer cardinal, - List responses - ){} - - @Builder - public record Response( - Long userId, - Integer penaltyCount, - Integer warningCount, - String name, - List cardinals, - List Penalties - ){} - - @Builder - public record Penalties( - Long penaltyId, - PenaltyType penaltyType, - Integer cardinal, - String penaltyDescription, - LocalDateTime time - ){} - -} - diff --git a/src/main/java/com/weeth/domain/penalty/application/exception/AutoPenaltyDeleteNotAllowedException.java b/src/main/java/com/weeth/domain/penalty/application/exception/AutoPenaltyDeleteNotAllowedException.java deleted file mode 100644 index 6ff34a58..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/exception/AutoPenaltyDeleteNotAllowedException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.weeth.domain.penalty.application.exception; - -import com.weeth.global.common.exception.BaseException; - -public class AutoPenaltyDeleteNotAllowedException extends BaseException { - public AutoPenaltyDeleteNotAllowedException() { - super(PenaltyErrorCode.AUTO_PENALTY_DELETE_NOT_ALLOWED); - } -} diff --git a/src/main/java/com/weeth/domain/penalty/application/exception/PenaltyErrorCode.java b/src/main/java/com/weeth/domain/penalty/application/exception/PenaltyErrorCode.java deleted file mode 100644 index f5a341a1..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/exception/PenaltyErrorCode.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.weeth.domain.penalty.application.exception; - -import com.weeth.global.common.exception.ErrorCodeInterface; -import com.weeth.global.common.exception.ExplainError; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -@AllArgsConstructor -public enum PenaltyErrorCode implements ErrorCodeInterface { - - @ExplainError("요청한 패널티 ID가 존재하지 않을 때 발생합니다.") - PENALTY_NOT_FOUND(2600, HttpStatus.NOT_FOUND, "존재하지 않는 패널티입니다."), - - @ExplainError("시스템에 의해 자동 부여된 패널티를 수동으로 삭제하려 할 때 발생합니다.") - AUTO_PENALTY_DELETE_NOT_ALLOWED(2601, HttpStatus.BAD_REQUEST, "자동 생성된 패널티는 삭제할 수 없습니다"); - - private final int code; - private final HttpStatus status; - private final String message; -} diff --git a/src/main/java/com/weeth/domain/penalty/application/exception/PenaltyNotFoundException.java b/src/main/java/com/weeth/domain/penalty/application/exception/PenaltyNotFoundException.java deleted file mode 100644 index cceb0ad6..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/exception/PenaltyNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.weeth.domain.penalty.application.exception; - -import com.weeth.global.common.exception.BaseException; - -public class PenaltyNotFoundException extends BaseException { - public PenaltyNotFoundException() { - super(PenaltyErrorCode.PENALTY_NOT_FOUND); - } -} diff --git a/src/main/java/com/weeth/domain/penalty/application/mapper/PenaltyMapper.java b/src/main/java/com/weeth/domain/penalty/application/mapper/PenaltyMapper.java deleted file mode 100644 index 06544027..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/mapper/PenaltyMapper.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.weeth.domain.penalty.application.mapper; - -import com.weeth.domain.penalty.application.dto.PenaltyDTO; -import com.weeth.domain.penalty.domain.entity.Penalty; -import com.weeth.domain.penalty.domain.entity.enums.PenaltyType; -import com.weeth.domain.user.domain.entity.Cardinal; -import com.weeth.domain.user.domain.entity.User; -import com.weeth.domain.user.domain.entity.UserCardinal; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingConstants; -import org.mapstruct.ReportingPolicy; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) -public interface PenaltyMapper { - - @Mapping(target = "user", source = "user") - @Mapping(target = "cardinal", source = "cardinal") - @Mapping(target = "id", ignore = true) - @Mapping(target = "createdAt", ignore = true) - @Mapping(target = "modifiedAt", ignore = true) - Penalty fromPenaltyDto(PenaltyDTO.Save dto, User user, Cardinal cardinal); - - @Mapping(target = "id", ignore = true) - @Mapping(target = "createdAt", ignore = true) - @Mapping(target = "modifiedAt", ignore = true) - Penalty toAutoPenalty(String penaltyDescription, User user, Cardinal cardinal, PenaltyType penaltyType); - - @Mapping(target = "Penalties", source = "penalties") - @Mapping(target = "userId", source = "user.id") - @Mapping(target = "cardinals", expression = "java( toCardinalNumbers(userCardinals) )") - PenaltyDTO.Response toPenaltyDto(User user, List penalties, List userCardinals); - - @Mapping(target = "time", source = "modifiedAt") - @Mapping(target = "penaltyId", source = "id") - @Mapping(target = "cardinal", - expression = "java(penalty.getCardinal() != null ? penalty.getCardinal().getCardinalNumber() : null)") - - PenaltyDTO.Penalties toPenalties(Penalty penalty); - - PenaltyDTO.ResponseAll toResponseAll(Integer cardinal, List responses); - - default List toCardinalNumbers(List userCardinals) { - if (userCardinals == null || userCardinals.isEmpty()) { - return Collections.emptyList(); - } - - return userCardinals.stream() - .map(uc -> uc.getCardinal().getCardinalNumber()) - .collect(Collectors.toList()); - } - -} diff --git a/src/main/java/com/weeth/domain/penalty/application/usecase/PenaltyUsecase.java b/src/main/java/com/weeth/domain/penalty/application/usecase/PenaltyUsecase.java deleted file mode 100644 index 3d30b4ca..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/usecase/PenaltyUsecase.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.weeth.domain.penalty.application.usecase; - -import com.weeth.domain.penalty.application.dto.PenaltyDTO; - -import java.util.List; - -public interface PenaltyUsecase { - - void save(PenaltyDTO.Save dto); - - void update(PenaltyDTO.Update dto); - - List findAll(Integer cardinalNumber); - - PenaltyDTO.Response find(Long userId); - - void delete(Long penaltyId); - -} diff --git a/src/main/java/com/weeth/domain/penalty/application/usecase/PenaltyUsecaseImpl.java b/src/main/java/com/weeth/domain/penalty/application/usecase/PenaltyUsecaseImpl.java deleted file mode 100644 index ee906d6d..00000000 --- a/src/main/java/com/weeth/domain/penalty/application/usecase/PenaltyUsecaseImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.weeth.domain.penalty.application.usecase; - -import jakarta.transaction.Transactional; -import com.weeth.domain.penalty.application.dto.PenaltyDTO; -import com.weeth.domain.penalty.application.exception.AutoPenaltyDeleteNotAllowedException; -import com.weeth.domain.penalty.application.mapper.PenaltyMapper; -import com.weeth.domain.penalty.domain.entity.Penalty; -import com.weeth.domain.penalty.domain.entity.enums.PenaltyType; -import com.weeth.domain.penalty.domain.service.PenaltyDeleteService; -import com.weeth.domain.penalty.domain.service.PenaltyFindService; -import com.weeth.domain.penalty.domain.service.PenaltySaveService; -import com.weeth.domain.penalty.domain.service.PenaltyUpdateService; -import com.weeth.domain.user.domain.entity.Cardinal; -import com.weeth.domain.user.domain.entity.User; -import com.weeth.domain.user.domain.entity.UserCardinal; -import com.weeth.domain.user.domain.service.CardinalGetService; -import com.weeth.domain.user.domain.service.UserCardinalGetService; -import com.weeth.domain.user.domain.service.UserGetService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class PenaltyUsecaseImpl implements PenaltyUsecase{ - - private static final String AUTO_PENALTY_DESCRIPTION = "누적경고 %d회"; - - private final PenaltySaveService penaltySaveService; - private final PenaltyFindService penaltyFindService; - private final PenaltyUpdateService penaltyUpdateService; - private final PenaltyDeleteService penaltyDeleteService; - - private final UserGetService userGetService; - - private final UserCardinalGetService userCardinalGetService; - private final CardinalGetService cardinalGetService; - - private final PenaltyMapper mapper; - - @Override - @Transactional - public void save(PenaltyDTO.Save dto) { - User user = userGetService.find(dto.userId()); - Cardinal cardinal = userCardinalGetService.getCurrentCardinal(user); - - Penalty penalty = mapper.fromPenaltyDto(dto, user, cardinal); - - penaltySaveService.save(penalty); - - if(penalty.getPenaltyType().equals(PenaltyType.PENALTY)){ - user.incrementPenaltyCount(); - } else if (penalty.getPenaltyType().equals(PenaltyType.WARNING)){ - user.incrementWarningCount(); - - Integer warningCount = user.getWarningCount(); - if(warningCount % 2 == 0){ - String penaltyDescription = String.format(AUTO_PENALTY_DESCRIPTION, warningCount); - Penalty autoPenalty = mapper.toAutoPenalty(penaltyDescription, user, cardinal, PenaltyType.AUTO_PENALTY); - penaltySaveService.save(autoPenalty); - user.incrementPenaltyCount(); - } - } - } - - @Override - @Transactional - public void update(PenaltyDTO.Update dto) { - Penalty penalty = penaltyFindService.find(dto.penaltyId()); - penaltyUpdateService.update(penalty, dto); - - } - - // Todo: 쿼리 최적화 필요 - @Override - public List findAll(Integer cardinalNumber) { - List cardinals = (cardinalNumber == null) - ? cardinalGetService.findAllCardinalNumberDesc() - : List.of(cardinalGetService.findByAdminSide(cardinalNumber)); - - List result = new ArrayList<>(); - - for (Cardinal cardinal : cardinals) { - List penalties = penaltyFindService.findAllByCardinalId(cardinal.getId()); - - Map> penaltiesByUser = penalties.stream() - .collect(Collectors.groupingBy(p -> p.getUser().getId())); - - List responses = penaltiesByUser.entrySet().stream() - .map(entry -> toPenaltyDto(entry.getKey(), entry.getValue())) - .sorted(Comparator.comparing(PenaltyDTO.Response::userId)) - .toList(); - - result.add(mapper.toResponseAll(cardinal.getCardinalNumber(), responses)); - } - return result; - } - - @Override - public PenaltyDTO.Response find(Long userId) { - User user = userGetService.find(userId); - Cardinal currentCardinal = userCardinalGetService.getCurrentCardinal(user); - List penalties = penaltyFindService.findAllByUserIdAndCardinalId(userId, currentCardinal.getId()); - - return toPenaltyDto(userId, penalties); - } - - @Override - @Transactional - public void delete(Long penaltyId) { - Penalty penalty = penaltyFindService.find(penaltyId); - if(penalty.getPenaltyType().equals(PenaltyType.AUTO_PENALTY)){ - throw new AutoPenaltyDeleteNotAllowedException(); - } - - User user = penalty.getUser(); - - if(penalty.getPenaltyType().equals(PenaltyType.PENALTY)){ - penalty.getUser().decrementPenaltyCount(); - } else if (penalty.getPenaltyType().equals(PenaltyType.WARNING)) { - if(user.getWarningCount() % 2 == 0){ - Penalty relatedAutoPenalty = penaltyFindService.getRelatedAutoPenalty(penalty); - if(relatedAutoPenalty != null){ - penaltyDeleteService.delete(relatedAutoPenalty.getId()); - } - user.decrementPenaltyCount(); - } - penalty.getUser().decrementWarningCount(); - } - - penaltyDeleteService.delete(penaltyId); - } - - private PenaltyDTO.Response toPenaltyDto(Long userId, List penalties) { - User user = userGetService.find(userId); - List userCardinals = userCardinalGetService.getUserCardinals(user); - - List penaltyDTOs = penalties.stream() - .map(mapper::toPenalties) - .toList(); - - return mapper.toPenaltyDto(user, penaltyDTOs, userCardinals); - } - -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/entity/Penalty.java b/src/main/java/com/weeth/domain/penalty/domain/entity/Penalty.java deleted file mode 100644 index 67c82810..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/entity/Penalty.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.weeth.domain.penalty.domain.entity; - -import jakarta.persistence.*; -import com.weeth.domain.penalty.domain.entity.enums.PenaltyType; -import com.weeth.domain.user.domain.entity.Cardinal; -import com.weeth.domain.user.domain.entity.User; -import com.weeth.global.common.entity.BaseEntity; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@SuperBuilder -public class Penalty extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "penalty_id") - private Long id; - - @ManyToOne - @JoinColumn(name = "user_id") - private User user; - - @ManyToOne - @JoinColumn(name = "cardinal_id") - private Cardinal cardinal; - - @Enumerated(EnumType.STRING) - private PenaltyType penaltyType; - - private String penaltyDescription; - - public void update(String penaltyDescription) { - this.penaltyDescription = penaltyDescription; - } - -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/entity/enums/PenaltyType.java b/src/main/java/com/weeth/domain/penalty/domain/entity/enums/PenaltyType.java deleted file mode 100644 index 44031768..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/entity/enums/PenaltyType.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.weeth.domain.penalty.domain.entity.enums; - -public enum PenaltyType { - PENALTY, - AUTO_PENALTY, - WARNING -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/repository/PenaltyRepository.java b/src/main/java/com/weeth/domain/penalty/domain/repository/PenaltyRepository.java deleted file mode 100644 index 95d14c91..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/repository/PenaltyRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.weeth.domain.penalty.domain.repository; - -import com.weeth.domain.penalty.domain.entity.Penalty; -import com.weeth.domain.penalty.domain.entity.enums.PenaltyType; -import com.weeth.domain.user.domain.entity.Cardinal; -import com.weeth.domain.user.domain.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -public interface PenaltyRepository extends JpaRepository { - - List findByUserIdAndCardinalIdOrderByIdDesc(Long userId, Long cardinalId); - - Optional findFirstByUserAndCardinalAndPenaltyTypeAndCreatedAtAfterOrderByCreatedAtAsc( - User user, Cardinal cardinal, PenaltyType penaltyType, LocalDateTime createdAt); - - List findByCardinalIdOrderByIdDesc(Long cardinalId); -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyDeleteService.java b/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyDeleteService.java deleted file mode 100644 index 457e239e..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyDeleteService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.weeth.domain.penalty.domain.service; - -import com.weeth.domain.penalty.domain.repository.PenaltyRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class PenaltyDeleteService { - - private final PenaltyRepository penaltyRepository; - - public void delete(Long penaltyId){ - penaltyRepository.deleteById(penaltyId); - } - -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyFindService.java b/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyFindService.java deleted file mode 100644 index 7972de54..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyFindService.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.weeth.domain.penalty.domain.service; - -import com.weeth.domain.penalty.domain.entity.Penalty; -import com.weeth.domain.penalty.domain.entity.enums.PenaltyType; -import com.weeth.domain.penalty.domain.repository.PenaltyRepository; -import com.weeth.domain.penalty.application.exception.PenaltyNotFoundException; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class PenaltyFindService { - - private final PenaltyRepository penaltyRepository; - - public Penalty find(Long penaltyId){ - return penaltyRepository.findById(penaltyId) - .orElseThrow(PenaltyNotFoundException::new); - } - - public List findAllByUserIdAndCardinalId(Long userId, Long cardinalId){ - return penaltyRepository.findByUserIdAndCardinalIdOrderByIdDesc(userId, cardinalId); - } - - public List findAll(){ - return penaltyRepository.findAll(); - } - - public Penalty getRelatedAutoPenalty(Penalty penalty) { - return penaltyRepository - .findFirstByUserAndCardinalAndPenaltyTypeAndCreatedAtAfterOrderByCreatedAtAsc( - penalty.getUser(), - penalty.getCardinal(), - PenaltyType.AUTO_PENALTY, - penalty.getCreatedAt() - ).orElse(null); - } - - public List findAllByCardinalId(Long cardinalId) { - return penaltyRepository.findByCardinalIdOrderByIdDesc(cardinalId); - } -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltySaveService.java b/src/main/java/com/weeth/domain/penalty/domain/service/PenaltySaveService.java deleted file mode 100644 index 9b40dff7..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltySaveService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.weeth.domain.penalty.domain.service; - -import com.weeth.domain.penalty.domain.entity.Penalty; -import com.weeth.domain.penalty.domain.repository.PenaltyRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class PenaltySaveService { - - private final PenaltyRepository penaltyRepository; - - public void save(Penalty penalty){ - penaltyRepository.save(penalty); - } - -} diff --git a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyUpdateService.java b/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyUpdateService.java deleted file mode 100644 index a4148162..00000000 --- a/src/main/java/com/weeth/domain/penalty/domain/service/PenaltyUpdateService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.weeth.domain.penalty.domain.service; - -import com.weeth.domain.penalty.application.dto.PenaltyDTO; -import com.weeth.domain.penalty.domain.entity.Penalty; -import org.springframework.stereotype.Service; - -@Service -public class PenaltyUpdateService { - - public void update(Penalty penalty, PenaltyDTO.Update dto) { - if (dto.penaltyDescription() != null && !dto.penaltyDescription().isBlank()) { - penalty.update(dto.penaltyDescription()); - } - } -} diff --git a/src/main/java/com/weeth/domain/penalty/presentation/PenaltyAdminController.java b/src/main/java/com/weeth/domain/penalty/presentation/PenaltyAdminController.java deleted file mode 100644 index ed1adb92..00000000 --- a/src/main/java/com/weeth/domain/penalty/presentation/PenaltyAdminController.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.weeth.domain.penalty.presentation; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import com.weeth.domain.penalty.application.dto.PenaltyDTO; -import com.weeth.domain.penalty.application.exception.PenaltyErrorCode; -import com.weeth.domain.penalty.application.usecase.PenaltyUsecase; -import com.weeth.global.common.exception.ApiErrorCodeExample; -import com.weeth.global.common.response.CommonResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static com.weeth.domain.penalty.presentation.PenaltyResponseCode.*; - -@Tag(name = "PENALTY ADMIN", description = "[ADMIN] 패널티 어드민 API") -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/admin/penalties") -@ApiErrorCodeExample(PenaltyErrorCode.class) -public class PenaltyAdminController { - - private final PenaltyUsecase penaltyUsecase; - - @PostMapping - @Operation(summary="패널티 부여") - public CommonResponse assignPenalty(@Valid @RequestBody PenaltyDTO.Save dto){ - penaltyUsecase.save(dto); - return CommonResponse.success(PENALTY_ASSIGN_SUCCESS); - } - - @PatchMapping - @Operation(summary = "패널티 수정") - public CommonResponse update(@Valid @RequestBody PenaltyDTO.Update dto){ - penaltyUsecase.update(dto); - return CommonResponse.success(PENALTY_UPDATE_SUCCESS); - } - - @GetMapping - @Operation(summary="전체 패널티 조회") - public CommonResponse> findAll(@RequestParam(required = false) Integer cardinal){ - return CommonResponse.success(PENALTY_FIND_ALL_SUCCESS, penaltyUsecase.findAll(cardinal)); - } - - @DeleteMapping - @Operation(summary="패널티 삭제") - public CommonResponse delete(@RequestParam Long penaltyId){ - penaltyUsecase.delete(penaltyId); - return CommonResponse.success(PENALTY_DELETE_SUCCESS); - } - -} diff --git a/src/main/java/com/weeth/domain/penalty/presentation/PenaltyResponseCode.java b/src/main/java/com/weeth/domain/penalty/presentation/PenaltyResponseCode.java deleted file mode 100644 index e73bdca4..00000000 --- a/src/main/java/com/weeth/domain/penalty/presentation/PenaltyResponseCode.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.weeth.domain.penalty.presentation; - -import com.weeth.global.common.response.ResponseCodeInterface; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public enum PenaltyResponseCode implements ResponseCodeInterface { - // penaltyAdminController 관련 - PENALTY_ASSIGN_SUCCESS(1600, HttpStatus.OK, "페널티가 성공적으로 부여되었습니다."), - PENALTY_FIND_ALL_SUCCESS(1601, HttpStatus.OK, "모든 패널티가 성공적으로 조회되었습니다."), - PENALTY_DELETE_SUCCESS(1602, HttpStatus.OK, "패널티가 성공적으로 삭제되었습니다."), - PENALTY_UPDATE_SUCCESS(1603, HttpStatus.OK, "패널티를 성공적으로 수정했습니다."), - // penaltyUserController - PENALTY_USER_FIND_SUCCESS(1604, HttpStatus.OK, "패널티가 성공적으로 조회되었습니다."); - - private final int code; - private final HttpStatus status; - private final String message; - - PenaltyResponseCode(int code, HttpStatus status, String message) { - this.code = code; - this.status = status; - this.message = message; - } -} diff --git a/src/main/java/com/weeth/domain/penalty/presentation/PenaltyUserController.java b/src/main/java/com/weeth/domain/penalty/presentation/PenaltyUserController.java deleted file mode 100644 index aed2feea..00000000 --- a/src/main/java/com/weeth/domain/penalty/presentation/PenaltyUserController.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.weeth.domain.penalty.presentation; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import com.weeth.domain.penalty.application.dto.PenaltyDTO; -import com.weeth.domain.penalty.application.exception.PenaltyErrorCode; -import com.weeth.domain.penalty.application.usecase.PenaltyUsecase; -import com.weeth.global.auth.annotation.CurrentUser; -import com.weeth.global.common.exception.ApiErrorCodeExample; -import com.weeth.global.common.response.CommonResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static com.weeth.domain.penalty.presentation.PenaltyResponseCode.PENALTY_USER_FIND_SUCCESS; - -@Tag(name = "PENALTY", description = "패널티 API") -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/penalties") -@ApiErrorCodeExample(PenaltyErrorCode.class) -public class PenaltyUserController { - - private final PenaltyUsecase penaltyUsecase; - - @GetMapping - @Operation(summary="본인 패널티 조회") - public CommonResponse findAllPenalties(@Parameter(hidden = true) @CurrentUser Long userId) { - PenaltyDTO.Response penalties = penaltyUsecase.find(userId); - return CommonResponse.success(PENALTY_USER_FIND_SUCCESS,penalties); - } - -} diff --git a/src/main/java/com/weeth/domain/user/domain/repository/UserRepository.java b/src/main/java/com/weeth/domain/user/domain/repository/UserRepository.java index ea074aae..586ce952 100644 --- a/src/main/java/com/weeth/domain/user/domain/repository/UserRepository.java +++ b/src/main/java/com/weeth/domain/user/domain/repository/UserRepository.java @@ -5,8 +5,12 @@ import com.weeth.domain.user.domain.entity.enums.Status; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import jakarta.persistence.LockModeType; +import jakarta.persistence.QueryHint; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.repository.query.Param; import java.util.List; @@ -14,6 +18,11 @@ public interface UserRepository extends JpaRepository { + @Lock(LockModeType.PESSIMISTIC_WRITE) + @QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "2000")) + @Query("SELECT u FROM User u WHERE u.id = :id") + Optional findByIdWithLock(@Param("id") Long id); + Optional findByEmail(String email); Optional findByKakaoId(long kakaoId); diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/dto/request/SavePenaltyRequest.kt b/src/main/kotlin/com/weeth/domain/penalty/application/dto/request/SavePenaltyRequest.kt new file mode 100644 index 00000000..5acd0f5b --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/dto/request/SavePenaltyRequest.kt @@ -0,0 +1,13 @@ +package com.weeth.domain.penalty.application.dto.request + +import com.weeth.domain.penalty.domain.enums.PenaltyType +import io.swagger.v3.oas.annotations.media.Schema + +data class SavePenaltyRequest( + @field:Schema(description = "패널티 대상 사용자 ID", example = "1") + val userId: Long, + @field:Schema(description = "패널티 유형", example = "WARNING") + val penaltyType: PenaltyType, + @field:Schema(description = "패널티 사유", example = "정기모임 무단 불참") + val penaltyDescription: String?, +) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/dto/request/UpdatePenaltyRequest.kt b/src/main/kotlin/com/weeth/domain/penalty/application/dto/request/UpdatePenaltyRequest.kt new file mode 100644 index 00000000..443c8d1e --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/dto/request/UpdatePenaltyRequest.kt @@ -0,0 +1,10 @@ +package com.weeth.domain.penalty.application.dto.request + +import io.swagger.v3.oas.annotations.media.Schema + +data class UpdatePenaltyRequest( + @field:Schema(description = "수정할 패널티 ID", example = "1") + val penaltyId: Long, + @field:Schema(description = "수정할 패널티 사유", example = "정기모임 무단 불참 (수정)") + val penaltyDescription: String?, +) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyByCardinalResponse.kt b/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyByCardinalResponse.kt new file mode 100644 index 00000000..ec2353b6 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyByCardinalResponse.kt @@ -0,0 +1,10 @@ +package com.weeth.domain.penalty.application.dto.response + +import io.swagger.v3.oas.annotations.media.Schema + +data class PenaltyByCardinalResponse( + @field:Schema(description = "기수 번호", example = "4") + val cardinal: Int?, + @field:Schema(description = "해당 기수의 유저별 패널티 목록") + val responses: List, +) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyDetailResponse.kt b/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyDetailResponse.kt new file mode 100644 index 00000000..b6e1ab5e --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyDetailResponse.kt @@ -0,0 +1,18 @@ +package com.weeth.domain.penalty.application.dto.response + +import com.weeth.domain.penalty.domain.enums.PenaltyType +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDateTime + +data class PenaltyDetailResponse( + @field:Schema(description = "패널티 ID", example = "1") + val penaltyId: Long, + @field:Schema(description = "패널티 유형", example = "WARNING") + val penaltyType: PenaltyType, + @field:Schema(description = "기수 번호", example = "4") + val cardinal: Int?, + @field:Schema(description = "패널티 사유", example = "정기모임 무단 불참") + val penaltyDescription: String, + @field:Schema(description = "최종 수정 시간", example = "2026-02-19T01:00:00") + val time: LocalDateTime, +) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyResponse.kt b/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyResponse.kt new file mode 100644 index 00000000..fa4a4b4e --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/dto/response/PenaltyResponse.kt @@ -0,0 +1,18 @@ +package com.weeth.domain.penalty.application.dto.response + +import io.swagger.v3.oas.annotations.media.Schema + +data class PenaltyResponse( + @field:Schema(description = "사용자 ID", example = "1") + val userId: Long, + @field:Schema(description = "사용자 이름", example = "홍길동") + val name: String, + @field:Schema(description = "패널티 횟수", example = "2") + val penaltyCount: Int, + @field:Schema(description = "경고 횟수", example = "3") + val warningCount: Int, + @field:Schema(description = "소속 기수 목록", example = "[3, 4]") + val cardinals: List, + @field:Schema(description = "패널티 상세 목록") + val penalties: List, +) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/exception/AutoPenaltyDeleteNotAllowedException.kt b/src/main/kotlin/com/weeth/domain/penalty/application/exception/AutoPenaltyDeleteNotAllowedException.kt new file mode 100644 index 00000000..23cef4fe --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/exception/AutoPenaltyDeleteNotAllowedException.kt @@ -0,0 +1,5 @@ +package com.weeth.domain.penalty.application.exception + +import com.weeth.global.common.exception.BaseException + +class AutoPenaltyDeleteNotAllowedException : BaseException(PenaltyErrorCode.AUTO_PENALTY_DELETE_NOT_ALLOWED) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/exception/PenaltyErrorCode.kt b/src/main/kotlin/com/weeth/domain/penalty/application/exception/PenaltyErrorCode.kt new file mode 100644 index 00000000..d5218937 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/exception/PenaltyErrorCode.kt @@ -0,0 +1,24 @@ +package com.weeth.domain.penalty.application.exception + +import com.weeth.global.common.exception.ErrorCodeInterface +import com.weeth.global.common.exception.ExplainError +import org.springframework.http.HttpStatus + +enum class PenaltyErrorCode( + private val code: Int, + private val status: HttpStatus, + private val message: String, +) : ErrorCodeInterface { + @ExplainError("요청한 패널티 ID가 존재하지 않을 때 발생합니다.") + PENALTY_NOT_FOUND(2600, HttpStatus.NOT_FOUND, "존재하지 않는 패널티입니다."), + + @ExplainError("시스템에 의해 자동 부여된 패널티를 수동으로 삭제하려 할 때 발생합니다.") + AUTO_PENALTY_DELETE_NOT_ALLOWED(2601, HttpStatus.BAD_REQUEST, "자동 생성된 패널티는 삭제할 수 없습니다"), + ; + + override fun getCode(): Int = code + + override fun getStatus(): HttpStatus = status + + override fun getMessage(): String = message +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/exception/PenaltyNotFoundException.kt b/src/main/kotlin/com/weeth/domain/penalty/application/exception/PenaltyNotFoundException.kt new file mode 100644 index 00000000..820c80df --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/exception/PenaltyNotFoundException.kt @@ -0,0 +1,5 @@ +package com.weeth.domain.penalty.application.exception + +import com.weeth.global.common.exception.BaseException + +class PenaltyNotFoundException : BaseException(PenaltyErrorCode.PENALTY_NOT_FOUND) diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/mapper/PenaltyMapper.kt b/src/main/kotlin/com/weeth/domain/penalty/application/mapper/PenaltyMapper.kt new file mode 100644 index 00000000..f4929e52 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/mapper/PenaltyMapper.kt @@ -0,0 +1,71 @@ +package com.weeth.domain.penalty.application.mapper + +import com.weeth.domain.penalty.application.dto.request.SavePenaltyRequest +import com.weeth.domain.penalty.application.dto.response.PenaltyByCardinalResponse +import com.weeth.domain.penalty.application.dto.response.PenaltyDetailResponse +import com.weeth.domain.penalty.application.dto.response.PenaltyResponse +import com.weeth.domain.penalty.domain.entity.Penalty +import com.weeth.domain.penalty.domain.enums.PenaltyType +import com.weeth.domain.user.domain.entity.Cardinal +import com.weeth.domain.user.domain.entity.User +import com.weeth.domain.user.domain.entity.UserCardinal +import org.springframework.stereotype.Component + +@Component +class PenaltyMapper { + fun toEntity( + request: SavePenaltyRequest, + user: User, + cardinal: Cardinal, + ): Penalty = + Penalty( + user = user, + cardinal = cardinal, + penaltyType = request.penaltyType, + penaltyDescription = request.penaltyDescription ?: "", + ) + + fun toAutoPenalty( + penaltyDescription: String, + user: User, + cardinal: Cardinal, + ): Penalty = + Penalty( + user = user, + cardinal = cardinal, + penaltyType = PenaltyType.AUTO_PENALTY, + penaltyDescription = penaltyDescription, + ) + + fun toResponse( + user: User, + penalties: List, + userCardinals: List, + ): PenaltyResponse = + PenaltyResponse( + userId = user.id, + name = user.name, + penaltyCount = user.penaltyCount, + warningCount = user.warningCount, + cardinals = userCardinals.map { it.cardinal.cardinalNumber }, + penalties = penalties.map(::toDetailResponse), + ) + + fun toDetailResponse(penalty: Penalty): PenaltyDetailResponse = + PenaltyDetailResponse( + penaltyId = penalty.id, + penaltyType = penalty.penaltyType, + cardinal = penalty.cardinal.cardinalNumber, + penaltyDescription = penalty.penaltyDescription, + time = penalty.modifiedAt, + ) + + fun toByCardinalResponse( + cardinal: Int?, + responses: List, + ): PenaltyByCardinalResponse = + PenaltyByCardinalResponse( + cardinal = cardinal, + responses = responses, + ) +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/DeletePenaltyUseCase.kt b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/DeletePenaltyUseCase.kt new file mode 100644 index 00000000..1cd077aa --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/DeletePenaltyUseCase.kt @@ -0,0 +1,63 @@ +package com.weeth.domain.penalty.application.usecase.command + +import com.weeth.domain.penalty.application.exception.AutoPenaltyDeleteNotAllowedException +import com.weeth.domain.penalty.application.exception.PenaltyNotFoundException +import com.weeth.domain.penalty.domain.enums.PenaltyType +import com.weeth.domain.penalty.domain.repository.PenaltyRepository +import com.weeth.domain.user.application.exception.UserNotFoundException +import com.weeth.domain.user.domain.repository.UserRepository +import org.springframework.data.domain.Pageable +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class DeletePenaltyUseCase( + private val penaltyRepository: PenaltyRepository, + private val userRepository: UserRepository, +) { + @Transactional + fun delete(penaltyId: Long) { + val penalty = + penaltyRepository.findByIdOrNull(penaltyId) + ?: throw PenaltyNotFoundException() + + if (penalty.penaltyType == PenaltyType.AUTO_PENALTY) { + throw AutoPenaltyDeleteNotAllowedException() + } + + val user = + userRepository + .findByIdWithLock(penalty.user.id) + .orElseThrow { UserNotFoundException() } + + when (penalty.penaltyType) { + PenaltyType.PENALTY -> { + user.decrementPenaltyCount() + } + + PenaltyType.WARNING -> { + if (user.warningCount % 2 == 0) { + val relatedAutoPenalty = + penaltyRepository + .findFirstAutoPenaltyAfter( + penalty.user.id, + penalty.cardinal.id, + PenaltyType.AUTO_PENALTY, + penalty.createdAt, + Pageable.ofSize(1), + ).firstOrNull() + if (relatedAutoPenalty != null) { + penaltyRepository.deleteById(relatedAutoPenalty.id) + } + user.decrementPenaltyCount() + } + user.decrementWarningCount() + } + + else -> {} + } + + penaltyRepository.deleteById(penaltyId) + } +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/SavePenaltyUseCase.kt b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/SavePenaltyUseCase.kt new file mode 100644 index 00000000..b9391d0a --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/SavePenaltyUseCase.kt @@ -0,0 +1,55 @@ +package com.weeth.domain.penalty.application.usecase.command + +import com.weeth.domain.penalty.application.dto.request.SavePenaltyRequest +import com.weeth.domain.penalty.application.mapper.PenaltyMapper +import com.weeth.domain.penalty.domain.enums.PenaltyType +import com.weeth.domain.penalty.domain.repository.PenaltyRepository +import com.weeth.domain.user.application.exception.UserNotFoundException +import com.weeth.domain.user.domain.repository.UserRepository +import com.weeth.domain.user.domain.service.UserCardinalGetService +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class SavePenaltyUseCase( + private val penaltyRepository: PenaltyRepository, + private val userRepository: UserRepository, + private val userCardinalGetService: UserCardinalGetService, + private val mapper: PenaltyMapper, +) { + companion object { + private const val AUTO_PENALTY_DESCRIPTION = "누적경고 %d회" + } + + @Transactional + fun save(request: SavePenaltyRequest) { + val user = + userRepository + .findByIdWithLock(request.userId) + .orElseThrow { UserNotFoundException() } + val cardinal = userCardinalGetService.getCurrentCardinal(user) + + val penalty = mapper.toEntity(request, user, cardinal) + penaltyRepository.save(penalty) + + when (penalty.penaltyType) { + PenaltyType.PENALTY -> { + user.incrementPenaltyCount() + } + + PenaltyType.WARNING -> { + user.incrementWarningCount() + + val warningCount = user.warningCount + if (warningCount % 2 == 0) { + val description = AUTO_PENALTY_DESCRIPTION.format(warningCount) + val autoPenalty = mapper.toAutoPenalty(description, user, cardinal) + penaltyRepository.save(autoPenalty) + user.incrementPenaltyCount() + } + } + + else -> {} + } + } +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/UpdatePenaltyUseCase.kt b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/UpdatePenaltyUseCase.kt new file mode 100644 index 00000000..aa55fe2d --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/command/UpdatePenaltyUseCase.kt @@ -0,0 +1,24 @@ +package com.weeth.domain.penalty.application.usecase.command + +import com.weeth.domain.penalty.application.dto.request.UpdatePenaltyRequest +import com.weeth.domain.penalty.application.exception.PenaltyNotFoundException +import com.weeth.domain.penalty.domain.repository.PenaltyRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class UpdatePenaltyUseCase( + private val penaltyRepository: PenaltyRepository, +) { + @Transactional + fun update(request: UpdatePenaltyRequest) { + val penalty = + penaltyRepository.findByIdOrNull(request.penaltyId) + ?: throw PenaltyNotFoundException() + + if (!request.penaltyDescription.isNullOrBlank()) { + penalty.update(request.penaltyDescription) + } + } +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/application/usecase/query/GetPenaltyQueryService.kt b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/query/GetPenaltyQueryService.kt new file mode 100644 index 00000000..cb842e2f --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/application/usecase/query/GetPenaltyQueryService.kt @@ -0,0 +1,59 @@ +package com.weeth.domain.penalty.application.usecase.query + +import com.weeth.domain.penalty.application.dto.response.PenaltyByCardinalResponse +import com.weeth.domain.penalty.application.dto.response.PenaltyResponse +import com.weeth.domain.penalty.application.mapper.PenaltyMapper +import com.weeth.domain.penalty.domain.repository.PenaltyRepository +import com.weeth.domain.user.domain.service.CardinalGetService +import com.weeth.domain.user.domain.service.UserCardinalGetService +import com.weeth.domain.user.domain.service.UserGetService +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +@Transactional(readOnly = true) +class GetPenaltyQueryService( + private val penaltyRepository: PenaltyRepository, + private val userGetService: UserGetService, + private val userCardinalGetService: UserCardinalGetService, + private val cardinalGetService: CardinalGetService, + private val mapper: PenaltyMapper, +) { + fun findAllByCardinal(cardinalNumber: Int?): List { + val cardinals = + if (cardinalNumber == null) { + cardinalGetService.findAllCardinalNumberDesc() + } else { + listOf(cardinalGetService.findByAdminSide(cardinalNumber)) + } + + return cardinals.map { cardinal -> + val penalties = penaltyRepository.findByCardinalIdOrderByIdDesc(cardinal.id) + val users = penalties.map { it.user }.distinct() + val userCardinalsMap = + userCardinalGetService + .findAll(users) + .groupBy { it.user.id } + + val responses = + penalties + .groupBy { it.user.id } + .entries + .map { (userId, userPenalties) -> + val userCardinals = userCardinalsMap[userId] ?: emptyList() + mapper.toResponse(userPenalties.first().user, userPenalties, userCardinals) + }.sortedBy { it.userId } + + mapper.toByCardinalResponse(cardinal.cardinalNumber, responses) + } + } + + fun findByUser(userId: Long): PenaltyResponse { + val user = userGetService.find(userId) + val currentCardinal = userCardinalGetService.getCurrentCardinal(user) + val penalties = penaltyRepository.findByUserIdAndCardinalIdOrderByIdDesc(userId, currentCardinal.id) + val userCardinals = userCardinalGetService.getUserCardinals(user) + + return mapper.toResponse(user, penalties, userCardinals) + } +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/domain/entity/Penalty.kt b/src/main/kotlin/com/weeth/domain/penalty/domain/entity/Penalty.kt new file mode 100644 index 00000000..25f0db0c --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/domain/entity/Penalty.kt @@ -0,0 +1,37 @@ +package com.weeth.domain.penalty.domain.entity + +import com.weeth.domain.penalty.domain.enums.PenaltyType +import com.weeth.domain.user.domain.entity.Cardinal +import com.weeth.domain.user.domain.entity.User +import com.weeth.global.common.entity.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity +class Penalty( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + val user: User, + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "cardinal_id") + val cardinal: Cardinal, + @Enumerated(EnumType.STRING) + val penaltyType: PenaltyType, + var penaltyDescription: String, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "penalty_id") + val id: Long = 0, +) : BaseEntity() { + fun update(penaltyDescription: String) { + this.penaltyDescription = penaltyDescription + } +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/domain/enums/PenaltyType.kt b/src/main/kotlin/com/weeth/domain/penalty/domain/enums/PenaltyType.kt new file mode 100644 index 00000000..8822b075 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/domain/enums/PenaltyType.kt @@ -0,0 +1,7 @@ +package com.weeth.domain.penalty.domain.enums + +enum class PenaltyType { + PENALTY, + AUTO_PENALTY, + WARNING, +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/domain/repository/PenaltyRepository.kt b/src/main/kotlin/com/weeth/domain/penalty/domain/repository/PenaltyRepository.kt new file mode 100644 index 00000000..0fb0a166 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/domain/repository/PenaltyRepository.kt @@ -0,0 +1,35 @@ +package com.weeth.domain.penalty.domain.repository + +import com.weeth.domain.penalty.domain.entity.Penalty +import com.weeth.domain.penalty.domain.enums.PenaltyType +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.time.LocalDateTime + +interface PenaltyRepository : JpaRepository { + @Query("SELECT p FROM Penalty p JOIN FETCH p.cardinal WHERE p.user.id = :userId AND p.cardinal.id = :cardinalId ORDER BY p.id DESC") + fun findByUserIdAndCardinalIdOrderByIdDesc( + userId: Long, + cardinalId: Long, + ): List + + @Query( + """ + SELECT p FROM Penalty p + WHERE p.user.id = :userId AND p.cardinal.id = :cardinalId + AND p.penaltyType = :penaltyType AND p.createdAt > :createdAt + ORDER BY p.createdAt ASC + """, + ) + fun findFirstAutoPenaltyAfter( + userId: Long, + cardinalId: Long, + penaltyType: PenaltyType, + createdAt: LocalDateTime, + pageable: Pageable, + ): List + + @Query("SELECT p FROM Penalty p JOIN FETCH p.user JOIN FETCH p.cardinal WHERE p.cardinal.id = :cardinalId ORDER BY p.id DESC") + fun findByCardinalIdOrderByIdDesc(cardinalId: Long): List +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyAdminController.kt b/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyAdminController.kt new file mode 100644 index 00000000..061dd1d6 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyAdminController.kt @@ -0,0 +1,68 @@ +package com.weeth.domain.penalty.presentation + +import com.weeth.domain.penalty.application.dto.request.SavePenaltyRequest +import com.weeth.domain.penalty.application.dto.request.UpdatePenaltyRequest +import com.weeth.domain.penalty.application.dto.response.PenaltyByCardinalResponse +import com.weeth.domain.penalty.application.exception.PenaltyErrorCode +import com.weeth.domain.penalty.application.usecase.command.DeletePenaltyUseCase +import com.weeth.domain.penalty.application.usecase.command.SavePenaltyUseCase +import com.weeth.domain.penalty.application.usecase.command.UpdatePenaltyUseCase +import com.weeth.domain.penalty.application.usecase.query.GetPenaltyQueryService +import com.weeth.global.common.exception.ApiErrorCodeExample +import com.weeth.global.common.response.CommonResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +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 + +@Tag(name = "PENALTY ADMIN", description = "[ADMIN] 패널티 어드민 API") +@RestController +@RequestMapping("/api/v1/admin/penalties") +@ApiErrorCodeExample(PenaltyErrorCode::class) +class PenaltyAdminController( + private val savePenaltyUseCase: SavePenaltyUseCase, + private val updatePenaltyUseCase: UpdatePenaltyUseCase, + private val deletePenaltyUseCase: DeletePenaltyUseCase, + private val getPenaltyQueryService: GetPenaltyQueryService, +) { + @PostMapping + @Operation(summary = "패널티 부여") + fun assignPenalty( + @Valid @RequestBody request: SavePenaltyRequest, + ): CommonResponse { + savePenaltyUseCase.save(request) + return CommonResponse.success(PenaltyResponseCode.PENALTY_ASSIGN_SUCCESS) + } + + @PatchMapping + @Operation(summary = "패널티 수정") + fun update( + @Valid @RequestBody request: UpdatePenaltyRequest, + ): CommonResponse { + updatePenaltyUseCase.update(request) + return CommonResponse.success(PenaltyResponseCode.PENALTY_UPDATE_SUCCESS) + } + + @GetMapping + @Operation(summary = "전체 패널티 조회") + fun findAll( + @RequestParam(required = false) cardinal: Int?, + ): CommonResponse> = + CommonResponse.success(PenaltyResponseCode.PENALTY_FIND_ALL_SUCCESS, getPenaltyQueryService.findAllByCardinal(cardinal)) + + @DeleteMapping + @Operation(summary = "패널티 삭제") + fun delete( + @RequestParam penaltyId: Long, + ): CommonResponse { + deletePenaltyUseCase.delete(penaltyId) + return CommonResponse.success(PenaltyResponseCode.PENALTY_DELETE_SUCCESS) + } +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyResponseCode.kt b/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyResponseCode.kt new file mode 100644 index 00000000..f6b05674 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyResponseCode.kt @@ -0,0 +1,16 @@ +package com.weeth.domain.penalty.presentation + +import com.weeth.global.common.response.ResponseCodeInterface +import org.springframework.http.HttpStatus + +enum class PenaltyResponseCode( + override val code: Int, + override val status: HttpStatus, + override val message: String, +) : ResponseCodeInterface { + PENALTY_ASSIGN_SUCCESS(1600, HttpStatus.OK, "페널티가 성공적으로 부여되었습니다."), + PENALTY_FIND_ALL_SUCCESS(1601, HttpStatus.OK, "모든 패널티가 성공적으로 조회되었습니다."), + PENALTY_DELETE_SUCCESS(1602, HttpStatus.OK, "패널티가 성공적으로 삭제되었습니다."), + PENALTY_UPDATE_SUCCESS(1603, HttpStatus.OK, "패널티를 성공적으로 수정했습니다."), + PENALTY_USER_FIND_SUCCESS(1604, HttpStatus.OK, "패널티가 성공적으로 조회되었습니다."), +} diff --git a/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyUserController.kt b/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyUserController.kt new file mode 100644 index 00000000..965fe5f9 --- /dev/null +++ b/src/main/kotlin/com/weeth/domain/penalty/presentation/PenaltyUserController.kt @@ -0,0 +1,29 @@ +package com.weeth.domain.penalty.presentation + +import com.weeth.domain.penalty.application.dto.response.PenaltyResponse +import com.weeth.domain.penalty.application.exception.PenaltyErrorCode +import com.weeth.domain.penalty.application.usecase.query.GetPenaltyQueryService +import com.weeth.global.auth.annotation.CurrentUser +import com.weeth.global.common.exception.ApiErrorCodeExample +import com.weeth.global.common.response.CommonResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "PENALTY", description = "패널티 API") +@RestController +@RequestMapping("/api/v1/penalties") +@ApiErrorCodeExample(PenaltyErrorCode::class) +class PenaltyUserController( + private val getPenaltyQueryService: GetPenaltyQueryService, +) { + @GetMapping + @Operation(summary = "본인 패널티 조회") + fun findAllPenalties( + @Parameter(hidden = true) @CurrentUser userId: Long, + ): CommonResponse = + CommonResponse.success(PenaltyResponseCode.PENALTY_USER_FIND_SUCCESS, getPenaltyQueryService.findByUser(userId)) +}