백엔드 실행 프로파일 작성

This commit is contained in:
cyjadela
2025-10-23 18:33:21 +09:00
parent e8fc0562b9
commit 71d6675d25
445 changed files with 2316 additions and 47 deletions
@@ -52,6 +52,21 @@ public class Minutes {
*/
private String createdBy;
/**
* 생성 시간
*/
private LocalDateTime createdAt;
/**
* 최종 수정 시간
*/
private LocalDateTime lastModifiedAt;
/**
* 최종 수정자 ID
*/
private String lastModifiedBy;
/**
* 확정자 ID
*/
@@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -51,6 +52,16 @@ public class Template {
*/
private String createdBy;
/**
* 생성 시간
*/
private LocalDateTime createdAt;
/**
* 최종 수정 시간
*/
private LocalDateTime lastModifiedAt;
/**
* 템플릿 섹션 내부 클래스
*/
@@ -62,11 +62,36 @@ public class Todo {
*/
private String priority;
/**
* 진행률 (0-100)
*/
private Integer progress;
/**
* 생성자 ID
*/
private String createdBy;
/**
* 생성 시간
*/
private LocalDateTime createdAt;
/**
* 최종 수정 시간
*/
private LocalDateTime lastModifiedAt;
/**
* 완료 일시
*/
private LocalDateTime completedAt;
/**
* 완료한 사용자 ID
*/
private String completedBy;
/**
* Todo 완료
*/
@@ -1,7 +1,9 @@
package com.unicorn.hgzero.meeting.biz.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@@ -78,4 +80,104 @@ public class MinutesDTO {
* 버전
*/
private final Integer version;
/**
* 메모
*/
private final String memo;
/**
* 회의 제목
*/
private final String meetingTitle;
/**
* 생성자
*/
private final String createdBy;
/**
* 수정자
*/
private final String lastModifiedBy;
/**
* 수정 시간
*/
private final LocalDateTime lastModifiedAt;
/**
* Todo 개수
*/
private final Integer todoCount;
/**
* 완료된 Todo 개수
*/
private final Integer completedTodoCount;
/**
* 회의 정보
*/
private final MeetingInfo meeting;
/**
* 섹션 목록
*/
private final List<SectionInfo> sectionsInfo;
/**
* Todo 목록
*/
private final List<TodoInfo> todos;
// 중첩 클래스들
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MeetingInfo {
private String meetingId;
private String title;
private LocalDateTime scheduledAt;
private LocalDateTime startedAt;
private LocalDateTime endedAt;
private String organizerId;
private String organizerName;
}
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SectionInfo {
private String sectionId;
private String title;
private String content;
private Integer orderIndex;
private boolean isLocked;
private boolean isVerified;
private String lockedBy;
private LocalDateTime lockedAt;
private String verifiedBy;
private LocalDateTime verifiedAt;
}
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TodoInfo {
private String todoId;
private String title;
private String description;
private String assigneeId;
private String assigneeName;
private String priority;
private String status;
private LocalDateTime dueDate;
private LocalDateTime completedAt;
private String completedBy;
}
}
@@ -1,7 +1,9 @@
package com.unicorn.hgzero.meeting.biz.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@@ -59,35 +61,82 @@ public class TemplateDTO {
*/
private final LocalDateTime updatedAt;
/**
* 카테고리
*/
private final String category;
/**
* 활성 상태
*/
private final boolean active;
/**
* 사용 횟수
*/
private final int usageCount;
/**
* 마지막 사용 시간
*/
private final LocalDateTime lastUsedAt;
/**
* 템플릿 섹션 정보
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TemplateSectionDTO {
/**
* 섹션 ID
*/
private String sectionId;
/**
* 섹션 제목
*/
private final String title;
private String title;
/**
* 섹션 유형
* 섹션 설명
*/
private final String sectionType;
private String description;
/**
* 섹션 내용
*/
private String content;
/**
* 섹션 순서
*/
private final Integer sectionOrder;
/**
* 기본 내용
*/
private final String defaultContent;
private Integer orderIndex;
/**
* 필수 여부
*/
private final Boolean isRequired;
private boolean required;
/**
* 입력 타입
*/
private String inputType;
/**
* 플레이스홀더
*/
private String placeholder;
/**
* 최대 길이
*/
private int maxLength;
/**
* 편집 가능 여부
*/
private boolean editable;
}
}
@@ -78,4 +78,39 @@ public class TodoDTO {
* 완료 시간
*/
private final LocalDateTime completedAt;
/**
* Todo 제목
*/
private final String title;
/**
* 담당자 ID
*/
private final String assigneeId;
/**
* 담당자 이름
*/
private final String assigneeName;
/**
* 완료한 사용자
*/
private final String completedBy;
/**
* 회의록 제목
*/
private final String minutesTitle;
/**
* 회의 ID
*/
private final String meetingId;
/**
* 회의 제목
*/
private final String meetingTitle;
}
@@ -171,6 +171,17 @@ public class MinutesSectionService implements
return unlockedSection;
}
/**
* 섹션 잠금 해제 (사용자 ID 포함)
*/
@Transactional
public MinutesSection unlockSection(String sectionId, String userId) {
log.info("Unlocking section: {} by user: {}", sectionId, userId);
// 권한 검증 등 추가 로직이 필요하면 여기에 구현
return unlockSection(sectionId);
}
/**
* 섹션 검증
*/
@@ -231,4 +242,41 @@ public class MinutesSectionService implements
return sectionReader.findByMinutesIdAndType(minutesId, type);
}
/**
* 섹션 내용 수정 (WebSocket용)
*/
@Transactional
public MinutesSection updateSectionContent(String sectionId, String content, String userId) {
log.info("Updating section content: {} by user: {}", sectionId, userId);
// 섹션 조회
MinutesSection section = sectionReader.findById(sectionId)
.orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));
// 잠금 상태 검증
if (Boolean.TRUE.equals(section.getLocked())) {
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
}
// 섹션 내용 수정
section.update(section.getTitle(), content);
// 저장
MinutesSection updatedSection = sectionWriter.save(section);
log.info("Section content updated successfully: {}", sectionId);
return updatedSection;
}
/**
* 섹션 검증 완료 (사용자 ID 포함)
*/
@Transactional
public MinutesSection verifySectionComplete(String sectionId, String userId) {
log.info("Verifying section complete: {} by user: {}", sectionId, userId);
// 권한 검증 등 추가 로직이 필요하면 여기에 구현
return verifySection(sectionId);
}
}
@@ -3,16 +3,21 @@ package com.unicorn.hgzero.meeting.biz.service;
import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.common.exception.ErrorCode;
import com.unicorn.hgzero.meeting.biz.domain.Minutes;
import com.unicorn.hgzero.meeting.biz.dto.MinutesDTO;
import com.unicorn.hgzero.meeting.biz.usecase.in.minutes.*;
import com.unicorn.hgzero.meeting.biz.usecase.out.MinutesReader;
import com.unicorn.hgzero.meeting.biz.usecase.out.MinutesWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 회의록 Service
@@ -190,4 +195,102 @@ public class MinutesService implements
return minutesReader.findByStatus(status);
}
/**
* 사용자 ID로 회의록 목록 조회 (페이징)
*/
@Transactional(readOnly = true)
public Page<MinutesDTO> getMinutesListByUserId(String userId, Pageable pageable) {
log.debug("Getting minutes list by userId: {}", userId);
// 여기서는 임시로 작성자 기준으로 조회 (실제로는 참석자나 권한 기반으로 조회해야 함)
List<Minutes> minutesList = minutesReader.findByCreatedBy(userId);
// Minutes를 MinutesDTO로 변환
List<MinutesDTO> minutesDTOList = minutesList.stream()
.map(this::convertToMinutesDTO)
.collect(Collectors.toList());
// 페이징 처리 (임시로 전체 목록 반환)
int start = (int) pageable.getOffset();
int end = Math.min((start + pageable.getPageSize()), minutesDTOList.size());
List<MinutesDTO> pageContent = minutesDTOList.subList(start, end);
return new PageImpl<>(pageContent, pageable, minutesDTOList.size());
}
/**
* ID로 회의록 조회 (DTO 반환)
*/
@Transactional(readOnly = true)
public MinutesDTO getMinutesById(String minutesId) {
log.debug("Getting minutes DTO by id: {}", minutesId);
Minutes minutes = minutesReader.findById(minutesId)
.orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));
return convertToMinutesDTO(minutes);
}
/**
* 회의록 수정 (제목, 메모)
*/
@Transactional
public MinutesDTO updateMinutes(String minutesId, String title, String memo, String userId) {
log.info("Updating minutes: {}", minutesId);
// 회의록 조회
Minutes minutes = minutesReader.findById(minutesId)
.orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));
// 상태 검증 (확정된 회의록은 수정 불가)
if ("FINALIZED".equals(minutes.getStatus())) {
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
}
// 제목과 메모 수정
if (title != null) {
minutes.updateTitle(title);
}
// memo 필드가 없어서 제목만 수정
// 저장
Minutes updatedMinutes = minutesWriter.save(minutes);
log.info("Minutes updated successfully: {}", minutesId);
return convertToMinutesDTO(updatedMinutes);
}
/**
* 회의록 확정 (DTO 반환)
*/
@Transactional
public MinutesDTO finalizeMinutesDTO(String minutesId, String userId) {
log.info("Finalizing minutes: {}", minutesId);
Minutes finalizedMinutes = finalizeMinutes(minutesId, userId);
return convertToMinutesDTO(finalizedMinutes);
}
/**
* Minutes 도메인을 MinutesDTO로 변환
*/
private MinutesDTO convertToMinutesDTO(Minutes minutes) {
return MinutesDTO.builder()
.minutesId(minutes.getMinutesId())
.meetingId(minutes.getMeetingId())
.title(minutes.getTitle())
.status(minutes.getStatus())
.version(minutes.getVersion())
.createdAt(minutes.getCreatedAt())
.lastModifiedAt(minutes.getLastModifiedAt())
.createdBy(minutes.getCreatedBy())
.lastModifiedBy(minutes.getLastModifiedBy())
// 추가 필드들은 임시로 기본값 설정
.meetingTitle("임시 회의 제목")
.todoCount(0)
.completedTodoCount(0)
.memo("")
.build();
}
}
@@ -3,6 +3,7 @@ package com.unicorn.hgzero.meeting.biz.service;
import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.common.exception.ErrorCode;
import com.unicorn.hgzero.meeting.biz.domain.Template;
import com.unicorn.hgzero.meeting.biz.dto.TemplateDTO;
import com.unicorn.hgzero.meeting.biz.usecase.in.template.CreateTemplateUseCase;
import com.unicorn.hgzero.meeting.biz.usecase.in.template.GetTemplateUseCase;
import com.unicorn.hgzero.meeting.biz.usecase.out.TemplateReader;
@@ -14,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 템플릿 Service
@@ -113,4 +115,57 @@ public class TemplateService implements
return templateReader.findByNameContaining(name);
}
/**
* 템플릿 목록 조회 (DTO 반환)
*/
@Transactional(readOnly = true)
public List<TemplateDTO> getTemplateList(String category, Boolean isActive) {
log.debug("Getting template list - category: {}, isActive: {}", category, isActive);
List<Template> templates;
if (category != null && !category.isEmpty()) {
templates = templateReader.findByCategory(category);
} else {
templates = templateReader.findByIsPublic(true); // 공개 템플릿 기본 조회
}
return templates.stream()
.map(this::convertToTemplateDTO)
.collect(Collectors.toList());
}
/**
* ID로 템플릿 조회 (DTO 반환)
*/
@Transactional(readOnly = true)
public TemplateDTO getTemplateById(String templateId) {
log.debug("Getting template DTO by id: {}", templateId);
Template template = templateReader.findById(templateId)
.orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));
return convertToTemplateDTO(template);
}
/**
* Template 도메인을 TemplateDTO로 변환
*/
private TemplateDTO convertToTemplateDTO(Template template) {
return TemplateDTO.builder()
.templateId(template.getTemplateId())
.name(template.getName())
.description(template.getDescription())
.templateType("STANDARD") // 임시값
.sections(List.of()) // 임시로 빈 리스트
.isActive(template.getIsPublic())
.createdBy(template.getCreatedBy())
.createdAt(template.getCreatedAt())
.updatedAt(template.getLastModifiedAt())
.category(template.getCategory())
.active(template.getIsPublic())
.usageCount(0) // 임시값
.lastUsedAt(null) // 임시값
.build();
}
}
@@ -3,17 +3,22 @@ package com.unicorn.hgzero.meeting.biz.service;
import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.common.exception.ErrorCode;
import com.unicorn.hgzero.meeting.biz.domain.Todo;
import com.unicorn.hgzero.meeting.biz.dto.TodoDTO;
import com.unicorn.hgzero.meeting.biz.usecase.in.todo.*;
import com.unicorn.hgzero.meeting.biz.usecase.out.TodoReader;
import com.unicorn.hgzero.meeting.biz.usecase.out.TodoWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Todo Service
@@ -216,4 +221,60 @@ public class TodoService implements
return todoReader.findByAssigneeIdAndDueDateBetween(assigneeId, startDate, endDate);
}
/**
* 담당자 ID로 Todo 목록 조회 (페이징, DTO 반환)
*/
@Transactional(readOnly = true)
public Page<TodoDTO> getTodoListByAssigneeId(String assigneeId, String status, Pageable pageable) {
log.debug("Getting todo list by assigneeId: {}, status: {}", assigneeId, status);
List<Todo> todoList;
if (status != null && !status.isEmpty()) {
todoList = todoReader.findByAssigneeIdAndStatus(assigneeId, status);
} else {
todoList = todoReader.findByAssigneeId(assigneeId);
}
// Todo를 TodoDTO로 변환
List<TodoDTO> todoDTOList = todoList.stream()
.map(this::convertToTodoDTO)
.collect(Collectors.toList());
// 페이징 처리 (임시로 전체 목록 반환)
int start = (int) pageable.getOffset();
int end = Math.min((start + pageable.getPageSize()), todoDTOList.size());
List<TodoDTO> pageContent = todoDTOList.subList(start, end);
return new PageImpl<>(pageContent, pageable, todoDTOList.size());
}
/**
* Todo 도메인을 TodoDTO로 변환
*/
private TodoDTO convertToTodoDTO(Todo todo) {
return TodoDTO.builder()
.todoId(todo.getTodoId())
.minutesId(todo.getMinutesId())
.content(todo.getDescription())
.assignee(todo.getAssigneeId())
.dueDate(todo.getDueDate())
.priority(todo.getPriority())
.status(todo.getStatus())
.progress(todo.getProgress())
.description(todo.getDescription())
.createdBy(todo.getCreatedBy())
.createdAt(todo.getCreatedAt())
.updatedAt(todo.getLastModifiedAt())
.completedAt(todo.getCompletedAt())
// 추가 필드들은 임시로 기본값 설정
.title(todo.getTitle())
.assigneeId(todo.getAssigneeId())
.assigneeName("임시 담당자명")
.completedBy(todo.getCompletedBy())
.minutesTitle("임시 회의록 제목")
.meetingId(todo.getMeetingId())
.meetingTitle("임시 회의 제목")
.build();
}
}
@@ -1,6 +1,7 @@
package com.unicorn.hgzero.meeting.infra.cache;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unicorn.hgzero.meeting.infra.dto.response.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
@@ -23,7 +24,14 @@ public class CacheService {
private static final String MEETING_PREFIX = "meeting:";
private static final String MINUTES_PREFIX = "minutes:";
private static final String MINUTES_LIST_PREFIX = "minutes:list:";
private static final String MINUTES_DETAIL_PREFIX = "minutes:detail:";
private static final String TODO_PREFIX = "todo:";
private static final String TODO_LIST_PREFIX = "todo:list:";
private static final String TODO_DETAIL_PREFIX = "todo:detail:";
private static final String TEMPLATE_PREFIX = "template:";
private static final String TEMPLATE_LIST_PREFIX = "template:list:";
private static final String TEMPLATE_DETAIL_PREFIX = "template:detail:";
private static final String DASHBOARD_PREFIX = "dashboard:";
private static final String SESSION_PREFIX = "session:";
@@ -215,4 +223,142 @@ public class CacheService {
log.error("패턴 캐시 삭제 실패 - pattern: {}", pattern, e);
}
}
// 회의록 관련 캐시 메서드
public void cacheMinutesList(String cacheKey, MinutesListResponse response) {
try {
String value = objectMapper.writeValueAsString(response);
redisTemplate.opsForValue().set(MINUTES_LIST_PREFIX + cacheKey, value, Duration.ofMinutes(10));
log.debug("회의록 목록 캐시 저장 - key: {}", cacheKey);
} catch (Exception e) {
log.error("회의록 목록 캐시 저장 실패 - key: {}", cacheKey, e);
}
}
public MinutesListResponse getCachedMinutesList(String cacheKey) {
try {
String value = redisTemplate.opsForValue().get(MINUTES_LIST_PREFIX + cacheKey);
if (value != null) {
return objectMapper.readValue(value, MinutesListResponse.class);
}
} catch (Exception e) {
log.error("회의록 목록 캐시 조회 실패 - key: {}", cacheKey, e);
}
return null;
}
public void cacheMinutesDetail(String minutesId, MinutesDetailResponse response) {
try {
String value = objectMapper.writeValueAsString(response);
redisTemplate.opsForValue().set(MINUTES_DETAIL_PREFIX + minutesId, value, Duration.ofMinutes(5));
log.debug("회의록 상세 캐시 저장 - minutesId: {}", minutesId);
} catch (Exception e) {
log.error("회의록 상세 캐시 저장 실패 - minutesId: {}", minutesId, e);
}
}
public MinutesDetailResponse getCachedMinutesDetail(String minutesId) {
try {
String value = redisTemplate.opsForValue().get(MINUTES_DETAIL_PREFIX + minutesId);
if (value != null) {
return objectMapper.readValue(value, MinutesDetailResponse.class);
}
} catch (Exception e) {
log.error("회의록 상세 캐시 조회 실패 - minutesId: {}", minutesId, e);
}
return null;
}
public void evictCacheMinutesDetail(String minutesId) {
redisTemplate.delete(MINUTES_DETAIL_PREFIX + minutesId);
log.debug("회의록 상세 캐시 삭제 - minutesId: {}", minutesId);
}
public void evictCacheMinutesList(String userId) {
evictCacheByPattern(MINUTES_LIST_PREFIX + "*" + userId + "*");
log.debug("회의록 목록 캐시 삭제 - userId: {}", userId);
}
// Todo 관련 캐시 메서드
public void cacheTodoList(String cacheKey, TodoListResponse response) {
try {
String value = objectMapper.writeValueAsString(response);
redisTemplate.opsForValue().set(TODO_LIST_PREFIX + cacheKey, value, Duration.ofMinutes(10));
log.debug("Todo 목록 캐시 저장 - key: {}", cacheKey);
} catch (Exception e) {
log.error("Todo 목록 캐시 저장 실패 - key: {}", cacheKey, e);
}
}
public TodoListResponse getCachedTodoList(String cacheKey) {
try {
String value = redisTemplate.opsForValue().get(TODO_LIST_PREFIX + cacheKey);
if (value != null) {
return objectMapper.readValue(value, TodoListResponse.class);
}
} catch (Exception e) {
log.error("Todo 목록 캐시 조회 실패 - key: {}", cacheKey, e);
}
return null;
}
public void evictCacheTodoList(String assigneeId) {
evictCacheByPattern(TODO_LIST_PREFIX + "*" + assigneeId + "*");
log.debug("Todo 목록 캐시 삭제 - assigneeId: {}", assigneeId);
}
public void evictCacheTodoDetail(String todoId) {
redisTemplate.delete(TODO_DETAIL_PREFIX + todoId);
log.debug("Todo 상세 캐시 삭제 - todoId: {}", todoId);
}
public void evictCacheDashboard(String userId) {
redisTemplate.delete(DASHBOARD_PREFIX + userId);
log.debug("대시보드 캐시 삭제 - userId: {}", userId);
}
// 템플릿 관련 캐시 메서드
public void cacheTemplateList(String cacheKey, TemplateListResponse response) {
try {
String value = objectMapper.writeValueAsString(response);
redisTemplate.opsForValue().set(TEMPLATE_LIST_PREFIX + cacheKey, value, Duration.ofHours(1));
log.debug("템플릿 목록 캐시 저장 - key: {}", cacheKey);
} catch (Exception e) {
log.error("템플릿 목록 캐시 저장 실패 - key: {}", cacheKey, e);
}
}
public TemplateListResponse getCachedTemplateList(String cacheKey) {
try {
String value = redisTemplate.opsForValue().get(TEMPLATE_LIST_PREFIX + cacheKey);
if (value != null) {
return objectMapper.readValue(value, TemplateListResponse.class);
}
} catch (Exception e) {
log.error("템플릿 목록 캐시 조회 실패 - key: {}", cacheKey, e);
}
return null;
}
public void cacheTemplateDetail(String templateId, TemplateDetailResponse response) {
try {
String value = objectMapper.writeValueAsString(response);
redisTemplate.opsForValue().set(TEMPLATE_DETAIL_PREFIX + templateId, value, Duration.ofHours(1));
log.debug("템플릿 상세 캐시 저장 - templateId: {}", templateId);
} catch (Exception e) {
log.error("템플릿 상세 캐시 저장 실패 - templateId: {}", templateId, e);
}
}
public TemplateDetailResponse getCachedTemplateDetail(String templateId) {
try {
String value = redisTemplate.opsForValue().get(TEMPLATE_DETAIL_PREFIX + templateId);
if (value != null) {
return objectMapper.readValue(value, TemplateDetailResponse.class);
}
} catch (Exception e) {
log.error("템플릿 상세 캐시 조회 실패 - templateId: {}", templateId, e);
}
return null;
}
}
@@ -8,7 +8,7 @@ import com.unicorn.hgzero.meeting.infra.dto.request.UpdateMinutesRequest;
import com.unicorn.hgzero.meeting.infra.dto.response.MinutesDetailResponse;
import com.unicorn.hgzero.meeting.infra.dto.response.MinutesListResponse;
import com.unicorn.hgzero.meeting.infra.cache.CacheService;
import com.unicorn.hgzero.meeting.infra.event.EventPublisher;
import com.unicorn.hgzero.meeting.infra.event.publisher.EventPublisher;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -197,7 +197,7 @@ public class MinutesController {
try {
// 회의록 확정
MinutesDTO finalizedMinutes = minutesService.finalizeMinutes(minutesId, userId);
MinutesDTO finalizedMinutes = minutesService.finalizeMinutesDTO(minutesId, userId);
// 응답 DTO 생성
MinutesDetailResponse response = convertToMinutesDetailResponse(finalizedMinutes);
@@ -344,7 +344,7 @@ public class MinutesController {
.createdBy(minutesDTO.getCreatedBy())
.lastModifiedBy(minutesDTO.getLastModifiedBy())
.meeting(convertToMeetingInfo(minutesDTO.getMeeting()))
.sections(convertToSectionInfoList(minutesDTO.getSections()))
.sections(convertToSectionInfoList(minutesDTO.getSectionsInfo()))
.todos(convertToTodoInfoList(minutesDTO.getTodos()))
.build();
}
@@ -85,7 +85,7 @@ public class TemplateController {
} catch (Exception e) {
log.error("템플릿 목록 조회 실패", e);
return ResponseEntity.badRequest()
.body(ApiResponse.error("템플릿 목록 조회에 실패했습니다"));
.body(ApiResponse.errorWithType("템플릿 목록 조회에 실패했습니다"));
}
}
@@ -125,7 +125,7 @@ public class TemplateController {
} catch (Exception e) {
log.error("템플릿 상세 조회 실패 - templateId: {}", templateId, e);
return ResponseEntity.badRequest()
.body(ApiResponse.error("템플릿 상세 조회에 실패했습니다"));
.body(ApiResponse.errorWithType("템플릿 상세 조회에 실패했습니다"));
}
}
@@ -7,7 +7,7 @@ import com.unicorn.hgzero.meeting.infra.dto.request.CreateTodoRequest;
import com.unicorn.hgzero.meeting.infra.dto.request.UpdateTodoRequest;
import com.unicorn.hgzero.meeting.infra.dto.response.TodoListResponse;
import com.unicorn.hgzero.meeting.infra.cache.CacheService;
import com.unicorn.hgzero.meeting.infra.event.EventPublisher;
import com.unicorn.hgzero.meeting.infra.event.publisher.EventPublisher;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -23,16 +23,26 @@ public class NotificationRequestEvent {
*/
private final String recipientId;
/**
* 수신자 이름
*/
private final String recipientName;
/**
* 수신자 이메일
*/
private final String recipientEmail;
/**
* 제목
* 제목 (Legacy)
*/
private final String subject;
/**
* 제목
*/
private final String title;
/**
* 메시지
*/
@@ -48,11 +58,31 @@ public class NotificationRequestEvent {
*/
private final String sender;
/**
* 요청자
*/
private final String requestedBy;
/**
* 요청자 이름
*/
private final String requestedByName;
/**
* 우선순위
*/
private final String priority;
/**
* 관련 엔티티 ID
*/
private final String relatedEntityId;
/**
* 관련 엔티티 타입
*/
private final String relatedEntityType;
/**
* 예약 발송 시간
*/
@@ -23,13 +23,28 @@ public class TodoAssignedEvent {
*/
private final String minutesId;
/**
* Todo 제목
*/
private final String title;
/**
* Todo 내용
*/
private final String content;
/**
* 담당자
* 담당자 ID
*/
private final String assigneeId;
/**
* 담당자 이름
*/
private final String assigneeName;
/**
* 담당자 (레거시)
*/
private final String assignee;
@@ -38,6 +53,16 @@ public class TodoAssignedEvent {
*/
private final String assignedBy;
/**
* 할당자 이름
*/
private final String assignedByName;
/**
* 할당 시간
*/
private final LocalDateTime assignedAt;
/**
* 마감일
*/
+31 -2
View File
@@ -1,6 +1,8 @@
spring:
application:
name: meeting
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
# Database Configuration
datasource:
@@ -43,7 +45,7 @@ spring:
# Server Configuration
server:
port: ${SERVER_PORT:8081}
port: ${SERVER_PORT:8082}
# JWT Configuration
jwt:
@@ -85,6 +87,7 @@ springdoc:
# Logging Configuration
logging:
level:
root: ${LOG_LEVEL_ROOT:INFO}
com.unicorn.hgzero.meeting: ${LOG_LEVEL_APP:DEBUG}
org.springframework.web: ${LOG_LEVEL_WEB:INFO}
org.springframework.security: ${LOG_LEVEL_SECURITY:DEBUG}
@@ -95,4 +98,30 @@ logging:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: ${LOG_FILE_PATH:logs/meeting.log}
name: ${LOG_FILE:logs/meeting-service.log}
logback:
rollingpolicy:
max-file-size: ${LOG_MAX_FILE_SIZE:10MB}
max-history: ${LOG_MAX_HISTORY:7}
total-size-cap: ${LOG_TOTAL_SIZE_CAP:100MB}
# External API Configuration
api:
claude:
key: ${CLAUDE_API_KEY:}
url: ${CLAUDE_API_URL:https://api.anthropic.com}
openai:
key: ${OPENAI_API_KEY:}
url: ${OPENAI_API_URL:https://api.openai.com}
openweather:
key: ${OPENWEATHER_API_KEY:}
url: ${OPENWEATHER_API_URL:https://api.openweathermap.org}
kakao:
key: ${KAKAO_API_KEY:}
url: ${KAKAO_API_URL:https://dapi.kakao.com}
# Azure EventHub Configuration
eventhub:
connection-string: ${EVENTHUB_CONNECTION_STRING:}
name: ${EVENTHUB_NAME:hgzero-eventhub-name}
consumer-group: ${EVENTHUB_CONSUMER_GROUP:$Default}