From c3a33a60f7b349ca0df9cfddaba98034daa3a620 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 12:26:42 -0700 Subject: [PATCH 01/15] init: setup --- src/event/crud.py | 0 src/event/models.py | 0 src/event/tables.py | 0 src/event/url.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/event/crud.py create mode 100644 src/event/models.py create mode 100644 src/event/tables.py create mode 100644 src/event/url.py diff --git a/src/event/crud.py b/src/event/crud.py new file mode 100644 index 0000000..e69de29 diff --git a/src/event/models.py b/src/event/models.py new file mode 100644 index 0000000..e69de29 diff --git a/src/event/tables.py b/src/event/tables.py new file mode 100644 index 0000000..e69de29 diff --git a/src/event/url.py b/src/event/url.py new file mode 100644 index 0000000..e69de29 From d517c2459af6f0a8848b70e81f805df2312ddf90 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 12:27:32 -0700 Subject: [PATCH 02/15] feat: created sql table schema --- src/event/tables.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/event/tables.py b/src/event/tables.py index e69de29..4ce732c 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -0,0 +1,46 @@ +from sqlalchemy import ( + String, + DateTime, + Text, +) +from sqlalchemy.org import Mapped, mapped_column + +from database import Base +import datetime + +class EventDB(Base): + __tablename__ = "event_info" + + eid: Mapped[int] = mapped_column( + int, + primary_key=True + ) + description: Mapped[str] = mapped_column( + Text, + nullable=True + ) + name: Mapped[str] = mapped_column(String(64)) + start_time: Mapped[datetime.datetime()] = mapped_column(DateTime) + end_time: Mapped[datetime.datetime()] = mapped_column(DateTime) + repeat: Mapped[str] = mapped_column(String(64)) + start_date: Mapped[datetime.date()] = mapped_column( + Date, + nullable=True + ) + end_date: Mapped[datetime.date()] = mapped_column( + Date, + nullable=True + ) + + def serialize(self) -> dict: + return{ + "eid": self.eid, + "name": self.name, + "description": self.description, + "start_time": self.start_time, + "end_time": self.start_time, + "start_date": self.start_time, + "end_date": self.start_time, + } + + \ No newline at end of file From 9ac1ab554f94d3870429a2fd7942506f15fa37a5 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 12:58:36 -0700 Subject: [PATCH 03/15] feat: create DTO models for events api --- src/event/models.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/event/models.py b/src/event/models.py index e69de29..e323a5e 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -0,0 +1,41 @@ +from pydantic import BaseModel, ConfigDict +import datetime + +class Event(BaseModel): + model_config = ConfigDict(from_attributes=True) + eid: int + name: str + start_time: datetime.datetime() + end_time: datetime.datetime() + description: str | None = None + repeat: str | None = None + start_date: datetime.date() | None = None + end_date: datetime.date() | None = None + +class EventPublic(BaseModel): + model_config = ConfigDict(from_attributes=True) + name: str + start_time: datetime.datetime() + end_time: datetime.datetime() + description: str | None = None + repeat: str | None = None + start_date: datetime.date() | None = None + end_date: datetime.date() | None = None + +class EventCreate(BaseModel): + name: str + start_time: datetime.datetime() + end_time: datetime.datetime() + description: str | None = None + repeat: str | None = None + start_date: datetime.date() | None = None + end_date: datetime.date() | None = None + +class EventUpdate(BaseModel): + name: str | None = None + start_time: datetime.datetime() | None = None + end_time: datetime.datetime() | None = None + description: str | None = None + repeat: str | None = None + start_date: datetime.date() | None = None + end_date: datetime.date() | None = None \ No newline at end of file From ea8d416f5a6e4654c3cd1b41503253e419854003 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 13:14:35 -0700 Subject: [PATCH 04/15] bug: correct type for mapped_column eid, fixed column type for all the date and datetime --- src/event/tables.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/event/tables.py b/src/event/tables.py index 4ce732c..b83b28f 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -2,8 +2,9 @@ String, DateTime, Text, + Date ) -from sqlalchemy.org import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column from database import Base import datetime @@ -12,7 +13,7 @@ class EventDB(Base): __tablename__ = "event_info" eid: Mapped[int] = mapped_column( - int, + Integer, primary_key=True ) description: Mapped[str] = mapped_column( @@ -20,14 +21,14 @@ class EventDB(Base): nullable=True ) name: Mapped[str] = mapped_column(String(64)) - start_time: Mapped[datetime.datetime()] = mapped_column(DateTime) - end_time: Mapped[datetime.datetime()] = mapped_column(DateTime) + start_time: Mapped[datetime.datetime] = mapped_column(DateTime) + end_time: Mapped[datetime.datetime] = mapped_column(DateTime) repeat: Mapped[str] = mapped_column(String(64)) - start_date: Mapped[datetime.date()] = mapped_column( + start_date: Mapped[datetime.date] = mapped_column( Date, nullable=True ) - end_date: Mapped[datetime.date()] = mapped_column( + end_date: Mapped[datetime.date] = mapped_column( Date, nullable=True ) From e03cede6ca5ba5bfc7aa580420b9a0a10aa85eb4 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 19:08:37 -0700 Subject: [PATCH 05/15] refactor: updated the DateTime to have timezone --- src/event/tables.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event/tables.py b/src/event/tables.py index b83b28f..9dfc881 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column from database import Base -import datetime +from datetime import datetime, date class EventDB(Base): __tablename__ = "event_info" @@ -21,14 +21,14 @@ class EventDB(Base): nullable=True ) name: Mapped[str] = mapped_column(String(64)) - start_time: Mapped[datetime.datetime] = mapped_column(DateTime) - end_time: Mapped[datetime.datetime] = mapped_column(DateTime) + start_time: Mapped[datetime] = mapped_column(DateTime(timezone=True)) + end_time: Mapped[datetime] = mapped_column(DateTime(timezone=True)) repeat: Mapped[str] = mapped_column(String(64)) - start_date: Mapped[datetime.date] = mapped_column( + start_date: Mapped[date] = mapped_column( Date, nullable=True ) - end_date: Mapped[datetime.date] = mapped_column( + end_date: Mapped[date] = mapped_column( Date, nullable=True ) From 108cd5bd4902b6d4070983397ca397bcceacbfa5 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 23:57:07 -0700 Subject: [PATCH 06/15] feat: completed alembic db migration for events table --- src/alembic/env.py | 1 + .../0990cd930610_created_events_info_table.py | 40 +++++++++++++++++++ src/event/tables.py | 4 +- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/alembic/versions/0990cd930610_created_events_info_table.py diff --git a/src/alembic/env.py b/src/alembic/env.py index 7605c32..3693ae0 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -12,6 +12,7 @@ import nominees.tables import officers.tables import candidates.tables +import event.tables from alembic import context # this is the Alembic Config object, which provides diff --git a/src/alembic/versions/0990cd930610_created_events_info_table.py b/src/alembic/versions/0990cd930610_created_events_info_table.py new file mode 100644 index 0000000..75c51ae --- /dev/null +++ b/src/alembic/versions/0990cd930610_created_events_info_table.py @@ -0,0 +1,40 @@ +"""created events_info table + +Revision ID: 0990cd930610 +Revises: 0a2c458d1ddd +Create Date: 2026-05-09 23:53:01.667539 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '0990cd930610' +down_revision: Union[str, None] = '0a2c458d1ddd' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('event_info', + sa.Column('eid', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('name', sa.String(length=64), nullable=False), + sa.Column('start_time', sa.DateTime(timezone=True), nullable=False), + sa.Column('end_time', sa.DateTime(timezone=True), nullable=False), + sa.Column('repeat', sa.String(length=64), nullable=False), + sa.Column('start_date', sa.Date(), nullable=True), + sa.Column('end_date', sa.Date(), nullable=True), + sa.PrimaryKeyConstraint('eid', name=op.f('pk_event_info')) + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('event_info') + # ### end Alembic commands ### diff --git a/src/event/tables.py b/src/event/tables.py index 9dfc881..731b3bf 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -1,4 +1,5 @@ from sqlalchemy import ( + Integer, String, DateTime, Text, @@ -14,7 +15,8 @@ class EventDB(Base): eid: Mapped[int] = mapped_column( Integer, - primary_key=True + primary_key=True, + autoincrement=True ) description: Mapped[str] = mapped_column( Text, From 700f13f862d8b9fdc72ddd85847bba909fc30c2f Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sun, 10 May 2026 19:34:15 -0700 Subject: [PATCH 07/15] feat: created GET(/event) API endpoint --- src/event/crud.py | 13 +++++++++++++ src/event/models.py | 32 ++++++++++++++++---------------- src/event/url.py | 0 src/event/urls.py | 34 ++++++++++++++++++++++++++++++++++ src/main.py | 2 ++ 5 files changed, 65 insertions(+), 16 deletions(-) delete mode 100644 src/event/url.py create mode 100644 src/event/urls.py diff --git a/src/event/crud.py b/src/event/crud.py index e69de29..03e23c4 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -0,0 +1,13 @@ +from collections.abc import Sequence + +import sqlalchemy +from sqlalchemy.ext.asyncio import AsyncSession + +from event.tables import EventDB + + +async def get_all_events( + db_session: AsyncSession +) -> Sequence[EventDB]: + events = (await db_session.scalars(sqlalchemy.select(EventDB))).all() + return events \ No newline at end of file diff --git a/src/event/models.py b/src/event/models.py index e323a5e..8faaf79 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -5,37 +5,37 @@ class Event(BaseModel): model_config = ConfigDict(from_attributes=True) eid: int name: str - start_time: datetime.datetime() - end_time: datetime.datetime() + start_time: datetime.datetime + end_time: datetime.datetime description: str | None = None repeat: str | None = None - start_date: datetime.date() | None = None - end_date: datetime.date() | None = None + start_date: datetime.date | None = None + end_date: datetime.date | None = None class EventPublic(BaseModel): model_config = ConfigDict(from_attributes=True) name: str - start_time: datetime.datetime() - end_time: datetime.datetime() + start_time: datetime.datetime + end_time: datetime.datetime description: str | None = None repeat: str | None = None - start_date: datetime.date() | None = None - end_date: datetime.date() | None = None + start_date: datetime.date | None = None + end_date: datetime.date | None = None class EventCreate(BaseModel): name: str - start_time: datetime.datetime() - end_time: datetime.datetime() + start_time: datetime.datetime + end_time: datetime.datetime description: str | None = None repeat: str | None = None - start_date: datetime.date() | None = None - end_date: datetime.date() | None = None + start_date: datetime.date | None = None + end_date: datetime.date | None = None class EventUpdate(BaseModel): name: str | None = None - start_time: datetime.datetime() | None = None - end_time: datetime.datetime() | None = None + start_time: datetime.datetime | None = None + end_time: datetime.datetime | None = None description: str | None = None repeat: str | None = None - start_date: datetime.date() | None = None - end_date: datetime.date() | None = None \ No newline at end of file + start_date: datetime.date | None = None + end_date: datetime.date | None = None \ No newline at end of file diff --git a/src/event/url.py b/src/event/url.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/event/urls.py b/src/event/urls.py new file mode 100644 index 0000000..e5d3ef2 --- /dev/null +++ b/src/event/urls.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.responses import JSONResponse + +import database +import event.crud +from event.models import ( + Event, + EventPublic, + EventCreate, + EventUpdate +) +from event.tables import EventDB +from utils.shared_models import DetailModel, SuccessResponse + +router = APIRouter( + prefix="/event", + tags=["event"], +) + +@router.get( + "", + description="Get all events", + response_model=list[EventPublic], + # responses={}, + operation_id="get_all_events", + # probably want it to be public so no dependecies? + # dependecies=[Depends()] +) +async def get_all_events( + db_session: database.DBSession, +): + events_list = await event.crud.get_all_events(db_session) + + return events_list \ No newline at end of file diff --git a/src/main.py b/src/main.py index 66734c0..720296f 100755 --- a/src/main.py +++ b/src/main.py @@ -13,6 +13,7 @@ import nominees.urls import officers.urls import permission.urls +import event.urls from constants import IS_PROD logging.basicConfig(level=logging.DEBUG) @@ -58,6 +59,7 @@ app.include_router(nominees.urls.router) app.include_router(officers.urls.router) app.include_router(permission.urls.router) +app.include_router(event.urls.router) @app.get("/") From 784e384efa4a0c7db38dfc045319dae000366962 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 14 May 2026 13:47:12 -0700 Subject: [PATCH 08/15] feat: added API and db function for get events for given year --- src/event/crud.py | 20 ++++++++++++++++++-- src/event/urls.py | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/event/crud.py b/src/event/crud.py index 03e23c4..1d655d8 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -1,13 +1,29 @@ from collections.abc import Sequence -import sqlalchemy +from sqlalchemy import select, or_, and_, extract from sqlalchemy.ext.asyncio import AsyncSession from event.tables import EventDB +from datetime import datetime, date + async def get_all_events( db_session: AsyncSession ) -> Sequence[EventDB]: - events = (await db_session.scalars(sqlalchemy.select(EventDB))).all() + events = (await db_session.scalars(select(EventDB))).all() + return events + + +async def get_events_for_this_year( + db_session: AsyncSession, + year: int, +) -> Sequence[EventDB]: + events = (await db_session.scalars(select(EventDB).where + ( + or_( + extract('year', EventDB.start_time) == year, + extract('year', EventDB.end_time) == year + ) + ))).all() return events \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index e5d3ef2..1ca9adf 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -11,6 +11,7 @@ ) from event.tables import EventDB from utils.shared_models import DetailModel, SuccessResponse +from datetime import datetime, date router = APIRouter( prefix="/event", @@ -31,4 +32,20 @@ async def get_all_events( ): events_list = await event.crud.get_all_events(db_session) + return events_list + + +@router.get( + "/{year}", + description="Get events that start OR end in this year", + response_model=list[EventPublic], + # responses= {} + operation_id="get_events_for_this_year" +) +async def get_events_for_this_year( + db_session: database.DBSession, + year: int, +): + events_list = await event.crud.get_events_for_this_year(db_session, year) + return events_list \ No newline at end of file From 0d1dcb10552a8a414c133ffa9ed788738748f418 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 14 May 2026 14:09:45 -0700 Subject: [PATCH 09/15] feat: added API and db function for get events for given year and month --- src/event/crud.py | 23 +++++++++++++++++++++++ src/event/urls.py | 19 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/event/crud.py b/src/event/crud.py index 1d655d8..6ba2c66 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -26,4 +26,27 @@ async def get_events_for_this_year( extract('year', EventDB.end_time) == year ) ))).all() + return events + +async def get_events_for_this_year_month( + db_session: AsyncSession, + year: int, + month: int, +) -> Sequence[EventDB]: + events = ( + await db_session.scalars( + select(EventDB).where( + or_( + and_( + extract('year', EventDB.start_time) == year, + extract('month', EventDB.start_time) == month + ), + and_( + extract('year', EventDB.end_time) == year, + extract('month', EventDB.end_time) == month + ) + ) + ) + ) + ).all() return events \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index 1ca9adf..87b55e0 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -48,4 +48,21 @@ async def get_events_for_this_year( ): events_list = await event.crud.get_events_for_this_year(db_session, year) - return events_list \ No newline at end of file + return events_list + + +@router.get( + "/{year}/{month}", + description="Get events that start OR end in the given year and month", + response_model=list[EventPublic], + # responses= {} + operation_id="get_events_for_this_year_month" +) +async def get_events_for_this_year_month( + db_session: database.DBSession, + year: int, + month: int +): + events_list = await event.crud.get_events_for_this_year_month(db_session, year, month) + + return events_list From 9f03e9201cb27a3dfdc10156cdf328825597e0ab Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 14 May 2026 15:41:30 -0700 Subject: [PATCH 10/15] feat: implement post API for /event route --- src/event/crud.py | 9 ++++++++- src/event/urls.py | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/event/crud.py b/src/event/crud.py index 6ba2c66..03e10f9 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -49,4 +49,11 @@ async def get_events_for_this_year_month( ) ) ).all() - return events \ No newline at end of file + return events + + +async def create_event( + db_session: AsyncSession, + info: EventDB +): + db_session.add(info) \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index 87b55e0..6473001 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -66,3 +66,28 @@ async def get_events_for_this_year_month( events_list = await event.crud.get_events_for_this_year_month(db_session, year, month) return events_list + + +@router.post( + "", + description="Create a new event", + response_model=Event, + status_code=status.HTTP_201_CREATED, + responses={500: {"description": "failed to fetch new event", "model": DetailModel}}, + operation_id="create_event", + # dependecies=[Depends()] +) +async def create_event( + db_session: database.DBSession, + body: EventCreate +): + new_event = EventDB(**body.model_dump()) + await event.crud.create_event( + db_session, + new_event, + ) + + await db_session.commit() + await db_session.refresh(new_event) + + return new_event \ No newline at end of file From 91aeb59ed93ec44cb60ceb244886cabe19ef6579 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 14 May 2026 16:08:54 -0700 Subject: [PATCH 11/15] feat: implement delete API for /event/ route --- src/event/crud.py | 15 +++++++++++++-- src/event/urls.py | 28 +++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/event/crud.py b/src/event/crud.py index 03e10f9..0ab1f3c 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -1,6 +1,6 @@ from collections.abc import Sequence -from sqlalchemy import select, or_, and_, extract +from sqlalchemy import select, or_, and_, extract, delete from sqlalchemy.ext.asyncio import AsyncSession from event.tables import EventDB @@ -56,4 +56,15 @@ async def create_event( db_session: AsyncSession, info: EventDB ): - db_session.add(info) \ No newline at end of file + await db_session.add(info) + + +async def delete_event( + db_session: AsyncSession, + eid: int +): + result = await db_session.execute(delete(EventDB).where( + EventDB.eid == eid + )) + # Return the number of rows affected + return result.rowcount \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index 6473001..a73c250 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -90,4 +90,30 @@ async def create_event( await db_session.commit() await db_session.refresh(new_event) - return new_event \ No newline at end of file + return new_event + + +@router.delete( + "/{eid}", + description="Delete an event", + response_model=SuccessResponse, + responses={ + 404:{"description": "Event doesn't exist."} + }, + operation_id="delete_event", + # dependecies=[Depends()], +) +async def delete_event( + db_session: database.DBSession, + eid: int +): + rows_deleted = await event.crud.delete_event(db_session, eid) + + if rows_deleted == 0: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Event doesn't exist." + ) + + await db_session.commit() + return SuccessResponse(success=True) \ No newline at end of file From 43ec487ee84cd60ca2f57c581452f21742844af0 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 14 May 2026 16:23:46 -0700 Subject: [PATCH 12/15] fix: correct event response logic --- src/event/models.py | 6 +++++- src/event/urls.py | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/event/models.py b/src/event/models.py index 8faaf79..219488f 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -38,4 +38,8 @@ class EventUpdate(BaseModel): description: str | None = None repeat: str | None = None start_date: datetime.date | None = None - end_date: datetime.date | None = None \ No newline at end of file + end_date: datetime.date | None = None + +class EventDelete(BaseModel): + result: bool + eid: int \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index a73c250..7e08ca8 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -7,7 +7,8 @@ Event, EventPublic, EventCreate, - EventUpdate + EventUpdate, + EventDelete ) from event.tables import EventDB from utils.shared_models import DetailModel, SuccessResponse @@ -96,7 +97,7 @@ async def create_event( @router.delete( "/{eid}", description="Delete an event", - response_model=SuccessResponse, + response_model=EventDelete, responses={ 404:{"description": "Event doesn't exist."} }, @@ -108,7 +109,7 @@ async def delete_event( eid: int ): rows_deleted = await event.crud.delete_event(db_session, eid) - + if rows_deleted == 0: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -116,4 +117,4 @@ async def delete_event( ) await db_session.commit() - return SuccessResponse(success=True) \ No newline at end of file + return EventDelete(result=True, eid=eid) \ No newline at end of file From d9105934fcec62439d0838af34b23ecb8d734954 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 14 May 2026 16:54:23 -0700 Subject: [PATCH 13/15] feat(event): implement patch API for event/ endpoint --- src/event/crud.py | 9 +++++++++ src/event/urls.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/event/crud.py b/src/event/crud.py index 0ab1f3c..95570a4 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -52,6 +52,15 @@ async def get_events_for_this_year_month( return events +async def get_event_by_eid( + db_session: AsyncSession, + eid: int +) -> EventDB | None: + return (await db_session.execute( + select(EventDB).where(EventDB.eid == eid) + )).scalar_one_or_none() + + async def create_event( db_session: AsyncSession, info: EventDB diff --git a/src/event/urls.py b/src/event/urls.py index 7e08ca8..b47d2b8 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -94,6 +94,39 @@ async def create_event( return new_event +@router.patch( + "/{eid}", + description="Update an Event detail", + response_model=Event, + responses={ + 404:{"description": "Event doesn't exist."} + }, + operation_id="update_event" +) +async def update_event( + db_session: database.DBSession, + eid: int, + body: EventUpdate +): + event_info = await event.crud.get_event_by_eid(db_session, eid) + + if event_info is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Event doesn't exist." + ) + + updated_event = body.model_dump(exclude_unset=True) + for key, value in updated_event.items(): + setattr(event_info, key, value) + + await db_session.commit() + await db_session.refresh(event_info) + + return event_info + + + @router.delete( "/{eid}", description="Delete an event", From 7f82f03b3153cb847176fea301908a910c2abd1b Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Fri, 15 May 2026 23:04:30 -0700 Subject: [PATCH 14/15] feat:add constraints for start and end dates/times --- ....py => f4c493a24799_create_event_table.py} | 10 +++++---- src/event/crud.py | 2 +- src/event/tables.py | 21 +++++++++++++++---- 3 files changed, 24 insertions(+), 9 deletions(-) rename src/alembic/versions/{0990cd930610_created_events_info_table.py => f4c493a24799_create_event_table.py} (77%) diff --git a/src/alembic/versions/0990cd930610_created_events_info_table.py b/src/alembic/versions/f4c493a24799_create_event_table.py similarity index 77% rename from src/alembic/versions/0990cd930610_created_events_info_table.py rename to src/alembic/versions/f4c493a24799_create_event_table.py index 75c51ae..50c0ae1 100644 --- a/src/alembic/versions/0990cd930610_created_events_info_table.py +++ b/src/alembic/versions/f4c493a24799_create_event_table.py @@ -1,8 +1,8 @@ -"""created events_info table +"""create_event_table -Revision ID: 0990cd930610 +Revision ID: f4c493a24799 Revises: 0a2c458d1ddd -Create Date: 2026-05-09 23:53:01.667539 +Create Date: 2026-05-15 23:00:45.680647 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ # revision identifiers, used by Alembic. -revision: str = '0990cd930610' +revision: str = 'f4c493a24799' down_revision: Union[str, None] = '0a2c458d1ddd' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -29,6 +29,8 @@ def upgrade() -> None: sa.Column('repeat', sa.String(length=64), nullable=False), sa.Column('start_date', sa.Date(), nullable=True), sa.Column('end_date', sa.Date(), nullable=True), + sa.CheckConstraint('start_date < end_date', name=op.f('ck_event_info_check_start_date_before_end_date')), + sa.CheckConstraint('start_time < end_time', name=op.f('ck_event_info_check_start_time_before_end_time')), sa.PrimaryKeyConstraint('eid', name=op.f('pk_event_info')) ) # ### end Alembic commands ### diff --git a/src/event/crud.py b/src/event/crud.py index 95570a4..323ddd2 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -65,7 +65,7 @@ async def create_event( db_session: AsyncSession, info: EventDB ): - await db_session.add(info) + db_session.add(info) async def delete_event( diff --git a/src/event/tables.py b/src/event/tables.py index 731b3bf..bd413b7 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -3,7 +3,8 @@ String, DateTime, Text, - Date + Date, + CheckConstraint ) from sqlalchemy.orm import Mapped, mapped_column @@ -35,15 +36,27 @@ class EventDB(Base): nullable=True ) + __table_args__ = ( + CheckConstraint( + 'start_time < end_time', + name='check_start_time_before_end_time' + ), + CheckConstraint( + 'start_date < end_date', + name='check_start_date_before_end_date' + ) + ) + + def serialize(self) -> dict: return{ "eid": self.eid, "name": self.name, "description": self.description, "start_time": self.start_time, - "end_time": self.start_time, - "start_date": self.start_time, - "end_date": self.start_time, + "end_time": self.end_time, + "start_date": self.start_date, + "end_date": self.end_date, } \ No newline at end of file From 8caf56972cc184bd2ca74bd1ef7de9b0171a5d89 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 16 May 2026 13:02:23 -0700 Subject: [PATCH 15/15] feat: add integrity check for requests --- src/event/models.py | 26 ++++++++++++++++++++++- src/event/tables.py | 16 ++++++++++---- src/event/urls.py | 51 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/src/event/models.py b/src/event/models.py index 219488f..f3794e1 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, model_validator import datetime class Event(BaseModel): @@ -31,6 +31,23 @@ class EventCreate(BaseModel): start_date: datetime.date | None = None end_date: datetime.date | None = None + @model_validator(mode="after") + def validate_time_range(self) -> "EventCreate": + if self.start_time >= self.end_time: + raise ValueError("The event start must be before the event end") + + if self.start_date and self.end_date: + if self.start_date > self.end_date: + raise ValueError("The event repeat start date must be before the end date") + + if self.start_date and not self.end_date: + raise ValueError("The event can't have start date but not end date") + + if not self.start_date and self.end_date: + raise ValueError("The event can't have end date but not start date") + + return self + class EventUpdate(BaseModel): name: str | None = None start_time: datetime.datetime | None = None @@ -40,6 +57,13 @@ class EventUpdate(BaseModel): start_date: datetime.date | None = None end_date: datetime.date | None = None + @model_validator(mode="after") + def validate_time_range(self) -> "EventUpdate": + if self.start_time and self.end_time: + if self.start_time > self.end_time: + raise ValueError("The event start time must be before end time") + return self + class EventDelete(BaseModel): result: bool eid: int \ No newline at end of file diff --git a/src/event/tables.py b/src/event/tables.py index bd413b7..cf770b6 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -23,10 +23,18 @@ class EventDB(Base): Text, nullable=True ) - name: Mapped[str] = mapped_column(String(64)) - start_time: Mapped[datetime] = mapped_column(DateTime(timezone=True)) - end_time: Mapped[datetime] = mapped_column(DateTime(timezone=True)) - repeat: Mapped[str] = mapped_column(String(64)) + name: Mapped[str] = mapped_column( + String(64) + ) + start_time: Mapped[datetime] = mapped_column( + DateTime(timezone=True) + ) + end_time: Mapped[datetime] = mapped_column( + DateTime(timezone=True) + ) + repeat: Mapped[str] = mapped_column( + String(64) + ) start_date: Mapped[date] = mapped_column( Date, nullable=True diff --git a/src/event/urls.py b/src/event/urls.py index b47d2b8..e708c33 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -74,7 +74,9 @@ async def get_events_for_this_year_month( description="Create a new event", response_model=Event, status_code=status.HTTP_201_CREATED, - responses={500: {"description": "failed to fetch new event", "model": DetailModel}}, + responses={ + 500: {"description": "failed to fetch new event", "model": DetailModel}, + }, operation_id="create_event", # dependecies=[Depends()] ) @@ -108,22 +110,53 @@ async def update_event( eid: int, body: EventUpdate ): - event_info = await event.crud.get_event_by_eid(db_session, eid) - - if event_info is None: + db_event = await event.crud.get_event_by_eid(db_session, eid) + if db_event is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Event doesn't exist." ) + + final_start_time = body.start_time if body.start_time is not None else db_event.start_time + final_end_time = body.end_time if body.end_time is not None else db_event.end_time + + if final_start_time > final_end_time: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="The event start time must be before the end time" + ) - updated_event = body.model_dump(exclude_unset=True) - for key, value in updated_event.items(): - setattr(event_info, key, value) + if not body.start_date and body.end_date: + if not db_event.start_date: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="The event start date and event end date must be initilized at the same time" + ) + if db_event.start_date > body.end_date: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="The event start date must be before the event end date" + ) + if body.start_date and not body.end_date: + if not db_event.end_date: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="The event start date and event end date must be initilized at the same time" + ) + if body.start_date > db_event.end_date: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="The event start date must be before the event end date" + ) + + updated_data = body.model_dump(exclude_unset=True) + for key, value in updated_data.items(): + setattr(db_event, key, value) await db_session.commit() - await db_session.refresh(event_info) + await db_session.refresh(db_event) - return event_info + return db_event