Skip to content

Commit 89c6a18

Browse files
authored
♻️ 이벤트 패키지 리팩토링 (#54)
1 parent baa1592 commit 89c6a18

17 files changed

Lines changed: 456 additions & 295 deletions

app/api/v1/router.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from fastapi import APIRouter
22

33
from app.api.v1.endpoints.application import router as application_router
4-
from app.api.v1.endpoints.event import router as event_router
54

65
router = APIRouter()
7-
router.include_router(event_router)
86
router.include_router(application_router)

app/event/__init__.py

Whitespace-only changes.

app/event/dependencies.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from fastapi import Depends
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
from app.db.session import get_db
5+
from app.event.repository import EventRepository
6+
from app.event.repository_impl import EventRepositoryImpl
7+
from app.event.service import EventService
8+
from app.event.service_impl import EventServiceImpl
9+
10+
11+
def get_event_repository(db: AsyncSession = Depends(get_db)) -> EventRepository:
12+
return EventRepositoryImpl(db)
13+
14+
15+
def get_event_service(
16+
repo: EventRepository = Depends(get_event_repository),
17+
) -> EventService:
18+
return EventServiceImpl(repo)

app/event/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from datetime import datetime
2+
from uuid import UUID, uuid4
3+
4+
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, String, Text
5+
from sqlalchemy.orm import Mapped, mapped_column
6+
from sqlalchemy.sql import func
7+
8+
from app.db.base import Base
9+
10+
11+
class Event(Base):
12+
__tablename__ = "events"
13+
14+
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
15+
title: Mapped[str] = mapped_column(String(200))
16+
condition: Mapped[str] = mapped_column(Text)
17+
reward: Mapped[int] = mapped_column(BigInteger)
18+
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
19+
contract_address: Mapped[str | None] = mapped_column(String(42), nullable=True)
20+
store_id: Mapped[UUID] = mapped_column(ForeignKey("stores.id"))
21+
created_at: Mapped[datetime] = mapped_column(
22+
DateTime(timezone=True), server_default=func.now()
23+
)

app/event/repository.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from abc import ABC, abstractmethod
2+
from uuid import UUID
3+
4+
from app.event.models import Event
5+
from app.models.application import Application
6+
from app.models.review_submission import ReviewSubmission
7+
from app.models.user import User
8+
from app.store.models import Store
9+
10+
11+
class EventRepository(ABC):
12+
@abstractmethod
13+
async def find_by_id(self, event_id: UUID) -> Event | None: ...
14+
15+
@abstractmethod
16+
async def find_by_owner_id(self, owner_id: UUID) -> list[Event]: ...
17+
18+
@abstractmethod
19+
async def find_store_by_id(self, store_id: UUID) -> Store | None: ...
20+
21+
@abstractmethod
22+
async def save(self, event: Event) -> Event: ...
23+
24+
@abstractmethod
25+
async def delete(self, event: Event) -> None: ...
26+
27+
@abstractmethod
28+
async def find_applications_by_event_id(
29+
self,
30+
event_id: UUID,
31+
*,
32+
status_filter: str | None,
33+
offset: int,
34+
limit: int,
35+
) -> tuple[list[Application], int]: ...
36+
37+
@abstractmethod
38+
async def find_user_by_id(self, user_id: UUID) -> User | None: ...
39+
40+
@abstractmethod
41+
async def find_submission_by_application_id(
42+
self, application_id: UUID
43+
) -> ReviewSubmission | None: ...

app/event/repository_impl.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from typing import cast
2+
from uuid import UUID
3+
4+
from sqlalchemy import func, select
5+
from sqlalchemy.ext.asyncio import AsyncSession
6+
7+
from app.event.models import Event
8+
from app.event.repository import EventRepository
9+
from app.models.application import Application
10+
from app.models.review_submission import ReviewSubmission
11+
from app.models.user import User
12+
from app.store.models import Store
13+
14+
15+
class EventRepositoryImpl(EventRepository):
16+
def __init__(self, db: AsyncSession) -> None:
17+
self.db = db
18+
19+
async def find_by_id(self, event_id: UUID) -> Event | None:
20+
return cast(
21+
Event | None,
22+
await self.db.scalar(select(Event).where(Event.id == event_id)),
23+
)
24+
25+
async def find_by_owner_id(self, owner_id: UUID) -> list[Event]:
26+
q = (
27+
select(Event)
28+
.join(Store, Event.store_id == Store.id)
29+
.where(Store.owner_id == owner_id)
30+
)
31+
return list((await self.db.scalars(q)).all())
32+
33+
async def find_store_by_id(self, store_id: UUID) -> Store | None:
34+
return cast(
35+
Store | None,
36+
await self.db.scalar(select(Store).where(Store.id == store_id)),
37+
)
38+
39+
async def save(self, event: Event) -> Event:
40+
self.db.add(event)
41+
await self.db.commit()
42+
await self.db.refresh(event)
43+
return event
44+
45+
async def delete(self, event: Event) -> None:
46+
await self.db.delete(event)
47+
await self.db.commit()
48+
49+
async def find_applications_by_event_id(
50+
self,
51+
event_id: UUID,
52+
*,
53+
status_filter: str | None,
54+
offset: int,
55+
limit: int,
56+
) -> tuple[list[Application], int]:
57+
q = select(Application).where(Application.event_id == event_id)
58+
if status_filter:
59+
q = q.where(Application.status == status_filter.upper())
60+
count_q = select(func.count()).select_from(q.subquery())
61+
total = await self.db.scalar(count_q) or 0
62+
apps = list((await self.db.scalars(q.offset(offset).limit(limit))).all())
63+
return apps, int(total)
64+
65+
async def find_user_by_id(self, user_id: UUID) -> User | None:
66+
return cast(
67+
User | None,
68+
await self.db.scalar(select(User).where(User.id == user_id)),
69+
)
70+
71+
async def find_submission_by_application_id(
72+
self, application_id: UUID
73+
) -> ReviewSubmission | None:
74+
return cast(
75+
ReviewSubmission | None,
76+
await self.db.scalar(
77+
select(ReviewSubmission).where(
78+
ReviewSubmission.application_id == application_id
79+
)
80+
),
81+
)
Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
from fastapi import APIRouter, Depends, Query
2-
from sqlalchemy.ext.asyncio import AsyncSession
32

43
from app.api.v1.deps import require_login
5-
from app.db.session import get_db
6-
from app.schemas.event import (
4+
from app.event.dependencies import get_event_service
5+
from app.event.schemas import (
76
EventApplicationsResp,
87
EventCreateReq,
98
EventListResp,
109
EventResp,
1110
)
12-
from app.services import event as event_service
11+
from app.event.service import EventService
1312

1413
router = APIRouter(prefix="/event", tags=["Event"])
1514

1615

1716
@router.get("/owner", response_model=EventListResp)
1817
async def get_owner_events(
19-
db: AsyncSession = Depends(get_db),
2018
owner_id: str = Depends(require_login),
19+
service: EventService = Depends(get_event_service),
2120
) -> EventListResp:
22-
events = await event_service.list_owner_events(db, owner_id)
21+
events = await service.list_owner_events(owner_id)
2322
return EventListResp(
2423
events=[
2524
EventResp(
@@ -35,12 +34,29 @@ async def get_owner_events(
3534
)
3635

3736

37+
@router.post("", response_model=EventResp)
38+
async def create_event(
39+
body: EventCreateReq,
40+
owner_id: str = Depends(require_login),
41+
service: EventService = Depends(get_event_service),
42+
) -> EventResp:
43+
event = await service.create_event(owner_id, body)
44+
return EventResp(
45+
id=str(event.id),
46+
title=event.title,
47+
condition=event.condition,
48+
reward=event.reward / 10**18,
49+
isActive=event.is_active,
50+
contractAddress=event.contract_address,
51+
)
52+
53+
3854
@router.get("/{eventId}", response_model=EventResp)
3955
async def get_event(
4056
eventId: str,
41-
db: AsyncSession = Depends(get_db),
57+
service: EventService = Depends(get_event_service),
4258
) -> EventResp:
43-
event = await event_service.get_event_or_404(db, eventId)
59+
event = await service.get_event(eventId)
4460
return EventResp(
4561
id=str(event.id),
4662
title=event.title,
@@ -51,17 +67,30 @@ async def get_event(
5167
)
5268

5369

70+
@router.delete("/{eventId}", response_model=None)
71+
async def delete_event(
72+
eventId: str,
73+
owner_id: str = Depends(require_login),
74+
service: EventService = Depends(get_event_service),
75+
) -> None:
76+
await service.delete_event(eventId, owner_id)
77+
78+
5479
@router.get("/{eventId}/applications", response_model=EventApplicationsResp)
5580
async def get_event_applications(
5681
eventId: str,
5782
status: str | None = Query(default=None),
5883
page: int = Query(default=0, ge=0),
5984
size: int = Query(default=20, ge=1),
60-
db: AsyncSession = Depends(get_db),
6185
owner_id: str = Depends(require_login),
86+
service: EventService = Depends(get_event_service),
6287
) -> EventApplicationsResp:
63-
applications, total = await event_service.list_event_applications(
64-
db, eventId, owner_id, status, page, size
88+
applications, total = await service.list_event_applications(
89+
eventId,
90+
owner_id,
91+
status_filter=status,
92+
page=page,
93+
size=size,
6594
)
6695
total_pages = max(1, (total + size - 1) // size)
6796
return EventApplicationsResp(
@@ -71,29 +100,3 @@ async def get_event_applications(
71100
totalPages=total_pages,
72101
hasNext=(page + 1) < total_pages,
73102
)
74-
75-
76-
@router.delete("/{eventId}", response_model=None)
77-
async def delete_event(
78-
eventId: str,
79-
db: AsyncSession = Depends(get_db),
80-
owner_id: str = Depends(require_login),
81-
) -> None:
82-
await event_service.delete_event(db, eventId, owner_id)
83-
84-
85-
@router.post("", response_model=EventResp)
86-
async def create_event(
87-
body: EventCreateReq,
88-
db: AsyncSession = Depends(get_db),
89-
owner_id: str = Depends(require_login),
90-
) -> EventResp:
91-
event = await event_service.create_event(db, owner_id, body)
92-
return EventResp(
93-
id=str(event.id),
94-
title=event.title,
95-
condition=event.condition,
96-
reward=event.reward / 10**18,
97-
isActive=event.is_active,
98-
contractAddress=event.contract_address,
99-
)

app/event/service.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from abc import ABC, abstractmethod
2+
3+
from app.event.models import Event
4+
from app.event.schemas import ApplicationSummary, EventCreateReq
5+
6+
7+
class EventService(ABC):
8+
@abstractmethod
9+
async def get_event(self, event_id: str) -> Event: ...
10+
11+
@abstractmethod
12+
async def list_owner_events(self, owner_id: str) -> list[Event]: ...
13+
14+
@abstractmethod
15+
async def create_event(self, owner_id: str, data: EventCreateReq) -> Event: ...
16+
17+
@abstractmethod
18+
async def delete_event(self, event_id: str, owner_id: str) -> None: ...
19+
20+
@abstractmethod
21+
async def list_event_applications(
22+
self,
23+
event_id: str,
24+
owner_id: str,
25+
*,
26+
status_filter: str | None,
27+
page: int,
28+
size: int,
29+
) -> tuple[list[ApplicationSummary], int]: ...

0 commit comments

Comments
 (0)