Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions systems/configuration/configuration-adapter-in/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ KOTLIN_DEPS = [
"@maven//:com_google_protobuf_protobuf_kotlin",
"@maven//:javax_annotation_javax_annotation_api",
"@maven//:org_springframework_boot_spring_boot_starter",
"@maven//:org_springframework_boot_spring_boot_starter_web",
"@maven//:org_springframework_boot_spring_boot_starter_validation",
"@maven//:com_fasterxml_jackson_module_jackson_module_kotlin",
"//contracts:configuration_grpc_java",
"//contracts:configuration_java_proto",
"//systems/configuration/configuration-domain:main",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package hs.kr.entrydsm.configuration.adapterin.common

import com.fasterxml.jackson.annotation.JsonInclude
import java.time.Instant

data class ApiResponse<T>(
val success: Boolean,
val data: T? = null,
val error: ErrorResponse? = null,
@get:JsonInclude(JsonInclude.Include.NON_NULL)
val timestamp: Instant? = null,
) {
companion object {
fun <T> success(data: T) = ApiResponse(success = true, data = data)

fun failure(errorCode: ErrorCode, message: String? = null) = ApiResponse<Nothing>(
success = false,
error = ErrorResponse(
code = errorCode.name,
message = message ?: errorCode.message,
status = errorCode.status.value(),
),
timestamp = Instant.now(),
)
}
}

data class ErrorResponse(
val code: String,
val message: String,
val status: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package hs.kr.entrydsm.configuration.adapterin.common

import hs.kr.entrydsm.configuration.domain.document.exception.FileDocumentNotFoundException
import hs.kr.entrydsm.configuration.domain.document.exception.FileTooLargeException
import hs.kr.entrydsm.configuration.domain.document.exception.InvalidFileFormatException
import hs.kr.entrydsm.configuration.domain.document.exception.InvalidFileNameException
import hs.kr.entrydsm.configuration.domain.document.exception.PresignFailedException
import hs.kr.entrydsm.configuration.domain.document.exception.StorageUploadFailedException
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MissingServletRequestParameterException
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.multipart.MaxUploadSizeExceededException

@RestControllerAdvice
class DocumentExceptionHandler {

private val log = LoggerFactory.getLogger(javaClass)

@ExceptionHandler(InvalidFileFormatException::class)
fun handleInvalidFileFormat(e: InvalidFileFormatException) =
respond(ErrorCode.INVALID_FILE_FORMAT, e)

@ExceptionHandler(FileTooLargeException::class, MaxUploadSizeExceededException::class)
fun handleFileTooLarge(e: Exception) =
respond(ErrorCode.FILE_TOO_LARGE, e)

@ExceptionHandler(FileDocumentNotFoundException::class)
fun handleFileNotFound(e: FileDocumentNotFoundException) =
respond(ErrorCode.FILE_NOT_FOUND, e)

@ExceptionHandler(
InvalidFileNameException::class,
MissingServletRequestParameterException::class,
MethodArgumentNotValidException::class,
IllegalArgumentException::class,
)
fun handleInvalidRequestParam(e: Exception) =
respond(ErrorCode.INVALID_REQUEST_PARAM, e)
Comment on lines +38 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# configuration 모듈에서 IllegalArgumentException을 생성하는 위치를 확인합니다.
rg -n -C 3 --glob '*.kt' \
  'throw\s+IllegalArgumentException|require\(|check\(' \
  systems/configuration

Repository: EntryDSM/entrydsm-platform

Length of output: 1400


🏁 Script executed:

#!/bin/bash
set -euo pipefail

handler='systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapter-in/common/DocumentExceptionHandler.kt'
# 실제 경로를 먼저 찾고, advice 구현과 예외 사용처 및 테스트를 함께 확인합니다.
fd -i 'DocumentExceptionHandler.kt|FileReferenceId.kt' systems/configuration
rg -n -C 5 --glob '*.kt' \
  '`@RestControllerAdvice`|`@ExceptionHandler`|handleInvalidRequestParam|handleUnexpected|FileReferenceId|IllegalArgumentException' \
  systems/configuration

Repository: EntryDSM/entrydsm-platform

Length of output: 11160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -i 'DocumentExceptionHandler.kt|FileReferenceId.kt' systems/configuration
rg -n -C 5 --glob '*.kt' \
  '`@RestControllerAdvice`|`@ExceptionHandler`|handleInvalidRequestParam|handleUnexpected|FileReferenceId|IllegalArgumentException' \
  systems/configuration

Repository: EntryDSM/entrydsm-platform

Length of output: 11160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- FileReferenceId 호출부 ---'
rg -n -C 6 --glob '*.kt' 'FileReferenceId\.(parse|of)\(' systems/configuration

echo '--- IllegalArgumentException 및 require/check 전체 사용처 ---'
rg -n -C 3 --glob '*.kt' \
  'IllegalArgumentException|require\(|check\(' systems/configuration

echo '--- 관련 테스트 파일 ---'
fd -i -t f 'test|Test|Spec' systems/configuration | head -200

Repository: EntryDSM/entrydsm-platform

Length of output: 192


🏁 Script executed:

#!/bin/bash
set -u

echo '--- FileReferenceId 모든 참조 ---'
rg -n -C 5 --glob '*.kt' 'FileReferenceId|parse\(' systems/configuration || true

echo '--- IllegalArgumentException 및 require/check 전체 사용처 ---'
rg -n -C 3 --glob '*.kt' \
  'IllegalArgumentException|require\(|check\(' systems/configuration || true

echo '--- configuration 테스트 파일 ---'
fd -i -t f 'test|Test|Spec' systems/configuration | head -200 || true

echo '--- 컨트롤러 및 advice 선언 ---'
rg -n -C 4 --glob '*.kt' \
  '`@RestController`|`@Controller`|`@RestControllerAdvice`|`@ControllerAdvice`' systems/configuration || true

Repository: EntryDSM/entrydsm-platform

Length of output: 7360


🏁 Script executed:

#!/bin/bash
set -u

echo '--- configuration Kotlin 파일 목록 ---'
git ls-files 'systems/configuration/**/*.kt' | head -300

echo '--- HTTP 엔드포인트 관련 선언 ---'
rg -n -C 4 --glob '*.kt' \
  'RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|Controller' \
  systems/configuration || true

echo '--- 예외 클래스 선언 및 상속 관계 ---'
rg -n -C 4 --glob '*.kt' \
  'class (InvalidFileNameException|InvalidFileFormatException|FileTooLargeException|FileDocumentNotFoundException|StorageUploadFailedException|PresignFailedException)|: .*Exception|: .*RuntimeException' \
  systems/configuration || true

Repository: EntryDSM/entrydsm-platform

Length of output: 38641


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

root = Path("systems/configuration")
kotlin_files = list(root.rglob("*.kt"))

direct_throws = []
controller_annotations = []
file_reference_refs = []

for path in kotlin_files:
    text = path.read_text()
    for match in re.finditer(r"throw\s+IllegalArgumentException", text):
        line = text.count("\n", 0, match.start()) + 1
        direct_throws.append((str(path), line))
    for match in re.finditer(r"^\s*@(?:RestController|Controller)\b", text, re.MULTILINE):
        line = text.count("\n", 0, match.start()) + 1
        controller_annotations.append((str(path), line))
    if "FileReferenceId" in text:
        file_reference_refs.append(str(path))

print("direct IllegalArgumentException throws:")
for item in direct_throws:
    print(item)

print("HTTP controller annotations:")
for item in controller_annotations:
    print(item)

print("files referencing FileReferenceId:")
for item in file_reference_refs:
    print(item)

print("DocumentExceptionHandler has global IllegalArgumentException mapping:",
      any(
          "DocumentExceptionHandler.kt" in path and "IllegalArgumentException::class" in Path(path).read_text()
          for path, _ in direct_throws
      ) if direct_throws else
      "systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/DocumentExceptionHandler.kt"
      in [str(p) for p in kotlin_files]
      and "IllegalArgumentException::class" in Path(
          "systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/DocumentExceptionHandler.kt"
      ).read_text())
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 612


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

root = Path("systems/configuration")
kotlin_files = list(root.rglob("*.kt"))

direct_throws = []
controller_annotations = []
file_reference_refs = []

for path in kotlin_files:
    text = path.read_text()
    for match in re.finditer(r"throw\s+IllegalArgumentException", text):
        line = text.count("\n", 0, match.start()) + 1
        direct_throws.append((str(path), line))
    for match in re.finditer(r"^\s*@(?:RestController|Controller)\b", text, re.MULTILINE):
        line = text.count("\n", 0, match.start()) + 1
        controller_annotations.append((str(path), line))
    if "FileReferenceId" in text:
        file_reference_refs.append(str(path))

print("direct IllegalArgumentException throws:")
for item in direct_throws:
    print(item)

print("HTTP controller annotations:")
for item in controller_annotations:
    print(item)

print("files referencing FileReferenceId:")
for item in file_reference_refs:
    print(item)

handler = next(
    (
        p for p in kotlin_files
        if p.name == "DocumentExceptionHandler.kt"
    ),
    None,
)
print(
    "DocumentExceptionHandler has global IllegalArgumentException mapping:",
    bool(handler and "IllegalArgumentException::class" in handler.read_text()),
)
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 611


IllegalArgumentException 매핑 범위를 요청 오류로 제한하세요.

현재 configuration 소스에는 HTTP controller와 도메인·인프라의 직접적인 IllegalArgumentException 발생 지점이 없습니다. 다만 이 매핑은 향후 모든 MVC controller의 IllegalArgumentExceptionINVALID_REQUEST_PARAM으로 분류합니다. 요청 바인딩 예외만 매핑하고, 해당 동작을 검증하는 adapter-in 테스트를 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/DocumentExceptionHandler.kt`
around lines 34 - 41, Update handleInvalidRequestParam to remove the broad
IllegalArgumentException mapping, limiting INVALID_REQUEST_PARAM handling to the
declared request-binding exceptions. Add adapter-in tests verifying the
supported binding exceptions map correctly and unrelated
IllegalArgumentException instances are not classified as INVALID_REQUEST_PARAM.


@ExceptionHandler(StorageUploadFailedException::class)
fun handleStorageUploadFailed(e: StorageUploadFailedException) =
respond(ErrorCode.STORAGE_UPLOAD_FAILED, e)

@ExceptionHandler(PresignFailedException::class)
fun handlePresignFailed(e: PresignFailedException) =
respond(ErrorCode.PRESIGN_FAILED, e)

@ExceptionHandler(Exception::class)
fun handleUnexpected(e: Exception): ResponseEntity<ApiResponse<Nothing>> {
log.error("Unhandled exception", e)
return ResponseEntity
.status(ErrorCode.INTERNAL_SERVER_ERROR.status)
.body(ApiResponse.failure(ErrorCode.INTERNAL_SERVER_ERROR))
}

private fun respond(errorCode: ErrorCode, e: Exception): ResponseEntity<ApiResponse<Nothing>> {
log.warn("{}: {}", errorCode.name, e.message)
return ResponseEntity.status(errorCode.status).body(ApiResponse.failure(errorCode))
Comment on lines +26 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

공통 응답 및 예외 변환 계약에 대한 테스트를 추가하세요.

PR 목표에 테스트가 아직 완료되지 않았다고 명시되어 있습니다. 이 변경은 HTTP 상태, 오류 코드, JSON 응답 구조를 외부 계약으로 추가합니다. 병합 전에 결정적 테스트로 계약을 고정하세요.

  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/DocumentExceptionHandler.kt#L22-L61: 각 도메인 예외, MaxUploadSizeExceededException, 예상치 못한 예외의 HTTP 상태와 ApiResponse.failure 본문을 검증하세요.
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/ApiResponse.kt#L13-L24: 성공 및 실패 응답의 success, data, error, timestamp 직렬화 계약을 검증하세요.
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/ErrorCode.kt#L5-L13: 각 ErrorCode의 HTTP 상태와 메시지를 검증하세요.

As per coding guidelines, "If production logic is changed in Kotlin or Go files, require corresponding test updates in the same subsystem unless the PR description explicitly justifies why tests are unnecessary" 규칙을 적용했습니다.

📍 Affects 3 files
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/DocumentExceptionHandler.kt#L22-L61 (this comment)
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/ApiResponse.kt#L13-L24
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/ErrorCode.kt#L5-L13
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/common/DocumentExceptionHandler.kt`
around lines 22 - 61, Add deterministic tests in the configuration adapter-in
subsystem for DocumentExceptionHandler.kt lines 22-61, covering each mapped
domain exception, MaxUploadSizeExceededException, and unexpected exceptions with
HTTP status and ApiResponse.failure body assertions; for ApiResponse.kt lines
13-24, verify success/failure success, data, error, and timestamp JSON
serialization; and for ErrorCode.kt lines 5-13, verify every code’s HTTP status
and message.

Source: Coding guidelines

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package hs.kr.entrydsm.configuration.adapterin.common

import org.springframework.http.HttpStatus

enum class ErrorCode(val status: HttpStatus, val message: String) {
INVALID_REQUEST_PARAM(HttpStatus.BAD_REQUEST, "요청 파라미터가 올바르지 않습니다."),
INVALID_FILE_FORMAT(HttpStatus.BAD_REQUEST, "지원하지 않는 파일 형식입니다."),
FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "파일을 찾을 수 없습니다."),
FILE_TOO_LARGE(HttpStatus.CONTENT_TOO_LARGE, "허용 용량을 초과했습니다."),
STORAGE_UPLOAD_FAILED(HttpStatus.BAD_GATEWAY, "파일 저장에 실패했습니다."),
PRESIGN_FAILED(HttpStatus.BAD_GATEWAY, "다운로드 URL 발급에 실패했습니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package hs.kr.entrydsm.configuration.adapterin.document

import hs.kr.entrydsm.configuration.domain.document.FileCategory

object FileReferenceId {

fun of(category: FileCategory, id: Long): String = "${prefixOf(category)}$id"

fun parse(category: FileCategory, value: String): Long =
value.removePrefix(prefixOf(category)).toLongOrNull()
?: throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

카테고리 접두사를 먼저 검증하십시오.

Line 10의 removePrefix는 접두사가 없을 때 원본 값을 그대로 반환합니다. 따라서 parse(category, "123")는 카테고리 접두사가 없어도 성공합니다. 참조 ID 형식이 카테고리 기반이라는 계약이 깨집니다.

수정 예시
-fun parse(category: FileCategory, value: String): Long =
-    value.removePrefix(prefixOf(category)).toLongOrNull()
-        ?: throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")
+fun parse(category: FileCategory, value: String): Long {
+    val prefix = prefixOf(category)
+    if (!value.startsWith(prefix)) {
+        throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")
+    }
+
+    return value.removePrefix(prefix).toLongOrNull()
+        ?: throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")
+}
📝 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 parse(category: FileCategory, value: String): Long =
value.removePrefix(prefixOf(category)).toLongOrNull()
?: throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")
fun parse(category: FileCategory, value: String): Long {
val prefix = prefixOf(category)
if (!value.startsWith(prefix)) {
throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")
}
return value.removePrefix(prefix).toLongOrNull()
?: throw IllegalArgumentException("Invalid ${category.name.lowercase()} id: $value")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/FileReferenceId.kt`
around lines 9 - 11, Update FileReferenceId.parse to validate that value starts
with the prefix returned by prefixOf(category) before removing it; reject values
with a missing or incorrect category prefix, while preserving the existing
numeric parsing and IllegalArgumentException behavior for invalid IDs.


private fun prefixOf(category: FileCategory) = "${category.name.lowercase()}_"
}
Comment on lines +8 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

동일 서브시스템에 대응 테스트를 추가하십시오.

새 Kotlin 프로덕션 로직에 대한 테스트 갱신이 없습니다. FileReferenceId의 정상·잘못된 접두사·잘못된 숫자 입력을 테스트하십시오. MultipartFileExtensions의 유효·무효 확장자와 UploadFileCommand 필드 변환을 테스트하십시오. UploadFileResponseDownloadUrlResponse의 도메인 변환 필드도 테스트하십시오.

  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/FileReferenceId.kt#L5-L14: 참조 ID 생성과 파싱 실패 조건을 검증하는 단위 테스트를 추가하십시오.
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/MultipartFileExtensions.kt#L9-L20: 확장자 검증과 업로드 명령 변환을 검증하는 단위 테스트를 추가하십시오.
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/dto/FileResponses.kt#L6-L49: 응답 DTO 변환 필드를 검증하는 단위 테스트를 추가하십시오.

코딩 가이드라인에 따라 Kotlin 프로덕션 로직 변경 시 동일 서브시스템의 테스트 갱신이 필요합니다.

📍 Affects 3 files
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/FileReferenceId.kt#L5-L14 (this comment)
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/MultipartFileExtensions.kt#L9-L20
  • systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/dto/FileResponses.kt#L6-L49
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/configuration/configuration-adapter-in/src/main/kotlin/hs/kr/entrydsm/configuration/adapterin/document/FileReferenceId.kt`
around lines 5 - 14, 동일 서브시스템의 테스트를 추가하십시오. FileReferenceId.kt:5-14에서는
FileReferenceId.of 및 parse의 정상 생성·파싱, 잘못된 접두사와 숫자 입력의 실패를 검증하고,
MultipartFileExtensions.kt:9-20에서는 유효·무효 확장자와 UploadFileCommand 필드 변환을 테스트하십시오.
FileResponses.kt:6-49에서는 UploadFileResponse와 DownloadUrlResponse의 도메인 변환 필드를
검증하십시오.

Source: Coding guidelines

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hs.kr.entrydsm.configuration.adapterin.document

import hs.kr.entrydsm.configuration.domain.document.FileCategory
import hs.kr.entrydsm.configuration.domain.document.FileExtension
import hs.kr.entrydsm.configuration.domain.document.command.UploadFileCommand
import hs.kr.entrydsm.configuration.domain.document.exception.InvalidFileFormatException
import org.springframework.web.multipart.MultipartFile

fun MultipartFile.requireExtension(category: FileCategory): FileExtension =
FileExtension.fromFileName(originalName())
?: throw InvalidFileFormatException(originalName(), category)

fun MultipartFile.toUploadCommand(category: FileCategory, fileName: String) = UploadFileCommand(
category = category,
originalName = originalName(),
fileName = fileName,
sizeBytes = size,
)

private fun MultipartFile.originalName(): String = originalFilename.orEmpty()
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package hs.kr.entrydsm.configuration.adapterin.document.dto

import hs.kr.entrydsm.configuration.domain.document.DownloadUrl
import hs.kr.entrydsm.configuration.domain.document.FileDocument

data class UploadFileResponse(
val key: String,
val fileName: String,
) {
companion object {
fun from(fileDocument: FileDocument) = UploadFileResponse(
key = fileDocument.objectKey,
fileName = fileDocument.fileName,
)
}
}

data class UploadPhotoResponse(
val key: String,
val fileName: String,
val url: String,
)

data class UploadAttachmentResponse(
val attachmentId: String,
val key: String,
val fileName: String,
val size: Long,
)

data class DownloadUrlResponse(
val fileName: String,
val downloadUrl: String,
val expiresIn: Long,
) {
companion object {
fun from(downloadUrl: DownloadUrl) = DownloadUrlResponse(
fileName = downloadUrl.fileName,
downloadUrl = downloadUrl.downloadUrl,
expiresIn = downloadUrl.expiresIn,
)
}
}

data class FileMetadataResponse(
val key: String,
val fileName: String,
val exists: Boolean,
)