From 0363543bf714debdd4801cd5809e11a99f03b76e Mon Sep 17 00:00:00 2001 From: DongHoon Lee <125895298+hoonyworld@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:39:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20app=20be=EB=A5=BC=20=EC=9C=84=ED=95=9C?= =?UTF-8?q?=20=EC=8A=A4=ED=84=B0=EB=94=94=20=EC=88=98=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84=20(#489)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(InternalMeetingStatsController): orgId로 참여한 스터디 개수를 반환하는 API 구현 * feat(ApprovedStudyCountResponseDto): 플그 id와 승인된 스터디 수를 반환하는 DTO 구현 * feat(InternalMeetingStatsService): orgId로 승인된 스터디 개수 조회 기능 추가 * feat(ApprovedStudyCountProjection): orgId와 approvedStudyCount를 반환하는 프로젝션 인터페이스 작성 * feat(ApplyRepository): orgId를 기준으로 승인된 스터디 수 조회 쿼리 구현 * feat(InternalMeetingStatsApi): 유저의 승인된 스터디 수 조회 API 스웨거 명세 구현 * chore(ApplyRepository): 주석 제거 * refactor: 반환 로직 DTO 내부로 이동 * feat: Transaction 읽기 속성 추가 * del: 프로젝션 인터페이스 삭제 * refactor(ApprovedStudyCountResponseDto): 정적 팩토리 메서드 리팩토링 * refactor(ApplyRepository): JPQL 쿼리 변경 * refactor(InternalMeetingStatsService): 비즈니스 로직 변경 * refactor(InternalMeetingStatsService): orgId에 해당하는 유저가 없을 경우 승인된 스터디 신청 수를 0으로 반환하도록 변경 * refactor(InternalMeetingStatsService): 유저 객체로 부터 orgId를 가져와서 반환하도록 리팩토링 --- .../main/entity/apply/ApplyRepository.java | 15 +++++++-- .../internal/InternalMeetingStatsApi.java | 25 ++++++++++++++ .../InternalMeetingStatsController.java | 26 +++++++++++++++ .../dto/ApprovedStudyCountResponseDto.java | 16 +++++++++ .../service/InternalMeetingStatsService.java | 33 +++++++++++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsApi.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsController.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/internal/dto/ApprovedStudyCountResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/internal/service/InternalMeetingStatsService.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java index 06350b91..b9dd9870 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java @@ -1,11 +1,12 @@ package org.sopt.makers.crew.main.entity.apply; -import static org.sopt.makers.crew.main.global.exception.ErrorStatus.NOT_FOUND_APPLY; +import static org.sopt.makers.crew.main.global.exception.ErrorStatus.*; import java.util.List; -import org.sopt.makers.crew.main.global.exception.BadRequestException; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.global.exception.BadRequestException; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -57,4 +58,12 @@ default Apply findByIdOrThrow(Integer applyId) { @Query("DELETE FROM Apply a WHERE a.meetingId = :meetingId") void deleteAllByMeetingIdQuery(Integer meetingId); -} \ No newline at end of file + @Query("SELECT COALESCE(COUNT(a.id), 0) " + + "FROM Apply a JOIN a.meeting m " + + "JOIN a.user u " + + "WHERE m.category = :category AND a.status = :status AND u.orgId = :orgId") + Long findApprovedStudyCountByOrgId( + @Param("category") MeetingCategory category, + @Param("status") EnApplyStatus status, + @Param("orgId") Integer orgId); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsApi.java b/main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsApi.java new file mode 100644 index 00000000..fecb694d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsApi.java @@ -0,0 +1,25 @@ +package org.sopt.makers.crew.main.internal; + +import java.util.Map; + +import org.springframework.http.ResponseEntity; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +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; + +@Tag(name = "internal API - 개수(스터디, 행사 등등)") +public interface InternalMeetingStatsApi { + + @Operation(summary = "유저의 승인된 스터디 수 조회", description = "특정 유저의 승인된 스터디 수를 조회하는 API입니다.", tags = { + "Internal Meeting Stats"}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "APPROVE된 스터디 수 조회 성공", content = @Content(mediaType = "application/json", schema = @Schema(example = "{\"orgId\": 1, \"approvedStudyCount\": 5}"))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저입니다.", content = @Content(mediaType = "application/json"))}) + ResponseEntity> getApprovedStudyCountByOrgId( + @Parameter(description = "플레이그라운드 유저 ID(orgId)", example = "1") Integer orgId); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsController.java b/main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsController.java new file mode 100644 index 00000000..488f543b --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/internal/InternalMeetingStatsController.java @@ -0,0 +1,26 @@ +package org.sopt.makers.crew.main.internal; + +import org.sopt.makers.crew.main.internal.dto.ApprovedStudyCountResponseDto; +import org.sopt.makers.crew.main.internal.service.InternalMeetingStatsService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/internal/meeting/stats") +@RequiredArgsConstructor +public class InternalMeetingStatsController { + private final InternalMeetingStatsService internalMeetingStatsService; + + @GetMapping("/approved-studies/{orgId}") + public ResponseEntity getApprovedStudyCountByOrgId( + @PathVariable Integer orgId + ) { + ApprovedStudyCountResponseDto response = internalMeetingStatsService.getApprovedStudyCountByOrgId(orgId); + return ResponseEntity.ok(response); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/internal/dto/ApprovedStudyCountResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/internal/dto/ApprovedStudyCountResponseDto.java new file mode 100644 index 00000000..f69c85fa --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/internal/dto/ApprovedStudyCountResponseDto.java @@ -0,0 +1,16 @@ +package org.sopt.makers.crew.main.internal.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "승인된 스터디 수를 나타내는 DTO") +public record ApprovedStudyCountResponseDto( + @Schema(description = "플레이그라운드 유저 ID(orgId)", example = "1") + Integer orgId, + + @Schema(description = "승인된 스터디 수", example = "5") + Long approvedStudyCount +) { + public static ApprovedStudyCountResponseDto of(Integer orgId, Long approvedStudyCount) { + return new ApprovedStudyCountResponseDto(orgId, approvedStudyCount); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/internal/service/InternalMeetingStatsService.java b/main/src/main/java/org/sopt/makers/crew/main/internal/service/InternalMeetingStatsService.java new file mode 100644 index 00000000..5e4211ae --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/internal/service/InternalMeetingStatsService.java @@ -0,0 +1,33 @@ +package org.sopt.makers.crew.main.internal.service; + +import org.sopt.makers.crew.main.entity.apply.ApplyRepository; +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.internal.dto.ApprovedStudyCountResponseDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class InternalMeetingStatsService { + private final ApplyRepository applyRepository; + private final UserRepository userRepository; + + public ApprovedStudyCountResponseDto getApprovedStudyCountByOrgId(Integer orgId) { + User user = userRepository.findByOrgId(orgId).orElse(null); + + if (user == null) { + return ApprovedStudyCountResponseDto.of(orgId, 0L); + } + + Long approvedStudyCount = applyRepository.findApprovedStudyCountByOrgId(MeetingCategory.STUDY, + EnApplyStatus.APPROVE, user.getOrgId()); + + return ApprovedStudyCountResponseDto.of(user.getOrgId(), approvedStudyCount); + } +}