Skip to content

Commit eab96b6

Browse files
authored
Fix/project image upload (#90)
- 프로젝트 이미지 업로드를 위한 api를 구현했습니다 - 프로젝트 이미지가 저장되는 버킷은 기존의 포트폴리오가 저장되는 버킷인 wacruit-protfolio-dev 또는 wacruit-protfolio-prod입니다. - 프로젝트 이미지의 key의 형식은 PROJECT/{project_id}/{file_name}입니다. - 프로젝트 이미지를 업로드할 때는 프로젝트가 먼저 생성되어 있어야 합니다 - api의 세부적인 내용은 노션을 참조해주세요 - project_image 테이블에서 url 대신 s3 객체 키를 저장하도록 수정하였습니다.
1 parent 217ceab commit eab96b6

10 files changed

Lines changed: 332 additions & 93 deletions

File tree

wacruit/src/apps/member/views.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from http import HTTPStatus
12
from typing import Annotated
23

34
from fastapi import APIRouter
45
from fastapi import Depends
6+
from fastapi import Response
57
from fastapi import Security
68
from fastapi.security import APIKeyHeader
79

@@ -17,13 +19,13 @@
1719
v3_router = APIRouter(prefix="/v3/members", tags=["members"])
1820

1921

20-
@v3_router.post("")
22+
@v3_router.post("", status_code=HTTPStatus.CREATED)
2123
def create_member(
2224
admin_user: AdminUser,
2325
request: MemberCreateRequest,
2426
member_service: Annotated[MemberService, Depends()],
2527
):
26-
return member_service.create_member(request)
28+
member_service.create_member(request)
2729

2830

2931
@v3_router.get("/{member_id}")

wacruit/src/apps/project/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ def __init__(self):
99
class ProjectAlreadyExistsException(WacruitException):
1010
def __init__(self):
1111
super().__init__(status_code=409, detail="이미 존재하는 프로젝트입니다.")
12+
13+
14+
class ProjectImageNotFoundException(WacruitException):
15+
def __init__(self):
16+
super().__init__(status_code=404, detail="존재하지 않는 프로젝트 이미지입니다.")

wacruit/src/apps/project/models.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Project(DeclarativeBase):
3232
urls: Mapped[list["ProjectURL"] | None] = relationship(
3333
back_populates="source_project"
3434
)
35-
image_urls: Mapped[list["ProjectImageURL"] | None] = relationship(
35+
images: Mapped[list["ProjectImage"] | None] = relationship(
3636
back_populates="source_project"
3737
)
3838

@@ -48,11 +48,12 @@ class ProjectURL(DeclarativeBase):
4848
source_project: Mapped["Project"] = relationship(back_populates="urls")
4949

5050

51-
class ProjectImageURL(DeclarativeBase):
52-
__tablename__ = "project_image_url"
51+
class ProjectImage(DeclarativeBase):
52+
__tablename__ = "project_image"
5353

5454
id: Mapped[intpk]
5555
project_id: Mapped[int] = mapped_column(ForeignKey("project.id"))
56-
url: Mapped[str255]
56+
object_key: Mapped[str255]
57+
is_uploaded: Mapped[bool] = mapped_column(default=False)
5758

58-
source_project: Mapped["Project"] = relationship(back_populates="image_urls")
59+
source_project: Mapped["Project"] = relationship(back_populates="images")

wacruit/src/apps/project/repositories.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from fastapi import Depends
2+
from sqlalchemy import update
23
from sqlalchemy.orm import joinedload
34
from sqlalchemy.orm import Session
45

56
from wacruit.src.apps.project.models import Project
6-
from wacruit.src.apps.project.models import ProjectImageURL
7+
from wacruit.src.apps.project.models import ProjectImage
78
from wacruit.src.apps.project.models import ProjectURL
89
from wacruit.src.database.connection import get_db_session
910
from wacruit.src.database.connection import Transaction
@@ -23,7 +24,7 @@ def get_project_by_id(self, project_id: int) -> Project | None:
2324
self.session.query(Project)
2425
.options(
2526
joinedload(Project.urls),
26-
joinedload(Project.image_urls),
27+
joinedload(Project.images),
2728
)
2829
.filter(Project.id == project_id)
2930
.first()
@@ -45,3 +46,24 @@ def update_project(self, project: Project) -> Project:
4546
self.session.merge(project)
4647

4748
return project
49+
50+
def get_project_image_by_id(self, image_id: int) -> ProjectImage | None:
51+
return (
52+
self.session.query(ProjectImage).filter(ProjectImage.id == image_id).first()
53+
)
54+
55+
def get_project_images_by_project_id(self, project_id: int) -> list[ProjectImage]:
56+
return (
57+
self.session.query(ProjectImage)
58+
.filter(ProjectImage.project_id == project_id)
59+
.all()
60+
)
61+
62+
def update_project_image(self, project_image_id: int) -> None:
63+
with self.transaction:
64+
query = (
65+
update(ProjectImage)
66+
.where(ProjectImage.id == project_image_id)
67+
.values(is_uploaded=True)
68+
)
69+
self.session.execute(query)

wacruit/src/apps/project/schemas.py

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class ProjectCreateRequest(BaseModel):
2020
thumbnail_url: str | None = Field(None, max_length=255)
2121
project_type: ProjectType
2222
is_active: bool
23-
images: list[str] | None
2423
urls: list[ProjectLinkDto] | None
2524

2625

@@ -31,10 +30,23 @@ class ProjectUpdateRequest(BaseModel):
3130
thumbnail_url: str | None = Field(None, max_length=255)
3231
project_type: ProjectType | None
3332
is_active: bool | None
34-
images: list[str] | None
3533
urls: list[ProjectLinkDto] | None
3634

3735

36+
class ProjectImageResponse(OrmModel):
37+
id: int
38+
project_id: int
39+
object_key: str
40+
is_uploaded: bool
41+
42+
43+
class PresignedUrlWithIdResponse(BaseModel):
44+
object_name: str
45+
presigned_url: str
46+
fields: dict[str, str] = Field(default={})
47+
project_image_id: int
48+
49+
3850
class ProjectDetailResponse(OrmModel):
3951
id: int
4052
name: str
@@ -43,33 +55,9 @@ class ProjectDetailResponse(OrmModel):
4355
thumbnail_url: str | None
4456
project_type: ProjectType
4557
is_active: bool
46-
images: list[str] | None
58+
images: list[PresignedUrlWithIdResponse] | None
4759
urls: list[ProjectLinkDto] | None
4860

49-
@classmethod
50-
def from_orm(cls, obj):
51-
urls = None
52-
if obj.urls:
53-
urls = [
54-
ProjectLinkDto(url_type=url.url_type, url=url.url) for url in obj.urls
55-
]
56-
57-
images = None
58-
if obj.image_urls:
59-
images = [img.url for img in obj.image_urls]
60-
61-
return cls(
62-
id=obj.id,
63-
name=obj.name,
64-
summary=obj.summary,
65-
introduction=obj.introduction,
66-
thumbnail_url=obj.thumbnail_url,
67-
project_type=obj.project_type.name,
68-
is_active=obj.is_active,
69-
images=images,
70-
urls=urls,
71-
)
72-
7361

7462
class ProjectBriefResponse(OrmModel):
7563
id: int
@@ -91,5 +79,10 @@ def from_orm(cls, obj):
9179
)
9280

9381

82+
class ProjectImageUploadRequest(BaseModel):
83+
project_id: int
84+
file_name: str
85+
86+
9487
class ProjectListResponse(OrmModel):
9588
projects: list[ProjectBriefResponse]

0 commit comments

Comments
 (0)