Skip to content

Conversation

@catturtle123
Copy link
Contributor

@catturtle123 catturtle123 commented Aug 17, 2025

📌 PR 개요

dataFile 삭제 기능 추가

🔍 관련 이슈

✨ 변경 사항

📸 스크린샷 / 동작 확인 결과 (선택)

필요하다면 이미지나 동영상 첨부.

✅ 체크리스트

  • 코드 스타일을 준수했습니다.
  • 불필요한 주석이나 콘솔 로그를 제거했습니다.
  • 관련 테스트를 작성/수정했습니다.
  • 로컬 환경에서 모든 기능이 정상 작동함을 확인했습니다.

🗒️ 추가 메모

기타 참고 사항을 적어주세요.

Summary by CodeRabbit

  • 신규 기능
    • 데이터 파일 삭제 기능 추가: 이제 업로드된 데이터 파일을 삭제할 수 있으며, 관련 태그도 함께 정리됩니다.
    • 삭제 요청 성공 시 확인 메시지를 반환합니다.
    • 존재하지 않는 파일 삭제 시 적절한 오류 응답을 제공합니다.

@catturtle123 catturtle123 self-assigned this Aug 17, 2025
@catturtle123 catturtle123 added the feature New feature or request label Aug 17, 2025
@catturtle123 catturtle123 linked an issue Aug 17, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Aug 17, 2025

Walkthrough

DELETE 엔드포인트(/api/v1/data-files/{dataFilesId})가 추가되어 서비스 계층에서 트랜잭션으로 파일과 연관 태그를 순차적으로 하드 삭제합니다. 리포지토리에 ID 조회(findDataFileById)와 태그 일괄 삭제(deleteAllByDataFile) 메서드가 추가되었습니다.

Changes

Cohort / File(s) Summary
Controller: 삭제 엔드포인트 추가
src/main/kotlin/.../controller/DataFileController.kt
DELETE /api/v1/data-files/{dataFilesId} 추가. 서비스 호출 후 ApiResponse.ok(null, "데이터 삭제 완료") 반환.
Service: 하드 삭제 트랜잭션
src/main/kotlin/.../service/DataFileService.kt
deleteFile(dataFilesId) 추가. ID로 조회 후 없으면 FileException(NOT_FOUND) 발생, 태그 일괄 삭제 → 파일 삭제. @transactional 적용.
Repository: DataFile 조회
src/main/kotlin/.../repository/DataFileRepository.kt
findDataFileById(dataFileId: Long): DataFile? 추가.
Repository: DataFileTag 일괄 삭제
src/main/kotlin/.../repository/DataFileTagRepository.kt
deleteAllByDataFile(dataFile: DataFile) 추가. 기존 findTagsByDataFile 유지.

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant Controller as DataFileController
  participant Service as DataFileService
  participant TagRepo as DataFileTagRepository
  participant FileRepo as DataFileRepository

  Client->>Controller: DELETE /api/v1/data-files/{id}
  Controller->>Service: deleteFile(id)
  Service->>FileRepo: findDataFileById(id)
  alt 존재하지 않음
    Service-->>Controller: throw FileException(NOT_FOUND)
    Controller-->>Client: 404 (ApiResponse)
  else 존재함
    Service->>TagRepo: deleteAllByDataFile(dataFile)
    Service->>FileRepo: delete(dataFile)
    Service-->>Controller: 완료
    Controller-->>Client: 200 ApiResponse(null, "데이터 삭제 완료")
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
문서 삭제 추가 (#4)
바로 hardDelete (#4)

Assessment against linked issues: Out-of-scope changes

(해당 없음)

Poem

초록 들판 깡총깡총, 삭제 버튼 띵동-띵!
태그도 쓱, 파일도 싹, 바람처럼 클린링.
아이디 확인, 없으면 휙—404의 그림자.
있으면 한 번에 툭, 하드딜리트의 마술사.
오늘도 레포 깔끔, 토끼는 박수 짝짝! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#4-document-delete

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa92c and 95a6a46.

📒 Files selected for processing (7)
  • src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt (2 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt (1 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt (1 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileTagRepository.kt (1 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt (3 hunks)
  • src/main/resources/application.yml (1 hunks)
  • src/test/kotlin/simplerag/ragback/domain/document/service/DataFileServiceTest.kt (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileTagRepository.kt (2)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFile.kt (1)
  • name (7-32)
src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt (3)
src/main/kotlin/simplerag/ragback/domain/index/entity/DataFileIndex.kt (1)
  • name (7-22)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFile.kt (1)
  • name (7-32)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt (2)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFile.kt (1)
  • name (7-32)
src/main/kotlin/simplerag/ragback/domain/index/entity/DataFileIndex.kt (1)
  • name (7-22)
src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt (1)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt (3)
src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileRequestDTO.kt (2)
  • message (13-20)
  • min (7-11)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFile.kt (1)
  • name (7-32)
src/test/kotlin/simplerag/ragback/domain/document/service/DataFileServiceTest.kt (1)
src/main/kotlin/simplerag/ragback/global/util/FileConvertUtil.kt (1)
  • sha256Hex (13-16)
🔇 Additional comments (6)
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt (1)

3-4: Slice/Pageable 사용은 적절합니다.

카운트 쿼리를 피하는 Slice 반환형 선택 좋습니다. 키셋 전환 시에도 Slice를 유지하면 hasNext 계산이 용이합니다.

src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt (2)

13-13: 필요 DTO import 추가 확인

신규 GET 응답 타입(DataFileDetailResponseList) 사용을 위한 import 추가 적절합니다.


54-60: 경로 변수 바인딩 및 연관 엔티티 삭제 로직 검증

검증 결과, DataFileService.deleteFile 내부에서 태그 연관 삭제(dataFileTagRepository.deleteAllByDataFile(dataFile) — DataFileService.kt:108)는 구현되어 있습니다.
다만 인덱스 연관 삭제(수동 로직 또는 JPA cascade) 설정 여부를 반드시 확인해주세요.

• Controller: @PathVariable("dataFilesId") dataFileId로 명시적 바인딩 및 변수명 일관성 유지
• Service: 태그 연관 삭제 로직 확인(DataFileService.kt:108)
• 추가 검증: DataFileIndex 엔티티에 대한 연관 삭제(또는 cascade) 구현 여부 확인

-    @DeleteMapping("/{dataFilesId}")
-    fun deleteFile(
-        @PathVariable dataFilesId: Long,
-    ): ApiResponse<Unit> {
-        dataFileService.deleteFile(dataFilesId)
-        return ApiResponse.ok(null, "데이터 삭제 완료")
-    }
+    @DeleteMapping("/{dataFilesId}")
+    fun deleteFile(
+        @PathVariable("dataFilesId") dataFileId: Long,
+    ): ApiResponse<Unit> {
+        dataFileService.deleteFile(dataFileId)
+        return ApiResponse.ok(null, "데이터 삭제 완료")
+    }

REST 관점에서는 204 No Content가 일반적이지만, 프로젝트의 ApiResponse 컨벤션을 유지한다면 200 OK도 수용 가능합니다.

src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt (3)

3-5: 필요 import 추가 확인

DataFile, LocalDateTime 사용을 위한 import 추가 적절합니다.


13-13: 사소: 후행 쉼표 유지 OK

Kotlin 포매터와 diff 최소화를 위해 후행 쉼표 유지하는 접근 문제 없습니다.


45-48: TagDTO 설계 적절

필수 정보만 노출하며 간결합니다. 추후 태그 색상/설명 등 확장 필요 시 별도 필드 추가로 대응 가능해 보입니다.

Comment on lines 45 to 52
@GetMapping
fun getDataFiles(
@RequestParam(name = "cursor") cursor: Long,
@RequestParam(name = "take") take: Int,
): ApiResponse<DataFileDetailResponseList> {
val data = dataFileService.getDataFiles(cursor, take)
return ApiResponse.ok(data)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

페이지네이션 파라미터에 기본값/검증 추가 제안

  • 초기 페이지 호출 시 cursor가 필수가 되면 400이 발생합니다. cursor는 선택적으로 받고 기본값(예: 0)을 둬주세요.
  • take는 과도한 값 방지를 위해 상한선을 두는 것이 안전합니다(예: 1~100).

다음과 같이 수정하면 초기 페이지 호출과 파라미터 검증이 개선됩니다:

-    fun getDataFiles(
-        @RequestParam(name = "cursor") cursor: Long,
-        @RequestParam(name = "take") take: Int,
-    ): ApiResponse<DataFileDetailResponseList> {
-        val data = dataFileService.getDataFiles(cursor, take)
-        return ApiResponse.ok(data)
-    }
+    fun getDataFiles(
+        @RequestParam(name = "cursor", required = false, defaultValue = "0") cursor: Long?,
+        @RequestParam(name = "take", defaultValue = "20") @Min(1) @Max(100) take: Int,
+    ): ApiResponse<DataFileDetailResponseList> {
+        val data = dataFileService.getDataFiles(cursor ?: 0L, take)
+        return ApiResponse.ok(data)
+    }

위 변경에 필요한 import(파일 상단에 추가):

import jakarta.validation.constraints.Min
import jakarta.validation.constraints.Max

다음 스크립트로 기본값 적용과 검증 추가 후, 컨트롤러 파라미터 사용 위치와 서비스 시그니처를 간단히 점검할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# getDataFiles 시그니처와 호출부 점검
rg -n -C2 --type=kotlin 'fun\s+getDataFiles\(' src/main | sed -n '1,200p'
rg -n -C2 --type=kotlin 'dataFileService\.getDataFiles\(' src/main

Length of output: 1957


🏁 Script executed:

#!/bin/bash
# Check if @Validated is present and display class annotations
rg -n "@Validated" src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt || echo "No @Validated annotation found"
rg -n -C3 "class\s+DataFileController" src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt

Length of output: 405


페이지네이션 기본값 및 요청 파라미터 검증 추가

컨트롤러에 이미 @Validated가 선언되어 있어, 파라미터 수준의 Bean Validation이 적용됩니다. 아래와 같이 수정해 초기 호출 시 cursor 기본값을 제공하고, take 값에 상한/하한 검증을 추가해주세요.

  • cursor를 선택(Optional) 파라미터로 받고 기본값 "0"을 지정한 뒤, null 병합 처리
  • take@Min(1)·@Max(100) 검증과 기본값 "20" 설정
-    fun getDataFiles(
-        @RequestParam(name = "cursor") cursor: Long,
-        @RequestParam(name = "take") take: Int,
-    ): ApiResponse<DataFileDetailResponseList> {
-        val data = dataFileService.getDataFiles(cursor, take)
-        return ApiResponse.ok(data)
-    }
+    fun getDataFiles(
+        @RequestParam(name = "cursor", required = false, defaultValue = "0") cursor: Long?,
+        @RequestParam(name = "take", defaultValue = "20") @Min(1) @Max(100) take: Int,
+    ): ApiResponse<DataFileDetailResponseList> {
+        val data = dataFileService.getDataFiles(cursor ?: 0L, take)
+        return ApiResponse.ok(data)
+    }

필요한 import (파일 상단에 추가):

import jakarta.validation.constraints.Min
import jakarta.validation.constraints.Max
🤖 Prompt for AI Agents
In
src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt
around lines 45 to 52, make pagination params safe: change cursor to an optional
request param with defaultValue = "0" and merge null to 0 before calling
service, and add validation and a default for take by annotating it with @Min(1)
and @Max(100) and setting defaultValue = "20"; also add the imports
jakarta.validation.constraints.Min and jakarta.validation.constraints.Max at the
file top.

Comment on lines 15 to 20
data class DataFileDetailResponseList(
val dataFilePreviewResponseList: List<DataFileDetailResponse>,
val cursor: Long?,
val hasNext: Boolean,
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

필드명 혼동: Detail 리스트인데 Preview 라벨 사용됨

DataFileDetailResponseList 내 컬렉션 필드명이 dataFilePreviewResponseList로 되어 있어 API 소비자에게 혼란을 줄 수 있습니다. Detail 응답에는 중립적인 이름(items) 또는 dataFileDetailResponseList가 더 명확합니다.

권장 수정:

 data class DataFileDetailResponseList(
-    val dataFilePreviewResponseList: List<DataFileDetailResponse>,
+    val items: List<DataFileDetailResponse>,
     val cursor: Long?,
     val hasNext: Boolean,
 )

이 변경은 직렬화 JSON 필드명이 바뀌므로(계약 변경), 서비스/테스트/클라이언트 사용처 점검이 필요합니다.

다음 스크립트로 Kotlin 코드에서 명명 인자를 사용하는 생성 호출을 우선적으로 찾아 일괄 수정 범위를 파악하세요.


🏁 Script executed:

#!/bin/bash
# Detail 리스트에 대한 명명 인자 사용 여부 확인
rg -n -C2 --type=kotlin '\bDataFileDetailResponseList\s*\('
rg -n -C2 --type=kotlin '\bdataFilePreviewResponseList\s*='

Length of output: 1166


DTO 필드명 변경: dataFilePreviewResponseListitems

DataFileDetailResponseList 생성 호출에서 명명 인자를 사용하지 않으므로 position 기반 호출에 영향이 없습니다. 다만 JSON 직렬화 시 필드명이 변경되므로 서비스, 테스트, 클라이언트 사용처를 반드시 점검해야 합니다.

수정 대상:

  • src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt (DTO 정의)
  • src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt (생성자 호출 – positional argument로 영향 없음)
  • 그 외 API 문서·테스트·클라이언트 코드 전반

제안된 diff:

 data class DataFileDetailResponseList(
-    val dataFilePreviewResponseList: List<DataFileDetailResponse>,
+    val items: List<DataFileDetailResponse>,
     val cursor: Long?,
     val hasNext: Boolean,
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data class DataFileDetailResponseList(
val dataFilePreviewResponseList: List<DataFileDetailResponse>,
val cursor: Long?,
val hasNext: Boolean,
)
data class DataFileDetailResponseList(
val items: List<DataFileDetailResponse>,
val cursor: Long?,
val hasNext: Boolean,
)
🤖 Prompt for AI Agents
In src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt
around lines 15–20, rename the DTO field dataFilePreviewResponseList to items
and update all usages; to avoid breaking existing JSON clients either (A) add a
JSON mapping annotation so the serialized name remains
"dataFilePreviewResponseList" (e.g., annotate the new property with
@get:JsonProperty("dataFilePreviewResponseList") / @JsonProperty on getter) or
(B) update all consumers, tests, API docs and client code to use the new "items"
field name; also review
src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt for
constructor calls (positional calls not broken) and update imports/usages and
tests accordingly so compilation and serialization behavior remain correct.

Comment on lines 21 to 43
data class DataFileDetailResponse(
var id: Long?,
val title: String,
val type: String,
val lastModified: LocalDateTime,
val sizeMB: Double,
val tags: List<TagDTO>,
val sha256: String,
) {
companion object {
fun of(dataFile: DataFile, tags: List<TagDTO>): DataFileDetailResponse {
return DataFileDetailResponse(
dataFile.id,
dataFile.title,
dataFile.type,
dataFile.updatedAt,
dataFile.sizeBytes / (1024.0 * 1024.0),
tags,
dataFile.sha256,
)
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

DTO 불변성 강화 및 sizeMB 표현 개선 제안

  • id를 var로 둘 이유가 없어 보입니다. DTO는 불변(val)로 유지하는 것이 안전합니다.
  • sizeMB는 소수점이 과도하게 길어질 수 있으니 표시 목적에 맞춰 반올림(예: 소수점 둘째 자리) 적용을 고려해 보세요.

다음과 같이 개선할 수 있습니다:

 data class DataFileDetailResponse(
-    var id: Long?,
+    val id: Long?,
     val title: String,
     val type: String,
     val lastModified: LocalDateTime,
     val sizeMB: Double,
     val tags: List<TagDTO>,
     val sha256: String,
 ) {
     companion object {
         fun of(dataFile: DataFile, tags: List<TagDTO>): DataFileDetailResponse {
             return DataFileDetailResponse(
                 dataFile.id,
                 dataFile.title,
                 dataFile.type,
                 dataFile.updatedAt,
-                dataFile.sizeBytes / (1024.0 * 1024.0),
+                ((dataFile.sizeBytes / (1024.0 * 1024.0)).let { kotlin.math.round(it * 100) / 100.0 }),
                 tags,
                 dataFile.sha256,
             )
         }
     }
 }

반올림 사용 시 다음 import가 필요합니다(파일 상단):

import kotlin.math.round

id를 non-null로 강제(Long)하려면 생성 시점에 항상 영속 상태임이 보장되어야 하므로, 그 전제 확인 후 적용하세요.

🤖 Prompt for AI Agents
In src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt
around lines 21 to 43, make the DTO fields immutable by changing id from var to
val (or to non-null Long only if you can guarantee persistent id at creation),
round sizeMB to two decimal places for display (e.g. compute sizeMB =
round(dataFile.sizeBytes / (1024.0 * 1024.0) * 100) / 100), and add the required
import kotlin.math.round at the top of the file; keep id nullable Long? only if
you cannot guarantee persistence at construction time.

Comment on lines 11 to 13
fun findByOrderById(pageable: Pageable): Slice<DataFile>

fun findDataFileById(dataFileId: Long): DataFile?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

"cursor" 의미와 구현이 어긋납니다: 페이지 번호가 아닌 키셋(cursor) 페이지네이션으로 전환 필요

현재 서비스는 응답에 마지막 아이템의 id를 cursor로 반환하지만, 조회는 PageRequest.of(cursor.toInt(), take)로 "cursor=페이지 번호"로 해석합니다. 두 번째 페이지부터 오동작합니다.

키셋 페이지네이션으로 전환 제안:

  • 리포지토리: 마지막 id를 기준으로 이후 레코드를 가져오도록 메서드 교체
  • 서비스: Page index는 항상 0, 조건은 "id > lastId"

아래처럼 수정 제안합니다.

-    fun findByOrderById(pageable: Pageable): Slice<DataFile>
-
-    fun findDataFileById(dataFileId: Long): DataFile?
+    fun findByIdGreaterThanOrderById(lastIdExclusive: Long, pageable: Pageable): Slice<DataFile>
+    // NOTE: 단건 조회는 기본 제공되는 findById를 사용하는 편이 일관성/가독성에 좋습니다.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun findByOrderById(pageable: Pageable): Slice<DataFile>
fun findDataFileById(dataFileId: Long): DataFile?
fun findByIdGreaterThanOrderById(lastIdExclusive: Long, pageable: Pageable): Slice<DataFile>
// NOTE: 단건 조회는 기본 제공되는 findById를 사용하는 편이 일관성/가독성에 좋습니다.
🤖 Prompt for AI Agents
In
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt
around lines 11-13, the repository currently assumes pageable page-index
semantics while the API returns a cursor (last item id); change to keyset
pagination by replacing findByOrderById(pageable: Pageable) with a method that
accepts a lastId and pageable/limit and returns records where id > lastId
ordered by id (ascending) so the DB selects records after the cursor; then
update callers/service to always use PageRequest.of(0, take) (or equivalent
limit-only pageable) and pass the lastId as the cursor condition (id > lastId)
so pagination uses the last item id instead of page number.

Comment on lines 13 to 21
@Query("""
SELECT DISTINCT t
FROM DataFileTag dft
JOIN dft.tag t
WHERE dft.dataFile = :dataFile
""")
fun findTagsByDataFile(@Param("dataFile") dataFile: DataFile): List<Tag>

fun deleteAllByDataFile(dataFile: DataFile)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

N+1 쿼리 유발: 다건 DataFile에 대한 태그를 한 번에 가져오는 메서드를 추가하세요.

현재 서비스는 파일마다 findTagsByDataFile를 호출하여 N+1이 발생합니다. 다음과 같은 일괄 조회용 쿼리(Projections 또는 엔티티 반환)를 도입해, 한 번의 쿼리로 모든 태그를 가져온 뒤 메모리에서 그룹핑하는 것을 권장합니다.

예시(프로젝션 사용):

interface DataFileTagProjection {
    fun getDataFileId(): Long
    fun getTagId(): Long
    fun getTagName(): String
}

@Query("""
    SELECT dft.dataFile.id as dataFileId, t.id as tagId, t.name as tagName
    FROM DataFileTag dft
    JOIN dft.tag t
    WHERE dft.dataFile in :dataFiles
""")
fun findTagsByDataFiles(@Param("dataFiles") dataFiles: Collection<DataFile>): List<DataFileTagProjection>

서비스에서는 groupBy { it.dataFileId }로 매핑하면 N+1을 제거할 수 있습니다.

🤖 Prompt for AI Agents
In
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileTagRepository.kt
around lines 13 to 21, the current findTagsByDataFile method causes N+1 queries
when called per DataFile; add a batch query method that accepts a collection of
DataFile and returns a lightweight projection (containing dataFileId, tagId,
tagName) or entities so the service can fetch all tags in one query and then
group them in memory (e.g., groupBy dataFileId) to map tags to each DataFile;
implement the projection interface, add a @Query method that selects
dft.dataFile.id, t.id, t.name for dft.dataFile IN :dataFiles, and use this new
repository method from the service to replace per-file calls.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 95a6a46 and 58971c6.

📒 Files selected for processing (4)
  • src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt (1 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt (1 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileTagRepository.kt (1 hunks)
  • src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt (1)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/controller/DataFileController.kt (3)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFile.kt (1)
  • name (7-32)
src/main/kotlin/simplerag/ragback/domain/index/entity/DataFileIndex.kt (1)
  • name (7-22)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileTagRepository.kt (1)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFileTag.kt (1)
  • name (6-24)
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt (2)
src/main/kotlin/simplerag/ragback/domain/document/entity/DataFile.kt (1)
  • name (7-32)
src/main/kotlin/simplerag/ragback/domain/document/dto/DataFileResponseDTO.kt (1)
  • id (7-10)

Comment on lines +56 to +62
@DeleteMapping("/{dataFilesId}")
fun deleteFile(
@PathVariable dataFilesId: Long,
): ApiResponse<Unit> {
dataFileService.deleteFile(dataFilesId)
return ApiResponse.ok(null, "데이터 삭제 완료")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

DELETE 응답은 204 No Content가 더 적합합니다(본문 없이 성공만 알림)

현재 200 OK + 본문 메시지를 반환합니다. REST 관점에서는 204 No Content가 일반적입니다. 전역 응답 규약(ApiResponse 래퍼 강제)과의 정합성을 확인한 뒤 적용을 고려해 주세요.

-    @DeleteMapping("/{dataFilesId}")
-    fun deleteFile(
-        @PathVariable dataFilesId: Long,
-    ): ApiResponse<Unit> {
-        dataFileService.deleteFile(dataFilesId)
-        return ApiResponse.ok(null, "데이터 삭제 완료")
-    }
+    @DeleteMapping("/{dataFilesId}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    fun deleteFile(
+        @PathVariable dataFilesId: Long,
+    ) {
+        dataFileService.deleteFile(dataFilesId)
+    }

검토 포인트:

  • 기존 API가 일관적으로 ApiResponse 래퍼를 강제한다면, 200 OK 유지도 허용될 수 있습니다. 팀 규약 확인 바랍니다.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@DeleteMapping("/{dataFilesId}")
fun deleteFile(
@PathVariable dataFilesId: Long,
): ApiResponse<Unit> {
dataFileService.deleteFile(dataFilesId)
return ApiResponse.ok(null, "데이터 삭제 완료")
}
@DeleteMapping("/{dataFilesId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteFile(
@PathVariable dataFilesId: Long,
) {
dataFileService.deleteFile(dataFilesId)
}


fun findByIdGreaterThanOrderById(cursorId: Long, pageable: Pageable): Slice<DataFile>

fun findDataFileById(dataFileId: Long): DataFile?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

불필요한 커스텀 조회 메서드: JpaRepository#findById 사용으로 단순화하세요

Line 13: findDataFileByIdJpaRepository#findById와 역할이 중복됩니다. 표준 메서드를 사용하면 일관성과 유지보수성이 좋아집니다.
[suggest_optional_refactor]

다음 변경을 제안합니다.

-    fun findDataFileById(dataFileId: Long): DataFile?

Service 사용부 교체(참고용):

val dataFile = dataFileRepository.findById(dataFilesId).orElseThrow {
    FileException(ErrorCode.NOT_FOUND, dataFilesId.toString())
}

사용처 점검 스크립트:


🏁 Script executed:

#!/bin/bash
rg -n --type=kt '\bfindDataFileById\('

Length of output: 67


rg가 kt 파일 타입을 인식하지 못해 검색이 실행되지 않았습니다. 호출 위치를 정확히 찾기 위해 아래 스크립트로 다시 한번 확인해주세요.


🏁 Script executed:

#!/bin/bash
# Kotlin 파일(.kt) 전체에서 findDataFileById 호출 위치 검색
rg -n '\bfindDataFileById\(' -g '*.kt'

Length of output: 357


불필요한 커스텀 메서드 제거 및 표준 findById 활용 제안

DataFileRepository의 findDataFileByIdJpaRepository#findById와 중복되므로 제거하고, 서비스 호출부에서 표준 메서드를 사용해 일관성을 높이길 권장합니다.

  • 수정 대상:

    • src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt (라인 13)
    • src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt (라인 104)
  • 제안 변경:

--- a/src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt
+++ b/src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt
@@ -13
-    fun findDataFileById(dataFileId: Long): DataFile?
--- a/src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt
+++ b/src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt
@@ -104
-        val dataFile = dataFileRepository.findDataFileById(dataFilesId)
-            ?: throw FileException(ErrorCode.NOT_FOUND, dataFilesId.toString())
+        val dataFile = dataFileRepository.findById(dataFilesId)
+            .orElseThrow { FileException(ErrorCode.NOT_FOUND, dataFilesId.toString()) }

전체 사용처가 findDataFileById 호출로 작성된 부분이 더 있는지 확인 후 안전하게 제거하세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun findDataFileById(dataFileId: Long): DataFile?

Comment on lines +20 to 21
fun deleteAllByDataFile(dataFile: DataFile)
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

삭제 행 수 반환으로 관측성 강화

Line 20: 파생 삭제 쿼리는 삭제 건수를 반환할 수 있습니다. 모니터링/검증에 유용하니 반환형을 Long으로 변경을 권장합니다. @Modifying은 파생 delete 메서드에선 불필요합니다.

-    fun deleteAllByDataFile(dataFile: DataFile)
+    fun deleteAllByDataFile(dataFile: DataFile): Long
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun deleteAllByDataFile(dataFile: DataFile)
}
fun deleteAllByDataFile(dataFile: DataFile): Long
}
🤖 Prompt for AI Agents
In
src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileTagRepository.kt
around lines 20-21, the derived delete method should return the number of
deleted rows for observability; change the signature from fun
deleteAllByDataFile(dataFile: DataFile) to return Long (e.g., fun
deleteAllByDataFile(dataFile: DataFile): Long) and remove any unnecessary
@Modifying annotation from this derived delete method so that the repository
returns the deleted count for monitoring/verification.

Comment on lines +102 to +112
@Transactional
fun deleteFile(dataFilesId: Long) {
val dataFile = dataFileRepository.findDataFileById(dataFilesId) ?: throw FileException(
ErrorCode.NOT_FOUND,
dataFilesId.toString()
)

dataFileTagRepository.deleteAllByDataFile(dataFile)

dataFileRepository.delete(dataFile)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

하드 삭제 누락: S3 원본 및 연관 인덱스(DataFileIndex) 미삭제로 인한 FK 위반/유기 객체 위험

  • Line 104: 커스텀 findDataFileById 사용 → 표준 findById(...).orElseThrow { ... }로 단순화 권장.
  • Lines 109-111: 태그만 삭제 후 본문 삭제. DataFileIndex(다대다 조인 테이블) 정리는 누락되어 FK 제약 위반 가능.
  • S3 원본 파일 삭제가 수행되지 않아 “hard delete” 요구사항에 미달. 트랜잭션 커밋 후(afterCommit) 삭제로 정합성 보장 필요.

아래와 같이 수정해 주세요.

[offer_assistance]

 @Transactional
 fun deleteFile(dataFilesId: Long) {
-    val dataFile = dataFileRepository.findDataFileById(dataFilesId) ?: throw FileException(
-        ErrorCode.NOT_FOUND,
-        dataFilesId.toString()
-    )
-
-    dataFileTagRepository.deleteAllByDataFile(dataFile)
-
-    dataFileRepository.delete(dataFile)
+    val dataFile = dataFileRepository.findById(dataFilesId).orElseThrow {
+        FileException(ErrorCode.NOT_FOUND, dataFilesId.toString())
+    }
+    val fileUrl = dataFile.fileUrl
+
+    // 1) 태그 삭제
+    dataFileTagRepository.deleteAllByDataFile(dataFile)
+    // 2) 인덱스 삭제 (FK 제약 방지)
+    dataFileIndexRepository.deleteAllByDataFile(dataFile)
+    // 3) 파일 레코드 삭제
+    dataFileRepository.delete(dataFile)
+    // 4) 트랜잭션 커밋 후 S3 삭제 (DB 롤백 시 외부 상태 불일치 방지)
+    TransactionSynchronizationManager.registerSynchronization(object : TransactionSynchronization {
+        override fun afterCommit() {
+            runCatching { s3Util.deleteByUrl(fileUrl) }
+        }
+    })
 }

서비스 주입부(클래스 생성자)에 리포지토리 의존성 추가 필요:

class DataFileService(
    private val dataFileRepository: DataFileRepository,
    private val tagRepository: TagRepository,
    private val dataFileTagRepository: DataFileTagRepository,
    private val s3Util: S3Util,
    private val dataFileIndexRepository: DataFileIndexRepository, // 추가
)

DataFileIndexRepository가 없다면 추가:

package simplerag.ragback.domain.index.repository

import org.springframework.data.jpa.repository.JpaRepository
import simplerag.ragback.domain.document.entity.DataFile
import simplerag.ragback.domain.index.entity.DataFileIndex

interface DataFileIndexRepository : JpaRepository<DataFileIndex, Long> {
    fun deleteAllByDataFile(dataFile: DataFile): Long
}

검증 스크립트(연관 삭제/캐스케이드 유무 확인):

원하시면 @TransactionalEventListener 기반의 이벤트-드리븐 삭제로도 리팩터링해 드리겠습니다.


🏁 Script executed:

#!/bin/bash
echo "== DataFileIndexRepository / deleteAllByDataFile 존재 확인 =="
rg -n -C2 -P 'interface\\s+DataFileIndexRepository|delete(All)?ByDataFile' --type=kt

echo "== 관계 캐스케이드/OnDelete 애너테이션 존재 확인 =="
rg -n -C2 -P '@OnDelete|cascade\\s*=' --type=kt

echo "== 서비스에서 dataFileIndexRepository 사용 여부 =="
rg -n -C2 -P '\\bdataFileIndexRepository\\b' --type=kt

Length of output: 573


아직 DataFileIndex 관련 정보가 파악되지 않아 추가 확인이 필요합니다. 아래 스크립트로 .kt 파일 전반을 검색해 보겠습니다.


🏁 Script executed:

#!/bin/bash
echo "== DataFileIndexRepository 및 deleteAllByDataFile 존재 여부 =="
rg -n -C2 -P 'interface\s+DataFileIndexRepository|delete(All)?ByDataFile' -g '*.kt'

echo "== DataFileIndex 엔티티 정의 검색 =="
rg -n -C2 -P 'class\s+DataFileIndex' -g '*.kt'

echo "== @OnDelete 또는 cascade 애너테이션 검색 =="
rg -n -C2 -P '@OnDelete|cascade' -g '*.kt'

echo "== 서비스에서 dataFileIndexRepository 사용 여부 =="
rg -n -C2 '\bdataFileIndexRepository\b' -g '*.kt'

Length of output: 2087


아래 추가 스크립트로 관련 리포지토리 메소드, 서비스 생성자, 엔티티 매핑을 한 번 더 확인해 주세요.


🏁 Script executed:

#!/bin/bash
echo "== DataFileRepository 정의 및 findDataFileById 확인 =="
sed -n '1,50p' src/main/kotlin/simplerag/ragback/domain/document/repository/DataFileRepository.kt

echo "== DataFileService 생성자 및 필드 주입 확인 =="
sed -n '1,30p' src/main/kotlin/simplerag/ragback/domain/document/service/DataFileService.kt

echo "== DataFileIndex 엔티티 매핑 확인 =="
sed -n '1,50p' src/main/kotlin/simplerag/ragback/domain/index/entity/DataFileIndex.kt

Length of output: 3137


DataFileIndex 삭제 & S3 하드 삭제 미구현

현재 deleteFile 메서드는 태그만 삭제하고, 인덱스 테이블(data_files_indexes) 레코드와 S3 원본 파일을 제거하지 않아 아래 리스크가 있습니다.

  • FK 제약 위반 또는 인덱스 데이터 유기
  • S3 버킷에 남은 “휴지통” 없는 잔존 객체

■ 수정 제안

  • 표준 findById(...).orElseThrow{} 사용으로 예외 처리 간결화
  • DataFileIndexRepository를 주입 및 deleteAllByDataFile 호출로 인덱스 레코드 삭제
  • 트랜잭션 커밋 후(afterCommit) s3Util.deleteByUrl(fileUrl)로 S3 하드 삭제

■ 주요 변경점

 @Transactional
 fun deleteFile(dataFileId: Long) {
-   val dataFile = dataFileRepository.findDataFileById(dataFilesId) ?: throw FileException(
-       ErrorCode.NOT_FOUND, dataFilesId.toString()
-   )
+   val dataFile = dataFileRepository.findById(dataFileId)
+       .orElseThrow { FileException(ErrorCode.NOT_FOUND, dataFileId.toString()) }
+   val fileUrl = dataFile.fileUrl

     // 1) 태그 삭제
     dataFileTagRepository.deleteAllByDataFile(dataFile)
+    // 2) 인덱스 삭제
+    dataFileIndexRepository.deleteAllByDataFile(dataFile)
     // 3) DB 레코드 삭제
     dataFileRepository.delete(dataFile)
     // 4) 트랜잭션 커밋 후 S3 삭제
     TransactionSynchronizationManager.registerSynchronization(object : TransactionSynchronization {
         override fun afterCommit() {
             runCatching { s3Util.deleteByUrl(fileUrl) }
         }
     })
 }

■ 서비스 클래스 생성자

@Service
class DataFileService(
    private val dataFileRepository: DataFileRepository,
    private val dataFileTagRepository: DataFileTagRepository,
    private val tagRepository: TagRepository,
    private val s3Util: S3Util,
+   private val dataFileIndexRepository: DataFileIndexRepository
) { … }

DataFileIndexRepository 추가

package simplerag.ragback.domain.index.repository

import org.springframework.data.jpa.repository.JpaRepository
import simplerag.ragback.domain.document.entity.DataFile
import simplerag.ragback.domain.index.entity.DataFileIndex

interface DataFileIndexRepository : JpaRepository<DataFileIndex, Long> {
    fun deleteAllByDataFile(dataFile: DataFile)
}

위 변경사항을 반영해 주시기 바랍니다.

@catturtle123 catturtle123 merged commit 1d0aeea into develop Aug 17, 2025
1 check passed
@catturtle123 catturtle123 deleted the feature/#4-document-delete branch August 17, 2025 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] 문서 삭제 추가

2 participants