mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 05:59:11 +00:00
백엔드 실행 프로파일 작성
This commit is contained in:
@@ -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;
|
||||
}
|
||||
+48
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+146
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
|
||||
+2
-2
@@ -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("템플릿 상세 조회에 실패했습니다"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+31
-1
@@ -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;
|
||||
|
||||
/**
|
||||
* 예약 발송 시간
|
||||
*/
|
||||
|
||||
+26
-1
@@ -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;
|
||||
|
||||
/**
|
||||
* 마감일
|
||||
*/
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user