템플릿 목록 조회 API 개발

This commit is contained in:
cyjadela
2025-10-24 15:19:29 +09:00
parent e74087e811
commit cf313259a5
23 changed files with 649 additions and 127 deletions
@@ -50,6 +50,7 @@ public class SecurityConfig {
// Meeting API endpoints (for testing)
.requestMatchers("/api/meetings/**").permitAll()
// All other requests require authentication
.requestMatchers("/api/templates/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
@@ -1,13 +1,8 @@
package com.unicorn.hgzero.meeting.infra.controller;
import com.unicorn.hgzero.common.dto.ApiResponse;
import com.unicorn.hgzero.meeting.biz.dto.TemplateDTO;
import com.unicorn.hgzero.meeting.biz.service.TemplateService;
import com.unicorn.hgzero.meeting.infra.dto.response.TemplateListResponse;
import com.unicorn.hgzero.meeting.infra.dto.response.TemplateDetailResponse;
import com.unicorn.hgzero.meeting.infra.cache.CacheService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@@ -15,12 +10,14 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 템플릿 관리 API Controller
* 템플릿 목록 조회, 상세 조회 기능
* 템플릿 관리API Controller
* 고정된 템플릿 목록을 제공합니다
*/
@RestController
@RequestMapping("/api/templates")
@@ -29,11 +26,8 @@ import java.util.stream.Collectors;
@Tag(name = "Template", description = "템플릿 관리 API")
public class TemplateController {
private final TemplateService templateService;
private final CacheService cacheService;
/**
* 템플릿 목록 조회
* 템플릿 목록 조회 (고정 데이터 반환)
* GET /api/templates
*/
@GetMapping
@@ -45,40 +39,19 @@ public class TemplateController {
})
public ResponseEntity<ApiResponse<TemplateListResponse>> getTemplateList(
@RequestHeader("X-User-Id") String userId,
@RequestHeader("X-User-Name") String userName,
@Parameter(description = "템플릿 카테고리") @RequestParam(required = false) String category,
@Parameter(description = "활성 상태 (true: 활성, false: 비활성)") @RequestParam(required = false) Boolean isActive) {
@RequestHeader("X-User-Name") String userName) {
log.info("템플릿 목록 조회 요청 - userId: {}, category: {}, isActive: {}",
userId, category, isActive);
log.info("템플릿 목록 조회 요청 - userId: {}", userId);
try {
// 캐시 확인
String cacheKey = String.format("templates:list:%s:%s",
(category != null ? category : "all"),
(isActive != null ? isActive.toString() : "all"));
TemplateListResponse cachedResponse = cacheService.getCachedTemplateList(cacheKey);
if (cachedResponse != null) {
log.debug("캐시된 템플릿 목록 반환");
return ResponseEntity.ok(ApiResponse.success(cachedResponse));
}
// 템플릿 목록 조회
List<TemplateDTO> templates = templateService.getTemplateList(category, isActive);
// 응답 DTO 생성
List<TemplateListResponse.TemplateItem> templateItems = templates.stream()
.map(this::convertToTemplateItem)
.collect(Collectors.toList());
// 고정된 템플릿 데이터 생성
List<TemplateListResponse.TemplateItem> templateItems = createFixedTemplates();
TemplateListResponse response = TemplateListResponse.builder()
.templateList(templateItems)
.totalCount(templateItems.size())
.build();
// 캐시 저장
cacheService.cacheTemplateList(cacheKey, response);
log.info("템플릿 목록 조회 성공 - count: {}", templateItems.size());
return ResponseEntity.ok(ApiResponse.success(response));
@@ -90,99 +63,103 @@ public class TemplateController {
}
/**
* 템플릿 상세 조회
* GET /api/templates/{templateId}
* 고정된 템플릿 데이터 생성
*/
@GetMapping("/{templateId}")
@Operation(summary = "템플릿 상세 조회", description = "템플릿 상세 정보를 조회합니다")
public ResponseEntity<ApiResponse<TemplateDetailResponse>> getTemplateDetail(
@RequestHeader("X-User-Id") String userId,
@RequestHeader("X-User-Name") String userName,
@Parameter(description = "템플릿 ID") @PathVariable String templateId) {
log.info("템플릿 상세 조회 요청 - userId: {}, templateId: {}", userId, templateId);
try {
// 캐시 확인
TemplateDetailResponse cachedResponse = cacheService.getCachedTemplateDetail(templateId);
if (cachedResponse != null) {
log.debug("캐시된 템플릿 상세 반환 - templateId: {}", templateId);
return ResponseEntity.ok(ApiResponse.success(cachedResponse));
}
// 템플릿 조회
TemplateDTO templateDTO = templateService.getTemplateById(templateId);
// 응답 DTO 생성
TemplateDetailResponse response = convertToTemplateDetailResponse(templateDTO);
// 캐시 저장
cacheService.cacheTemplateDetail(templateId, response);
log.info("템플릿 상세 조회 성공 - templateId: {}", templateId);
return ResponseEntity.ok(ApiResponse.success(response));
} catch (Exception e) {
log.error("템플릿 상세 조회 실패 - templateId: {}", templateId, e);
return ResponseEntity.badRequest()
.body(ApiResponse.errorWithType("템플릿 상세 조회에 실패했습니다"));
}
private List<TemplateListResponse.TemplateItem> createFixedTemplates() {
List<TemplateListResponse.TemplateItem> templates = new ArrayList<>();
// 일반 회의 템플릿
templates.add(TemplateListResponse.TemplateItem.builder()
.templateId("general")
.name("일반 회의")
.description("기본 회의록 형식")
.category("meeting")
.icon("📋")
.isActive(true)
.usageCount(0)
.createdAt(LocalDateTime.now())
.lastUsedAt(null)
.createdBy("system")
.sections(Arrays.asList(
createSectionInfo("회의 개요", "회의 기본 정보", 1, true),
createSectionInfo("논의 사항", "주요 논의 내용", 2, true),
createSectionInfo("결정 사항", "회의에서 결정된 사항", 3, true),
createSectionInfo("액션 아이템", "향후 진행할 작업", 4, true)
))
.build());
// 스크럼 회의 템플릿
templates.add(TemplateListResponse.TemplateItem.builder()
.templateId("scrum")
.name("스크럼 회의")
.description("데일리 스탠드업 형식")
.category("agile")
.icon("🏃")
.isActive(true)
.usageCount(0)
.createdAt(LocalDateTime.now())
.lastUsedAt(null)
.createdBy("system")
.sections(Arrays.asList(
createSectionInfo("어제 한 일", "지난 작업일에 완료한 작업", 1, true),
createSectionInfo("오늘 할 일", "오늘 진행할 예정 작업", 2, true),
createSectionInfo("블로커/이슈", "진행을 방해하는 요소", 3, false)
))
.build());
// 킥오프 회의 템플릿
templates.add(TemplateListResponse.TemplateItem.builder()
.templateId("kickoff")
.name("킥오프 회의")
.description("프로젝트 시작 회의")
.category("project")
.icon("🚀")
.isActive(true)
.usageCount(0)
.createdAt(LocalDateTime.now())
.lastUsedAt(null)
.createdBy("system")
.sections(Arrays.asList(
createSectionInfo("프로젝트 개요", "프로젝트 기본 정보", 1, true),
createSectionInfo("목표 및 범위", "프로젝트 목표와 범위", 2, true),
createSectionInfo("역할 및 책임", "팀원별 역할과 책임", 3, true),
createSectionInfo("일정 및 마일스톤", "프로젝트 일정", 4, true)
))
.build());
// 주간 회의 템플릿
templates.add(TemplateListResponse.TemplateItem.builder()
.templateId("weekly")
.name("주간 회의")
.description("주간 리뷰 및 계획")
.category("review")
.icon("📅")
.isActive(true)
.usageCount(0)
.createdAt(LocalDateTime.now())
.lastUsedAt(null)
.createdBy("system")
.sections(Arrays.asList(
createSectionInfo("지난주 성과", "지난주 달성한 성과", 1, true),
createSectionInfo("이번주 계획", "이번주 진행할 계획", 2, true),
createSectionInfo("주요 이슈", "해결이 필요한 이슈", 3, false),
createSectionInfo("다음 액션", "다음 주 액션 아이템", 4, true)
))
.build());
return templates;
}
// Helper methods
private TemplateListResponse.TemplateItem convertToTemplateItem(TemplateDTO templateDTO) {
// 섹션 정보 변환
List<TemplateListResponse.TemplateSectionInfo> sections = templateDTO.getSections().stream()
.map(section -> TemplateListResponse.TemplateSectionInfo.builder()
.title(section.getTitle())
.description(section.getDescription())
.orderIndex(section.getOrderIndex())
.isRequired(section.isRequired())
.build())
.collect(Collectors.toList());
return TemplateListResponse.TemplateItem.builder()
.templateId(templateDTO.getTemplateId())
.name(templateDTO.getName())
.description(templateDTO.getDescription())
.category(templateDTO.getCategory())
.isActive(templateDTO.isActive())
.usageCount(templateDTO.getUsageCount())
.createdAt(templateDTO.getCreatedAt())
.lastUsedAt(templateDTO.getLastUsedAt())
.createdBy(templateDTO.getCreatedBy())
.sections(sections)
.build();
}
private TemplateDetailResponse convertToTemplateDetailResponse(TemplateDTO templateDTO) {
// 섹션 상세 정보 변환
List<TemplateDetailResponse.SectionDetail> sections = templateDTO.getSections().stream()
.map(section -> TemplateDetailResponse.SectionDetail.builder()
.sectionId(section.getSectionId())
.title(section.getTitle())
.description(section.getDescription())
.content(section.getContent())
.orderIndex(section.getOrderIndex())
.isRequired(section.isRequired())
.inputType(section.getInputType())
.placeholder(section.getPlaceholder())
.maxLength(section.getMaxLength())
.isEditable(section.isEditable())
.build())
.collect(Collectors.toList());
return TemplateDetailResponse.builder()
.templateId(templateDTO.getTemplateId())
.name(templateDTO.getName())
.description(templateDTO.getDescription())
.category(templateDTO.getCategory())
.isActive(templateDTO.isActive())
.usageCount(templateDTO.getUsageCount())
.createdAt(templateDTO.getCreatedAt())
.lastUsedAt(templateDTO.getLastUsedAt())
.createdBy(templateDTO.getCreatedBy())
.sections(sections)
/**
* 템플릿 섹션 정보 생성 헬퍼 메서드
*/
private TemplateListResponse.TemplateSectionInfo createSectionInfo(
String title, String description, int orderIndex, boolean isRequired) {
return TemplateListResponse.TemplateSectionInfo.builder()
.title(title)
.description(description)
.orderIndex(orderIndex)
.isRequired(isRequired)
.build();
}
}
@@ -29,6 +29,7 @@ public class TemplateListResponse {
private String name;
private String description;
private String category;
private String icon; // 아이콘 추가
private boolean isActive;
private int usageCount;
private LocalDateTime createdAt;