Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public ResponseEntity<ApiResponse<List<PotBadgeMemberDto>>> getBadgeMembersByPot
@Operation(summary = "팟에서 가장 많은 `투두를 완료한' 멤버에게 '할 일 정복자' 뱃지 부여")
@PostMapping("/{potId}")
@ApiErrorCodeExamples({
ErrorStatus.BADGE_NOT_FOUND,
ErrorStatus.BADGE_INSUFFICIENT_TODO_COUNTS,
ErrorStatus.POT_MEMBER_NOT_FOUND
})
Expand Down
38 changes: 21 additions & 17 deletions src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long> topUserIds = userTodoRepository.findTop2UserIds(potId, TodoStatus.COMPLETED);
if (topUserIds.size() < 2) {
throw new PotHandler(ErrorStatus.BADGE_INSUFFICIENT_TOP_MEMBERS);
}

// 3. PotMember 조회
List<PotMember> 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()
);
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/stackpot/stackpot/pot/controller/PotController.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,14 @@ public ResponseEntity<ApiResponse<Map<String, Object>>> getMyRecruitingPots(
return ResponseEntity.ok(ApiResponse.onSuccess(response));
}

@Operation(summary = "팟 이름 수정 API")
@PatchMapping("/{pot_id}/rename")
public ResponseEntity<ApiResponse<String>> updatePotName(
@PathVariable Long pot_id,
@Valid @RequestBody PotNameUpdateRequestDto request
) {
String res = potCommandService.updatePotName(pot_id, request);
return ResponseEntity.ok(ApiResponse.onSuccess(res));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class CompletedPotRequestDto {
@NotBlank(message = "팟 이름은 필수입니다.")
private String potName;

private LocalDate potStartDate;
private String potStartDate;

@NotBlank(message = "사용 언어는 필수입니다.")
private String potLan;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions src/main/java/stackpot/stackpot/pot/entity/Pot.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class Pot extends BaseEntity {
@OneToMany(mappedBy = "pot", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<PotMember> potMembers;

@Setter
@Column(nullable = false, length = 255)
private String potName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ public interface PotMemberRepository extends JpaRepository<PotMember, Long> {
@Query("DELETE FROM PotMember pm WHERE pm.pot.potId = :potId")
void deleteByPotId(@Param("potId") Long potId);

Optional<PotMember> 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);

Expand Down Expand Up @@ -94,4 +92,8 @@ public interface PotMemberRepository extends JpaRepository<PotMember, Long> {
@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<Long> potIds);

long countByPot_PotId(Long potId);
Optional<PotMember> findByPot_PotIdAndUser_Id(Long potId, Long userId);

}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;


Expand Down Expand Up @@ -197,7 +195,7 @@ public PotResponseDto patchPotWithRecruitments(Long potId, CompletedPotRequestDt
Map<String, Object> 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());
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<UserTodo, Long> {

@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<Long> 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);
Expand All @@ -29,4 +27,33 @@ public interface UserTodoRepository extends JpaRepository<UserTodo, Long> {

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<Long> findTopUserIdsByPotAndStatus(@Param("potId") Long potId,
@Param("status") TodoStatus status,
Pageable pageable);

// 상위 2명 편의 메서드
default List<Long> findTop2UserIds(Long potId, TodoStatus status) {
return findTopUserIdsByPotAndStatus(potId, status, PageRequest.of(0, 2)).getContent();
}


}