Feat: 회의록 수정 API 개발

This commit is contained in:
cyjadela 2025-10-29 16:06:32 +09:00
parent 7e2094bcbc
commit f1e9565d5b
6 changed files with 1863 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
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.AgendaSection;
import com.unicorn.hgzero.meeting.infra.dto.request.UpdateAgendaSectionsRequest;
import com.unicorn.hgzero.meeting.infra.gateway.entity.AgendaSectionEntity;
import com.unicorn.hgzero.meeting.infra.gateway.repository.AgendaSectionRepository;
import lombok.RequiredArgsConstructor;
@ -88,4 +91,50 @@ public class AgendaSectionService {
.map(AgendaSectionEntity::toDomain)
.collect(Collectors.toList());
}
/**
* 안건 섹션 요약 수정
* @param agendaId 안건 ID
* @param summary 새로운 요약 내용
* @return 수정된 안건 섹션
*/
@Transactional
public AgendaSection updateAgendaSummary(String agendaId, String summary) {
log.info("안건 섹션 요약 수정 - agendaId: {}, summary length: {}",
agendaId, summary != null ? summary.length() : 0);
// 먼저 존재 여부 확인
AgendaSectionEntity entity = agendaSectionRepository.findById(agendaId)
.orElseThrow(() -> new BusinessException(ErrorCode.AGENDA_SECTION_NOT_FOUND,
"안건 섹션을 찾을 수 없습니다: " + agendaId));
// Native Query로 summary만 업데이트
agendaSectionRepository.updateSummaryById(agendaId, summary);
// 업데이트된 엔티티 다시 조회
AgendaSectionEntity updatedEntity = agendaSectionRepository.findById(agendaId)
.orElseThrow(() -> new BusinessException(ErrorCode.AGENDA_SECTION_NOT_FOUND,
"업데이트된 안건 섹션을 찾을 수 없습니다: " + agendaId));
log.info("안건 섹션 요약 수정 완료 - agendaId: {}", agendaId);
return updatedEntity.toDomain();
}
/**
* 여러 안건 섹션의 요약을 번에 수정
* @param updateItems 안건 ID와 새로운 요약 내용의 목록
* @return 수정된 안건 섹션 목록
*/
@Transactional
public List<AgendaSection> updateMultipleAgendaSummaries(
List<UpdateAgendaSectionsRequest.AgendaUpdateItem> updateItems) {
log.info("안건 섹션 일괄 수정 - 개수: {}", updateItems.size());
List<AgendaSection> updatedSections = updateItems.stream()
.map(item -> updateAgendaSummary(item.getAgendaId(), item.getContent()))
.collect(Collectors.toList());
log.info("안건 섹션 일괄 수정 완료 - 개수: {}", updatedSections.size());
return updatedSections;
}
}

View File

@ -14,6 +14,7 @@ import com.unicorn.hgzero.meeting.biz.service.MinutesSectionService;
import com.unicorn.hgzero.meeting.biz.service.TodoService;
import com.unicorn.hgzero.meeting.biz.service.AgendaSectionService;
import com.unicorn.hgzero.meeting.infra.dto.request.UpdateMinutesRequest;
import com.unicorn.hgzero.meeting.infra.dto.request.UpdateAgendaSectionsRequest;
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;
@ -414,6 +415,54 @@ public class MinutesController {
.body(ApiResponse.errorWithType("섹션 잠금 해제에 실패했습니다"));
}
}
/**
* 회의록 안건별 수정
* PUT /api/minutes/{minutesId}/agenda-sections
*/
@PutMapping("/{minutesId}/agenda-sections")
@Operation(summary = "회의록 안건별 수정",
description = "회의록의 안건별 내용을 수정합니다. 각 안건의 summary 필드만 업데이트됩니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "권한 없음"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "회의록을 찾을 수 없음")
})
public ResponseEntity<ApiResponse<String>> updateAgendaSections(
@RequestHeader("X-User-Id") String userId,
@RequestHeader(value = "X-User-Name", defaultValue = "System") String userName,
@PathVariable String minutesId,
@Valid @RequestBody UpdateAgendaSectionsRequest request) {
log.info("[API] 회의록 안건별 수정 요청 - userId: {}, minutesId: {}, agendaCount: {}",
userId, minutesId, request.getAgendas().size());
try {
// 권한 검증 제거 - 모든 사용자가 수정 가능
log.info("회의록 수정 권한 검증 스킵 - 모든 사용자 수정 가능");
// 안건별 내용 수정
List<AgendaSection> updatedSections = agendaSectionService.updateMultipleAgendaSummaries(request.getAgendas());
// 캐시 무효화
cacheService.evictCacheMinutesDetail(minutesId);
log.info("회의록 안건별 수정 성공 - minutesId: {}, updatedCount: {}",
minutesId, updatedSections.size());
return ResponseEntity.ok(ApiResponse.success("회의록이 성공적으로 수정되었습니다"));
} catch (BusinessException e) {
log.error("회의록 안건별 수정 실패 - 비즈니스 예외 - minutesId: {}", minutesId, e);
return ResponseEntity.badRequest()
.body(ApiResponse.errorWithType(e.getMessage()));
} catch (Exception e) {
log.error("회의록 안건별 수정 실패 - minutesId: {}", minutesId, e);
return ResponseEntity.badRequest()
.body(ApiResponse.errorWithType("회의록 수정에 실패했습니다"));
}
}
// Helper methods

View File

@ -0,0 +1,44 @@
package com.unicorn.hgzero.meeting.infra.dto.request;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.List;
/**
* 회의록 안건별 수정 요청 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UpdateAgendaSectionsRequest {
/**
* 수정할 안건 목록
*/
@NotNull(message = "안건 목록은 필수입니다")
private List<AgendaUpdateItem> agendas;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class AgendaUpdateItem {
/**
* 안건 ID (agenda_sections.id)
*/
@NotBlank(message = "안건 ID는 필수입니다")
private String agendaId;
/**
* 안건 내용 (agenda_sections.summary)
*/
@NotBlank(message = "안건 내용은 필수입니다")
private String content;
}
}

View File

@ -7,6 +7,8 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
/**
* 안건별 회의록 섹션 Entity
@ -39,9 +41,11 @@ public class AgendaSectionEntity extends BaseTimeEntity {
@Column(name = "ai_summary_short", columnDefinition = "TEXT")
private String aiSummaryShort;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "pending_items", columnDefinition = "json")
private String pendingItems;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "todos", columnDefinition = "json")
private String todos;
@ -83,5 +87,12 @@ public class AgendaSectionEntity extends BaseTimeEntity {
.summary(section.getSummary())
.build();
}
/**
* 요약 내용 업데이트
*/
public void updateSummary(String summary) {
this.summary = summary;
}
}

View File

@ -2,6 +2,9 @@ package com.unicorn.hgzero.meeting.infra.gateway.repository;
import com.unicorn.hgzero.meeting.infra.gateway.entity.AgendaSectionEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@ -25,4 +28,14 @@ public interface AgendaSectionRepository extends JpaRepository<AgendaSectionEnti
* @return 안건 섹션 목록
*/
List<AgendaSectionEntity> findByMeetingIdOrderByAgendaNumber(String meetingId);
/**
* 안건 섹션 요약 업데이트 (Native Query 사용)
* @param agendaId 안건 ID
* @param summary 요약 내용
*/
@Modifying
@Query(value = "UPDATE agenda_sections SET summary = :summary, updated_at = CURRENT_TIMESTAMP WHERE id = :agendaId",
nativeQuery = true)
void updateSummaryById(@Param("agendaId") String agendaId, @Param("summary") String summary);
}