From 835b6af1ad4b5e2662758fa432896ff42ea543cd Mon Sep 17 00:00:00 2001 From: Hyun-Seo Jeong <90139789+hynseoj@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:54:24 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EB=AC=B8=EC=84=9C=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=ED=9A=8C=EC=9B=90=EC=9D=98=20=EC=86=8C=EC=9C=A0=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80,=20OCR,=20STT=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95=20(#76)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: OCR, STT 기능 개선 및 버그 수정 (#32) * Feat: POST 저장 기능, OCR 구현, 폴더 수정, 페이지별 조회 기능 구현 및 배포 (#66) * Fix: merge 하면서 잘못된 부분 수정 * Feat: Post 저장 기능 구현 (#17) * Feat: Post 저장 기능 구현 * Feat: Post Id로 Post 1개 찾기 기능 구현 * Style: 코드리뷰 반영 - 변수선언과 변수 할당을 한줄에 하도록 변경 * Refactor: 코드리뷰 반영 : Post 조회 수행 위치 변경 - JPA 사용하는 단순 조회 쿼리를 PostService 와 PostRepository 에서 관리하도록 변경 (기존에는 PostQueryService 와 PostQueryRepository에서 관리했음) * Fix: 코드리뷰 반영 : Post 조회시 ResponseBody 삭제 -uri 반환중이라 굳이 불필요해서 삭제함 * Fix: 코드리뷰 반영 : Post 저장 기능에서 Request와 Command 를 분리 * FIx: MemberAPI에 리소스가 생성되는 Response Status 200 -> 201로 변경 (#22) * Release: 0.0.1 배포 (#23) * Fix: 인증토큰 관련 오류 해결 및 오류메시지 추가 * Fix: 서버환경에 맞게 tesseract경로 수정 * Build: 도커파일 작성 * Fix: audio basePath 도커 볼륨에 연결 * Feat: OCR 로직 구현 (#21) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Style: formatting통일 (#30) * Chore : Annotation 폴더 구조 설정 * Feat : Annotation Domain Entity 설정 * Feat: Annotaion crud 구현 1. controller : api 명세대로 구현 2. service : R-CUD를 분리하는 service 구현 3. presentation : dto 구현 * Feat: 요약 및 문제 생성 API 구현 (#32) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Style: 코드 포맷팅 통일 (#36) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Revert "Style: 코드 포맷팅 통일 (#36)" (#38) This reverts commit ad9062e737992caa2bbb3a07dda0072ec086c7ef. * Feat: 폴더 관련 기능 구현 (#39) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- --------- * Style: 코드 포맷팅 통일 (#40) * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Remove: .idea 폴더 삭제 * Style: 코드 포맷팅 통일 (#41) * refactor: 폴더의 삭제 방법 재귀형태로 찾아 삭제하도록 개선 개선 * chore: API 매칭 URL 수정 작업 진행 API 매칭 URL 수정 작업 진행 * remove: 불필요한 문서 상태 삭제 불필요한 문서 상태 삭제 * feat: PDF 저장하는 기능 구현 * remove: 불필요한 라이브러리 삭제 불필요한 라이브러리 삭제 * feat: Document 저장하는 기능 구현 * test: PDF Service에서 PDF 저장 로직 테스트코드 작성 PDF 저장 로직 테스트코드 작성 * feat: Document 저장 하는 기능 구현 Document 저장하는 기능 구현 * chore: PDF 처리 및 OCR 라이브러리 라이브러리 import * feat: Document 생성 API 구현 Document 생성 API 구현 * feat: 자료 이름 수정 기능 구현 자료 이름 수정 기능 구현 * feat: 자료 조회 기능 구현 자료 조회 기능 구현 * feat: 자료 삭제 기능 구현 자료 삭제 기능 구현 * feat: 폴더 삭제시 자료도 함께 삭제되도록 기능 구현 삭제 기능 구현 * chore: PDF Setup pdf 세팅 * Feat: 녹음 파일 업로드 기능 구현 (#8) * Feat: 녹음 파일 업로드 기능 구현 - Recording 엔티티, 레포지토리, 컨트롤러 코드 작성 - 오디오 디코딩, 파일 저장 코드 작성 * Chore: Weekly5 로 rebase * Refactor: file base path @value 를 사용하도록 변경 --------- * feat: 루트(메인)에 생성되는 Document 설정 루트 자료 추가 기능 구현 * Feat: 녹음-페이지 저장 기능 구현 (#10) * Refactor: 메서드, 파라미터 이름 변경 * Feat: 녹음-페이지 저장 기능 구현 - 페이지 넘김 이벤트에 따라 녹음-페이지 테이블에 타임스탬프 저장 * Refactor: 예외 메시지 수정 * Feature/annotation 구현 완료 (#12) * Feat : Annotation CRUD 구현 1. Controller : API 명세서 구현 2. Service : R-CUD를 QueryService, Serivce를 이용하여 구현 3. presentation : DTO 구현 * Refactor: @Positive를 이용한 양수 검증 * Refactor: @NoArgsConstructor의 접근 수준을 PROTECTED로 변경 * Refactor: CreateAnnotationRequest에서 좌표 및 크기 검증 추가 * Refactor: DTO를 record로 통일 * Refactor: getById로 변경 * Refactor: record로 인한 형식 변경 * Refactor: 정적 팩토리 from으로 변경 * Refactor: ManyToOne의 fetch 형식 LAZY로 설정 * Refactor : 정적팩토리 from으로 인한 코드 변경 * Refactor : createAnnotation에서 누락된 savedAnnotatio 추가 * Refactor : pageNumbers 누락 -> 해당 내용을 반영한 Read 구현 * Refactor: CRUD test code 작성 * Refactor : getById로 변경 * Revert "Feature/annotation 구현 완료 (#12)" (#14) This reverts commit 0dcf1d29f5897fd5db772dbf8035d877f84b19e9. * Feat: Annotation API 구현 (#15) * Feat : Annotation CRUD 구현 1. Controller : API 명세서 구현 2. Service : R-CUD를 QueryService, Serivce를 이용하여 구현 3. presentation : DTO 구현 * Refactor: @Positive를 이용한 양수 검증 * Refactor: @NoArgsConstructor의 접근 수준을 PROTECTED로 변경 * Refactor: CreateAnnotationRequest에서 좌표 및 크기 검증 추가 * Refactor: DTO를 record로 통일 * Refactor: getById로 변경 * Refactor: record로 인한 형식 변경 * Refactor: 정적 팩토리 from으로 변경 * Refactor: ManyToOne의 fetch 형식 LAZY로 설정 * Refactor : 정적팩토리 from으로 인한 코드 변경 * Refactor : createAnnotation에서 누락된 savedAnnotatio 추가 * Refactor : pageNumbers 누락 -> 해당 내용을 반영한 Read 구현 * Refactor: CRUD test code 작성 * Refactor : getById로 변경 --------- * Feat: 문서 도메인 관련 기능 구현 (#13) * refactor: 폴더의 삭제 방법 재귀형태로 찾아 삭제하도록 개선 개선 * chore: API 매칭 URL 수정 작업 진행 API 매칭 URL 수정 작업 진행 * remove: 불필요한 문서 상태 삭제 불필요한 문서 상태 삭제 * feat: PDF 저장하는 기능 구현 * remove: 불필요한 라이브러리 삭제 불필요한 라이브러리 삭제 * feat: Document 저장하는 기능 구현 * test: PDF Service에서 PDF 저장 로직 테스트코드 작성 PDF 저장 로직 테스트코드 작성 * feat: Document 저장 하는 기능 구현 Document 저장하는 기능 구현 * chore: PDF 처리 및 OCR 라이브러리 라이브러리 import * feat: Document 생성 API 구현 Document 생성 API 구현 * feat: 자료 이름 수정 기능 구현 자료 이름 수정 기능 구현 * feat: 자료 조회 기능 구현 자료 조회 기능 구현 * feat: 자료 삭제 기능 구현 자료 삭제 기능 구현 * feat: 폴더 삭제시 자료도 함께 삭제되도록 기능 구현 삭제 기능 구현 * chore: PDF Setup pdf 세팅 * feat: 루트(메인)에 생성되는 Document 설정 루트 자료 추가 기능 구현 --------- * chore: OCR 도메인 생성 도메인 생성 * feat: ID를 사용하여 엔티티 꺼내오는 작업 수행 엔티티 꺼내오는 작업 수행 * rename: PDF 관련 파일 이동 파일 이동 * feat: 문서 저장시 OCR 작업수행하는 기능 OCR 자동 작업 기능 구현 * refactor: PDF 저장 로직 개선 저장 로직 개선 * test: OCR 기능 작동 여부 테스트코드 작성 테스트 코드 작성 * refactor: 상수값 static 으로 따로 관리 상수값 관리하도록 피드백 반영 * feat: AOP 를 활용하여 Get요청시 ok를 바로 보내줄 수 있도록 기능 구현 AOP 활용 래퍼클래스 개발 * remove: 상의 후 도입할지 정해야하기때문에 우선 삭제 * Feat: AI Client기능 구현 (#18) * Feat: AI Client기능 구현 * Test: AI Client 통합테스트 작성 * build: 빌드시 테스트에서 제외해야 할 테스트 태그 추가 * Test: AI Client 단위테스트 작성 * Feat: LLMService에 AI 서버 요청 기능 추가 * Test: LLMService AI_기능_요청 메서드에 클라이언트 단위테스트 코드 추가 * Chore: 불필요한 주석 제거 * Chore: 더미 AI서버 주소 추가 * Chore: 불필요한 impl클래스 제거 및 테스트코드 수정 * Feat: PDF 처리 기능 및 OCR 기능 (#19) * chore: OCR 도메인 생성 도메인 생성 * feat: ID를 사용하여 엔티티 꺼내오는 작업 수행 엔티티 꺼내오는 작업 수행 * rename: PDF 관련 파일 이동 파일 이동 * feat: 문서 저장시 OCR 작업수행하는 기능 OCR 자동 작업 기능 구현 * refactor: PDF 저장 로직 개선 저장 로직 개선 * test: OCR 기능 작동 여부 테스트코드 작성 테스트 코드 작성 * refactor: 상수값 static 으로 따로 관리 상수값 관리하도록 피드백 반영 * feat: AOP 를 활용하여 Get요청시 ok를 바로 보내줄 수 있도록 기능 구현 AOP 활용 래퍼클래스 개발 * remove: 상의 후 도입할지 정해야하기때문에 우선 삭제 * Refactor: 에러 메시지 상수화 (#20) * Refactor: 에러 메시지 상수화 * Refactor: 코드 통합 후 에러 메시지 상수화 * test: 테스트코드 재작성 테스트코드 재작성 * refactor: Controller -> RestController 로 변경 변경작업 수행 * feat: PDF의 총 페이지 수 가져오는 기능 구현 총 페이지 수 가져오는 기능 구현 * feat: Document 도메인에서 페이지 정보 유효성 검사 페이지 정보 유효성 검사 * refactor: pdf주소만 받아 생성하던 Document 생성 로직 수정 수정 * remove: consumes 를 다 허용해줌 다 허용해주도럭 제약 조건 삭제 * feat: 폴더 이름 변경 기능 추가 폴더 이름 변경 기능 추가 * feat: OCR 결과 조회 기능 구현 OCR 결과 조회 기능 구현 * refactor: 폴더 삭제시 연결된 문서 삭제 기능 수정 삭제 기능을 수정 * chore: 에러 메시지 INVALID_DOCUMENT_PAGE 추가 * refactor: 하나의 페이지 당 최대 하나의 OCR 정보만 조회되도록 수정 기존 List -> Optional 로 리턴 타입 수정 --------- * Refactor: 녹음 API 상태 코드 변경 (#25) * Style: formatting통일 (#30) * Feat: 요약 및 문제 생성 API 구현 (#32) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Style: 코드 포맷팅 통일 (#36) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Revert "Style: 코드 포맷팅 통일 (#36)" (#38) This reverts commit ad9062e737992caa2bbb3a07dda0072ec086c7ef. * Feat: 폴더 관련 기능 구현 (#39) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- --------- * Style: 코드 포맷팅 통일 (#40) * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Remove: .idea 폴더 삭제 * Style: 코드 포맷팅 통일 (#41) * Feat: 녹음 파일 업로드 기능 구현 (#8) * Feat: 녹음 파일 업로드 기능 구현 - Recording 엔티티, 레포지토리, 컨트롤러 코드 작성 - 오디오 디코딩, 파일 저장 코드 작성 * Chore: Weekly5 로 rebase * Refactor: file base path @value 를 사용하도록 변경 --------- * Refactor: 메서드, 파라미터 이름 변경 * Feat: 녹음-페이지 저장 기능 구현 - 페이지 넘김 이벤트에 따라 녹음-페이지 테이블에 타임스탬프 저장 * Refactor: 예외 메시지 수정 * refactor: 녹음 파일 업로드, 페이지 넘김 이벤트 처리 api 상태코드 변경 - 200 -> 201 --------- * Feat: 페이지별 api 구현 및 리팩토링 (#24) * Refactor: 예외 로깅 추가 - LLMQueryService 예외 로깅 * Refactor: LLM 테이블 이름 변경 * Refactor: 명세 변경으로 인한 AI 서버 결과 Request DTO 변경 * Refactor: 요약, 문제와 LLM 연관관계 변경 - 다대일에서 일대일로 변경 - LLM 이 연관관계 주인 * Refactor: LLMService 메서드 네이밍 변경 - task 를 페이지별로 생성하므로 의미를 더 잘 나타낼 수 있도록 복수형으로 변경 * Refactor: Problem, Summary QueryRepository 인터페이스 사용 * Refactor: LLM 테이블 이름 변경 * Refactor: 명세 변경으로 인한 AI 서버 결과 Request DTO 변경 * Refactor: 요약, 문제와 LLM 연관관계 변경 - 다대일에서 일대일로 변경 - LLM 이 연관관계 주인 * Refactor: LLMService 메서드 네이밍 변경 - task 를 페이지별로 생성하므로 의미를 더 잘 나타낼 수 있도록 복수형으로 변경 * Rebase: Weekly8 로 리베이스 * Refactor: Problem, Summary QueryRepository 인터페이스화 * Feat: 페이지별 AI 기능 상태 조회 api 구현 * Refactor: Problem, Summary 도메인 unique 제약 조건 추가 * Feat: 페이지별 AI 기능 결과 확인 API 구현 * Refactor: 상태, 결과 조회시 요청 기록이 없는 경우 처리 수정 - 예외를 발생시키지 않고, 요청없음 enum 값이나 empty list 등을 반환하도록 수정 * feat: AI 기능 요청시 기존 내역이 존재하는 경우 처리 - Summary 와 Problem 레코드 재활용 - LLM task 삭제 후 재생성(taskId 변경으로 인한) * Style: LLM으로 시작하는 클래스명 Llm으로 통일 (#26) --------- Co-authored-by: Hyun-Seo Jeong <90139789+hynseoj@users.noreply.github.com> Co-authored-by: hynseoj Co-authored-by: Cindy <93774025+Shsin9797@users.noreply.github.com> Co-authored-by: 윤정훈 <76200940+yunjunghun0116@users.noreply.github.com> Co-authored-by: mingjuu Co-authored-by: Minju Song <101880766+mingjuu@users.noreply.github.com> * Feat: AI서버에서 STT 결과 응답 왔을때 STT 결과 처리하는 기능 구현 (#27) * Feat: POST 저장 기능, OCR 구현, 폴더 수정, 페이지별 조회 기능 구현 및 배포 (#66) (#67) * Fix: merge 하면서 잘못된 부분 수정 * Feat: Post 저장 기능 구현 (#17) * Feat: Post 저장 기능 구현 * Feat: Post Id로 Post 1개 찾기 기능 구현 * Style: 코드리뷰 반영 - 변수선언과 변수 할당을 한줄에 하도록 변경 * Refactor: 코드리뷰 반영 : Post 조회 수행 위치 변경 - JPA 사용하는 단순 조회 쿼리를 PostService 와 PostRepository 에서 관리하도록 변경 (기존에는 PostQueryService 와 PostQueryRepository에서 관리했음) * Fix: 코드리뷰 반영 : Post 조회시 ResponseBody 삭제 -uri 반환중이라 굳이 불필요해서 삭제함 * Fix: 코드리뷰 반영 : Post 저장 기능에서 Request와 Command 를 분리 * FIx: MemberAPI에 리소스가 생성되는 Response Status 200 -> 201로 변경 (#22) * Release: 0.0.1 배포 (#23) * Fix: 인증토큰 관련 오류 해결 및 오류메시지 추가 * Fix: 서버환경에 맞게 tesseract경로 수정 * Build: 도커파일 작성 * Fix: audio basePath 도커 볼륨에 연결 * Feat: OCR 로직 구현 (#21) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Style: formatting통일 (#30) * Chore : Annotation 폴더 구조 설정 * Feat : Annotation Domain Entity 설정 * Feat: Annotaion crud 구현 1. controller : api 명세대로 구현 2. service : R-CUD를 분리하는 service 구현 3. presentation : dto 구현 * Feat: 요약 및 문제 생성 API 구현 (#32) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Style: 코드 포맷팅 통일 (#36) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Revert "Style: 코드 포맷팅 통일 (#36)" (#38) This reverts commit ad9062e737992caa2bbb3a07dda0072ec086c7ef. * Feat: 폴더 관련 기능 구현 (#39) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- --------- * Style: 코드 포맷팅 통일 (#40) * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Remove: .idea 폴더 삭제 * Style: 코드 포맷팅 통일 (#41) * refactor: 폴더의 삭제 방법 재귀형태로 찾아 삭제하도록 개선 개선 * chore: API 매칭 URL 수정 작업 진행 API 매칭 URL 수정 작업 진행 * remove: 불필요한 문서 상태 삭제 불필요한 문서 상태 삭제 * feat: PDF 저장하는 기능 구현 * remove: 불필요한 라이브러리 삭제 불필요한 라이브러리 삭제 * feat: Document 저장하는 기능 구현 * test: PDF Service에서 PDF 저장 로직 테스트코드 작성 PDF 저장 로직 테스트코드 작성 * feat: Document 저장 하는 기능 구현 Document 저장하는 기능 구현 * chore: PDF 처리 및 OCR 라이브러리 라이브러리 import * feat: Document 생성 API 구현 Document 생성 API 구현 * feat: 자료 이름 수정 기능 구현 자료 이름 수정 기능 구현 * feat: 자료 조회 기능 구현 자료 조회 기능 구현 * feat: 자료 삭제 기능 구현 자료 삭제 기능 구현 * feat: 폴더 삭제시 자료도 함께 삭제되도록 기능 구현 삭제 기능 구현 * chore: PDF Setup pdf 세팅 * Feat: 녹음 파일 업로드 기능 구현 (#8) * Feat: 녹음 파일 업로드 기능 구현 - Recording 엔티티, 레포지토리, 컨트롤러 코드 작성 - 오디오 디코딩, 파일 저장 코드 작성 * Chore: Weekly5 로 rebase * Refactor: file base path @value 를 사용하도록 변경 --------- * feat: 루트(메인)에 생성되는 Document 설정 루트 자료 추가 기능 구현 * Feat: 녹음-페이지 저장 기능 구현 (#10) * Refactor: 메서드, 파라미터 이름 변경 * Feat: 녹음-페이지 저장 기능 구현 - 페이지 넘김 이벤트에 따라 녹음-페이지 테이블에 타임스탬프 저장 * Refactor: 예외 메시지 수정 * Feature/annotation 구현 완료 (#12) * Feat : Annotation CRUD 구현 1. Controller : API 명세서 구현 2. Service : R-CUD를 QueryService, Serivce를 이용하여 구현 3. presentation : DTO 구현 * Refactor: @Positive를 이용한 양수 검증 * Refactor: @NoArgsConstructor의 접근 수준을 PROTECTED로 변경 * Refactor: CreateAnnotationRequest에서 좌표 및 크기 검증 추가 * Refactor: DTO를 record로 통일 * Refactor: getById로 변경 * Refactor: record로 인한 형식 변경 * Refactor: 정적 팩토리 from으로 변경 * Refactor: ManyToOne의 fetch 형식 LAZY로 설정 * Refactor : 정적팩토리 from으로 인한 코드 변경 * Refactor : createAnnotation에서 누락된 savedAnnotatio 추가 * Refactor : pageNumbers 누락 -> 해당 내용을 반영한 Read 구현 * Refactor: CRUD test code 작성 * Refactor : getById로 변경 * Revert "Feature/annotation 구현 완료 (#12)" (#14) This reverts commit 0dcf1d29f5897fd5db772dbf8035d877f84b19e9. * Feat: Annotation API 구현 (#15) * Feat : Annotation CRUD 구현 1. Controller : API 명세서 구현 2. Service : R-CUD를 QueryService, Serivce를 이용하여 구현 3. presentation : DTO 구현 * Refactor: @Positive를 이용한 양수 검증 * Refactor: @NoArgsConstructor의 접근 수준을 PROTECTED로 변경 * Refactor: CreateAnnotationRequest에서 좌표 및 크기 검증 추가 * Refactor: DTO를 record로 통일 * Refactor: getById로 변경 * Refactor: record로 인한 형식 변경 * Refactor: 정적 팩토리 from으로 변경 * Refactor: ManyToOne의 fetch 형식 LAZY로 설정 * Refactor : 정적팩토리 from으로 인한 코드 변경 * Refactor : createAnnotation에서 누락된 savedAnnotatio 추가 * Refactor : pageNumbers 누락 -> 해당 내용을 반영한 Read 구현 * Refactor: CRUD test code 작성 * Refactor : getById로 변경 --------- * Feat: 문서 도메인 관련 기능 구현 (#13) * refactor: 폴더의 삭제 방법 재귀형태로 찾아 삭제하도록 개선 개선 * chore: API 매칭 URL 수정 작업 진행 API 매칭 URL 수정 작업 진행 * remove: 불필요한 문서 상태 삭제 불필요한 문서 상태 삭제 * feat: PDF 저장하는 기능 구현 * remove: 불필요한 라이브러리 삭제 불필요한 라이브러리 삭제 * feat: Document 저장하는 기능 구현 * test: PDF Service에서 PDF 저장 로직 테스트코드 작성 PDF 저장 로직 테스트코드 작성 * feat: Document 저장 하는 기능 구현 Document 저장하는 기능 구현 * chore: PDF 처리 및 OCR 라이브러리 라이브러리 import * feat: Document 생성 API 구현 Document 생성 API 구현 * feat: 자료 이름 수정 기능 구현 자료 이름 수정 기능 구현 * feat: 자료 조회 기능 구현 자료 조회 기능 구현 * feat: 자료 삭제 기능 구현 자료 삭제 기능 구현 * feat: 폴더 삭제시 자료도 함께 삭제되도록 기능 구현 삭제 기능 구현 * chore: PDF Setup pdf 세팅 * feat: 루트(메인)에 생성되는 Document 설정 루트 자료 추가 기능 구현 --------- * chore: OCR 도메인 생성 도메인 생성 * feat: ID를 사용하여 엔티티 꺼내오는 작업 수행 엔티티 꺼내오는 작업 수행 * rename: PDF 관련 파일 이동 파일 이동 * feat: 문서 저장시 OCR 작업수행하는 기능 OCR 자동 작업 기능 구현 * refactor: PDF 저장 로직 개선 저장 로직 개선 * test: OCR 기능 작동 여부 테스트코드 작성 테스트 코드 작성 * refactor: 상수값 static 으로 따로 관리 상수값 관리하도록 피드백 반영 * feat: AOP 를 활용하여 Get요청시 ok를 바로 보내줄 수 있도록 기능 구현 AOP 활용 래퍼클래스 개발 * remove: 상의 후 도입할지 정해야하기때문에 우선 삭제 * Feat: AI Client기능 구현 (#18) * Feat: AI Client기능 구현 * Test: AI Client 통합테스트 작성 * build: 빌드시 테스트에서 제외해야 할 테스트 태그 추가 * Test: AI Client 단위테스트 작성 * Feat: LLMService에 AI 서버 요청 기능 추가 * Test: LLMService AI_기능_요청 메서드에 클라이언트 단위테스트 코드 추가 * Chore: 불필요한 주석 제거 * Chore: 더미 AI서버 주소 추가 * Chore: 불필요한 impl클래스 제거 및 테스트코드 수정 * Feat: PDF 처리 기능 및 OCR 기능 (#19) * chore: OCR 도메인 생성 도메인 생성 * feat: ID를 사용하여 엔티티 꺼내오는 작업 수행 엔티티 꺼내오는 작업 수행 * rename: PDF 관련 파일 이동 파일 이동 * feat: 문서 저장시 OCR 작업수행하는 기능 OCR 자동 작업 기능 구현 * refactor: PDF 저장 로직 개선 저장 로직 개선 * test: OCR 기능 작동 여부 테스트코드 작성 테스트 코드 작성 * refactor: 상수값 static 으로 따로 관리 상수값 관리하도록 피드백 반영 * feat: AOP 를 활용하여 Get요청시 ok를 바로 보내줄 수 있도록 기능 구현 AOP 활용 래퍼클래스 개발 * remove: 상의 후 도입할지 정해야하기때문에 우선 삭제 * Refactor: 에러 메시지 상수화 (#20) * Refactor: 에러 메시지 상수화 * Refactor: 코드 통합 후 에러 메시지 상수화 * test: 테스트코드 재작성 테스트코드 재작성 * refactor: Controller -> RestController 로 변경 변경작업 수행 * feat: PDF의 총 페이지 수 가져오는 기능 구현 총 페이지 수 가져오는 기능 구현 * feat: Document 도메인에서 페이지 정보 유효성 검사 페이지 정보 유효성 검사 * refactor: pdf주소만 받아 생성하던 Document 생성 로직 수정 수정 * remove: consumes 를 다 허용해줌 다 허용해주도럭 제약 조건 삭제 * feat: 폴더 이름 변경 기능 추가 폴더 이름 변경 기능 추가 * feat: OCR 결과 조회 기능 구현 OCR 결과 조회 기능 구현 * refactor: 폴더 삭제시 연결된 문서 삭제 기능 수정 삭제 기능을 수정 * chore: 에러 메시지 INVALID_DOCUMENT_PAGE 추가 * refactor: 하나의 페이지 당 최대 하나의 OCR 정보만 조회되도록 수정 기존 List -> Optional 로 리턴 타입 수정 --------- * Refactor: 녹음 API 상태 코드 변경 (#25) * Style: formatting통일 (#30) * Feat: 요약 및 문제 생성 API 구현 (#32) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Style: 코드 포맷팅 통일 (#36) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Revert "Style: 코드 포맷팅 통일 (#36)" (#38) This reverts commit ad9062e737992caa2bbb3a07dda0072ec086c7ef. * Feat: 폴더 관련 기능 구현 (#39) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- --------- * Style: 코드 포맷팅 통일 (#40) * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Remove: .idea 폴더 삭제 * Style: 코드 포맷팅 통일 (#41) * Feat: 녹음 파일 업로드 기능 구현 (#8) * Feat: 녹음 파일 업로드 기능 구현 - Recording 엔티티, 레포지토리, 컨트롤러 코드 작성 - 오디오 디코딩, 파일 저장 코드 작성 * Chore: Weekly5 로 rebase * Refactor: file base path @value 를 사용하도록 변경 --------- * Refactor: 메서드, 파라미터 이름 변경 * Feat: 녹음-페이지 저장 기능 구현 - 페이지 넘김 이벤트에 따라 녹음-페이지 테이블에 타임스탬프 저장 * Refactor: 예외 메시지 수정 * refactor: 녹음 파일 업로드, 페이지 넘김 이벤트 처리 api 상태코드 변경 - 200 -> 201 --------- * Feat: 페이지별 api 구현 및 리팩토링 (#24) * Refactor: 예외 로깅 추가 - LLMQueryService 예외 로깅 * Refactor: LLM 테이블 이름 변경 * Refactor: 명세 변경으로 인한 AI 서버 결과 Request DTO 변경 * Refactor: 요약, 문제와 LLM 연관관계 변경 - 다대일에서 일대일로 변경 - LLM 이 연관관계 주인 * Refactor: LLMService 메서드 네이밍 변경 - task 를 페이지별로 생성하므로 의미를 더 잘 나타낼 수 있도록 복수형으로 변경 * Refactor: Problem, Summary QueryRepository 인터페이스 사용 * Refactor: LLM 테이블 이름 변경 * Refactor: 명세 변경으로 인한 AI 서버 결과 Request DTO 변경 * Refactor: 요약, 문제와 LLM 연관관계 변경 - 다대일에서 일대일로 변경 - LLM 이 연관관계 주인 * Refactor: LLMService 메서드 네이밍 변경 - task 를 페이지별로 생성하므로 의미를 더 잘 나타낼 수 있도록 복수형으로 변경 * Rebase: Weekly8 로 리베이스 * Refactor: Problem, Summary QueryRepository 인터페이스화 * Feat: 페이지별 AI 기능 상태 조회 api 구현 * Refactor: Problem, Summary 도메인 unique 제약 조건 추가 * Feat: 페이지별 AI 기능 결과 확인 API 구현 * Refactor: 상태, 결과 조회시 요청 기록이 없는 경우 처리 수정 - 예외를 발생시키지 않고, 요청없음 enum 값이나 empty list 등을 반환하도록 수정 * feat: AI 기능 요청시 기존 내역이 존재하는 경우 처리 - Summary 와 Problem 레코드 재활용 - LLM task 삭제 후 재생성(taskId 변경으로 인한) * Style: LLM으로 시작하는 클래스명 Llm으로 통일 (#26) --------- Co-authored-by: Hyun-Seo Jeong <90139789+hynseoj@users.noreply.github.com> Co-authored-by: hynseoj Co-authored-by: Cindy <93774025+Shsin9797@users.noreply.github.com> Co-authored-by: 윤정훈 <76200940+yunjunghun0116@users.noreply.github.com> Co-authored-by: mingjuu Co-authored-by: Minju Song <101880766+mingjuu@users.noreply.github.com> * Feat: AI서버에서 STT 결과 응답 왔을때 STT 결과 처리하는 기능 구현 --------- Co-authored-by: Hyun-Seo Jeong <90139789+hynseoj@users.noreply.github.com> Co-authored-by: hynseoj Co-authored-by: Cindy <93774025+Shsin9797@users.noreply.github.com> Co-authored-by: 윤정훈 <76200940+yunjunghun0116@users.noreply.github.com> Co-authored-by: mingjuu Co-authored-by: Minju Song <101880766+mingjuu@users.noreply.github.com> * Chore: 배포 자동화 및 Slack Webhook 설정 (#29) * Feat: POST 저장 기능, OCR 구현, 폴더 수정, 페이지별 조회 기능 구현 및 배포 (#66) (#67) * Fix: merge 하면서 잘못된 부분 수정 * Feat: Post 저장 기능 구현 (#17) * Feat: Post 저장 기능 구현 * Feat: Post Id로 Post 1개 찾기 기능 구현 * Style: 코드리뷰 반영 - 변수선언과 변수 할당을 한줄에 하도록 변경 * Refactor: 코드리뷰 반영 : Post 조회 수행 위치 변경 - JPA 사용하는 단순 조회 쿼리를 PostService 와 PostRepository 에서 관리하도록 변경 (기존에는 PostQueryService 와 PostQueryRepository에서 관리했음) * Fix: 코드리뷰 반영 : Post 조회시 ResponseBody 삭제 -uri 반환중이라 굳이 불필요해서 삭제함 * Fix: 코드리뷰 반영 : Post 저장 기능에서 Request와 Command 를 분리 * FIx: MemberAPI에 리소스가 생성되는 Response Status 200 -> 201로 변경 (#22) * Release: 0.0.1 배포 (#23) * Fix: 인증토큰 관련 오류 해결 및 오류메시지 추가 * Fix: 서버환경에 맞게 tesseract경로 수정 * Build: 도커파일 작성 * Fix: audio basePath 도커 볼륨에 연결 * Feat: OCR 로직 구현 (#21) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Style: formatting통일 (#30) * Chore : Annotation 폴더 구조 설정 * Feat : Annotation Domain Entity 설정 * Feat: Annotaion crud 구현 1. controller : api 명세대로 구현 2. service : R-CUD를 분리하는 service 구현 3. presentation : dto 구현 * Feat: 요약 및 문제 생성 API 구현 (#32) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Style: 코드 포맷팅 통일 (#36) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Revert "Style: 코드 포맷팅 통일 (#36)" (#38) This reverts commit ad9062e737992caa2bbb3a07dda0072ec086c7ef. * Feat: 폴더 관련 기능 구현 (#39) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- --------- * Style: 코드 포맷팅 통일 (#40) * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Remove: .idea 폴더 삭제 * Style: 코드 포맷팅 통일 (#41) * refactor: 폴더의 삭제 방법 재귀형태로 찾아 삭제하도록 개선 개선 * chore: API 매칭 URL 수정 작업 진행 API 매칭 URL 수정 작업 진행 * remove: 불필요한 문서 상태 삭제 불필요한 문서 상태 삭제 * feat: PDF 저장하는 기능 구현 * remove: 불필요한 라이브러리 삭제 불필요한 라이브러리 삭제 * feat: Document 저장하는 기능 구현 * test: PDF Service에서 PDF 저장 로직 테스트코드 작성 PDF 저장 로직 테스트코드 작성 * feat: Document 저장 하는 기능 구현 Document 저장하는 기능 구현 * chore: PDF 처리 및 OCR 라이브러리 라이브러리 import * feat: Document 생성 API 구현 Document 생성 API 구현 * feat: 자료 이름 수정 기능 구현 자료 이름 수정 기능 구현 * feat: 자료 조회 기능 구현 자료 조회 기능 구현 * feat: 자료 삭제 기능 구현 자료 삭제 기능 구현 * feat: 폴더 삭제시 자료도 함께 삭제되도록 기능 구현 삭제 기능 구현 * chore: PDF Setup pdf 세팅 * Feat: 녹음 파일 업로드 기능 구현 (#8) * Feat: 녹음 파일 업로드 기능 구현 - Recording 엔티티, 레포지토리, 컨트롤러 코드 작성 - 오디오 디코딩, 파일 저장 코드 작성 * Chore: Weekly5 로 rebase * Refactor: file base path @value 를 사용하도록 변경 --------- * feat: 루트(메인)에 생성되는 Document 설정 루트 자료 추가 기능 구현 * Feat: 녹음-페이지 저장 기능 구현 (#10) * Refactor: 메서드, 파라미터 이름 변경 * Feat: 녹음-페이지 저장 기능 구현 - 페이지 넘김 이벤트에 따라 녹음-페이지 테이블에 타임스탬프 저장 * Refactor: 예외 메시지 수정 * Feature/annotation 구현 완료 (#12) * Feat : Annotation CRUD 구현 1. Controller : API 명세서 구현 2. Service : R-CUD를 QueryService, Serivce를 이용하여 구현 3. presentation : DTO 구현 * Refactor: @Positive를 이용한 양수 검증 * Refactor: @NoArgsConstructor의 접근 수준을 PROTECTED로 변경 * Refactor: CreateAnnotationRequest에서 좌표 및 크기 검증 추가 * Refactor: DTO를 record로 통일 * Refactor: getById로 변경 * Refactor: record로 인한 형식 변경 * Refactor: 정적 팩토리 from으로 변경 * Refactor: ManyToOne의 fetch 형식 LAZY로 설정 * Refactor : 정적팩토리 from으로 인한 코드 변경 * Refactor : createAnnotation에서 누락된 savedAnnotatio 추가 * Refactor : pageNumbers 누락 -> 해당 내용을 반영한 Read 구현 * Refactor: CRUD test code 작성 * Refactor : getById로 변경 * Revert "Feature/annotation 구현 완료 (#12)" (#14) This reverts commit 0dcf1d29f5897fd5db772dbf8035d877f84b19e9. * Feat: Annotation API 구현 (#15) * Feat : Annotation CRUD 구현 1. Controller : API 명세서 구현 2. Service : R-CUD를 QueryService, Serivce를 이용하여 구현 3. presentation : DTO 구현 * Refactor: @Positive를 이용한 양수 검증 * Refactor: @NoArgsConstructor의 접근 수준을 PROTECTED로 변경 * Refactor: CreateAnnotationRequest에서 좌표 및 크기 검증 추가 * Refactor: DTO를 record로 통일 * Refactor: getById로 변경 * Refactor: record로 인한 형식 변경 * Refactor: 정적 팩토리 from으로 변경 * Refactor: ManyToOne의 fetch 형식 LAZY로 설정 * Refactor : 정적팩토리 from으로 인한 코드 변경 * Refactor : createAnnotation에서 누락된 savedAnnotatio 추가 * Refactor : pageNumbers 누락 -> 해당 내용을 반영한 Read 구현 * Refactor: CRUD test code 작성 * Refactor : getById로 변경 --------- * Feat: 문서 도메인 관련 기능 구현 (#13) * refactor: 폴더의 삭제 방법 재귀형태로 찾아 삭제하도록 개선 개선 * chore: API 매칭 URL 수정 작업 진행 API 매칭 URL 수정 작업 진행 * remove: 불필요한 문서 상태 삭제 불필요한 문서 상태 삭제 * feat: PDF 저장하는 기능 구현 * remove: 불필요한 라이브러리 삭제 불필요한 라이브러리 삭제 * feat: Document 저장하는 기능 구현 * test: PDF Service에서 PDF 저장 로직 테스트코드 작성 PDF 저장 로직 테스트코드 작성 * feat: Document 저장 하는 기능 구현 Document 저장하는 기능 구현 * chore: PDF 처리 및 OCR 라이브러리 라이브러리 import * feat: Document 생성 API 구현 Document 생성 API 구현 * feat: 자료 이름 수정 기능 구현 자료 이름 수정 기능 구현 * feat: 자료 조회 기능 구현 자료 조회 기능 구현 * feat: 자료 삭제 기능 구현 자료 삭제 기능 구현 * feat: 폴더 삭제시 자료도 함께 삭제되도록 기능 구현 삭제 기능 구현 * chore: PDF Setup pdf 세팅 * feat: 루트(메인)에 생성되는 Document 설정 루트 자료 추가 기능 구현 --------- * chore: OCR 도메인 생성 도메인 생성 * feat: ID를 사용하여 엔티티 꺼내오는 작업 수행 엔티티 꺼내오는 작업 수행 * rename: PDF 관련 파일 이동 파일 이동 * feat: 문서 저장시 OCR 작업수행하는 기능 OCR 자동 작업 기능 구현 * refactor: PDF 저장 로직 개선 저장 로직 개선 * test: OCR 기능 작동 여부 테스트코드 작성 테스트 코드 작성 * refactor: 상수값 static 으로 따로 관리 상수값 관리하도록 피드백 반영 * feat: AOP 를 활용하여 Get요청시 ok를 바로 보내줄 수 있도록 기능 구현 AOP 활용 래퍼클래스 개발 * remove: 상의 후 도입할지 정해야하기때문에 우선 삭제 * Feat: AI Client기능 구현 (#18) * Feat: AI Client기능 구현 * Test: AI Client 통합테스트 작성 * build: 빌드시 테스트에서 제외해야 할 테스트 태그 추가 * Test: AI Client 단위테스트 작성 * Feat: LLMService에 AI 서버 요청 기능 추가 * Test: LLMService AI_기능_요청 메서드에 클라이언트 단위테스트 코드 추가 * Chore: 불필요한 주석 제거 * Chore: 더미 AI서버 주소 추가 * Chore: 불필요한 impl클래스 제거 및 테스트코드 수정 * Feat: PDF 처리 기능 및 OCR 기능 (#19) * chore: OCR 도메인 생성 도메인 생성 * feat: ID를 사용하여 엔티티 꺼내오는 작업 수행 엔티티 꺼내오는 작업 수행 * rename: PDF 관련 파일 이동 파일 이동 * feat: 문서 저장시 OCR 작업수행하는 기능 OCR 자동 작업 기능 구현 * refactor: PDF 저장 로직 개선 저장 로직 개선 * test: OCR 기능 작동 여부 테스트코드 작성 테스트 코드 작성 * refactor: 상수값 static 으로 따로 관리 상수값 관리하도록 피드백 반영 * feat: AOP 를 활용하여 Get요청시 ok를 바로 보내줄 수 있도록 기능 구현 AOP 활용 래퍼클래스 개발 * remove: 상의 후 도입할지 정해야하기때문에 우선 삭제 * Refactor: 에러 메시지 상수화 (#20) * Refactor: 에러 메시지 상수화 * Refactor: 코드 통합 후 에러 메시지 상수화 * test: 테스트코드 재작성 테스트코드 재작성 * refactor: Controller -> RestController 로 변경 변경작업 수행 * feat: PDF의 총 페이지 수 가져오는 기능 구현 총 페이지 수 가져오는 기능 구현 * feat: Document 도메인에서 페이지 정보 유효성 검사 페이지 정보 유효성 검사 * refactor: pdf주소만 받아 생성하던 Document 생성 로직 수정 수정 * remove: consumes 를 다 허용해줌 다 허용해주도럭 제약 조건 삭제 * feat: 폴더 이름 변경 기능 추가 폴더 이름 변경 기능 추가 * feat: OCR 결과 조회 기능 구현 OCR 결과 조회 기능 구현 * refactor: 폴더 삭제시 연결된 문서 삭제 기능 수정 삭제 기능을 수정 * chore: 에러 메시지 INVALID_DOCUMENT_PAGE 추가 * refactor: 하나의 페이지 당 최대 하나의 OCR 정보만 조회되도록 수정 기존 List -> Optional 로 리턴 타입 수정 --------- * Refactor: 녹음 API 상태 코드 변경 (#25) * Style: formatting통일 (#30) * Feat: 요약 및 문제 생성 API 구현 (#32) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Style: 코드 포맷팅 통일 (#36) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Revert "Style: 코드 포맷팅 통일 (#36)" (#38) This reverts commit ad9062e737992caa2bbb3a07dda0072ec086c7ef. * Feat: 폴더 관련 기능 구현 (#39) * Feat: 요약 및 문제 생성 API 구현 (#4) * Rename: AI Task 도메인 이름 변경 - llm 으로 변경 * Refactor: LLM 도메인 애플리케이션 계층과 프레젠테이션 계층 응답 분리 * Feat: LLM 작업 진행 상태 확인 기능 구현 * Feat: 요약 및 문제 결과 조회 기능 구현 * Feat: 요약 및 문제 생성 기능 구현 - AI 서버와 통신하는 부분 제외하고 기능 구현 - 임시 UUID 를 통해 task 저장 * Feat: LLM 서버 콜백 기능 구현 - LLM 서버가 API 콜을 통해 페이지별 요약 및 문제 내용 전달 - task id 를 통해 조회하여 요약 내용과 문제 내용 업데이트 * Refactor: 변수 이름 변경 - 필드명 카멜케이스로 변경 * Refactor: 통일성 없는 부분 수정 - 필드명 변경 - 변수 추출 * Refactor: 예외 종류, 메서드 네이밍 변경 - LLMQueryService 예외 타입 변경 - SummaryAndProblemUpdateResponse 메서드 네이밍 변경 * Refactor: LLMQueryService 응답과 LLMController 응답 분리 * Feat: 폴더 관련 기능 구현 (#6) * Init: 프로젝트 기본설정 세팅 - 프로젝트 생성 - .gitignore설정 - 프로젝트 의존성 추가 - application.yml 설정파일 구성 * Init: 프로젝트 기본 구조 및 공통 컴포넌트 설정 - 공통 설정 클래스 추가 (JPA, QueryDSL, Swagger, Web) - 공통 도메인 엔티티 (RootEntity) 정의 - 예외 처리 관련 클래스 및 타입 구현 - JSON 변환을 위한 AttributeConverter 추가 - 유틸리티 클래스 (Math) 추가 * Chore: Folder 도메인 폴더 구조 셋업 폴더 구조 셋업 작업 * Feat: Folder 도메인의 엔티티 생성 엔티티 생성자, 부모-자식간 연결로직 생성 * refactor: 자기 참조 관계 설정 수정 기존, 다대일 양방향 관계에서 다대일 단방향 관계로 설정하고, 삭제 등의 이슈 발생시 Service 계층에서 함수의 재귀사용을 통해 삭제할 예정 * Chore: Document 도메인 폴더 구조 셋업 폴더 구조 셋업 및 엔티티 생성 * Feat: Lombok 라이브러리 활용하여 기본 생성자 생성 기본 생성자 생성 lombok 라이브러리 활용하여 대체 * chore: name 필드의 length 50으로 설정 name 필드 (Document, Folder) 의 length = 50 으로 설정 * chore: Domain 계층의 Repository가 QueryRepository 상속받도록 함 상속 작업 수행 * chore: Member 도메인 매핑 작업 수행 Member 도메인 매핑 작업 수행 * chore: Member 도메인과 Folder 도메인 연결 작업 수행 Member 도메인과 Folder 도메인 연결 작업 수행 * feat: 루트 폴더 생성하는 기능 구현 루트 폴더 생성하는 기능 구현 * feat: 서브폴더 생성하는 기능 구현 서브 폴더 생성하는 기능 구현 * feat: 폴더를 루트로 이동시키는 기능 구현 폴더를 루트로 이동시키는 기능 구현 * feat: 새로운 폴더 내부로 이동시키는 기능 구현 새로운 폴더 내부로 이동시키는 기능 구현 * feat: 계층형 구조의 폴더 탐색 기능 구현 계층형 구조의 폴더 탐색 기능 구현 * test: 재귀적으로 폴더를 조회하는 테스트 코드 작성 재귀적으로 폴더 조회하는 테스트코드 작성 * remove: 사용하지 않는 QueryDSL 관련 파일 삭제 사용하지 않는 QueryDSL 관련 파일 삭제 * refactor: formatting 적용 formatting 적용 * feat: 폴더 재귀적으로 삭제하는 기능 구현 폴더 재귀적으로 삭제하는 기능 구현 * feat: @OnDelete 어노테이션을 사용하여 삭제 기능 구현 삭제 기능 구현 * feat: 폴더 구조의 조회를 간편하게 개선 폴더 구조의 조회 간편하게 개선 * feat: 루트에 폴더를 생성하는 API 구현 루트에 폴더를 생성하는 API 구현 * feat: 서브 폴더를 생성하는 API 구현 서브 폴더 생성하는 API 구현 * feat: 폴더 이동하는 API 구현 폴더 이동하는 API 구현 * refactor: 중복된 함수 기능 병합 작업 수행 중복된 함수 기능 병합 작업 수행 * feat: 폴더 조회 API 구현 폴더 조회 API 구현 * feat: 폴더 삭제 API 구현 폴더 삭제 API 구현 * rename: 함수명 변경 함수 명 변경 * refactor: 메서드 분리 작업 수행 메서드 분리 작업 수행 * refactor: Delete API 204 로 반환 204로 반환 * feat: 요청마다 DTO를 다르게 설정 요청마다 DTO 다르게 설정 * refactor: 타입추론방식에서 타입명시방식으로 변경 타입명시방식으로 코드 스타일 변경 * refactor: 도메인 값에 대한 검증은 도메인계층으로 옮김 도메인 계층으로 값에 대한 검증 이동 * refactor: Owner가 아닌 폴더에 접근하려고 하는 경우 NotFoundException 예외 발생 예외 발생 --------- --------- * Style: 코드 포맷팅 통일 (#40) * Style: 코드 포맷팅 통일 * Refactor: 예외 종류 변경 --------- * Remove: .idea 폴더 삭제 * Style: 코드 포맷팅 통일 (#41) * Feat: 녹음 파일 업로드 기능 구현 (#8) * Feat: 녹음 파일 업로드 기능 구현 - Recording 엔티티, 레포지토리, 컨트롤러 코드 작성 - 오디오 디코딩, 파일 저장 코드 작성 * Chore: Weekly5 로 rebase * Refactor: file base path @value 를 사용하도록 변경 --------- * Refactor: 메서드, 파라미터 이름 변경 * Feat: 녹음-페이지 저장 기능 구현 - 페이지 넘김 이벤트에 따라 녹음-페이지 테이블에 타임스탬프 저장 * Refactor: 예외 메시지 수정 * refactor: 녹음 파일 업로드, 페이지 넘김 이벤트 처리 api 상태코드 변경 - 200 -> 201 --------- * Feat: 페이지별 api 구현 및 리팩토링 (#24) * Refactor: 예외 로깅 추가 - LLMQueryService 예외 로깅 * Refactor: LLM 테이블 이름 변경 * Refactor: 명세 변경으로 인한 AI 서… * Feat: 문서에 대한 회원의 소유 검증 추가 (#31) * fix: 문서에 대한 회원의 소유 검증 추가 - argument resolver 에서 member 를 반환하도록 변경 - documentId 를 포함한 모든 엔드포인트에서 소유 검증 추가 * Style: 코드 포맷팅 수정 * Fix: 병합 과정 오류 수정 --------- Co-authored-by: yugyeom <48901587+rladbrua0207@users.noreply.github.com> Co-authored-by: Cindy <93774025+Shsin9797@users.noreply.github.com> Co-authored-by: 윤정훈 <76200940+yunjunghun0116@users.noreply.github.com> Co-authored-by: mingjuu Co-authored-by: Minju Song <101880766+mingjuu@users.noreply.github.com> --- .gitmodules | 2 +- Dockerfile | 5 - Team29_BE_Submodule | 2 +- build.gradle | 2 +- .../application/AnnotationQueryService.java | 9 +- .../application/AnnotationService.java | 18 ++- .../presentation/AnnotationController.java | 22 ++- .../request/CreateAnnotationRequest.java | 14 +- .../java/notai/auth/AuthArgumentResolver.java | 5 +- src/main/java/notai/client/ai/AiClient.java | 6 +- .../java/notai/client/ai/AiClientConfig.java | 32 ++-- .../java/notai/common/config/AuthConfig.java | 8 +- .../notai/common/config/SwaggerConfig.java | 2 +- .../java/notai/common/domain/vo/FilePath.java | 7 +- .../exception/type/BadRequestException.java | 3 + .../application/DocumentQueryService.java | 5 +- .../document/application/DocumentService.java | 26 ++-- .../presentation/DocumentController.java | 25 ++-- .../application/FolderQueryService.java | 11 +- .../folder/application/FolderService.java | 28 ++-- src/main/java/notai/folder/domain/Folder.java | 4 +- .../folder/presentation/FolderController.java | 33 ++-- .../llm/application/LlmTaskQueryService.java | 44 +++--- .../notai/llm/application/LlmTaskService.java | 3 + src/main/java/notai/llm/domain/LlmTask.java | 4 +- .../llm/presentation/LlmTaskController.java | 27 +++- .../request/LlmTaskSubmitRequest.java | 4 +- .../ocr/application/OCRQueryService.java | 10 +- src/main/java/notai/ocr/domain/OCR.java | 2 +- .../notai/ocr/presentation/OCRController.java | 6 +- .../application/PageRecordingService.java | 19 +-- .../pageRecording/domain/PageRecording.java | 1 - .../presentation/PageRecordingController.java | 18 +-- .../application/RecordingService.java | 11 +- .../notai/recording/domain/Recording.java | 8 +- .../presentation/RecordingController.java | 18 +-- .../notai/stt/application/SttService.java | 1 + .../notai/stt/application/SttTaskService.java | 16 +- src/main/java/notai/stt/domain/Stt.java | 21 ++- .../request/SttCallbackRequest.java | 31 ++-- .../AnnotationControllerTest.java} | 50 +++++-- .../client/ai/AiClientIntegrationTest.java | 6 +- .../java/notai/client/ai/AiClientTest.java | 9 +- .../application/FolderQueryServiceTest.java | 19 ++- .../application/LlmTaskQueryServiceTest.java | 141 ++++++++++++------ .../llm/application/LlmTaskServiceTest.java | 28 ++-- .../application/PageRecordingServiceTest.java | 26 +++- .../application/RecordingServiceTest.java | 15 +- 48 files changed, 487 insertions(+), 320 deletions(-) delete mode 100644 Dockerfile rename src/test/java/notai/annotation/{AnnotationServiceTest.java => presentation/AnnotationControllerTest.java} (75%) 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/application/AnnotationQueryService.java b/src/main/java/notai/annotation/application/AnnotationQueryService.java index 54b7b79..62aa952 100644 --- a/src/main/java/notai/annotation/application/AnnotationQueryService.java +++ b/src/main/java/notai/annotation/application/AnnotationQueryService.java @@ -5,7 +5,9 @@ import notai.annotation.domain.AnnotationRepository; import notai.annotation.presentation.response.AnnotationResponse; import notai.common.exception.type.NotFoundException; +import notai.document.domain.Document; import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,8 +24,11 @@ public class AnnotationQueryService { private final DocumentRepository documentRepository; @Transactional(readOnly = true) - public List getAnnotationsByDocumentAndPageNumbers(Long documentId, List pageNumbers) { - documentRepository.getById(documentId); + public List getAnnotationsByDocumentAndPageNumbers( + Member member, Long documentId, List pageNumbers + ) { + Document document = documentRepository.getById(documentId); + document.validateOwner(member); List annotations = annotationRepository.findByDocumentIdAndPageNumberIn(documentId, pageNumbers); if (annotations.isEmpty()) { diff --git a/src/main/java/notai/annotation/application/AnnotationService.java b/src/main/java/notai/annotation/application/AnnotationService.java index 7aba5be..983c0c3 100644 --- a/src/main/java/notai/annotation/application/AnnotationService.java +++ b/src/main/java/notai/annotation/application/AnnotationService.java @@ -6,6 +6,7 @@ import notai.annotation.presentation.response.AnnotationResponse; import notai.document.domain.Document; import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,8 +18,11 @@ public class AnnotationService { private final DocumentRepository documentRepository; @Transactional - public AnnotationResponse createAnnotation(Long documentId, int pageNumber, int x, int y, int width, int height, String content) { + public AnnotationResponse createAnnotation( + Member member, Long documentId, int pageNumber, int x, int y, int width, int height, String content + ) { Document document = documentRepository.getById(documentId); + document.validateOwner(member); Annotation annotation = new Annotation(document, pageNumber, x, y, width, height, content); Annotation savedAnnotation = annotationRepository.save(annotation); @@ -26,16 +30,20 @@ public AnnotationResponse createAnnotation(Long documentId, int pageNumber, int } @Transactional - public AnnotationResponse updateAnnotation(Long documentId, Long annotationId, int x, int y, int width, int height, String content) { - documentRepository.getById(documentId); + public AnnotationResponse updateAnnotation( + Member member, Long documentId, Long annotationId, int x, int y, int width, int height, String content + ) { + Document document = documentRepository.getById(documentId); + document.validateOwner(member); Annotation annotation = annotationRepository.getById(annotationId); annotation.updateAnnotation(x, y, width, height, content); return AnnotationResponse.from(annotation); } @Transactional - public void deleteAnnotation(Long documentId, Long annotationId) { - documentRepository.getById(documentId); + public void deleteAnnotation(Member member, Long documentId, Long annotationId) { + Document document = documentRepository.getById(documentId); + document.validateOwner(member); Annotation annotation = annotationRepository.getById(annotationId); annotationRepository.delete(annotation); } diff --git a/src/main/java/notai/annotation/presentation/AnnotationController.java b/src/main/java/notai/annotation/presentation/AnnotationController.java index a57414c..f6eaabd 100644 --- a/src/main/java/notai/annotation/presentation/AnnotationController.java +++ b/src/main/java/notai/annotation/presentation/AnnotationController.java @@ -6,6 +6,8 @@ import notai.annotation.application.AnnotationService; import notai.annotation.presentation.request.CreateAnnotationRequest; import notai.annotation.presentation.response.AnnotationResponse; +import notai.auth.Auth; +import notai.member.domain.Member; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -22,10 +24,12 @@ public class AnnotationController { @PostMapping public ResponseEntity createAnnotation( - @PathVariable Long documentId, @RequestBody @Valid CreateAnnotationRequest request + @Auth Member member, @PathVariable Long documentId, @RequestBody @Valid CreateAnnotationRequest request ) { - AnnotationResponse response = annotationService.createAnnotation(documentId, + AnnotationResponse response = annotationService.createAnnotation( + member, + documentId, request.pageNumber(), request.x(), request.y(), @@ -40,10 +44,11 @@ public ResponseEntity createAnnotation( @GetMapping public ResponseEntity> getAnnotations( - @PathVariable Long documentId, @RequestParam List pageNumbers + @Auth Member member, @PathVariable Long documentId, @RequestParam List pageNumbers ) { List response = annotationQueryService.getAnnotationsByDocumentAndPageNumbers( + member, documentId, pageNumbers ); @@ -53,12 +58,15 @@ public ResponseEntity> getAnnotations( @PutMapping("/{annotationId}") public ResponseEntity updateAnnotation( + @Auth Member member, @PathVariable Long documentId, @PathVariable Long annotationId, @RequestBody @Valid CreateAnnotationRequest request ) { - AnnotationResponse response = annotationService.updateAnnotation(documentId, + AnnotationResponse response = annotationService.updateAnnotation( + member, + documentId, annotationId, request.x(), request.y(), @@ -72,10 +80,10 @@ public ResponseEntity updateAnnotation( @DeleteMapping("/{annotationId}") public ResponseEntity deleteAnnotation( - @PathVariable Long documentId, @PathVariable Long annotationId + @Auth Member member, @PathVariable Long documentId, @PathVariable Long annotationId ) { - annotationService.deleteAnnotation(documentId, annotationId); - return new ResponseEntity<>(HttpStatus.NO_CONTENT); + annotationService.deleteAnnotation(member, documentId, annotationId); + 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/auth/AuthArgumentResolver.java b/src/main/java/notai/auth/AuthArgumentResolver.java index 4065082..0682f5c 100644 --- a/src/main/java/notai/auth/AuthArgumentResolver.java +++ b/src/main/java/notai/auth/AuthArgumentResolver.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import notai.member.domain.Member; import notai.member.domain.MemberRepository; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; @@ -23,7 +24,7 @@ public boolean supportsParameter(MethodParameter parameter) { } @Override - public Long resolveArgument( + public Member resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @@ -31,6 +32,6 @@ public Long resolveArgument( ) { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); Long memberId = (Long) request.getAttribute("memberId"); - return memberRepository.getById(memberId).getId(); + return memberRepository.getById(memberId); } } 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/application/DocumentQueryService.java b/src/main/java/notai/document/application/DocumentQueryService.java index 0e45f85..02d6e03 100644 --- a/src/main/java/notai/document/application/DocumentQueryService.java +++ b/src/main/java/notai/document/application/DocumentQueryService.java @@ -4,6 +4,7 @@ import notai.document.application.result.DocumentFindResult; import notai.document.domain.Document; import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import org.springframework.stereotype.Service; import java.util.List; @@ -19,8 +20,8 @@ public List findDocuments(Long folderId) { return documents.stream().map(this::getDocumentFindResult).toList(); } - public List findRootDocuments(Long memberId) { - List documents = documentRepository.findAllByMemberIdAndFolderIdIsNull(memberId); + public List findRootDocuments(Member member) { + List documents = documentRepository.findAllByMemberIdAndFolderIdIsNull(member.getId()); return documents.stream().map(this::getDocumentFindResult).toList(); } diff --git a/src/main/java/notai/document/application/DocumentService.java b/src/main/java/notai/document/application/DocumentService.java index 6a04c5e..3084dbb 100644 --- a/src/main/java/notai/document/application/DocumentService.java +++ b/src/main/java/notai/document/application/DocumentService.java @@ -10,7 +10,6 @@ import notai.folder.domain.Folder; import notai.folder.domain.FolderRepository; import notai.member.domain.Member; -import notai.member.domain.MemberRepository; import notai.ocr.application.OCRService; import notai.pdf.PdfService; import notai.pdf.result.PdfSaveResult; @@ -27,34 +26,32 @@ public class DocumentService { private final OCRService ocrService; private final DocumentRepository documentRepository; private final FolderRepository folderRepository; - private final MemberRepository memberRepository; private static final Long ROOT_FOLDER_ID = -1L; public DocumentSaveResult saveDocument( - Long memberId, Long folderId, MultipartFile pdfFile, DocumentSaveRequest documentSaveRequest + Member member, Long folderId, MultipartFile pdfFile, DocumentSaveRequest documentSaveRequest ) { PdfSaveResult pdfSaveResult = pdfService.savePdf(pdfFile); - Document document = saveAndReturnDocument(memberId, folderId, documentSaveRequest, pdfSaveResult); + Document document = saveAndReturnDocument(member, folderId, documentSaveRequest, pdfSaveResult); ocrService.saveOCR(document, pdfSaveResult.pdf()); return DocumentSaveResult.of(document.getId(), document.getName(), document.getUrl()); } public DocumentSaveResult saveRootDocument( - Long memberId, MultipartFile pdfFile, DocumentSaveRequest documentSaveRequest + Member member, MultipartFile pdfFile, DocumentSaveRequest documentSaveRequest ) { PdfSaveResult pdfSaveResult = pdfService.savePdf(pdfFile); - Document document = saveAndReturnRootDocument(memberId, documentSaveRequest, pdfSaveResult); + Document document = saveAndReturnRootDocument(member, documentSaveRequest, pdfSaveResult); ocrService.saveOCR(document, pdfSaveResult.pdf()); return DocumentSaveResult.of(document.getId(), document.getName(), document.getUrl()); } public DocumentUpdateResult updateDocument( - Long memberId, Long folderId, Long documentId, DocumentUpdateRequest documentUpdateRequest + Member member, Long folderId, Long documentId, DocumentUpdateRequest documentUpdateRequest ) { Document document = documentRepository.getById(documentId); - Member member = memberRepository.getById(memberId); document.validateOwner(member); @@ -67,10 +64,9 @@ public DocumentUpdateResult updateDocument( } public void deleteDocument( - Long memberId, Long folderId, Long documentId + Member member, Long folderId, Long documentId ) { Document document = documentRepository.getById(documentId); - Member member = memberRepository.getById(memberId); document.validateOwner(member); @@ -82,18 +78,17 @@ public void deleteDocument( } public void deleteAllByFolder( - Long memberId, Folder folder + Member member, Folder folder ) { List documents = documentRepository.findAllByFolderId(folder.getId()); for (Document document : documents) { - deleteDocument(memberId, folder.getId(), document.getId()); + deleteDocument(member, folder.getId(), document.getId()); } } private Document saveAndReturnDocument( - Long memberId, Long folderId, DocumentSaveRequest documentSaveRequest, PdfSaveResult pdfSaveResult + Member member, Long folderId, DocumentSaveRequest documentSaveRequest, PdfSaveResult pdfSaveResult ) { - Member member = memberRepository.getById(memberId); Folder folder = folderRepository.getById(folderId); Document document = new Document(folder, member, @@ -105,9 +100,8 @@ private Document saveAndReturnDocument( } private Document saveAndReturnRootDocument( - Long memberId, DocumentSaveRequest documentSaveRequest, PdfSaveResult pdfSaveResult + Member member, DocumentSaveRequest documentSaveRequest, PdfSaveResult pdfSaveResult ) { - Member member = memberRepository.getById(memberId); Document document = new Document(member, documentSaveRequest.name(), pdfSaveResult.pdfUrl(), diff --git a/src/main/java/notai/document/presentation/DocumentController.java b/src/main/java/notai/document/presentation/DocumentController.java index 12423c5..5539c9d 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,8 @@ import notai.document.presentation.response.DocumentFindResponse; import notai.document.presentation.response.DocumentSaveResponse; import notai.document.presentation.response.DocumentUpdateResponse; +import notai.member.domain.Member; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -30,19 +34,20 @@ 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, + @Auth Member member, @PathVariable Long folderId, + @Parameter(content = @Content(mediaType = MediaType.APPLICATION_PDF_VALUE)) @RequestPart MultipartFile pdfFile, @RequestPart DocumentSaveRequest documentSaveRequest ) { DocumentSaveResult documentSaveResult; if (folderId.equals(ROOT_FOLDER_ID)) { - documentSaveResult = documentService.saveRootDocument(memberId, pdfFile, documentSaveRequest); + documentSaveResult = documentService.saveRootDocument(member, pdfFile, documentSaveRequest); } else { - documentSaveResult = documentService.saveDocument(memberId, folderId, pdfFile, documentSaveRequest); + documentSaveResult = documentService.saveDocument(member, folderId, pdfFile, documentSaveRequest); } DocumentSaveResponse response = DocumentSaveResponse.from(documentSaveResult); String url = String.format(FOLDER_URL_FORMAT, folderId, response.id()); @@ -51,13 +56,13 @@ public ResponseEntity saveDocument( @PutMapping(value = "/{id}") public ResponseEntity updateDocument( - @Auth Long memberId, + @Auth Member member, @PathVariable Long folderId, @PathVariable Long id, @RequestBody DocumentUpdateRequest documentUpdateRequest ) { DocumentUpdateResult documentUpdateResult = documentService.updateDocument( - memberId, + member, folderId, id, documentUpdateRequest @@ -68,11 +73,11 @@ public ResponseEntity updateDocument( @GetMapping public ResponseEntity> getDocuments( - @Auth Long memberId, @PathVariable Long folderId + @Auth Member member, @PathVariable Long folderId ) { List documentResults; if (folderId.equals(ROOT_FOLDER_ID)) { - documentResults = documentQueryService.findRootDocuments(memberId); + documentResults = documentQueryService.findRootDocuments(member); } else { documentResults = documentQueryService.findDocuments(folderId); } @@ -83,9 +88,9 @@ public ResponseEntity> getDocuments( @DeleteMapping("/{id}") public ResponseEntity deleteDocument( - @Auth Long memberId, @PathVariable Long folderId, @PathVariable Long id + @Auth Member member, @PathVariable Long folderId, @PathVariable Long id ) { - documentService.deleteDocument(memberId, folderId, id); + documentService.deleteDocument(member, folderId, id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/notai/folder/application/FolderQueryService.java b/src/main/java/notai/folder/application/FolderQueryService.java index 1a3a830..b172767 100644 --- a/src/main/java/notai/folder/application/FolderQueryService.java +++ b/src/main/java/notai/folder/application/FolderQueryService.java @@ -4,6 +4,7 @@ import notai.folder.application.result.FolderFindResult; import notai.folder.domain.Folder; import notai.folder.domain.FolderRepository; +import notai.member.domain.Member; import org.springframework.stereotype.Service; import java.util.List; @@ -14,17 +15,17 @@ public class FolderQueryService { private final FolderRepository folderRepository; - public List getFolders(Long memberId, Long parentFolderId) { - List folders = getFoldersWithMemberAndParent(memberId, parentFolderId); + public List getFolders(Member member, Long parentFolderId) { + List folders = getFoldersWithMemberAndParent(member, parentFolderId); // document read return folders.stream().map(this::getFolderResult).toList(); } - private List getFoldersWithMemberAndParent(Long memberId, Long parentFolderId) { + private List getFoldersWithMemberAndParent(Member member, Long parentFolderId) { if (parentFolderId == null) { - return folderRepository.findAllByMemberIdAndParentFolderIsNull(memberId); + return folderRepository.findAllByMemberIdAndParentFolderIsNull(member.getId()); } - return folderRepository.findAllByMemberIdAndParentFolderId(memberId, parentFolderId); + return folderRepository.findAllByMemberIdAndParentFolderId(member.getId(), parentFolderId); } private FolderFindResult getFolderResult(Folder folder) { diff --git a/src/main/java/notai/folder/application/FolderService.java b/src/main/java/notai/folder/application/FolderService.java index 11abcae..96a7091 100644 --- a/src/main/java/notai/folder/application/FolderService.java +++ b/src/main/java/notai/folder/application/FolderService.java @@ -11,7 +11,6 @@ import notai.folder.presentation.request.FolderSaveRequest; import notai.folder.presentation.request.FolderUpdateRequest; import notai.member.domain.Member; -import notai.member.domain.MemberRepository; import org.springframework.stereotype.Service; import java.util.List; @@ -21,57 +20,54 @@ public class FolderService { private final FolderRepository folderRepository; - private final MemberRepository memberRepository; private final DocumentService documentService; - public FolderSaveResult saveRootFolder(Long memberId, FolderSaveRequest folderSaveRequest) { - Member member = memberRepository.getById(memberId); + public FolderSaveResult saveRootFolder(Member member, FolderSaveRequest folderSaveRequest) { Folder folder = new Folder(member, folderSaveRequest.name()); Folder savedFolder = folderRepository.save(folder); return getFolderSaveResult(savedFolder); } - public FolderSaveResult saveSubFolder(Long memberId, FolderSaveRequest folderSaveRequest) { - Member member = memberRepository.getById(memberId); + public FolderSaveResult saveSubFolder(Member member, FolderSaveRequest folderSaveRequest) { Folder parentFolder = folderRepository.getById(folderSaveRequest.parentFolderId()); Folder folder = new Folder(member, folderSaveRequest.name(), parentFolder); Folder savedFolder = folderRepository.save(folder); return getFolderSaveResult(savedFolder); } - public FolderMoveResult moveRootFolder(Long memberId, Long id) { + public FolderMoveResult moveRootFolder(Member member, Long id) { Folder folder = folderRepository.getById(id); - folder.validateOwner(memberId); + folder.validateOwner(member); folder.moveRootFolder(); folderRepository.save(folder); return getFolderMoveResult(folder); } - public FolderMoveResult moveNewParentFolder(Long memberId, Long id, FolderMoveRequest folderMoveRequest) { + public FolderMoveResult moveNewParentFolder(Member member, Long id, FolderMoveRequest folderMoveRequest) { Folder folder = folderRepository.getById(id); Folder parentFolder = folderRepository.getById(folderMoveRequest.targetFolderId()); - folder.validateOwner(memberId); + folder.validateOwner(member); folder.moveNewParentFolder(parentFolder); folderRepository.save(folder); return getFolderMoveResult(folder); } - public FolderUpdateResult updateFolder(Long memberId, Long id, FolderUpdateRequest folderUpdateRequest) { + public FolderUpdateResult updateFolder(Member member, Long id, FolderUpdateRequest folderUpdateRequest) { Folder folder = folderRepository.getById(id); - folder.validateOwner(memberId); + folder.validateOwner(member); folder.updateName(folderUpdateRequest.name()); folderRepository.save(folder); return getFolderUpdateResult(folder); } - public void deleteFolder(Long memberId, Long id) { + public void deleteFolder(Member member, Long id) { Folder folder = folderRepository.getById(id); - folder.validateOwner(memberId); + folder.validateOwner(member); List subFolders = folderRepository.findAllByParentFolder(folder); for (Folder subFolder : subFolders) { - deleteFolder(memberId, subFolder.getId()); + deleteFolder(member, subFolder.getId()); } - documentService.deleteAllByFolder(memberId, folder); + documentService.deleteAllByFolder(member, folder); folderRepository.delete(folder); } diff --git a/src/main/java/notai/folder/domain/Folder.java b/src/main/java/notai/folder/domain/Folder.java index 8abfef3..7e62c38 100644 --- a/src/main/java/notai/folder/domain/Folder.java +++ b/src/main/java/notai/folder/domain/Folder.java @@ -59,8 +59,8 @@ public void moveNewParentFolder(Folder parentFolder) { this.parentFolder = parentFolder; } - public void validateOwner(Long memberId) { - if (!this.member.getId().equals(memberId)) { + public void validateOwner(Member member) { + if (!this.member.getId().equals(member.getId())) { log.info("폴더 소유자가 요청 사용자와 다릅니다."); throw new NotFoundException(FOLDER_NOT_FOUND); } diff --git a/src/main/java/notai/folder/presentation/FolderController.java b/src/main/java/notai/folder/presentation/FolderController.java index ab2c4af..91daf29 100644 --- a/src/main/java/notai/folder/presentation/FolderController.java +++ b/src/main/java/notai/folder/presentation/FolderController.java @@ -16,6 +16,7 @@ import notai.folder.presentation.response.FolderMoveResponse; import notai.folder.presentation.response.FolderSaveResponse; import notai.folder.presentation.response.FolderUpdateResponse; +import notai.member.domain.Member; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -32,59 +33,59 @@ public class FolderController { @PostMapping public ResponseEntity saveFolder( - @Auth Long memberId, @Valid @RequestBody FolderSaveRequest folderSaveRequest + @Auth Member member, @Valid @RequestBody FolderSaveRequest folderSaveRequest ) { - FolderSaveResult folderResult = saveFolderResult(memberId, folderSaveRequest); + FolderSaveResult folderResult = saveFolderResult(member, folderSaveRequest); FolderSaveResponse response = FolderSaveResponse.from(folderResult); return ResponseEntity.created(URI.create("/api/folders/" + response.id())).body(response); } @PostMapping("/{id}/move") public ResponseEntity moveFolder( - @Auth Long memberId, @PathVariable Long id, @Valid @RequestBody FolderMoveRequest folderMoveRequest + @Auth Member member, @PathVariable Long id, @Valid @RequestBody FolderMoveRequest folderMoveRequest ) { - FolderMoveResult folderResult = moveFolderWithRequest(memberId, id, folderMoveRequest); + FolderMoveResult folderResult = moveFolderWithRequest(member, id, folderMoveRequest); FolderMoveResponse response = FolderMoveResponse.from(folderResult); return ResponseEntity.ok(response); } @PutMapping("/{id}") public ResponseEntity updateFolder( - @Auth Long memberId, @PathVariable Long id, @Valid @RequestBody FolderUpdateRequest folderUpdateRequest + @Auth Member member, @PathVariable Long id, @Valid @RequestBody FolderUpdateRequest folderUpdateRequest ) { - FolderUpdateResult folderResult = folderService.updateFolder(memberId, id, folderUpdateRequest); + FolderUpdateResult folderResult = folderService.updateFolder(member, id, folderUpdateRequest); FolderUpdateResponse response = FolderUpdateResponse.from(folderResult); return ResponseEntity.ok(response); } @GetMapping public ResponseEntity> getFolders( - @Auth Long memberId, @RequestParam(required = false) Long parentFolderId + @Auth Member member, @RequestParam(required = false) Long parentFolderId ) { - List folderResults = folderQueryService.getFolders(memberId, parentFolderId); + List folderResults = folderQueryService.getFolders(member, parentFolderId); List response = folderResults.stream().map(FolderFindResponse::from).toList(); return ResponseEntity.ok(response); } @DeleteMapping("/{id}") public ResponseEntity deleteFolder( - @Auth Long memberId, @PathVariable Long id + @Auth Member member, @PathVariable Long id ) { - folderService.deleteFolder(memberId, id); + folderService.deleteFolder(member, id); return ResponseEntity.noContent().build(); } - private FolderSaveResult saveFolderResult(Long memberId, FolderSaveRequest folderSaveRequest) { + private FolderSaveResult saveFolderResult(Member member, FolderSaveRequest folderSaveRequest) { if (folderSaveRequest.parentFolderId() != null) { - return folderService.saveSubFolder(memberId, folderSaveRequest); + return folderService.saveSubFolder(member, folderSaveRequest); } - return folderService.saveRootFolder(memberId, folderSaveRequest); + return folderService.saveRootFolder(member, folderSaveRequest); } - private FolderMoveResult moveFolderWithRequest(Long memberId, Long id, FolderMoveRequest folderMoveRequest) { + private FolderMoveResult moveFolderWithRequest(Member member, Long id, FolderMoveRequest folderMoveRequest) { if (folderMoveRequest.targetFolderId() != null) { - return folderService.moveNewParentFolder(memberId, id, folderMoveRequest); + return folderService.moveNewParentFolder(member, id, folderMoveRequest); } - return folderService.moveRootFolder(memberId, id); + return folderService.moveRootFolder(member, id); } } diff --git a/src/main/java/notai/llm/application/LlmTaskQueryService.java b/src/main/java/notai/llm/application/LlmTaskQueryService.java index 39c642a..ec55477 100644 --- a/src/main/java/notai/llm/application/LlmTaskQueryService.java +++ b/src/main/java/notai/llm/application/LlmTaskQueryService.java @@ -2,10 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import static notai.common.exception.ErrorMessages.DOCUMENT_NOT_FOUND; -import static notai.common.exception.ErrorMessages.LLM_TASK_RESULT_ERROR; import notai.common.exception.type.InternalServerErrorException; -import notai.common.exception.type.NotFoundException; +import notai.document.domain.Document; import notai.document.domain.DocumentRepository; import notai.llm.application.command.LlmTaskPageResultCommand; import notai.llm.application.command.LlmTaskPageStatusCommand; @@ -16,8 +14,8 @@ import notai.llm.application.result.LlmTaskPageResult; import notai.llm.application.result.LlmTaskPageStatusResult; import notai.llm.domain.TaskStatus; -import static notai.llm.domain.TaskStatus.*; import notai.llm.query.LlmTaskQueryRepository; +import notai.member.domain.Member; import notai.problem.domain.ProblemRepository; import notai.problem.query.result.ProblemPageContentResult; import notai.summary.domain.SummaryRepository; @@ -27,6 +25,9 @@ import java.util.Collections; import java.util.List; +import static notai.common.exception.ErrorMessages.LLM_TASK_RESULT_ERROR; +import static notai.llm.domain.TaskStatus.*; + @Slf4j @Service @RequiredArgsConstructor @@ -37,8 +38,10 @@ public class LlmTaskQueryService { private final SummaryRepository summaryRepository; private final ProblemRepository problemRepository; - public LlmTaskOverallStatusResult fetchOverallStatus(Long documentId) { - checkDocumentExists(documentId); + public LlmTaskOverallStatusResult fetchOverallStatus(Member member, Long documentId) { + Document foundDocument = documentRepository.getById(documentId); + foundDocument.validateOwner(member); + List summaryIds = summaryRepository.getSummaryIdsByDocumentId(documentId); if (summaryIds.isEmpty()) { @@ -56,8 +59,10 @@ public LlmTaskOverallStatusResult fetchOverallStatus(Long documentId) { return LlmTaskOverallStatusResult.of(documentId, IN_PROGRESS, totalPages, completedPages); } - public LlmTaskPageStatusResult fetchPageStatus(LlmTaskPageStatusCommand command) { // TODO: 페이지 번호 검증 추가 - checkDocumentExists(command.documentId()); + public LlmTaskPageStatusResult fetchPageStatus(Member member, LlmTaskPageStatusCommand command) { // TODO: 페이지 번호 검증 추가 + Document foundDocument = documentRepository.getById(command.documentId()); + foundDocument.validateOwner(member); + Long summaryId = summaryRepository.getSummaryIdByDocumentIdAndPageNumber(command.documentId(), command.pageNumber()); @@ -67,8 +72,9 @@ public LlmTaskPageStatusResult fetchPageStatus(LlmTaskPageStatusCommand command) return LlmTaskPageStatusResult.from(llmTaskQueryRepository.getTaskStatusBySummaryId(summaryId)); } - public LlmTaskAllPagesResult findAllPagesResult(Long documentId) { - checkDocumentExists(documentId); + public LlmTaskAllPagesResult findAllPagesResult(Member member, Long documentId) { + Document foundDocument = documentRepository.getById(documentId); + foundDocument.validateOwner(member); List summaryResults = summaryRepository.getPageNumbersAndContentByDocumentId(documentId); @@ -92,8 +98,9 @@ public LlmTaskAllPagesResult findAllPagesResult(Long documentId) { return LlmTaskAllPagesResult.of(documentId, results); } - public LlmTaskPageResult findPageResult(LlmTaskPageResultCommand command) { // TODO: 페이지 번호 검증 추가 - checkDocumentExists(command.documentId()); + public LlmTaskPageResult findPageResult(Member member, LlmTaskPageResultCommand command) { // TODO: 페이지 번호 검증 추가 + Document foundDocument = documentRepository.getById(command.documentId()); + foundDocument.validateOwner(member); String summaryResult = summaryRepository.getSummaryContentByDocumentIdAndPageNumber( command.documentId(), command.pageNumber()); @@ -105,9 +112,10 @@ public LlmTaskPageResult findPageResult(LlmTaskPageResultCommand command) { // T return LlmTaskPageResult.of(summaryResult, problemResult); } - private static void checkSummaryAndProblemConsistency( + private void checkSummaryAndProblemConsistency( LlmTaskPageResultCommand command, String summaryResult, - String problemResult) { + String problemResult + ) { if (summaryResult == null && problemResult != null) { log.error("요약과 문제 생성 결과가 매칭되지 않습니다. {} 페이지에 대한 요약 결과가 없습니다.", command.pageNumber()); throw new InternalServerErrorException(LLM_TASK_RESULT_ERROR); @@ -119,13 +127,7 @@ private static void checkSummaryAndProblemConsistency( } } - private void checkDocumentExists(Long documentId) { - if (!documentRepository.existsById(documentId)) { - throw new NotFoundException(DOCUMENT_NOT_FOUND); - } - } - - private static void checkSummaryAndProblemCountsEqual( + private void checkSummaryAndProblemCountsEqual( List summaryResults, List problemResults ) { if (summaryResults.size() != problemResults.size()) { 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/LlmTaskController.java b/src/main/java/notai/llm/presentation/LlmTaskController.java index 352698b..e259d21 100644 --- a/src/main/java/notai/llm/presentation/LlmTaskController.java +++ b/src/main/java/notai/llm/presentation/LlmTaskController.java @@ -2,6 +2,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import notai.auth.Auth; import notai.llm.application.LlmTaskQueryService; import notai.llm.application.LlmTaskService; import notai.llm.application.command.LlmTaskPageResultCommand; @@ -12,6 +13,7 @@ import notai.llm.presentation.request.LlmTaskSubmitRequest; import notai.llm.presentation.request.SummaryAndProblemUpdateRequest; import notai.llm.presentation.response.*; +import notai.member.domain.Member; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -31,31 +33,40 @@ public ResponseEntity submitTask(@RequestBody @Valid LlmT } @GetMapping("/status/{documentId}") - public ResponseEntity fetchOverallStatus(@PathVariable("documentId") Long documentId) { - LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(documentId); + public ResponseEntity fetchOverallStatus( + @Auth Member member, @PathVariable("documentId") Long documentId + ) { + LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(member, documentId); return ResponseEntity.ok(LlmTaskOverallStatusResponse.from(result)); } @GetMapping("/status/{documentId}/{pageNumber}") public ResponseEntity fetchPageStatus( - @PathVariable("documentId") Long documentId, @PathVariable("pageNumber") Integer pageNumber + @Auth Member member, + @PathVariable("documentId") Long documentId, + @PathVariable("pageNumber") Integer pageNumber ) { LlmTaskPageStatusCommand command = LlmTaskPageStatusCommand.of(documentId, pageNumber); - LlmTaskPageStatusResult result = llmTaskQueryService.fetchPageStatus(command); + LlmTaskPageStatusResult result = llmTaskQueryService.fetchPageStatus(member, command); return ResponseEntity.ok(LlmTaskPageStatusResponse.from(result)); } @GetMapping("/results/{documentId}") - public ResponseEntity findAllPagesResult(@PathVariable("documentId") Long documentId) { - LlmTaskAllPagesResult result = llmTaskQueryService.findAllPagesResult(documentId); + public ResponseEntity findAllPagesResult( + @Auth Member member, @PathVariable("documentId") Long documentId + ) { + LlmTaskAllPagesResult result = llmTaskQueryService.findAllPagesResult(member, documentId); return ResponseEntity.ok(LlmTaskAllPagesResultResponse.from(result)); } @GetMapping("/results/{documentId}/{pageNumber}") public ResponseEntity findPageResult( - @PathVariable("documentId") Long documentId, @PathVariable("pageNumber") Integer pageNumber) { + @Auth Member member, + @PathVariable("documentId") Long documentId, + @PathVariable("pageNumber") Integer pageNumber + ) { LlmTaskPageResultCommand command = LlmTaskPageResultCommand.of(documentId, pageNumber); - LlmTaskPageResult result = llmTaskQueryService.findPageResult(command); + LlmTaskPageResult result = llmTaskQueryService.findPageResult(member, command); return ResponseEntity.ok(LlmTaskPageResultResponse.from(result)); } 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/application/OCRQueryService.java b/src/main/java/notai/ocr/application/OCRQueryService.java index c0b4c87..102b5ca 100644 --- a/src/main/java/notai/ocr/application/OCRQueryService.java +++ b/src/main/java/notai/ocr/application/OCRQueryService.java @@ -1,23 +1,25 @@ package notai.ocr.application; import lombok.RequiredArgsConstructor; -import static notai.common.exception.ErrorMessages.OCR_RESULT_NOT_FOUND; import notai.common.exception.type.NotFoundException; +import notai.member.domain.Member; import notai.ocr.application.result.OCRFindResult; import notai.ocr.domain.OCR; import notai.ocr.domain.OCRRepository; import org.springframework.stereotype.Service; +import static notai.common.exception.ErrorMessages.OCR_RESULT_NOT_FOUND; + @Service @RequiredArgsConstructor public class OCRQueryService { private final OCRRepository ocrRepository; - public OCRFindResult findOCR(Long documentId, Integer pageNumber) { - OCR ocr = ocrRepository.findOCRByDocumentIdAndPageNumber(documentId, - pageNumber + public OCRFindResult findOCR(Member member, Long documentId, Integer pageNumber) { + OCR ocr = ocrRepository.findOCRByDocumentIdAndPageNumber(documentId, pageNumber ).orElseThrow(() -> new NotFoundException(OCR_RESULT_NOT_FOUND)); + ocr.getDocument().validateOwner(member); return OCRFindResult.of(documentId, pageNumber, ocr.getContent()); } 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/ocr/presentation/OCRController.java b/src/main/java/notai/ocr/presentation/OCRController.java index e042773..06786b9 100644 --- a/src/main/java/notai/ocr/presentation/OCRController.java +++ b/src/main/java/notai/ocr/presentation/OCRController.java @@ -1,6 +1,8 @@ package notai.ocr.presentation; import lombok.RequiredArgsConstructor; +import notai.auth.Auth; +import notai.member.domain.Member; import notai.ocr.application.OCRQueryService; import notai.ocr.application.result.OCRFindResult; import notai.ocr.presentation.response.OCRFindResponse; @@ -16,9 +18,9 @@ public class OCRController { @GetMapping public ResponseEntity getDocuments( - @PathVariable Long documentId, @RequestParam Integer pageNumber + @Auth Member member, @PathVariable Long documentId, @RequestParam Integer pageNumber ) { - OCRFindResult result = ocrQueryService.findOCR(documentId, pageNumber); + OCRFindResult result = ocrQueryService.findOCR(member, documentId, pageNumber); OCRFindResponse response = OCRFindResponse.from(result); return ResponseEntity.ok(response); } diff --git a/src/main/java/notai/pageRecording/application/PageRecordingService.java b/src/main/java/notai/pageRecording/application/PageRecordingService.java index c8b4a44..a468a69 100644 --- a/src/main/java/notai/pageRecording/application/PageRecordingService.java +++ b/src/main/java/notai/pageRecording/application/PageRecordingService.java @@ -1,7 +1,9 @@ package notai.pageRecording.application; import lombok.RequiredArgsConstructor; -import notai.common.exception.type.NotFoundException; +import notai.document.domain.Document; +import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import notai.pageRecording.application.command.PageRecordingSaveCommand; import notai.pageRecording.domain.PageRecording; import notai.pageRecording.domain.PageRecordingRepository; @@ -10,8 +12,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static notai.common.exception.ErrorMessages.RECORDING_NOT_FOUND; - @Service @Transactional @RequiredArgsConstructor @@ -19,10 +19,13 @@ public class PageRecordingService { private final PageRecordingRepository pageRecordingRepository; private final RecordingRepository recordingRepository; + private final DocumentRepository documentRepository; - public void savePageRecording(PageRecordingSaveCommand command) { + public void savePageRecording(Member member, PageRecordingSaveCommand command) { Recording foundRecording = recordingRepository.getById(command.recordingId()); - checkDocumentOwnershipOfRecording(command, foundRecording); + Document foundDocument = documentRepository.getById(command.documentId()); + foundDocument.validateOwner(member); + foundRecording.validateDocumentOwnership(foundDocument); command.sessions().forEach(session -> { PageRecording pageRecording = new PageRecording( @@ -34,10 +37,4 @@ public void savePageRecording(PageRecordingSaveCommand command) { pageRecordingRepository.save(pageRecording); }); } - - private static void checkDocumentOwnershipOfRecording(PageRecordingSaveCommand command, Recording foundRecording) { - if (!foundRecording.isRecordingOwnedByDocument(command.documentId())) { - throw new NotFoundException(RECORDING_NOT_FOUND); - } - } } 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/pageRecording/presentation/PageRecordingController.java b/src/main/java/notai/pageRecording/presentation/PageRecordingController.java index 9506f7f..6bfeced 100644 --- a/src/main/java/notai/pageRecording/presentation/PageRecordingController.java +++ b/src/main/java/notai/pageRecording/presentation/PageRecordingController.java @@ -1,17 +1,15 @@ package notai.pageRecording.presentation; -import static org.springframework.http.HttpStatus.CREATED; - import lombok.RequiredArgsConstructor; +import notai.auth.Auth; +import notai.member.domain.Member; import notai.pageRecording.application.PageRecordingService; import notai.pageRecording.application.command.PageRecordingSaveCommand; import notai.pageRecording.presentation.request.PageRecordingSaveRequest; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.http.HttpStatus.CREATED; @RestController @RequestMapping("/api/documents/{documentId}/recordings/page-turns") @@ -22,10 +20,12 @@ public class PageRecordingController { @PostMapping public ResponseEntity savePageRecording( - @PathVariable("documentId") Long documentId, @RequestBody PageRecordingSaveRequest request + @Auth Member member, + @PathVariable("documentId") Long documentId, + @RequestBody PageRecordingSaveRequest request ) { PageRecordingSaveCommand command = request.toCommand(documentId); - pageRecordingService.savePageRecording(command); + pageRecordingService.savePageRecording(member, command); return ResponseEntity.status(CREATED).build(); } } diff --git a/src/main/java/notai/recording/application/RecordingService.java b/src/main/java/notai/recording/application/RecordingService.java index d09e819..0d87b96 100644 --- a/src/main/java/notai/recording/application/RecordingService.java +++ b/src/main/java/notai/recording/application/RecordingService.java @@ -2,14 +2,13 @@ import lombok.RequiredArgsConstructor; import notai.common.domain.vo.FilePath; -import static notai.common.exception.ErrorMessages.FILE_SAVE_ERROR; -import static notai.common.exception.ErrorMessages.INVALID_AUDIO_ENCODING; import notai.common.exception.type.BadRequestException; import notai.common.exception.type.InternalServerErrorException; import notai.common.utils.AudioDecoder; import notai.common.utils.FileManager; import notai.document.domain.Document; import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import notai.recording.application.command.RecordingSaveCommand; import notai.recording.application.result.RecordingSaveResult; import notai.recording.domain.Recording; @@ -24,6 +23,9 @@ import java.nio.file.Path; import java.nio.file.Paths; +import static notai.common.exception.ErrorMessages.FILE_SAVE_ERROR; +import static notai.common.exception.ErrorMessages.INVALID_AUDIO_ENCODING; + @Service @Transactional @RequiredArgsConstructor @@ -38,8 +40,9 @@ public class RecordingService { @Value("${file.audio.basePath}") private String audioBasePath; - public RecordingSaveResult saveRecording(RecordingSaveCommand command) { + public RecordingSaveResult saveRecording(Member member, RecordingSaveCommand command) { Document foundDocument = documentRepository.getById(command.documentId()); + foundDocument.validateOwner(member); Recording recording = new Recording(foundDocument); Recording savedRecording = recordingRepository.save(recording); @@ -60,7 +63,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/recording/domain/Recording.java b/src/main/java/notai/recording/domain/Recording.java index 64a8c3b..20a995f 100644 --- a/src/main/java/notai/recording/domain/Recording.java +++ b/src/main/java/notai/recording/domain/Recording.java @@ -6,11 +6,13 @@ import lombok.NoArgsConstructor; import notai.common.domain.RootEntity; import notai.common.domain.vo.FilePath; +import notai.common.exception.type.NotFoundException; import notai.document.domain.Document; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import static notai.common.exception.ErrorMessages.RECORDING_NOT_FOUND; @Getter @NoArgsConstructor(access = PROTECTED) @@ -37,7 +39,9 @@ public void updateFilePath(FilePath filePath) { this.filePath = filePath; } - public boolean isRecordingOwnedByDocument(Long documentId) { - return this.document.getId().equals(documentId); + public void validateDocumentOwnership(Document document) { + if (this.document.getId().equals(document.getId())) { + throw new NotFoundException(RECORDING_NOT_FOUND); + } } } diff --git a/src/main/java/notai/recording/presentation/RecordingController.java b/src/main/java/notai/recording/presentation/RecordingController.java index 9224cd4..4daf0dc 100644 --- a/src/main/java/notai/recording/presentation/RecordingController.java +++ b/src/main/java/notai/recording/presentation/RecordingController.java @@ -1,20 +1,18 @@ package notai.recording.presentation; -import static org.springframework.http.HttpStatus.CREATED; - import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import notai.auth.Auth; +import notai.member.domain.Member; import notai.recording.application.RecordingService; import notai.recording.application.command.RecordingSaveCommand; import notai.recording.application.result.RecordingSaveResult; import notai.recording.presentation.request.RecordingSaveRequest; import notai.recording.presentation.response.RecordingSaveResponse; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.http.HttpStatus.CREATED; @RestController @RequestMapping("/api/documents/{documentId}/recordings") @@ -25,10 +23,12 @@ public class RecordingController { @PostMapping public ResponseEntity saveRecording( - @PathVariable("documentId") Long documentId, @RequestBody @Valid RecordingSaveRequest request + @Auth Member member, + @PathVariable("documentId") Long documentId, + @RequestBody @Valid RecordingSaveRequest request ) { RecordingSaveCommand command = request.toCommand(documentId); - RecordingSaveResult result = recordingService.saveRecording(command); + RecordingSaveResult result = recordingService.saveRecording(member, command); return ResponseEntity.status(CREATED).body(RecordingSaveResponse.from(result)); } } 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/annotation/AnnotationServiceTest.java b/src/test/java/notai/annotation/presentation/AnnotationControllerTest.java similarity index 75% rename from src/test/java/notai/annotation/AnnotationServiceTest.java rename to src/test/java/notai/annotation/presentation/AnnotationControllerTest.java index 36d21f0..fcdf64a 100644 --- a/src/test/java/notai/annotation/AnnotationServiceTest.java +++ b/src/test/java/notai/annotation/presentation/AnnotationControllerTest.java @@ -1,22 +1,29 @@ -package notai.annotation; +package notai.annotation.presentation; import notai.annotation.application.AnnotationQueryService; import notai.annotation.application.AnnotationService; -import notai.annotation.presentation.AnnotationController; import notai.annotation.presentation.request.CreateAnnotationRequest; import notai.annotation.presentation.response.AnnotationResponse; +import notai.auth.TokenService; +import notai.member.domain.Member; +import notai.member.domain.MemberRepository; +import notai.member.domain.OauthId; +import notai.member.domain.OauthProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -31,15 +38,26 @@ class AnnotationControllerTest { @Mock private AnnotationQueryService annotationQueryService; + @Mock + private MemberRepository memberRepository; + @InjectMocks private AnnotationController annotationController; private MockMvc mockMvc; + private static final Member member = new Member( + new OauthId("oauthId", OauthProvider.KAKAO), "email.com", "nickname" + ); + @BeforeEach void setUp() { MockitoAnnotations.initMocks(this); mockMvc = MockMvcBuilders.standaloneSetup(annotationController).build(); + + given(memberRepository.findById(1L)).willReturn(Optional.of(member)); + TokenService tokenService = Mockito.mock(TokenService.class); + given(tokenService.extractMemberId(any())).willReturn(1L); } @Test @@ -48,12 +66,13 @@ void testCreateAnnotation_success() throws Exception { LocalDateTime now = LocalDateTime.now(); AnnotationResponse response = new AnnotationResponse(1L, 1L, 1, 100, 200, 300, 100, "굵은글씨", now.toString(), now.toString()); - when(annotationService.createAnnotation(anyLong(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyString())) + when(annotationService.createAnnotation(any(Member.class), anyLong(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyString())) .thenReturn(response); mockMvc.perform(post("/api/documents/1/annotations") .contentType("application/json") - .content("{\"pageNumber\": 1, \"x\": 100, \"y\": 200, \"width\": 300, \"height\": 100, \"content\": \"굵은글씨\"}")) + .content("{\"pageNumber\": 1, \"x\": 100, \"y\": 200, \"width\": 300, \"height\": 100, \"content\": \"굵은글씨\"}") + .header("Authorization", "Bearer token")) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").value(1L)) .andExpect(jsonPath("$.pageNumber").value(1)) @@ -74,11 +93,12 @@ void testGetAnnotations_success() throws Exception { new AnnotationResponse(2L, 1L, 2, 150, 250, 350, 120, "", now.toString(), now.toString()) ); - when(annotationQueryService.getAnnotationsByDocumentAndPageNumbers(anyLong(), anyList())).thenReturn(responses); + when(annotationQueryService.getAnnotationsByDocumentAndPageNumbers(any(Member.class), anyLong(), anyList())).thenReturn(responses); mockMvc.perform(get("/api/documents/1/annotations?pageNumbers=1,2") - .contentType("application/json")) - .andExpect(status().isOk()) + .contentType("application/json") + .header("Authorization", "Bearer token")) + .andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(1L)) .andExpect(jsonPath("$[0].pageNumber").value(1)) .andExpect(jsonPath("$[0].x").value(100)) @@ -102,13 +122,14 @@ void testUpdateAnnotation_success() throws Exception { LocalDateTime now = LocalDateTime.now(); AnnotationResponse response = new AnnotationResponse(1L, 1L, 1, 150, 250, 350, 120, "수정된 주석", now.toString(), now.toString()); - when(annotationService.updateAnnotation(anyLong(), anyLong(), anyInt(), anyInt(), anyInt(), anyInt(), anyString())) + when(annotationService.updateAnnotation(any(Member.class), anyLong(), anyLong(), anyInt(), anyInt(), anyInt(), anyInt(), anyString())) .thenReturn(response); mockMvc.perform(put("/api/documents/1/annotations/1") .contentType("application/json") - .content("{\"pageNumber\": 1, \"x\": 150, \"y\": 250, \"width\": 350, \"height\": 120, \"content\": \"수정된 주석\"}")) - .andExpect(status().isOk()) + .content("{\"pageNumber\": 1, \"x\": 150, \"y\": 250, \"width\": 350, \"height\": 120, \"content\": \"수정된 주석\"}") + .header("Authorization", "Bearer token")) + .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1L)) .andExpect(jsonPath("$.pageNumber").value(1)) .andExpect(jsonPath("$.content").value("수정된 주석")) @@ -121,12 +142,13 @@ void testUpdateAnnotation_success() throws Exception { @Test void testDeleteAnnotation_success() throws Exception { - doNothing().when(annotationService).deleteAnnotation(anyLong(), anyLong()); + doNothing().when(annotationService).deleteAnnotation(any(Member.class), anyLong(), anyLong()); mockMvc.perform(delete("/api/documents/1/annotations/1") - .contentType("application/json")) - .andExpect(status().isNoContent()); + .contentType("application/json") + .header("Authorization", "Bearer token")) + .andExpect(status().isOk()); - verify(annotationService, times(1)).deleteAnnotation(1L, 1L); + verify(annotationService, times(1)).deleteAnnotation(any(Member.class),anyLong(), anyLong()); } } 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/folder/application/FolderQueryServiceTest.java b/src/test/java/notai/folder/application/FolderQueryServiceTest.java index a567e1d..2071361 100644 --- a/src/test/java/notai/folder/application/FolderQueryServiceTest.java +++ b/src/test/java/notai/folder/application/FolderQueryServiceTest.java @@ -5,17 +5,20 @@ import notai.folder.domain.FolderRepository; import notai.member.domain.Member; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.mockito.ArgumentMatchers.any; import org.mockito.InjectMocks; import org.mockito.Mock; -import static org.mockito.Mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + @ExtendWith(MockitoExtension.class) class FolderQueryServiceTest { @@ -24,6 +27,14 @@ class FolderQueryServiceTest { @InjectMocks private FolderQueryService folderQueryService; + @Mock + private Member member; + + @BeforeEach + void setUp() { + given(member.getId()).willReturn(1L); + } + @Test @DisplayName("루트 폴더 조회") void getFolders_success_parentFolderIdIsNull() { @@ -33,7 +44,7 @@ void getFolders_success_parentFolderIdIsNull() { when(folderRepository.findAllByMemberIdAndParentFolderIsNull(any(Long.class))).thenReturn(expectedResults); //when - List folders = folderQueryService.getFolders(1L, null); + List folders = folderQueryService.getFolders(member, null); Assertions.assertThat(folders.size()).isEqualTo(1); } @@ -50,7 +61,7 @@ void getFolders_success_parentFolderId() { when(folderRepository.findAllByMemberIdAndParentFolderId(any(Long.class), any(Long.class))).thenReturn( expectedResults); //when - List folders = folderQueryService.getFolders(1L, 1L); + List folders = folderQueryService.getFolders(member, 1L); Assertions.assertThat(folders.size()).isEqualTo(2); } diff --git a/src/test/java/notai/llm/application/LlmTaskQueryServiceTest.java b/src/test/java/notai/llm/application/LlmTaskQueryServiceTest.java index 390b0bc..dd04d5b 100644 --- a/src/test/java/notai/llm/application/LlmTaskQueryServiceTest.java +++ b/src/test/java/notai/llm/application/LlmTaskQueryServiceTest.java @@ -2,6 +2,7 @@ import notai.common.exception.type.InternalServerErrorException; import notai.common.exception.type.NotFoundException; +import notai.document.domain.Document; import notai.document.domain.DocumentRepository; import notai.llm.application.command.LlmTaskPageResultCommand; import notai.llm.application.command.LlmTaskPageStatusCommand; @@ -9,26 +10,29 @@ import notai.llm.application.result.LlmTaskOverallStatusResult; import notai.llm.application.result.LlmTaskPageResult; import notai.llm.application.result.LlmTaskPageStatusResult; -import static notai.llm.domain.TaskStatus.*; import notai.llm.query.LlmTaskQueryRepository; +import notai.member.domain.Member; import notai.problem.domain.ProblemRepository; import notai.problem.query.result.ProblemPageContentResult; import notai.summary.domain.SummaryRepository; import notai.summary.query.result.SummaryPageContentResult; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; import org.mockito.InjectMocks; import org.mockito.Mock; -import static org.mockito.Mockito.verify; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Collections; import java.util.List; +import static notai.llm.domain.TaskStatus.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.verify; + @ExtendWith(MockitoExtension.class) class LlmTaskQueryServiceTest { @@ -47,15 +51,22 @@ class LlmTaskQueryServiceTest { @Mock private ProblemRepository problemRepository; + @Mock + private Member member; + + @Mock + private Document document; + @Test void 작업_상태_확인시_존재하지_않는_문서ID로_요청한_경우_예외_발생() { // given - given(documentRepository.existsById(anyLong())).willReturn(false); + given(documentRepository.getById(eq(2L))).willThrow(NotFoundException.class); // when & then - assertAll(() -> assertThrows(NotFoundException.class, () -> llmTaskQueryService.fetchOverallStatus(1L)), - () -> verify(documentRepository).existsById(anyLong()) - ); + assertAll(() -> assertThrows( + NotFoundException.class, + () -> llmTaskQueryService.fetchOverallStatus(member, 2L) + )); } @Test @@ -64,20 +75,22 @@ class LlmTaskQueryServiceTest { Long documentId = 1L; List summaryIds = List.of(1L, 2L, 3L); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getSummaryIdsByDocumentId(documentId)).willReturn(summaryIds); given(llmTaskQueryRepository.getTaskStatusBySummaryId(1L)).willReturn(COMPLETED); given(llmTaskQueryRepository.getTaskStatusBySummaryId(2L)).willReturn(COMPLETED); given(llmTaskQueryRepository.getTaskStatusBySummaryId(3L)).willReturn(COMPLETED); // when - LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(documentId); + LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(member, documentId); // then - assertAll(() -> assertThat(result.overallStatus()).isEqualTo(COMPLETED), + assertAll( + () -> assertThat(result.overallStatus()).isEqualTo(COMPLETED), () -> assertThat(result.totalPages()).isEqualTo(3), () -> assertThat(result.completedPages()).isEqualTo(3), - () -> verify(documentRepository).existsById(documentId), () -> verify(summaryRepository).getSummaryIdsByDocumentId(documentId), () -> verify(llmTaskQueryRepository).getTaskStatusBySummaryId(documentId) ); @@ -89,20 +102,22 @@ class LlmTaskQueryServiceTest { Long documentId = 1L; List summaryIds = List.of(1L, 2L, 3L); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getSummaryIdsByDocumentId(documentId)).willReturn(summaryIds); given(llmTaskQueryRepository.getTaskStatusBySummaryId(1L)).willReturn(COMPLETED); given(llmTaskQueryRepository.getTaskStatusBySummaryId(2L)).willReturn(IN_PROGRESS); given(llmTaskQueryRepository.getTaskStatusBySummaryId(3L)).willReturn(PENDING); // when - LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(documentId); + LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(member, documentId); // then - assertAll(() -> assertThat(result.overallStatus()).isEqualTo(IN_PROGRESS), + assertAll( + () -> assertThat(result.overallStatus()).isEqualTo(IN_PROGRESS), () -> assertThat(result.totalPages()).isEqualTo(3), () -> assertThat(result.completedPages()).isEqualTo(1), - () -> verify(documentRepository).existsById(documentId), () -> verify(summaryRepository).getSummaryIdsByDocumentId(documentId), () -> verify(llmTaskQueryRepository).getTaskStatusBySummaryId(documentId) ); @@ -116,12 +131,14 @@ class LlmTaskQueryServiceTest { Integer pageNumber = 20; LlmTaskPageStatusCommand command = new LlmTaskPageStatusCommand(documentId, pageNumber); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getSummaryIdByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(summaryId); given(llmTaskQueryRepository.getTaskStatusBySummaryId(summaryId)).willReturn(IN_PROGRESS); // when - LlmTaskPageStatusResult result = llmTaskQueryService.fetchPageStatus(command); + LlmTaskPageStatusResult result = llmTaskQueryService.fetchPageStatus(member, command); // then assertThat(result.status()).isEqualTo(IN_PROGRESS); @@ -134,11 +151,13 @@ class LlmTaskQueryServiceTest { Integer pageNumber = 20; LlmTaskPageStatusCommand command = new LlmTaskPageStatusCommand(documentId, pageNumber); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getSummaryIdByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(null); // when - LlmTaskPageStatusResult result = llmTaskQueryService.fetchPageStatus(command); + LlmTaskPageStatusResult result = llmTaskQueryService.fetchPageStatus(member, command); // then assertThat(result.status()).isEqualTo(NOT_REQUESTED); @@ -147,12 +166,13 @@ class LlmTaskQueryServiceTest { @Test void 작업_결과_확인시_존재하지_않는_문서ID로_요청한_경우_예외_발생() { // given - given(documentRepository.existsById(anyLong())).willReturn(false); + given(documentRepository.getById(eq(2L))).willThrow(NotFoundException.class); // when & then - assertAll(() -> assertThrows(NotFoundException.class, () -> llmTaskQueryService.findAllPagesResult(1L)), - () -> verify(documentRepository).existsById(anyLong()) - ); + assertAll(() -> assertThrows( + NotFoundException.class, + () -> llmTaskQueryService.findAllPagesResult(member, 2L) + )); } @Test @@ -160,17 +180,23 @@ class LlmTaskQueryServiceTest { // given Long documentId = 1L; List summaryResults = List.of(new SummaryPageContentResult(1, "요약 내용")); - List problemResults = List.of(new ProblemPageContentResult(1, "문제 내용"), + List problemResults = List.of( + new ProblemPageContentResult(1, "문제 내용"), new ProblemPageContentResult(2, "문제 내용") ); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getPageNumbersAndContentByDocumentId(documentId)).willReturn(summaryResults); given(problemRepository.getPageNumbersAndContentByDocumentId(documentId)).willReturn(problemResults); // when & then - assertAll(() -> assertThrows(InternalServerErrorException.class, () -> llmTaskQueryService.findAllPagesResult(1L)), - () -> verify(documentRepository).existsById(documentId), + assertAll( + () -> assertThrows( + InternalServerErrorException.class, + () -> llmTaskQueryService.findAllPagesResult(member, 1L) + ), () -> verify(summaryRepository).getPageNumbersAndContentByDocumentId(documentId), () -> verify(problemRepository).getPageNumbersAndContentByDocumentId(documentId) ); @@ -180,24 +206,28 @@ class LlmTaskQueryServiceTest { void 작업_결과_확인() { // given Long documentId = 1L; - List summaryResults = List.of(new SummaryPageContentResult(1, "요약 내용"), + List summaryResults = List.of( + new SummaryPageContentResult(1, "요약 내용"), new SummaryPageContentResult(2, "요약 내용") ); - List problemResults = List.of(new ProblemPageContentResult(1, "문제 내용"), + List problemResults = List.of( + new ProblemPageContentResult(1, "문제 내용"), new ProblemPageContentResult(2, "문제 내용") ); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getPageNumbersAndContentByDocumentId(documentId)).willReturn(summaryResults); given(problemRepository.getPageNumbersAndContentByDocumentId(documentId)).willReturn(problemResults); // when - LlmTaskAllPagesResult response = llmTaskQueryService.findAllPagesResult(documentId); + LlmTaskAllPagesResult response = llmTaskQueryService.findAllPagesResult(member, documentId); // then - assertAll(() -> assertEquals(documentId, response.documentId()), + assertAll( + () -> assertEquals(documentId, response.documentId()), () -> assertEquals(2, response.results().size()), - () -> verify(documentRepository).existsById(documentId), () -> verify(summaryRepository).getPageNumbersAndContentByDocumentId(documentId), () -> verify(problemRepository).getPageNumbersAndContentByDocumentId(documentId) ); @@ -212,12 +242,16 @@ class LlmTaskQueryServiceTest { String problemResult = "문제 내용"; LlmTaskPageResultCommand command = new LlmTaskPageResultCommand(documentId, pageNumber); - given(documentRepository.existsById(anyLong())).willReturn(true); - given(summaryRepository.getSummaryContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(summaryResult); - given(problemRepository.getProblemContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(problemResult); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + + given(summaryRepository.getSummaryContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn( + summaryResult); + given(problemRepository.getProblemContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn( + problemResult); // when - LlmTaskPageResult result = llmTaskQueryService.findPageResult(command); + LlmTaskPageResult result = llmTaskQueryService.findPageResult(member, command); // then assertAll( @@ -233,12 +267,14 @@ class LlmTaskQueryServiceTest { Integer pageNumber = 20; LlmTaskPageResultCommand command = new LlmTaskPageResultCommand(documentId, pageNumber); - given(documentRepository.existsById(anyLong())).willReturn(true); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + given(summaryRepository.getSummaryContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(null); given(problemRepository.getProblemContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(null); // when - LlmTaskPageResult result = llmTaskQueryService.findPageResult(command); + LlmTaskPageResult result = llmTaskQueryService.findPageResult(member, command); // then assertAll( @@ -255,12 +291,15 @@ class LlmTaskQueryServiceTest { String summaryResult = "요약 내용"; LlmTaskPageResultCommand command = new LlmTaskPageResultCommand(documentId, pageNumber); - given(documentRepository.existsById(anyLong())).willReturn(true); - given(summaryRepository.getSummaryContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(summaryResult); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + + given(summaryRepository.getSummaryContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn( + summaryResult); given(problemRepository.getProblemContentByDocumentIdAndPageNumber(documentId, pageNumber)).willReturn(null); // when & then - assertThrows(InternalServerErrorException.class, () -> llmTaskQueryService.findPageResult(command)); + assertThrows(InternalServerErrorException.class, () -> llmTaskQueryService.findPageResult(member, command)); } @Test @@ -268,11 +307,13 @@ class LlmTaskQueryServiceTest { // given Long documentId = 1L; - given(documentRepository.existsById(anyLong())).willReturn(true); given(summaryRepository.getSummaryIdsByDocumentId(documentId)).willReturn(Collections.emptyList()); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + // when - LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(documentId); + LlmTaskOverallStatusResult result = llmTaskQueryService.fetchOverallStatus(member, documentId); // then assertAll( @@ -287,11 +328,13 @@ class LlmTaskQueryServiceTest { // given Long documentId = 1L; - given(documentRepository.existsById(anyLong())).willReturn(true); given(summaryRepository.getPageNumbersAndContentByDocumentId(documentId)).willReturn(Collections.emptyList()); + given(documentRepository.getById(eq(1L))).willReturn(document); + willDoNothing().given(document).validateOwner(member); + // when - LlmTaskAllPagesResult result = llmTaskQueryService.findAllPagesResult(documentId); + LlmTaskAllPagesResult result = llmTaskQueryService.findAllPagesResult(member, documentId); // then assertAll( diff --git a/src/test/java/notai/llm/application/LlmTaskServiceTest.java b/src/test/java/notai/llm/application/LlmTaskServiceTest.java index 1f2f1b7..4082625 100644 --- a/src/test/java/notai/llm/application/LlmTaskServiceTest.java +++ b/src/test/java/notai/llm/application/LlmTaskServiceTest.java @@ -21,20 +21,21 @@ import notai.problem.domain.ProblemRepository; import notai.summary.domain.Summary; import notai.summary.domain.SummaryRepository; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; import org.mockito.InjectMocks; import org.mockito.Mock; -import static org.mockito.Mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + @ExtendWith(MockitoExtension.class) class LlmTaskServiceTest { @@ -69,7 +70,8 @@ class LlmTaskServiceTest { given(documentRepository.getById(anyLong())).willThrow(NotFoundException.class); // when & then - assertAll(() -> assertThrows(NotFoundException.class, () -> llmTaskService.submitTasks(command)), + assertAll( + () -> assertThrows(NotFoundException.class, () -> llmTaskService.submitTasks(command)), () -> verify(documentRepository, times(1)).getById(documentId), () -> verify(llmTaskRepository, never()).save(any(LlmTask.class)) ); @@ -84,9 +86,10 @@ 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"), + List annotations = List.of( + new Annotation(document, 1, 10, 20, 100, 50, "Annotation 1"), new Annotation(document, 1, 30, 40, 80, 60, "Annotation 2"), new Annotation(document, 2, 50, 60, 120, 70, "Annotation 3") ); @@ -103,7 +106,8 @@ class LlmTaskServiceTest { LlmTaskSubmitResult result = llmTaskService.submitTasks(command); // then - assertAll(() -> verify(documentRepository, times(1)).getById(documentId), + assertAll( + () -> verify(documentRepository, times(1)).getById(documentId), () -> verify(annotationRepository, times(1)).findByDocumentId(documentId), () -> verify(aiClient, times(2)).submitLlmTask(any(LlmTaskRequest.class)), () -> verify(llmTaskRepository, times(2)).save(any(LlmTask.class)) @@ -127,7 +131,8 @@ class LlmTaskServiceTest { Summary summary = mock(Summary.class); Problem problem = mock(Problem.class); - SummaryAndProblemUpdateCommand command = new SummaryAndProblemUpdateCommand(taskId, + SummaryAndProblemUpdateCommand command = new SummaryAndProblemUpdateCommand( + taskId, summaryContent, problemContent ); @@ -146,7 +151,8 @@ class LlmTaskServiceTest { Integer resultPageNumber = llmTaskService.updateSummaryAndProblem(command); // then - assertAll(() -> verify(taskRecord).completeTask(), + assertAll( + () -> verify(taskRecord).completeTask(), () -> verify(summary).updateContent(summaryContent), () -> verify(problem).updateContent(problemContent), () -> verify(llmTaskRepository, times(1)).save(taskRecord), diff --git a/src/test/java/notai/pageRecording/application/PageRecordingServiceTest.java b/src/test/java/notai/pageRecording/application/PageRecordingServiceTest.java index 085a4ca..a57ec5f 100644 --- a/src/test/java/notai/pageRecording/application/PageRecordingServiceTest.java +++ b/src/test/java/notai/pageRecording/application/PageRecordingServiceTest.java @@ -1,5 +1,8 @@ package notai.pageRecording.application; +import notai.document.domain.Document; +import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import notai.pageRecording.application.command.PageRecordingSaveCommand; import notai.pageRecording.application.command.PageRecordingSaveCommand.PageRecordingSession; import notai.pageRecording.domain.PageRecording; @@ -8,14 +11,14 @@ import notai.recording.domain.RecordingRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.mockito.BDDMockito.given; import org.mockito.InjectMocks; import org.mockito.Mock; -import static org.mockito.Mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; +import static org.mockito.BDDMockito.*; + @ExtendWith(MockitoExtension.class) class PageRecordingServiceTest { @@ -28,23 +31,36 @@ class PageRecordingServiceTest { @Mock private RecordingRepository recordingRepository; + @Mock + private DocumentRepository documentRepository; + + @Mock + private Member member; + + @Mock + private Document document; + @Test void 페이지_넘김_이벤트에_따라_페이지별_녹음_시간을_저장() { // given Long recordingId = 1L; Long documentId = 1L; - PageRecordingSaveCommand command = new PageRecordingSaveCommand(recordingId, + PageRecordingSaveCommand command = new PageRecordingSaveCommand( + recordingId, documentId, List.of(new PageRecordingSession(1, 100.0, 185.5), new PageRecordingSession(5, 185.5, 290.3)) ); Recording foundRecording = mock(Recording.class); given(recordingRepository.getById(recordingId)).willReturn(foundRecording); - given(foundRecording.isRecordingOwnedByDocument(documentId)).willReturn(true); + + given(documentRepository.getById(anyLong())).willReturn(document); + willDoNothing().given(document).validateOwner(member); + willDoNothing().given(foundRecording).validateDocumentOwnership(any(Document.class)); // when - pageRecordingService.savePageRecording(command); + pageRecordingService.savePageRecording(member, command); // then verify(pageRecordingRepository, times(2)).save(any(PageRecording.class)); diff --git a/src/test/java/notai/recording/application/RecordingServiceTest.java b/src/test/java/notai/recording/application/RecordingServiceTest.java index c97dde0..d0545ad 100644 --- a/src/test/java/notai/recording/application/RecordingServiceTest.java +++ b/src/test/java/notai/recording/application/RecordingServiceTest.java @@ -6,10 +6,12 @@ import notai.common.utils.FileManager; import notai.document.domain.Document; import notai.document.domain.DocumentRepository; +import notai.member.domain.Member; import notai.recording.application.command.RecordingSaveCommand; import notai.recording.application.result.RecordingSaveResult; import notai.recording.domain.Recording; import notai.recording.domain.RecordingRepository; +import notai.stt.application.SttTaskService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.mock; @ExtendWith(MockitoExtension.class) @@ -41,12 +44,18 @@ class RecordingServiceTest { @Mock private DocumentRepository documentRepository; + @Mock + private SttTaskService sttTaskService; + @Spy private final AudioDecoder audioDecoder = new AudioDecoder(); @Spy private final FileManager fileManager = new FileManager(); + @Mock + private Member member; + @BeforeEach void setUp() { ReflectionTestUtils.setField(recordingService, "audioBasePath", "src/main/resources/audio/"); @@ -70,7 +79,7 @@ void setUp() { // when & then assertThrows(BadRequestException.class, () -> { - recordingService.saveRecording(command); + recordingService.saveRecording(member, command); }); } @@ -91,8 +100,10 @@ void setUp() { given(recordingRepository.save(any(Recording.class))).willReturn(savedRecording); given(document.getName()).willReturn("안녕하세요백종원입니다"); + willDoNothing().given(sttTaskService).submitSttTask(any()); + // when - RecordingSaveResult result = recordingService.saveRecording(command); + RecordingSaveResult result = recordingService.saveRecording(member, command); // then FilePath filePath = FilePath.from("안녕하세요백종원입니다_1.mp3");