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
2 changes: 0 additions & 2 deletions app/api/v1/router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from fastapi import APIRouter

from app.api.v1.endpoints.application import router as application_router
from app.api.v1.endpoints.event import router as event_router

router = APIRouter()
router.include_router(event_router)
router.include_router(application_router)
Empty file added app/event/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions app/event/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.session import get_db
from app.event.repository import EventRepository
from app.event.repository_impl import EventRepositoryImpl
from app.event.service import EventService
from app.event.service_impl import EventServiceImpl


def get_event_repository(db: AsyncSession = Depends(get_db)) -> EventRepository:
return EventRepositoryImpl(db)


def get_event_service(
repo: EventRepository = Depends(get_event_repository),
) -> EventService:
return EventServiceImpl(repo)
23 changes: 23 additions & 0 deletions app/event/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import datetime
from uuid import UUID, uuid4

from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func

from app.db.base import Base


class Event(Base):
__tablename__ = "events"

id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
title: Mapped[str] = mapped_column(String(200))
condition: Mapped[str] = mapped_column(Text)
reward: Mapped[int] = mapped_column(BigInteger)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
contract_address: Mapped[str | None] = mapped_column(String(42), nullable=True)
store_id: Mapped[UUID] = mapped_column(ForeignKey("stores.id"))
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
43 changes: 43 additions & 0 deletions app/event/repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from abc import ABC, abstractmethod
from uuid import UUID

from app.event.models import Event
from app.models.application import Application
from app.models.review_submission import ReviewSubmission
from app.models.user import User
from app.store.models import Store


class EventRepository(ABC):
@abstractmethod
async def find_by_id(self, event_id: UUID) -> Event | None: ...

@abstractmethod
async def find_by_owner_id(self, owner_id: UUID) -> list[Event]: ...

@abstractmethod
async def find_store_by_id(self, store_id: UUID) -> Store | None: ...

@abstractmethod
async def save(self, event: Event) -> Event: ...

@abstractmethod
async def delete(self, event: Event) -> None: ...

@abstractmethod
async def find_applications_by_event_id(
self,
event_id: UUID,
*,
status_filter: str | None,
offset: int,
limit: int,
) -> tuple[list[Application], int]: ...

@abstractmethod
async def find_user_by_id(self, user_id: UUID) -> User | None: ...

@abstractmethod
async def find_submission_by_application_id(
self, application_id: UUID
) -> ReviewSubmission | None: ...
81 changes: 81 additions & 0 deletions app/event/repository_impl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import cast
from uuid import UUID

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.event.models import Event
from app.event.repository import EventRepository
from app.models.application import Application
from app.models.review_submission import ReviewSubmission
from app.models.user import User
from app.store.models import Store


class EventRepositoryImpl(EventRepository):
def __init__(self, db: AsyncSession) -> None:
self.db = db

async def find_by_id(self, event_id: UUID) -> Event | None:
return cast(
Event | None,
await self.db.scalar(select(Event).where(Event.id == event_id)),
)

async def find_by_owner_id(self, owner_id: UUID) -> list[Event]:
q = (
select(Event)
.join(Store, Event.store_id == Store.id)
.where(Store.owner_id == owner_id)
)
return list((await self.db.scalars(q)).all())

async def find_store_by_id(self, store_id: UUID) -> Store | None:
return cast(
Store | None,
await self.db.scalar(select(Store).where(Store.id == store_id)),
)

async def save(self, event: Event) -> Event:
self.db.add(event)
await self.db.commit()
await self.db.refresh(event)
return event

async def delete(self, event: Event) -> None:
await self.db.delete(event)
await self.db.commit()

async def find_applications_by_event_id(
self,
event_id: UUID,
*,
status_filter: str | None,
offset: int,
limit: int,
) -> tuple[list[Application], int]:
q = select(Application).where(Application.event_id == event_id)
if status_filter:
q = q.where(Application.status == status_filter.upper())
count_q = select(func.count()).select_from(q.subquery())
total = await self.db.scalar(count_q) or 0
apps = list((await self.db.scalars(q.offset(offset).limit(limit))).all())
return apps, int(total)

async def find_user_by_id(self, user_id: UUID) -> User | None:
return cast(
User | None,
await self.db.scalar(select(User).where(User.id == user_id)),
)

async def find_submission_by_application_id(
self, application_id: UUID
) -> ReviewSubmission | None:
return cast(
ReviewSubmission | None,
await self.db.scalar(
select(ReviewSubmission).where(
ReviewSubmission.application_id == application_id
)
),
)
77 changes: 40 additions & 37 deletions app/api/v1/endpoints/event.py → app/event/router.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.v1.deps import require_login
from app.db.session import get_db
from app.schemas.event import (
from app.event.dependencies import get_event_service
from app.event.schemas import (
EventApplicationsResp,
EventCreateReq,
EventListResp,
EventResp,
)
from app.services import event as event_service
from app.event.service import EventService

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


@router.get("/owner", response_model=EventListResp)
async def get_owner_events(
db: AsyncSession = Depends(get_db),
owner_id: str = Depends(require_login),
service: EventService = Depends(get_event_service),
) -> EventListResp:
events = await event_service.list_owner_events(db, owner_id)
events = await service.list_owner_events(owner_id)
return EventListResp(
events=[
EventResp(
Expand All @@ -35,12 +34,29 @@ async def get_owner_events(
)


@router.post("", response_model=EventResp)
async def create_event(
body: EventCreateReq,
owner_id: str = Depends(require_login),
service: EventService = Depends(get_event_service),
) -> EventResp:
event = await service.create_event(owner_id, body)
return EventResp(
id=str(event.id),
title=event.title,
condition=event.condition,
reward=event.reward / 10**18,
isActive=event.is_active,
contractAddress=event.contract_address,
)


@router.get("/{eventId}", response_model=EventResp)
async def get_event(
eventId: str,
db: AsyncSession = Depends(get_db),
service: EventService = Depends(get_event_service),
) -> EventResp:
event = await event_service.get_event_or_404(db, eventId)
event = await service.get_event(eventId)
return EventResp(
id=str(event.id),
title=event.title,
Expand All @@ -51,17 +67,30 @@ async def get_event(
)


@router.delete("/{eventId}", response_model=None)
async def delete_event(
eventId: str,
owner_id: str = Depends(require_login),
service: EventService = Depends(get_event_service),
) -> None:
await service.delete_event(eventId, owner_id)


@router.get("/{eventId}/applications", response_model=EventApplicationsResp)
async def get_event_applications(
eventId: str,
status: str | None = Query(default=None),
page: int = Query(default=0, ge=0),
size: int = Query(default=20, ge=1),
db: AsyncSession = Depends(get_db),
owner_id: str = Depends(require_login),
service: EventService = Depends(get_event_service),
) -> EventApplicationsResp:
applications, total = await event_service.list_event_applications(
db, eventId, owner_id, status, page, size
applications, total = await service.list_event_applications(
eventId,
owner_id,
status_filter=status,
page=page,
size=size,
)
total_pages = max(1, (total + size - 1) // size)
return EventApplicationsResp(
Expand All @@ -71,29 +100,3 @@ async def get_event_applications(
totalPages=total_pages,
hasNext=(page + 1) < total_pages,
)


@router.delete("/{eventId}", response_model=None)
async def delete_event(
eventId: str,
db: AsyncSession = Depends(get_db),
owner_id: str = Depends(require_login),
) -> None:
await event_service.delete_event(db, eventId, owner_id)


@router.post("", response_model=EventResp)
async def create_event(
body: EventCreateReq,
db: AsyncSession = Depends(get_db),
owner_id: str = Depends(require_login),
) -> EventResp:
event = await event_service.create_event(db, owner_id, body)
return EventResp(
id=str(event.id),
title=event.title,
condition=event.condition,
reward=event.reward / 10**18,
isActive=event.is_active,
contractAddress=event.contract_address,
)
File renamed without changes.
29 changes: 29 additions & 0 deletions app/event/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from abc import ABC, abstractmethod

from app.event.models import Event
from app.event.schemas import ApplicationSummary, EventCreateReq


class EventService(ABC):
@abstractmethod
async def get_event(self, event_id: str) -> Event: ...

@abstractmethod
async def list_owner_events(self, owner_id: str) -> list[Event]: ...

@abstractmethod
async def create_event(self, owner_id: str, data: EventCreateReq) -> Event: ...

@abstractmethod
async def delete_event(self, event_id: str, owner_id: str) -> None: ...

@abstractmethod
async def list_event_applications(
self,
event_id: str,
owner_id: str,
*,
status_filter: str | None,
page: int,
size: int,
) -> tuple[list[ApplicationSummary], int]: ...
Loading
Loading