mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 04:49:11 +00:00
회의록 목록 조회 API (mock) 구현
This commit is contained in:
+267
-28
@@ -58,44 +58,51 @@ public class MinutesController {
|
||||
@RequestHeader("X-User-Id") String userId,
|
||||
@RequestHeader("X-User-Name") String userName,
|
||||
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
|
||||
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size,
|
||||
@Parameter(description = "정렬 기준 (createdAt, lastModifiedAt)") @RequestParam(defaultValue = "lastModifiedAt") String sortBy,
|
||||
@Parameter(description = "정렬 방향 (asc, desc)") @RequestParam(defaultValue = "desc") String sortDir) {
|
||||
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "10") int size,
|
||||
@Parameter(description = "정렬 기준 (modified, meeting, title)") @RequestParam(defaultValue = "modified") String sortBy,
|
||||
@Parameter(description = "정렬 방향 (asc, desc)") @RequestParam(defaultValue = "desc") String sortDir,
|
||||
@Parameter(description = "상태 필터 (all, draft, complete)") @RequestParam(defaultValue = "all") String status,
|
||||
@Parameter(description = "참여 유형 (attended, created)") @RequestParam(required = false) String participationType,
|
||||
@Parameter(description = "검색 키워드") @RequestParam(required = false) String search) {
|
||||
|
||||
log.info("회의록 목록 조회 요청 - userId: {}, page: {}, size: {}", userId, page, size);
|
||||
log.info("회의록 목록 조회 요청 - userId: {}, page: {}, size: {}, status: {}, participationType: {}, search: {}",
|
||||
userId, page, size, status, participationType, search);
|
||||
|
||||
try {
|
||||
// 캐시 확인
|
||||
String cacheKey = String.format("minutes:list:%s:%d:%d:%s:%s", userId, page, size, sortBy, sortDir);
|
||||
MinutesListResponse cachedResponse = cacheService.getCachedMinutesList(cacheKey);
|
||||
if (cachedResponse != null) {
|
||||
log.debug("캐시된 회의록 목록 반환 - userId: {}", userId);
|
||||
return ResponseEntity.ok(ApiResponse.success(cachedResponse));
|
||||
}
|
||||
|
||||
// 정렬 설정
|
||||
Sort.Direction direction = sortDir.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC;
|
||||
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
|
||||
|
||||
// 회의록 목록 조회
|
||||
var minutesPage = minutesService.getMinutesListByUserId(userId, pageable);
|
||||
// Mock 데이터 생성 (프론트엔드 테스트용)
|
||||
List<MinutesListResponse.MinutesItem> mockMinutes = createMockMinutesList(userId);
|
||||
|
||||
// 응답 DTO 생성
|
||||
List<MinutesListResponse.MinutesItem> minutesItems = minutesPage.getContent().stream()
|
||||
.map(this::convertToMinutesItem)
|
||||
// 필터링 적용
|
||||
List<MinutesListResponse.MinutesItem> filteredMinutes = mockMinutes.stream()
|
||||
.filter(item -> filterByStatus(item, status))
|
||||
.filter(item -> filterByParticipationType(item, participationType, userId))
|
||||
.filter(item -> filterBySearch(item, search))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 정렬 적용
|
||||
applySorting(filteredMinutes, sortBy, sortDir);
|
||||
|
||||
// 페이징 적용
|
||||
int startIndex = page * size;
|
||||
int endIndex = Math.min(startIndex + size, filteredMinutes.size());
|
||||
List<MinutesListResponse.MinutesItem> pagedMinutes =
|
||||
startIndex < filteredMinutes.size() ?
|
||||
filteredMinutes.subList(startIndex, endIndex) :
|
||||
List.of();
|
||||
|
||||
// 통계 계산
|
||||
MinutesListResponse.Statistics stats = calculateStatistics(mockMinutes, participationType, userId);
|
||||
|
||||
MinutesListResponse response = MinutesListResponse.builder()
|
||||
.minutesList(minutesItems)
|
||||
.totalCount(minutesPage.getTotalElements())
|
||||
.minutesList(pagedMinutes)
|
||||
.totalCount(filteredMinutes.size())
|
||||
.currentPage(page)
|
||||
.totalPages(minutesPage.getTotalPages())
|
||||
.totalPages((int) Math.ceil((double) filteredMinutes.size() / size))
|
||||
.statistics(stats)
|
||||
.build();
|
||||
|
||||
// 캐시 저장
|
||||
cacheService.cacheMinutesList(cacheKey, response);
|
||||
|
||||
log.info("회의록 목록 조회 성공 - userId: {}, count: {}", userId, minutesItems.size());
|
||||
log.info("회의록 목록 조회 성공 - userId: {}, total: {}, filtered: {}, paged: {}",
|
||||
userId, mockMinutes.size(), filteredMinutes.size(), pagedMinutes.size());
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -345,6 +352,238 @@ public class MinutesController {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock 데이터 생성 (프론트엔드 테스트용)
|
||||
*/
|
||||
private List<MinutesListResponse.MinutesItem> createMockMinutesList(String userId) {
|
||||
List<MinutesListResponse.MinutesItem> mockData = List.of(
|
||||
// 사용자가 생성한 회의록들
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-001")
|
||||
.title("2024년 1분기 성과리뷰 회의록")
|
||||
.meetingTitle("2024년 1분기 성과리뷰")
|
||||
.status("FINALIZED")
|
||||
.version(3)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 15, 14, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 15, 16, 30))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 15, 14, 0))
|
||||
.createdBy(userId)
|
||||
.lastModifiedBy(userId)
|
||||
.participantCount(8)
|
||||
.todoCount(5)
|
||||
.completedTodoCount(5)
|
||||
.completionRate(100)
|
||||
.isCreatedByUser(true)
|
||||
.build(),
|
||||
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-002")
|
||||
.title("신규 프로젝트 킥오프 회의록")
|
||||
.meetingTitle("신규 프로젝트 킥오프")
|
||||
.status("DRAFT")
|
||||
.version(1)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 20, 10, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 20, 11, 45))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 20, 10, 0))
|
||||
.createdBy(userId)
|
||||
.lastModifiedBy("user-002")
|
||||
.participantCount(6)
|
||||
.todoCount(8)
|
||||
.completedTodoCount(3)
|
||||
.completionRate(75)
|
||||
.isCreatedByUser(true)
|
||||
.build(),
|
||||
|
||||
// 사용자가 참석한 회의록들
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-003")
|
||||
.title("마케팅 전략 회의록")
|
||||
.meetingTitle("마케팅 전략 논의")
|
||||
.status("FINALIZED")
|
||||
.version(2)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 18, 15, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 18, 17, 0))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 18, 15, 0))
|
||||
.createdBy("user-003")
|
||||
.lastModifiedBy("user-003")
|
||||
.participantCount(5)
|
||||
.todoCount(4)
|
||||
.completedTodoCount(4)
|
||||
.completionRate(100)
|
||||
.isCreatedByUser(false)
|
||||
.build(),
|
||||
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-004")
|
||||
.title("기술 아키텍처 리뷰 회의록")
|
||||
.meetingTitle("기술 아키텍처 리뷰")
|
||||
.status("DRAFT")
|
||||
.version(1)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 22, 9, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 22, 10, 30))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 22, 9, 0))
|
||||
.createdBy("user-004")
|
||||
.lastModifiedBy("user-004")
|
||||
.participantCount(7)
|
||||
.todoCount(6)
|
||||
.completedTodoCount(2)
|
||||
.completionRate(60)
|
||||
.isCreatedByUser(false)
|
||||
.build(),
|
||||
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-005")
|
||||
.title("주간 스프린트 회고 회의록")
|
||||
.meetingTitle("주간 스프린트 회고")
|
||||
.status("FINALIZED")
|
||||
.version(1)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 25, 16, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 25, 17, 0))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 25, 16, 0))
|
||||
.createdBy("user-005")
|
||||
.lastModifiedBy("user-005")
|
||||
.participantCount(4)
|
||||
.todoCount(3)
|
||||
.completedTodoCount(3)
|
||||
.completionRate(100)
|
||||
.isCreatedByUser(false)
|
||||
.build(),
|
||||
|
||||
// 추가 더미 데이터들
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-006")
|
||||
.title("고객 피드백 분석 회의록")
|
||||
.meetingTitle("고객 피드백 분석")
|
||||
.status("DRAFT")
|
||||
.version(2)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 28, 14, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 28, 15, 20))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 28, 14, 0))
|
||||
.createdBy(userId)
|
||||
.lastModifiedBy(userId)
|
||||
.participantCount(5)
|
||||
.todoCount(7)
|
||||
.completedTodoCount(4)
|
||||
.completionRate(85)
|
||||
.isCreatedByUser(true)
|
||||
.build(),
|
||||
|
||||
MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId("minutes-007")
|
||||
.title("보안 정책 수립 회의록")
|
||||
.meetingTitle("보안 정책 수립")
|
||||
.status("FINALIZED")
|
||||
.version(1)
|
||||
.createdAt(LocalDateTime.of(2024, 3, 12, 10, 0))
|
||||
.lastModifiedAt(LocalDateTime.of(2024, 3, 12, 12, 0))
|
||||
.meetingDate(LocalDateTime.of(2024, 3, 12, 10, 0))
|
||||
.createdBy("user-006")
|
||||
.lastModifiedBy("user-006")
|
||||
.participantCount(6)
|
||||
.todoCount(4)
|
||||
.completedTodoCount(4)
|
||||
.completionRate(100)
|
||||
.isCreatedByUser(false)
|
||||
.build()
|
||||
);
|
||||
|
||||
return mockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태별 필터링
|
||||
*/
|
||||
private boolean filterByStatus(MinutesListResponse.MinutesItem item, String status) {
|
||||
if ("all".equals(status)) {
|
||||
return true;
|
||||
}
|
||||
if ("draft".equals(status)) {
|
||||
return "DRAFT".equals(item.getStatus());
|
||||
}
|
||||
if ("complete".equals(status)) {
|
||||
return "FINALIZED".equals(item.getStatus());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 참여 유형별 필터링
|
||||
*/
|
||||
private boolean filterByParticipationType(MinutesListResponse.MinutesItem item, String participationType, String userId) {
|
||||
if (participationType == null || participationType.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if ("created".equals(participationType)) {
|
||||
return item.isCreatedByUser();
|
||||
}
|
||||
if ("attended".equals(participationType)) {
|
||||
return !item.isCreatedByUser();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검색어 필터링
|
||||
*/
|
||||
private boolean filterBySearch(MinutesListResponse.MinutesItem item, String search) {
|
||||
if (search == null || search.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String searchLower = search.toLowerCase();
|
||||
return item.getTitle().toLowerCase().contains(searchLower) ||
|
||||
item.getMeetingTitle().toLowerCase().contains(searchLower);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정렬 적용
|
||||
*/
|
||||
private void applySorting(List<MinutesListResponse.MinutesItem> items, String sortBy, String sortDir) {
|
||||
boolean ascending = "asc".equalsIgnoreCase(sortDir);
|
||||
|
||||
switch (sortBy) {
|
||||
case "title":
|
||||
items.sort((a, b) -> ascending ?
|
||||
a.getTitle().compareTo(b.getTitle()) :
|
||||
b.getTitle().compareTo(a.getTitle()));
|
||||
break;
|
||||
case "meeting":
|
||||
items.sort((a, b) -> ascending ?
|
||||
a.getMeetingDate().compareTo(b.getMeetingDate()) :
|
||||
b.getMeetingDate().compareTo(a.getMeetingDate()));
|
||||
break;
|
||||
case "modified":
|
||||
default:
|
||||
items.sort((a, b) -> ascending ?
|
||||
a.getLastModifiedAt().compareTo(b.getLastModifiedAt()) :
|
||||
b.getLastModifiedAt().compareTo(a.getLastModifiedAt()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 통계 계산
|
||||
*/
|
||||
private MinutesListResponse.Statistics calculateStatistics(List<MinutesListResponse.MinutesItem> allItems,
|
||||
String participationType, String userId) {
|
||||
List<MinutesListResponse.MinutesItem> filteredItems = allItems.stream()
|
||||
.filter(item -> filterByParticipationType(item, participationType, userId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long totalCount = filteredItems.size();
|
||||
long draftCount = filteredItems.stream()
|
||||
.filter(item -> "DRAFT".equals(item.getStatus()))
|
||||
.count();
|
||||
long completeCount = filteredItems.stream()
|
||||
.filter(item -> "FINALIZED".equals(item.getStatus()))
|
||||
.count();
|
||||
|
||||
return MinutesListResponse.Statistics.builder()
|
||||
.totalCount(totalCount)
|
||||
.draftCount(draftCount)
|
||||
.completeCount(completeCount)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MinutesDetailResponse convertToMinutesDetailResponse(MinutesDTO minutesDTO) {
|
||||
return MinutesDetailResponse.builder()
|
||||
.minutesId(minutesDTO.getMinutesId())
|
||||
|
||||
+15
@@ -21,6 +21,17 @@ public class MinutesListResponse {
|
||||
private long totalCount;
|
||||
private int currentPage;
|
||||
private int totalPages;
|
||||
private Statistics statistics; // 상태별 통계
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Statistics {
|
||||
private long totalCount;
|
||||
private long draftCount;
|
||||
private long completeCount;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@@ -34,9 +45,13 @@ public class MinutesListResponse {
|
||||
private int version;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime lastModifiedAt;
|
||||
private LocalDateTime meetingDate; // 회의 일시
|
||||
private String createdBy;
|
||||
private String lastModifiedBy;
|
||||
private int participantCount; // 참석자 수
|
||||
private int todoCount;
|
||||
private int completedTodoCount;
|
||||
private int completionRate; // 검증완료율
|
||||
private boolean isCreatedByUser; // 사용자가 생성한 회의록 여부
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user