diff --git a/.gitmodules b/.gitmodules index fc0f695..b0abf98 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "Team29_BE_Submodule"] path = Team29_BE_Submodule url = git@github.com:29ana-notai/Team29_BE_Submodule.git - branch = release/0.0.2 + branch = release/0.0.3 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 6442c2e..0000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM gradle:jdk21-jammy - -WORKDIR /app - -CMD ["gradle", "bootRun"] \ No newline at end of file diff --git a/Team29_BE_Submodule b/Team29_BE_Submodule index 9786d71..2908ffc 160000 --- a/Team29_BE_Submodule +++ b/Team29_BE_Submodule @@ -1 +1 @@ -Subproject commit 9786d71108d3333313244c73fb01e56b6993916f +Subproject commit 2908ffc46ec197ceffb930fd23349ebc48f75b8f diff --git a/build.gradle b/build.gradle index 60edd53..a0d1a96 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'notai' -version = '0.0.2' +version = '0.0.3' java { toolchain { diff --git a/src/main/java/notai/annotation/presentation/AnnotationController.java b/src/main/java/notai/annotation/presentation/AnnotationController.java index a57414c..474f69b 100644 --- a/src/main/java/notai/annotation/presentation/AnnotationController.java +++ b/src/main/java/notai/annotation/presentation/AnnotationController.java @@ -76,6 +76,6 @@ public ResponseEntity deleteAnnotation( ) { annotationService.deleteAnnotation(documentId, annotationId); - return new ResponseEntity<>(HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.OK); } } diff --git a/src/main/java/notai/annotation/presentation/request/CreateAnnotationRequest.java b/src/main/java/notai/annotation/presentation/request/CreateAnnotationRequest.java index 8e05acc..e2dc072 100644 --- a/src/main/java/notai/annotation/presentation/request/CreateAnnotationRequest.java +++ b/src/main/java/notai/annotation/presentation/request/CreateAnnotationRequest.java @@ -1,29 +1,29 @@ package notai.annotation.presentation.request; -import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; public record CreateAnnotationRequest( - @Positive(message = "페이지 번호는 양수여야 합니다.") + @PositiveOrZero(message = "페이지 번호는 0 이상이어야 합니다.") int pageNumber, -// @Max(value = ?, message = "x 좌표는 최대 ? 이하여야 합니다.") + // @Max(value = ?, message = "x 좌표는 최대 ? 이하여야 합니다.") @PositiveOrZero(message = "x 좌표는 0 이상이어야 합니다.") int x, -// @Max(value = ?, message = "x 좌표는 최대 ? 이하여야 합니다.") + // @Max(value = ?, message = "x 좌표는 최대 ? 이하여야 합니다.") @PositiveOrZero(message = "y 좌표는 0 이상이어야 합니다.") int y, -// @Max(value = ?, message = "width는 최대 ? 이하여야 합니다.") + // @Max(value = ?, message = "width는 최대 ? 이하여야 합니다.") @Positive(message = "width는 양수여야 합니다.") int width, -// @Max(value = ?, message = "height는 최대 ? 이하여야 합니다.") + // @Max(value = ?, message = "height는 최대 ? 이하여야 합니다.") @Positive(message = "height는 양수여야 합니다.") int height, String content -) {} +) { +} diff --git a/src/main/java/notai/client/ai/AiClient.java b/src/main/java/notai/client/ai/AiClient.java index c03507b..83f505e 100644 --- a/src/main/java/notai/client/ai/AiClient.java +++ b/src/main/java/notai/client/ai/AiClient.java @@ -2,17 +2,17 @@ import notai.client.ai.request.LlmTaskRequest; import notai.client.ai.response.TaskResponse; +import org.springframework.core.io.ByteArrayResource; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.service.annotation.PostExchange; -import java.io.InputStream; - public interface AiClient { @PostExchange(url = "/api/ai/llm") TaskResponse submitLlmTask(@RequestBody LlmTaskRequest request); @PostExchange(url = "/api/ai/stt", contentType = MediaType.MULTIPART_FORM_DATA_VALUE) - TaskResponse submitSttTask(@RequestBody InputStream audioFileStream); + TaskResponse submitSttTask(@RequestPart("audio") ByteArrayResource audioFile); } diff --git a/src/main/java/notai/client/ai/AiClientConfig.java b/src/main/java/notai/client/ai/AiClientConfig.java index 5ab3db6..4bd3e5b 100644 --- a/src/main/java/notai/client/ai/AiClientConfig.java +++ b/src/main/java/notai/client/ai/AiClientConfig.java @@ -6,7 +6,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatusCode; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestTemplate; import static notai.client.HttpInterfaceUtil.createHttpInterface; import static notai.common.exception.ErrorMessages.AI_SERVER_ERROR; @@ -20,15 +23,26 @@ public class AiClientConfig { @Bean public AiClient aiClient() { - RestClient restClient = - RestClient.builder().baseUrl(aiServerUrl).requestInterceptor((request, body, execution) -> { - request.getHeaders().setContentLength(body.length); // Content-Length 설정 안하면 411 에러 발생 - return execution.execute(request, body); - }).defaultStatusHandler(HttpStatusCode::isError, (request, response) -> { - String responseBody = new String(response.getBody().readAllBytes()); - log.error("Response Status: {}", response.getStatusCode()); - throw new ExternalApiException(AI_SERVER_ERROR, response.getStatusCode().value()); - }).build(); + RestClient restClient = RestClient.builder() + .baseUrl(aiServerUrl) + .messageConverters(converters -> { + converters.addAll(new RestTemplate().getMessageConverters()); + converters.add(new FormHttpMessageConverter()); + }) + .requestInterceptor((request, body, execution) -> { + request.getHeaders().setContentLength(body.length); // Content-Length 설정 안하면 411 에러 발생 + return execution.execute(request, body); + }) + .defaultStatusHandler(HttpStatusCode::isError, (request, response) -> { + String responseBody = new String(response.getBody().readAllBytes()); + log.error("AI 서버에서 오류가 발생했습니다. - Status: {}, Body: {}", + response.getStatusCode(), + responseBody + ); + throw new ExternalApiException(AI_SERVER_ERROR, response.getStatusCode().value()); + }) + .build(); + return createHttpInterface(restClient, AiClient.class); } } diff --git a/src/main/java/notai/common/config/AuthConfig.java b/src/main/java/notai/common/config/AuthConfig.java index c0e8bc9..7cca462 100644 --- a/src/main/java/notai/common/config/AuthConfig.java +++ b/src/main/java/notai/common/config/AuthConfig.java @@ -17,8 +17,12 @@ public class AuthConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(authInterceptor).addPathPatterns("/api/**").excludePathPatterns( - "/api/members/oauth/login/**").excludePathPatterns("/api/members/token/refresh"); + registry.addInterceptor(authInterceptor) + .addPathPatterns("/api/**") + .excludePathPatterns("/api/members/oauth/login/**") + .excludePathPatterns("/api/members/token/refresh") + .excludePathPatterns("/api/ai/stt/callback") + .excludePathPatterns("/api/ai/llm/callback"); } @Override diff --git a/src/main/java/notai/common/config/SwaggerConfig.java b/src/main/java/notai/common/config/SwaggerConfig.java index bcf6279..40959e5 100644 --- a/src/main/java/notai/common/config/SwaggerConfig.java +++ b/src/main/java/notai/common/config/SwaggerConfig.java @@ -38,6 +38,6 @@ public OpenAPI openAPI() { } private Info apiInfo() { - return new Info().title("notai API").description("notai API 문서입니다.").version("0.0.2"); + return new Info().title("notai API").description("notai API 문서입니다.").version("0.0.3"); } } diff --git a/src/main/java/notai/common/domain/vo/FilePath.java b/src/main/java/notai/common/domain/vo/FilePath.java index 78c7abd..eca6fc5 100644 --- a/src/main/java/notai/common/domain/vo/FilePath.java +++ b/src/main/java/notai/common/domain/vo/FilePath.java @@ -14,7 +14,7 @@ @NoArgsConstructor(access = PROTECTED) public class FilePath { - @Column(length = 50) + @Column(length = 255) private String filePath; private FilePath(String filePath) { @@ -22,11 +22,6 @@ private FilePath(String filePath) { } public static FilePath from(String filePath) { - // 추후 확장자 추가 - if (!filePath.matches( - "[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣()\\[\\]+\\-&/_\\s]+(/[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣()\\[\\]+\\-&/_\\s]+)*/?[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣()\\[\\]+\\-&/_\\s]+\\.mp3")) { - throw new BadRequestException(INVALID_FILE_TYPE); - } return new FilePath(filePath); } } diff --git a/src/main/java/notai/common/exception/type/BadRequestException.java b/src/main/java/notai/common/exception/type/BadRequestException.java index 55e0cbc..fc07ea0 100644 --- a/src/main/java/notai/common/exception/type/BadRequestException.java +++ b/src/main/java/notai/common/exception/type/BadRequestException.java @@ -9,4 +9,7 @@ public class BadRequestException extends ApplicationException { public BadRequestException(ErrorMessages message) { super(message, 400); } + public BadRequestException(String message) { + super(message, 400); + } } diff --git a/src/main/java/notai/document/presentation/DocumentController.java b/src/main/java/notai/document/presentation/DocumentController.java index 12423c5..36b341d 100644 --- a/src/main/java/notai/document/presentation/DocumentController.java +++ b/src/main/java/notai/document/presentation/DocumentController.java @@ -1,5 +1,7 @@ package notai.document.presentation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; import lombok.RequiredArgsConstructor; import notai.auth.Auth; import notai.document.application.DocumentQueryService; @@ -12,6 +14,7 @@ import notai.document.presentation.response.DocumentFindResponse; import notai.document.presentation.response.DocumentSaveResponse; import notai.document.presentation.response.DocumentUpdateResponse; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -30,10 +33,11 @@ public class DocumentController { private static final Long ROOT_FOLDER_ID = -1L; private static final String FOLDER_URL_FORMAT = "/api/folders/%s/documents/%s"; - @PostMapping + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity saveDocument( @Auth Long memberId, @PathVariable Long folderId, + @Parameter(content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)) @RequestPart MultipartFile pdfFile, @RequestPart DocumentSaveRequest documentSaveRequest ) { diff --git a/src/main/java/notai/llm/application/LlmTaskService.java b/src/main/java/notai/llm/application/LlmTaskService.java index b6f41f3..d5f56fb 100644 --- a/src/main/java/notai/llm/application/LlmTaskService.java +++ b/src/main/java/notai/llm/application/LlmTaskService.java @@ -73,6 +73,8 @@ private void submitPageTask(Integer pageNumber, Map> a if (foundSummary.isEmpty() && foundProblem.isEmpty()) { Summary summary = new Summary(foundDocument, pageNumber); Problem problem = new Problem(foundDocument, pageNumber); + summaryRepository.save(summary); + problemRepository.save(problem); LlmTask taskRecord = new LlmTask(taskId, summary, problem); llmTaskRepository.save(taskRecord); @@ -80,6 +82,7 @@ private void submitPageTask(Integer pageNumber, Map> a if (foundSummary.isPresent() && foundProblem.isPresent()) { LlmTask foundTaskRecord = llmTaskRepository.getBySummaryAndProblem(foundSummary.get(), foundProblem.get()); llmTaskRepository.delete(foundTaskRecord); + llmTaskRepository.flush(); LlmTask taskRecord = new LlmTask(taskId, foundSummary.get(), foundProblem.get()); llmTaskRepository.save(taskRecord); diff --git a/src/main/java/notai/llm/domain/LlmTask.java b/src/main/java/notai/llm/domain/LlmTask.java index 24233d8..9ba0dec 100644 --- a/src/main/java/notai/llm/domain/LlmTask.java +++ b/src/main/java/notai/llm/domain/LlmTask.java @@ -25,12 +25,12 @@ public class LlmTask extends RootEntity { private UUID id; @NotNull - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "summary_id") private Summary summary; @NotNull - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "problem_id") private Problem problem; diff --git a/src/main/java/notai/llm/presentation/request/LlmTaskSubmitRequest.java b/src/main/java/notai/llm/presentation/request/LlmTaskSubmitRequest.java index 7c03443..e46e10d 100644 --- a/src/main/java/notai/llm/presentation/request/LlmTaskSubmitRequest.java +++ b/src/main/java/notai/llm/presentation/request/LlmTaskSubmitRequest.java @@ -1,7 +1,7 @@ package notai.llm.presentation.request; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import notai.llm.application.command.LlmTaskSubmitCommand; import java.util.List; @@ -10,7 +10,7 @@ public record LlmTaskSubmitRequest( @NotNull(message = "문서 ID는 필수 입력 값입니다.") Long documentId, - List<@Positive(message = "페이지 번호는 양수여야 합니다.") Integer> pages + List<@PositiveOrZero(message = "페이지 번호는 음수일 수 없습니다.") Integer> pages ) { public LlmTaskSubmitCommand toCommand() { return new LlmTaskSubmitCommand(documentId, pages); diff --git a/src/main/java/notai/ocr/domain/OCR.java b/src/main/java/notai/ocr/domain/OCR.java index 061bcd7..00f4273 100644 --- a/src/main/java/notai/ocr/domain/OCR.java +++ b/src/main/java/notai/ocr/domain/OCR.java @@ -28,7 +28,7 @@ public class OCR extends RootEntity { private Integer pageNumber; @NotNull - @Column(name = "content", columnDefinition = "TEXT") + @Column(name = "content", columnDefinition = "LONGTEXT") private String content; public OCR(Document document, Integer pageNumber, String content) { diff --git a/src/main/java/notai/pageRecording/domain/PageRecording.java b/src/main/java/notai/pageRecording/domain/PageRecording.java index 288ae1f..a0cc39b 100644 --- a/src/main/java/notai/pageRecording/domain/PageRecording.java +++ b/src/main/java/notai/pageRecording/domain/PageRecording.java @@ -31,7 +31,6 @@ public class PageRecording { @NotNull private Double startTime; - @NotNull private Double endTime; public PageRecording(Recording recording, Integer pageNumber, Double startTime, Double endTime) { diff --git a/src/main/java/notai/recording/application/RecordingService.java b/src/main/java/notai/recording/application/RecordingService.java index d09e819..4140908 100644 --- a/src/main/java/notai/recording/application/RecordingService.java +++ b/src/main/java/notai/recording/application/RecordingService.java @@ -60,7 +60,7 @@ public RecordingSaveResult saveRecording(RecordingSaveCommand command) { return RecordingSaveResult.of(savedRecording.getId(), foundDocument.getId(), savedRecording.getCreatedAt()); } catch (IllegalArgumentException e) { - throw new BadRequestException(INVALID_AUDIO_ENCODING); + throw new BadRequestException(INVALID_AUDIO_ENCODING + " : " + e.getMessage()); } catch (IOException e) { throw new InternalServerErrorException(FILE_SAVE_ERROR); // TODO: 재시도 로직 추가? } diff --git a/src/main/java/notai/stt/application/SttService.java b/src/main/java/notai/stt/application/SttService.java index f48545d..105f617 100644 --- a/src/main/java/notai/stt/application/SttService.java +++ b/src/main/java/notai/stt/application/SttService.java @@ -33,6 +33,7 @@ public void updateSttResult(UpdateSttResultCommand command) { SttTask sttTask = sttTaskRepository.getById(command.taskId()); Stt stt = sttTask.getStt(); Recording recording = stt.getRecording(); + List pageRecordings = pageRecordingRepository.findAllByRecordingIdOrderByStartTime(recording.getId()); SttPageMatchedDto matchedResult = stt.matchWordsWithPages(command.words(), pageRecordings); diff --git a/src/main/java/notai/stt/application/SttTaskService.java b/src/main/java/notai/stt/application/SttTaskService.java index af2cfe9..7fedcc6 100644 --- a/src/main/java/notai/stt/application/SttTaskService.java +++ b/src/main/java/notai/stt/application/SttTaskService.java @@ -15,12 +15,13 @@ import notai.stt.domain.SttRepository; import notai.sttTask.domain.SttTask; import notai.sttTask.domain.SttTaskRepository; +import org.springframework.core.io.ByteArrayResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; +import java.nio.file.Files; @Service @Transactional @@ -35,8 +36,17 @@ public void submitSttTask(SttRequestCommand command) { Recording recording = recordingRepository.getById(command.recordingId()); File audioFile = validateAudioFile(command.audioFilePath()); - try (FileInputStream fileInputStream = new FileInputStream(audioFile)) { - TaskResponse response = aiClient.submitSttTask(fileInputStream); + try { + byte[] audioBytes = Files.readAllBytes(audioFile.toPath()); + + ByteArrayResource resource = new ByteArrayResource(audioBytes) { + @Override + public String getFilename() { + return audioFile.getName(); + } + }; + + TaskResponse response = aiClient.submitSttTask(resource); createAndSaveSttTask(recording, response); } catch (IOException e) { throw new FileProcessException(FILE_READ_ERROR); diff --git a/src/main/java/notai/stt/domain/Stt.java b/src/main/java/notai/stt/domain/Stt.java index 46794d6..b09ed97 100644 --- a/src/main/java/notai/stt/domain/Stt.java +++ b/src/main/java/notai/stt/domain/Stt.java @@ -92,7 +92,7 @@ public SttPageMatchedDto matchWordsWithPages( for (PageRecording page : pageRecordings) { List pageWords = new ArrayList<>(); double pageStart = page.getStartTime(); - double pageEnd = page.getEndTime(); + Double pageEnd = page.getEndTime(); // 현재 페이지의 시간 범위에 속하는 단어들을 찾아 매칭 while (wordIndex < words.size()) { @@ -104,18 +104,17 @@ public SttPageMatchedDto matchWordsWithPages( continue; } - // 마지막 페이지가 아닐 경우, 페이지 종료 시간을 벗어난 단어가 나오면 다음 페이지로 - if (page != lastPage && word.start() - TIME_THRESHOLD >= pageEnd) { + // 마지막 페이지이거나 endTime이 null이면 시작 시간만 체크 + if ((page == lastPage || pageEnd == null) || word.start() - TIME_THRESHOLD < pageEnd) { + pageWords.add(new SttPageMatchedDto.PageMatchedWord( + word.word(), + (int) word.start(), + (int) word.end() + )); + wordIndex++; + } else { break; } - - // 현재 페이지에 단어 매칭하여 추가 - pageWords.add(new SttPageMatchedDto.PageMatchedWord( - word.word(), - (int) word.start(), - (int) word.end() - )); - wordIndex++; } // 매칭된 단어가 있는 경우만 맵에 추가 diff --git a/src/main/java/notai/stt/presentation/request/SttCallbackRequest.java b/src/main/java/notai/stt/presentation/request/SttCallbackRequest.java index 1e58bc7..0633ee3 100644 --- a/src/main/java/notai/stt/presentation/request/SttCallbackRequest.java +++ b/src/main/java/notai/stt/presentation/request/SttCallbackRequest.java @@ -1,38 +1,29 @@ package notai.stt.presentation.request; import notai.stt.application.command.UpdateSttResultCommand; -import notai.stt.application.command.UpdateSttResultCommand.Word; import java.util.List; import java.util.UUID; public record SttCallbackRequest( String taskId, - String state, - SttResult result + String text, + List words ) { public UpdateSttResultCommand toCommand() { - List words = result.words().stream() - .map(word -> new Word( + List commandWords = words().stream() + .map(word -> new UpdateSttResultCommand.Word( word.word(), word.start(), - word.end() - )) + word.end())) .toList(); - return new UpdateSttResultCommand(UUID.fromString(taskId), words); + return new UpdateSttResultCommand(UUID.fromString(taskId), commandWords); } - - public record SttResult( - double audioLength, - String language, - double languageProbability, - String text, - List words + + public record Word( + String word, + double start, + double end ) { - public record Word( - double start, - double end, - String word - ) {} } } diff --git a/src/test/java/notai/client/ai/AiClientIntegrationTest.java b/src/test/java/notai/client/ai/AiClientIntegrationTest.java index 782f4b8..c71dbe1 100644 --- a/src/test/java/notai/client/ai/AiClientIntegrationTest.java +++ b/src/test/java/notai/client/ai/AiClientIntegrationTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ByteArrayResource; + import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -36,10 +38,10 @@ class AiClientIntegrationTest { void STT_태스크_제출_통합_테스트() { // Given byte[] audioBytes = "test audio content".getBytes(); - InputStream audioInputStream = new ByteArrayInputStream(audioBytes); + ByteArrayResource byteArrayResource = new ByteArrayResource(audioBytes); // When - TaskResponse response = aiClient.submitSttTask(audioInputStream); + TaskResponse response = aiClient.submitSttTask(byteArrayResource); // Then assertNotNull(response); diff --git a/src/test/java/notai/client/ai/AiClientTest.java b/src/test/java/notai/client/ai/AiClientTest.java index b447e0a..c1004f0 100644 --- a/src/test/java/notai/client/ai/AiClientTest.java +++ b/src/test/java/notai/client/ai/AiClientTest.java @@ -8,6 +8,7 @@ import org.mockito.Mock; import static org.mockito.Mockito.*; import org.mockito.MockitoAnnotations; +import org.springframework.core.io.ByteArrayResource; import java.io.InputStream; import java.util.UUID; @@ -41,16 +42,16 @@ void setUp() { @Test void STT_테스크_전달_테스트() { // Given - InputStream inputStream = mock(InputStream.class); + ByteArrayResource byteArrayResource = mock(ByteArrayResource.class); UUID expectedTaskId = UUID.randomUUID(); TaskResponse expectedResponse = new TaskResponse(expectedTaskId, "stt"); - when(aiClient.submitSttTask(inputStream)).thenReturn(expectedResponse); + when(aiClient.submitSttTask(byteArrayResource)).thenReturn(expectedResponse); // When - TaskResponse response = aiClient.submitSttTask(inputStream); + TaskResponse response = aiClient.submitSttTask(byteArrayResource); // Then assertEquals(expectedResponse, response); - verify(aiClient, times(1)).submitSttTask(inputStream); + verify(aiClient, times(1)).submitSttTask(byteArrayResource); } } diff --git a/src/test/java/notai/llm/application/LlmTaskServiceTest.java b/src/test/java/notai/llm/application/LlmTaskServiceTest.java index 1f2f1b7..3f796ed 100644 --- a/src/test/java/notai/llm/application/LlmTaskServiceTest.java +++ b/src/test/java/notai/llm/application/LlmTaskServiceTest.java @@ -84,7 +84,7 @@ class LlmTaskServiceTest { Member member = new Member(new OauthId("12345", OauthProvider.KAKAO), "test@example.com", "TestUser"); Folder folder = new Folder(member, "TestFolder"); - Document document = new Document(folder, "TestDocument", "http://example.com/test.pdf", 43); + Document document = new Document(folder, member, "TestDocument", "http://example.com/test.pdf", 43); List annotations = List.of(new Annotation(document, 1, 10, 20, 100, 50, "Annotation 1"), new Annotation(document, 1, 30, 40, 80, 60, "Annotation 2"),