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
@@ -0,0 +1,24 @@
package redot.redot_server.domain.admin.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redot.redot_server.domain.admin.controller.docs.AdminDashboardControllerDocs;
import redot.redot_server.domain.admin.dto.response.AdminDashboardStatsResponse;
import redot.redot_server.domain.admin.service.AdminDashboardService;

@RestController
@RequestMapping("/api/v1/redot/admin/dashboard")
@RequiredArgsConstructor
public class AdminDashboardController implements AdminDashboardControllerDocs {

private final AdminDashboardService adminDashboardService;

@GetMapping("/stats")
@Override
public ResponseEntity<AdminDashboardStatsResponse> getDashboardStats() {
return ResponseEntity.ok(adminDashboardService.getDashboardStats());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package redot.redot_server.domain.admin.controller.docs;

import io.swagger.v3.oas.annotations.Operation;
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.tags.Tag;
import org.springframework.http.ResponseEntity;
import redot.redot_server.domain.admin.dto.response.AdminDashboardStatsResponse;

@Tag(name = "Admin Dashboard", description = "관리자 대시보드 통계 API")
public interface AdminDashboardControllerDocs {

@Operation(summary = "대시보드 통계 조회", description = "관리자 대시보드에 필요한 요약 통계를 제공합니다.")
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = AdminDashboardStatsResponse.class)))
ResponseEntity<AdminDashboardStatsResponse> getDashboardStats();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package redot.redot_server.domain.admin.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record AdminDashboardStatsResponse(
@Schema(description = "전체 Redot 회원 수") long totalRedotMembers,
@Schema(description = "전일까지 누적된 Redot 회원 수") long redotMembersUntilYesterday,
@Schema(description = "상태가 PENDING 인 상담 요청 수") long pendingConsultationCount,
@Schema(description = "등록된 관리자 수") long adminCount
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package redot.redot_server.domain.admin.service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import redot.redot_server.domain.admin.dto.response.AdminDashboardStatsResponse;
import redot.redot_server.domain.admin.repository.AdminRepository;
import redot.redot_server.domain.redot.consultation.entity.ConsultationStatus;
import redot.redot_server.domain.redot.consultation.repository.ConsultationRepository;
import redot.redot_server.domain.redot.member.repository.RedotMemberRepository;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AdminDashboardService {

private final RedotMemberRepository redotMemberRepository;
private final ConsultationRepository consultationRepository;
private final AdminRepository adminRepository;

public AdminDashboardStatsResponse getDashboardStats() {
ZoneId kstZone = ZoneId.of("Asia/Seoul");
LocalDateTime startOfTodayKst = LocalDate.now(kstZone).atStartOfDay();
LocalDateTime startOfTodayUtc = startOfTodayKst.atZone(kstZone)
.withZoneSameInstant(ZoneOffset.UTC)
.toLocalDateTime();

long totalRedotMembers = redotMemberRepository.count();
long redotMembersUntilYesterday = redotMemberRepository.countByCreatedAtBefore(startOfTodayUtc);

long pendingConsultationCount = consultationRepository.countByStatus(ConsultationStatus.PENDING);
long adminCount = adminRepository.count();

return new AdminDashboardStatsResponse(
totalRedotMembers,
redotMembersUntilYesterday,
pendingConsultationCount,
adminCount
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import org.springframework.data.jpa.repository.JpaRepository;
import redot.redot_server.domain.redot.consultation.entity.Consultation;
import redot.redot_server.domain.redot.consultation.entity.ConsultationStatus;

public interface ConsultationRepository extends JpaRepository<Consultation, Long>, ConsultationRepositoryCustom {

long countByStatus(ConsultationStatus status);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package redot.redot_server.domain.redot.member.repository;

import java.time.LocalDateTime;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import redot.redot_server.domain.redot.member.entity.RedotMember;
Expand All @@ -11,4 +12,6 @@ public interface RedotMemberRepository extends JpaRepository<RedotMember, Long>,
Optional<RedotMember> findByEmail(String email);

Optional<RedotMember> findBySocialProviderAndSocialProviderId(SocialProvider provider, String socialProviderId);

long countByCreatedAtBefore(LocalDateTime before);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE INDEX IF NOT EXISTS idx_redot_members_created_at ON redot_members(created_at);