Skip to content

Commit b11c659

Browse files
authored
♻️ s3 패키지 리팩토링 (#50)
1 parent 90cd6b5 commit b11c659

11 files changed

Lines changed: 81 additions & 59 deletions

File tree

app/api/v1/endpoints/s3.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

app/api/v1/router.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
from app.api.v1.endpoints.application import router as application_router
44
from app.api.v1.endpoints.event import router as event_router
5-
from app.api.v1.endpoints.s3 import router as s3_router
65
from app.api.v1.endpoints.store import router as store_router
76

87
router = APIRouter()
98
router.include_router(store_router)
109
router.include_router(event_router)
1110
router.include_router(application_router)
12-
router.include_router(s3_router)

app/core/exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def __init__(self, error: ErrorCode) -> None:
5757
)
5858
GEN_005 = ErrorCode(400, "GEN_005", "Invalid wallet address format")
5959

60+
# S3
61+
S3_001 = ErrorCode(500, "S3_001", "Failed to generate presigned URL")
62+
6063
# Image (server-side only, not in mock)
6164
IMAGE_001 = ErrorCode(422, "IMAGE_001", "Image does not meet the event conditions")
6265
IMAGE_002 = ErrorCode(400, "IMAGE_002", "Image not found or inaccessible")

app/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from app.core.exceptions import AppException
1313
from app.db.seed import reset_and_seed
1414
from app.db.session import engine
15+
from app.s3.router import router as s3_router
1516

1617

1718
@asynccontextmanager
@@ -24,6 +25,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
2425
app = FastAPI(title=settings.app_name, debug=settings.debug, lifespan=lifespan)
2526

2627
app.include_router(auth_router, prefix="/api")
28+
app.include_router(s3_router, prefix="/api")
2729
app.include_router(api_router, prefix="/api")
2830

2931

app/s3/__init__.py

Whitespace-only changes.

app/s3/dependencies.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from app.s3.service import S3Service
2+
from app.s3.service_impl import S3ServiceImpl
3+
4+
5+
def get_s3_service() -> S3Service:
6+
return S3ServiceImpl()

app/s3/router.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from fastapi import APIRouter, Depends
2+
3+
from app.s3.dependencies import get_s3_service
4+
from app.s3.schemas import S3PresignedUploadReq, S3PresignedUploadResp
5+
from app.s3.service import S3Service
6+
from app.schemas.common import SuccessResponse
7+
8+
router = APIRouter(prefix="/s3", tags=["S3"])
9+
10+
11+
@router.post("", response_model=SuccessResponse[S3PresignedUploadResp])
12+
def create_presigned_upload_url(
13+
body: S3PresignedUploadReq,
14+
service: S3Service = Depends(get_s3_service),
15+
) -> SuccessResponse[S3PresignedUploadResp]:
16+
result = service.generate_presigned_upload_url(
17+
body.fileName, body.fileType, body.contentType
18+
)
19+
return SuccessResponse(data=result)
File renamed without changes.

app/s3/service.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from abc import ABC, abstractmethod
2+
3+
from app.s3.schemas import S3PresignedUploadResp
4+
from app.schemas.common import S3FileType
5+
6+
7+
class S3Service(ABC):
8+
@abstractmethod
9+
def generate_presigned_upload_url(
10+
self, file_name: str, file_type: S3FileType, content_type: str
11+
) -> S3PresignedUploadResp: ...

app/s3/service_impl.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import uuid
2+
3+
import boto3 # type: ignore[import-untyped]
4+
from botocore.exceptions import ClientError # type: ignore[import-untyped]
5+
6+
from app.core.config import settings
7+
from app.core.exceptions import S3_001, AppException
8+
from app.s3.schemas import S3PresignedUploadResp
9+
from app.s3.service import S3Service
10+
from app.schemas.common import S3FileType
11+
12+
13+
class S3ServiceImpl(S3Service):
14+
def generate_presigned_upload_url(
15+
self, file_name: str, file_type: S3FileType, content_type: str
16+
) -> S3PresignedUploadResp:
17+
bucket = (
18+
settings.s3_public_bucket
19+
if file_type == S3FileType.STORE
20+
else settings.s3_private_bucket
21+
)
22+
s3_key = f"{file_type.value.lower()}/{uuid.uuid4()}/{file_name}"
23+
24+
client = boto3.client(
25+
"s3",
26+
aws_access_key_id=settings.aws_access_key_id,
27+
aws_secret_access_key=settings.aws_secret_access_key,
28+
region_name=settings.aws_region,
29+
endpoint_url=f"https://s3.{settings.aws_region}.amazonaws.com",
30+
)
31+
try:
32+
url: str = client.generate_presigned_url(
33+
"put_object",
34+
Params={"Bucket": bucket, "Key": s3_key, "ContentType": content_type},
35+
ExpiresIn=settings.s3_presigned_expiry,
36+
)
37+
except ClientError as e:
38+
raise AppException(S3_001) from e
39+
40+
return S3PresignedUploadResp(url=url, s3Key=s3_key)

0 commit comments

Comments
 (0)