Skip to content

Commit 4bd39a1

Browse files
authored
Feat: 파일 다운로드 기능 구현 (#15)
## 작업 내역 (관련 이슈) - 파일 id를 받으면, 그것을 가지고 s3_bucket과 s3_key를 받아서 사용자에게 다운로드 링크를 제공하고, 다운로드 횟수를 증가시킵니다. ## 특이 사항 -
2 parents 09b83d1 + 4424a56 commit 4bd39a1

13 files changed

Lines changed: 721 additions & 9 deletions

File tree

poetry.lock

Lines changed: 574 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ package-mode = false
99
[tool.poetry.dependencies]
1010
python = "^3.12"
1111
fastapi = "^0.115.11"
12-
uvicorn = "^0.34.0"
12+
uvicorn = {extras = ["standard"], version = "^0.34.0"}
1313
pytest = "^8.3.5"
1414
boto3 = "^1.37.16"
1515
botocore = "^1.37.16"
1616
dotenv = "^0.9.9"
1717
httpx = "^0.28.1"
18+
pymongo = "^4.11.3"
19+
requests = "^2.32.3"
1820

1921
[build-system]
2022
requires = ["poetry-core"]

src/config/S3Config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os
2+
import boto3
3+
from dotenv import load_dotenv
4+
5+
load_dotenv()
6+
7+
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
8+
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
9+
AWS_S3_BUCKET_NAME = os.getenv("AWS_S3_BUCKET_NAME")
10+
AWS_S3_REGION = os.getenv("AWS_S3_REGION")
11+
12+
s3_client = boto3.client(
13+
"s3",
14+
aws_access_key_id=AWS_ACCESS_KEY_ID,
15+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
16+
region_name=AWS_S3_REGION
17+
)

src/config/mongodb.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import os
2+
from dotenv import load_dotenv
3+
from pymongo import MongoClient
4+
5+
load_dotenv()
6+
7+
MONGODB_URL = os.getenv("MONGODB_URL")
8+
9+
def get_mongo_client():
10+
client = MongoClient(MONGODB_URL + "?retryWrites=true")
11+
return client
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from bson import ObjectId
2+
import boto3
3+
from botocore.exceptions import ClientError
4+
from src.config.S3Config import s3_client
5+
from src.config.mongodb import get_mongo_client
6+
7+
class FileRepository:
8+
def __init__(self):
9+
client = get_mongo_client()
10+
self.db = client["xrpedia-data"]
11+
self.files_collection = self.db["files"]
12+
13+
def get_file_info(self, file_id: str):
14+
return self.files_collection.find_one(
15+
{"_id": ObjectId(file_id)},
16+
{"s3_key": 1, "s3_bucket": 1}
17+
)
18+
19+
def get_presigned_url(self, s3_bucket: str, s3_key: str):
20+
try:
21+
s3_client.head_object(Bucket=s3_bucket, Key=s3_key)
22+
return s3_client.generate_presigned_url(
23+
"get_object",
24+
Params={"Bucket": s3_bucket, "Key": s3_key},
25+
ExpiresIn=3600,
26+
)
27+
except ClientError as e:
28+
# 파일 존재 X : 404 반환
29+
if e.response["Error"]["Code"] == "404":
30+
return None
31+
else:
32+
raise e
33+
34+
# MongoDB에서 해당 파일의 다운로드 횟수 +1
35+
def increment_download_count(self, file_id: str):
36+
self.files_collection.update_one(
37+
{"_id": ObjectId(file_id)},
38+
{"$inc": {"download_count": 1}}
39+
)

src/main/file/repository/__init__.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from fastapi import APIRouter, Depends
2+
from src.auth.dependencies import get_current_user
3+
from src.main.file.service.FileService import FileService
4+
import uuid
5+
6+
router = APIRouter(
7+
prefix="/file",
8+
tags=["file"],
9+
)
10+
11+
# S3에서 파일 다운로드
12+
@router.get("/{file_id}")
13+
async def download_file(
14+
file_id: str,
15+
user_id: uuid.UUID = Depends(get_current_user),
16+
file_service: FileService = Depends()
17+
):
18+
return file_service.download_file(file_id)

src/main/file/router/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from src.main.file.repository.FileRepository import FileRepository
2+
3+
class FileService:
4+
def __init__(self):
5+
self.file_repository = FileRepository()
6+
7+
def download_file(self, file_id: str):
8+
file_info = self.file_repository.get_file_info(file_id)
9+
10+
if not file_info:
11+
return {
12+
"status": 404,
13+
"message": "파일을 찾을 수 없습니다.",
14+
"detail": "해당 file_key에 대한 S3 정보가 존재하지 않습니다."
15+
}
16+
17+
s3_key = file_info["s3_key"]
18+
s3_bucket = file_info["s3_bucket"]
19+
presigned_url = self.file_repository.get_presigned_url(s3_bucket, s3_key)
20+
21+
if not presigned_url:
22+
return {
23+
"status": 404,
24+
"message": "S3에서 파일을 찾을 수 없습니다.",
25+
"detail": "요청한 파일이 S3에 존재하지 않습니다."
26+
}
27+
28+
self.file_repository.increment_download_count(file_id)
29+
30+
return {
31+
"status": 200,
32+
"file_url": presigned_url
33+
}

src/main/file/service/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)