diff --git a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java index f5b258ee..a671b7a0 100755 --- a/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/stackpot/stackpot/apiPayload/code/status/ErrorStatus.java @@ -75,7 +75,8 @@ public enum ErrorStatus implements BaseErrorCode { // Badge 관련 에러 BADGE_NOT_FOUND(HttpStatus.NOT_FOUND, "BADGE4004", "해당 BADGE를 찾을 수 없습니다."), BADGE_INSUFFICIENT_TOP_MEMBERS(HttpStatus.BAD_REQUEST, "BADGE4001", "팀원이 1명 이하라 뱃지 수여 조건을 만족하지 않습니다. 팀원은 최소 2명 이상이어야 합니다."), - BADGE_INSUFFICIENT_TODO_COUNTS(HttpStatus.BAD_REQUEST, "BADGE4002", "TODO를 완료한 사람이 존재하지 않습니다."), + BADGE_INSUFFICIENT_TODO_COUNTS(HttpStatus.BAD_REQUEST, "BADGE4002", "TODO를 완료한 사람이 2명 미만입니다. 최소 2명 이상이어야 합니다."), + // 검색 관련 에러 INVALID_SEARCH_TYPE(HttpStatus.BAD_REQUEST, "SEARCH_STATUS4000", "검색 Type 형식이 올바르지 않습니다 (pot/feed)"), diff --git a/src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java b/src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java index cfef91f9..d6852b53 100644 --- a/src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java +++ b/src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java @@ -37,7 +37,6 @@ public ResponseEntity>> getBadgeMembersByPot @Operation(summary = "팟에서 가장 많은 `투두를 완료한' 멤버에게 '할 일 정복자' 뱃지 부여") @PostMapping("/{potId}") @ApiErrorCodeExamples({ - ErrorStatus.BADGE_NOT_FOUND, ErrorStatus.BADGE_INSUFFICIENT_TODO_COUNTS, ErrorStatus.POT_MEMBER_NOT_FOUND }) diff --git a/src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java b/src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java index 16c66a5e..b6885096 100644 --- a/src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java +++ b/src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java @@ -41,32 +41,36 @@ public Badge getBadge(Long badgeId) { @Transactional @Override public void assignBadgeToTopMembers(Long potId) { - // 1. 완료된 Todo 개수가 0이면 예외 - long completedTodoCount = userTodoRepository.countByPot_PotIdAndStatus(potId, TodoStatus.COMPLETED); - if (completedTodoCount == 0) { + // case [2] 총 팀 멤버가 2명 이하 -> 배지 부여 X (아무 동작 안 함) + long memberCount = potMemberRepository.countByPot_PotId(potId); + if (memberCount <= 2) return; + + // 완료한 '서로 다른 사용자 수' 집계 + long completedUserCount = + userTodoRepository.countDistinctUserIdsByPotAndStatus(potId, TodoStatus.COMPLETED); + + // case [3] 팀 멤버 2명 이상 && 완료 사용자 수 < 2 -> 에러 + if (completedUserCount < 2) { throw new PotHandler(ErrorStatus.BADGE_INSUFFICIENT_TODO_COUNTS); } - // 2. Todo를 많이 완료한 상위 2명의 userId 조회 + // case [1] 팀 멤버 2명 이상 && 완료 사용자 수 >= 2 -> 정상 (상위 2명 배지 부여) List topUserIds = userTodoRepository.findTop2UserIds(potId, TodoStatus.COMPLETED); if (topUserIds.size() < 2) { throw new PotHandler(ErrorStatus.BADGE_INSUFFICIENT_TOP_MEMBERS); } - // 3. PotMember 조회 - List topPotMembers = topUserIds.stream() - .map(userId -> potMemberRepository.findByPot_PotIdAndUser_Id(potId, userId) - .orElseThrow(() -> new PotHandler(ErrorStatus.POT_MEMBER_NOT_FOUND))) - .toList(); - - // 4. Todo 배지 부여 Badge badge = getBadge(1L); - for (PotMember potMember : topPotMembers) { - PotMemberBadge potMemberBadge = PotMemberBadge.builder() - .badge(badge) - .potMember(potMember) - .build(); - potMemberBadgeRepository.save(potMemberBadge); + for (Long userId : topUserIds) { + PotMember pm = potMemberRepository.findByPot_PotIdAndUser_Id(potId, userId) + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_MEMBER_NOT_FOUND)); + + potMemberBadgeRepository.save( + PotMemberBadge.builder() + .badge(badge) + .potMember(pm) + .build() + ); } } diff --git a/src/main/java/stackpot/stackpot/pot/controller/PotController.java b/src/main/java/stackpot/stackpot/pot/controller/PotController.java index 1c2deb07..d5ffc77f 100644 --- a/src/main/java/stackpot/stackpot/pot/controller/PotController.java +++ b/src/main/java/stackpot/stackpot/pot/controller/PotController.java @@ -152,4 +152,14 @@ public ResponseEntity>> getMyRecruitingPots( return ResponseEntity.ok(ApiResponse.onSuccess(response)); } + @Operation(summary = "팟 이름 수정 API") + @PatchMapping("/{pot_id}/rename") + public ResponseEntity> updatePotName( + @PathVariable Long pot_id, + @Valid @RequestBody PotNameUpdateRequestDto request + ) { + String res = potCommandService.updatePotName(pot_id, request); + return ResponseEntity.ok(ApiResponse.onSuccess(res)); + } + } diff --git a/src/main/java/stackpot/stackpot/pot/dto/CompletedPotRequestDto.java b/src/main/java/stackpot/stackpot/pot/dto/CompletedPotRequestDto.java index cd51e433..d33b8a2d 100644 --- a/src/main/java/stackpot/stackpot/pot/dto/CompletedPotRequestDto.java +++ b/src/main/java/stackpot/stackpot/pot/dto/CompletedPotRequestDto.java @@ -15,7 +15,7 @@ public class CompletedPotRequestDto { @NotBlank(message = "팟 이름은 필수입니다.") private String potName; - private LocalDate potStartDate; + private String potStartDate; @NotBlank(message = "사용 언어는 필수입니다.") private String potLan; diff --git a/src/main/java/stackpot/stackpot/pot/dto/PotNameUpdateRequestDto.java b/src/main/java/stackpot/stackpot/pot/dto/PotNameUpdateRequestDto.java new file mode 100644 index 00000000..f8eda6f0 --- /dev/null +++ b/src/main/java/stackpot/stackpot/pot/dto/PotNameUpdateRequestDto.java @@ -0,0 +1,14 @@ +package stackpot.stackpot.pot.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class PotNameUpdateRequestDto { + private String potName; +} diff --git a/src/main/java/stackpot/stackpot/pot/entity/Pot.java b/src/main/java/stackpot/stackpot/pot/entity/Pot.java index 41ff9dd0..fe8f84a4 100644 --- a/src/main/java/stackpot/stackpot/pot/entity/Pot.java +++ b/src/main/java/stackpot/stackpot/pot/entity/Pot.java @@ -38,6 +38,7 @@ public class Pot extends BaseEntity { @OneToMany(mappedBy = "pot", cascade = CascadeType.REMOVE, orphanRemoval = true) private List potMembers; + @Setter @Column(nullable = false, length = 255) private String potName; diff --git a/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java b/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java index 9d935557..9f9b559a 100644 --- a/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java +++ b/src/main/java/stackpot/stackpot/pot/repository/PotMemberRepository.java @@ -51,8 +51,6 @@ public interface PotMemberRepository extends JpaRepository { @Query("DELETE FROM PotMember pm WHERE pm.pot.potId = :potId") void deleteByPotId(@Param("potId") Long potId); - Optional findByPot_PotIdAndUser_Id(Long potId, Long userId); - @Query("SELECT pm FROM PotMember pm WHERE pm.pot.potId = :potId AND pm.user.id = :userId") PotMember findByPotIdAndUserId(Long potId, Long userId); @@ -94,4 +92,8 @@ public interface PotMemberRepository extends JpaRepository { @Modifying @Query("DELETE FROM PotMember pm WHERE pm.pot.potId IN :potIds AND pm.user.id = :userId") void deleteByUserIdAndPotIdIn(@Param("userId") Long userId, @Param("potIds") List potIds); + + long countByPot_PotId(Long potId); + Optional findByPot_PotIdAndUser_Id(Long potId, Long userId); + } diff --git a/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandService.java b/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandService.java index a5e03d47..c1f40b82 100644 --- a/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandService.java +++ b/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandService.java @@ -1,6 +1,7 @@ package stackpot.stackpot.pot.service.pot; import stackpot.stackpot.pot.dto.CompletedPotRequestDto; +import stackpot.stackpot.pot.dto.PotNameUpdateRequestDto; import stackpot.stackpot.pot.dto.PotRequestDto; import stackpot.stackpot.pot.dto.PotResponseDto; @@ -19,4 +20,6 @@ public interface PotCommandService { void patchLikes(Long potId, Long applicationId, Boolean liked); PotResponseDto updateCompletedPot(Long potId, CompletedPotRequestDto requestDto); + + String updatePotName(Long potId, PotNameUpdateRequestDto request); } diff --git a/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandServiceImpl.java b/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandServiceImpl.java index 2c4cbe4b..b75d9976 100644 --- a/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandServiceImpl.java +++ b/src/main/java/stackpot/stackpot/pot/service/pot/PotCommandServiceImpl.java @@ -19,10 +19,7 @@ import stackpot.stackpot.pot.converter.PotConverter; import stackpot.stackpot.pot.converter.PotMemberConverter; import stackpot.stackpot.pot.converter.PotDetailConverter; -import stackpot.stackpot.pot.dto.CompletedPotRequestDto; -import stackpot.stackpot.pot.dto.PotRequestDto; -import stackpot.stackpot.pot.dto.PotResponseDto; -import stackpot.stackpot.pot.dto.RecruitingPotResponseDto; +import stackpot.stackpot.pot.dto.*; import stackpot.stackpot.pot.entity.Pot; import stackpot.stackpot.pot.entity.PotRecruitmentDetails; import stackpot.stackpot.pot.entity.mapping.PotApplication; @@ -41,6 +38,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @@ -197,7 +195,7 @@ public PotResponseDto patchPotWithRecruitments(Long potId, CompletedPotRequestDt Map updateValues = new LinkedHashMap<>(); updateValues.put("potName", requestDto.getPotName()); updateValues.put("potStartDate", requestDto.getPotStartDate()); - updateValues.put("potEndDate", LocalDate.now()); + updateValues.put("potEndDate", String.valueOf(LocalDate.now())); updateValues.put("potStatus", "COMPLETED"); updateValues.put("potLan", requestDto.getPotLan()); updateValues.put("potSummary", requestDto.getPotSummary()); @@ -272,4 +270,19 @@ public PotResponseDto updateCompletedPot(Long potId, CompletedPotRequestDto requ return potConverter.toDto(pot, recruitmentDetails); } + + @Override + @Transactional + public String updatePotName(Long potId, PotNameUpdateRequestDto request) { + User user = authService.getCurrentUser(); + Pot pot = potRepository.findById(potId) + .orElseThrow(() -> new PotHandler(ErrorStatus.POT_NOT_FOUND)); + + if (!pot.getUser().equals(user)) { + throw new PotHandler(ErrorStatus.POT_FORBIDDEN); + } + pot.setPotName(request.getPotName()); + + return pot.getPotName(); + } } diff --git a/src/main/java/stackpot/stackpot/todo/repository/UserTodoRepository.java b/src/main/java/stackpot/stackpot/todo/repository/UserTodoRepository.java index 6d7b7f19..194bbbc2 100644 --- a/src/main/java/stackpot/stackpot/todo/repository/UserTodoRepository.java +++ b/src/main/java/stackpot/stackpot/todo/repository/UserTodoRepository.java @@ -1,5 +1,7 @@ package stackpot.stackpot.todo.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -9,16 +11,12 @@ import stackpot.stackpot.todo.entity.mapping.UserTodo; import java.util.List; +import org.springframework.data.domain.Pageable; + @Repository public interface UserTodoRepository extends JpaRepository { - @Query("SELECT u.id " + - "FROM UserTodo t JOIN t.user u " + - "WHERE t.pot.potId = :potId AND t.status = :status " + - "GROUP BY u.id ORDER BY COUNT(t) DESC") - List findTop2UserIds(@Param("potId") Long potId, @Param("status") TodoStatus status); - @Modifying @Query("DELETE FROM UserTodo f WHERE f.user.id = :userId") void deleteByUserId(@Param("userId") Long userId); @@ -29,4 +27,33 @@ public interface UserTodoRepository extends JpaRepository { long countByPot_PotIdAndStatus(Long potPotId, TodoStatus status); + // 완료한 서로 다른 사용자 수 (distinct) + @Query(""" + SELECT COUNT(DISTINCT ut.user.id) + FROM UserTodo ut + WHERE ut.pot.potId = :potId + AND ut.status = :status + """) + long countDistinctUserIdsByPotAndStatus(@Param("potId") Long potId, + @Param("status") TodoStatus status); + + // 완료 개수 기준 상위 사용자 조회 (페이징 적용) + @Query(""" + SELECT ut.user.id + FROM UserTodo ut + WHERE ut.pot.potId = :potId + AND ut.status = :status + GROUP BY ut.user.id + ORDER BY COUNT(ut.todoId) DESC + """) + Page findTopUserIdsByPotAndStatus(@Param("potId") Long potId, + @Param("status") TodoStatus status, + Pageable pageable); + + // 상위 2명 편의 메서드 + default List findTop2UserIds(Long potId, TodoStatus status) { + return findTopUserIdsByPotAndStatus(potId, status, PageRequest.of(0, 2)).getContent(); + } + + }