Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
581 changes: 574 additions & 7 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.115.11"
uvicorn = "^0.34.0"
uvicorn = {extras = ["standard"], version = "^0.34.0"}
pytest = "^8.3.5"
boto3 = "^1.37.16"
botocore = "^1.37.16"
dotenv = "^0.9.9"
httpx = "^0.28.1"
pymongo = "^4.11.3"
requests = "^2.32.3"

[build-system]
requires = ["poetry-core"]
Expand Down
17 changes: 17 additions & 0 deletions src/config/S3Config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os
import boto3
from dotenv import load_dotenv

load_dotenv()

AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
AWS_S3_BUCKET_NAME = os.getenv("AWS_S3_BUCKET_NAME")
AWS_S3_REGION = os.getenv("AWS_S3_REGION")

s3_client = boto3.client(
"s3",
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
region_name=AWS_S3_REGION
)
11 changes: 11 additions & 0 deletions src/config/mongodb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os
from dotenv import load_dotenv
from pymongo import MongoClient

load_dotenv()

MONGODB_URL = os.getenv("MONGODB_URL")

def get_mongo_client():
client = MongoClient(MONGODB_URL + "?retryWrites=true")
return client
39 changes: 39 additions & 0 deletions src/main/file/repository/FileRepository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from bson import ObjectId
import boto3
from botocore.exceptions import ClientError
from src.config.S3Config import s3_client
from src.config.mongodb import get_mongo_client

class FileRepository:
def __init__(self):
client = get_mongo_client()
self.db = client["xrpedia-data"]
self.files_collection = self.db["files"]

def get_file_info(self, file_id: str):
return self.files_collection.find_one(
{"_id": ObjectId(file_id)},
{"s3_key": 1, "s3_bucket": 1}
)

def get_presigned_url(self, s3_bucket: str, s3_key: str):
try:
s3_client.head_object(Bucket=s3_bucket, Key=s3_key)
return s3_client.generate_presigned_url(
"get_object",
Params={"Bucket": s3_bucket, "Key": s3_key},
ExpiresIn=3600,
)
except ClientError as e:
# 파일 존재 X : 404 반환
if e.response["Error"]["Code"] == "404":
return None
else:
raise e

# MongoDB에서 해당 파일의 다운로드 횟수 +1
def increment_download_count(self, file_id: str):
self.files_collection.update_one(
{"_id": ObjectId(file_id)},
{"$inc": {"download_count": 1}}
)
Empty file.
18 changes: 18 additions & 0 deletions src/main/file/router/FileAPIRouter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from fastapi import APIRouter, Depends
from src.auth.dependencies import get_current_user
from src.main.file.service.FileService import FileService
import uuid

router = APIRouter(
prefix="/file",
tags=["file"],
)

# S3에서 파일 다운로드
@router.get("/{file_id}")
async def download_file(
file_id: str,
user_id: uuid.UUID = Depends(get_current_user),
file_service: FileService = Depends()
):
return file_service.download_file(file_id)
Empty file.
33 changes: 33 additions & 0 deletions src/main/file/service/FileService.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from src.main.file.repository.FileRepository import FileRepository

class FileService:
def __init__(self):
self.file_repository = FileRepository()

def download_file(self, file_id: str):
file_info = self.file_repository.get_file_info(file_id)

if not file_info:
return {
"status": 404,
"message": "파일을 찾을 수 없습니다.",
"detail": "해당 file_key에 대한 S3 정보가 존재하지 않습니다."
}

s3_key = file_info["s3_key"]
s3_bucket = file_info["s3_bucket"]
presigned_url = self.file_repository.get_presigned_url(s3_bucket, s3_key)

if not presigned_url:
return {
"status": 404,
"message": "S3에서 파일을 찾을 수 없습니다.",
"detail": "요청한 파일이 S3에 존재하지 않습니다."
}

self.file_repository.increment_download_count(file_id)

return {
"status": 200,
"file_url": presigned_url
}
Empty file.
4 changes: 3 additions & 1 deletion src/router.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from fastapi import APIRouter

from src.main.health.router import HealthAPIRouter
from src.main.file.router import FileAPIRouter


router = APIRouter(
prefix="",
)

router.include_router(HealthAPIRouter.router)
router.include_router(HealthAPIRouter.router)
router.include_router(FileAPIRouter.router)
Empty file added src/tests/file/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions src/tests/file/test_file_api_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# import requests
# import uuid

# BASE_URL = "http://localhost:8081"
# TEST_USER_ID = "11111111-2222-3333-4444-555555555555"
# TEST_FILE_KEY = "67de75eca76bd4020aab2da5"

# headers = {
# "x-auth-sub": TEST_USER_ID
# }

# def test_download_file():
# response = requests.get(
# f"{BASE_URL}/file/{TEST_FILE_KEY}",
# headers=headers
# )

# print("Download response:", response.status_code, response.text)

# assert response.status_code == 200
# data = response.json()
# assert "file_url" in data
# assert data["file_url"].startswith("https://") or data["file_url"].startswith("http://")