From c3a33a60f7b349ca0df9cfddaba98034daa3a620 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 12:26:42 -0700 Subject: [PATCH 01/26] 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 00000000..e69de29b diff --git a/src/event/models.py b/src/event/models.py new file mode 100644 index 00000000..e69de29b diff --git a/src/event/tables.py b/src/event/tables.py new file mode 100644 index 00000000..e69de29b diff --git a/src/event/url.py b/src/event/url.py new file mode 100644 index 00000000..e69de29b From d517c2459af6f0a8848b70e81f805df2312ddf90 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sat, 9 May 2026 12:27:32 -0700 Subject: [PATCH 02/26] 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 e69de29b..4ce732c1 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/26] 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 e69de29b..e323a5e0 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/26] 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 4ce732c1..b83b28f7 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/26] 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 b83b28f7..9dfc8812 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/26] 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 7605c32c..3693ae0e 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 00000000..75c51aef --- /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 9dfc8812..731b3bf9 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/26] 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 e69de29b..03e23c42 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 e323a5e0..8faaf794 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 e69de29b..00000000 diff --git a/src/event/urls.py b/src/event/urls.py new file mode 100644 index 00000000..e5d3ef28 --- /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 66734c07..720296fb 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/26] 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 03e23c42..1d655d82 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 e5d3ef28..1ca9adf3 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/26] 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 1d655d82..6ba2c66a 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 1ca9adf3..87b55e0f 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/26] 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 6ba2c66a..03e10f97 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 87b55e0f..64730016 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/26] 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 03e10f97..0ab1f3ce 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 64730016..a73c2505 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/26] 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 8faaf794..219488ff 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 a73c2505..7e08ca84 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/26] 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 0ab1f3ce..95570a45 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 7e08ca84..b47d2b86 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/26] 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 75c51aef..50c0ae1d 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 95570a45..323ddd2b 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 731b3bf9..bd413b7e 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/26] 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 219488ff..f3794e1a 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 bd413b7e..cf770b66 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 b47d2b86..e708c330 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 From 3324092fd74efcc96263ff40d8d4173897cf02c4 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Wed, 20 May 2026 16:48:31 -0700 Subject: [PATCH 16/26] refactor: change col names of event_info table from start_date to repeate_start_date and end_date to repeat_end_date --- ....py => 87bca29af018_create_event_table.py} | 12 +++++----- src/event/models.py | 24 +++++++++---------- src/event/tables.py | 12 +++++----- src/event/urls.py | 12 +++++----- 4 files changed, 30 insertions(+), 30 deletions(-) rename src/alembic/versions/{f4c493a24799_create_event_table.py => 87bca29af018_create_event_table.py} (77%) diff --git a/src/alembic/versions/f4c493a24799_create_event_table.py b/src/alembic/versions/87bca29af018_create_event_table.py similarity index 77% rename from src/alembic/versions/f4c493a24799_create_event_table.py rename to src/alembic/versions/87bca29af018_create_event_table.py index 50c0ae1d..eca26e34 100644 --- a/src/alembic/versions/f4c493a24799_create_event_table.py +++ b/src/alembic/versions/87bca29af018_create_event_table.py @@ -1,8 +1,8 @@ """create_event_table -Revision ID: f4c493a24799 +Revision ID: 87bca29af018 Revises: 0a2c458d1ddd -Create Date: 2026-05-15 23:00:45.680647 +Create Date: 2026-05-20 16:40:02.515549 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ # revision identifiers, used by Alembic. -revision: str = 'f4c493a24799' +revision: str = '87bca29af018' down_revision: Union[str, None] = '0a2c458d1ddd' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -27,9 +27,9 @@ def upgrade() -> None: 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.CheckConstraint('start_date < end_date', name=op.f('ck_event_info_check_start_date_before_end_date')), + sa.Column('repeat_start_date', sa.Date(), nullable=True), + sa.Column('repeat_end_date', sa.Date(), nullable=True), + sa.CheckConstraint('repeat_start_date < repeat_end_date', name=op.f('ck_event_info_check_repeat_start_date_before_repeat_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')) ) diff --git a/src/event/models.py b/src/event/models.py index f3794e1a..b7fbd05d 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -9,8 +9,8 @@ class Event(BaseModel): end_time: datetime.datetime description: str | None = None repeat: str | None = None - start_date: datetime.date | None = None - end_date: datetime.date | None = None + repeat_start_date: datetime.date | None = None + repeat_end_date: datetime.date | None = None class EventPublic(BaseModel): model_config = ConfigDict(from_attributes=True) @@ -19,8 +19,8 @@ class EventPublic(BaseModel): end_time: datetime.datetime description: str | None = None repeat: str | None = None - start_date: datetime.date | None = None - end_date: datetime.date | None = None + repeat_start_date: datetime.date | None = None + repeat_end_date: datetime.date | None = None class EventCreate(BaseModel): name: str @@ -28,22 +28,22 @@ class EventCreate(BaseModel): end_time: datetime.datetime description: str | None = None repeat: str | None = None - start_date: datetime.date | None = None - end_date: datetime.date | None = None + repeat_start_date: datetime.date | None = None + repeat_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: + if self.repeat_start_date and self.repeat_end_date: + if self.repeat_start_date > self.repeat_end_date: raise ValueError("The event repeat start date must be before the end date") - if self.start_date and not self.end_date: + if self.repeat_start_date and not self.repeat_end_date: raise ValueError("The event can't have start date but not end date") - if not self.start_date and self.end_date: + if not self.repeat_start_date and self.repeat_end_date: raise ValueError("The event can't have end date but not start date") return self @@ -54,8 +54,8 @@ class EventUpdate(BaseModel): 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 + repeat_start_date: datetime.date | None = None + repeat_end_date: datetime.date | None = None @model_validator(mode="after") def validate_time_range(self) -> "EventUpdate": diff --git a/src/event/tables.py b/src/event/tables.py index cf770b66..5b9cac94 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -35,11 +35,11 @@ class EventDB(Base): repeat: Mapped[str] = mapped_column( String(64) ) - start_date: Mapped[date] = mapped_column( + repeat_start_date: Mapped[date] = mapped_column( Date, nullable=True ) - end_date: Mapped[date] = mapped_column( + repeat_end_date: Mapped[date] = mapped_column( Date, nullable=True ) @@ -50,8 +50,8 @@ class EventDB(Base): name='check_start_time_before_end_time' ), CheckConstraint( - 'start_date < end_date', - name='check_start_date_before_end_date' + 'repeat_start_date < repeat_end_date', + name='check_repeat_start_date_before_repeat_end_date' ) ) @@ -63,8 +63,8 @@ def serialize(self) -> dict: "description": self.description, "start_time": self.start_time, "end_time": self.end_time, - "start_date": self.start_date, - "end_date": self.end_date, + "repeat_start_date": self.repeat_start_date, + "repeat_end_date": self.repeat_end_date, } \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index e708c330..e4990fd2 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -126,24 +126,24 @@ async def update_event( detail="The event start time must be before the end time" ) - if not body.start_date and body.end_date: - if not db_event.start_date: + if not body.repeat_start_date and body.repeat_end_date: + if not db_event.repeat_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: + if db_event.repeat_start_date > body.repeat_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: + if body.repeat_start_date and not body.repeat_end_date: + if not db_event.repeat_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: + if body.repeat_start_date > db_event.repeat_end_date: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="The event start date must be before the event end date" From d7c4dd04cf32535146c27f2f7617b1aab34a7b2a Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Wed, 20 May 2026 16:54:17 -0700 Subject: [PATCH 17/26] refactor: removed EventPublic Pydantic model and using just Event model for all response --- src/event/models.py | 2 +- src/event/urls.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event/models.py b/src/event/models.py index b7fbd05d..e0407c57 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -12,7 +12,7 @@ class Event(BaseModel): repeat_start_date: datetime.date | None = None repeat_end_date: datetime.date | None = None -class EventPublic(BaseModel): +class Event(BaseModel): model_config = ConfigDict(from_attributes=True) name: str start_time: datetime.datetime diff --git a/src/event/urls.py b/src/event/urls.py index e4990fd2..0f42aed1 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -5,7 +5,7 @@ import event.crud from event.models import ( Event, - EventPublic, + Event, EventCreate, EventUpdate, EventDelete @@ -22,7 +22,7 @@ @router.get( "", description="Get all events", - response_model=list[EventPublic], + response_model=list[Event], # responses={}, operation_id="get_all_events", # probably want it to be public so no dependecies? @@ -39,7 +39,7 @@ async def get_all_events( @router.get( "/{year}", description="Get events that start OR end in this year", - response_model=list[EventPublic], + response_model=list[Event], # responses= {} operation_id="get_events_for_this_year" ) @@ -55,7 +55,7 @@ async def get_events_for_this_year( @router.get( "/{year}/{month}", description="Get events that start OR end in the given year and month", - response_model=list[EventPublic], + response_model=list[Event], # responses= {} operation_id="get_events_for_this_year_month" ) From 7cdbd0ec328d66c844c2647f2ef1d79e45e63f34 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Wed, 20 May 2026 16:57:49 -0700 Subject: [PATCH 18/26] chore: removed comments and unused code --- src/event/models.py | 10 ---------- src/event/urls.py | 4 ---- 2 files changed, 14 deletions(-) diff --git a/src/event/models.py b/src/event/models.py index e0407c57..ba3f3a07 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -12,16 +12,6 @@ class Event(BaseModel): repeat_start_date: datetime.date | None = None repeat_end_date: datetime.date | None = None -class Event(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 - repeat_start_date: datetime.date | None = None - repeat_end_date: datetime.date | None = None - class EventCreate(BaseModel): name: str start_time: datetime.datetime diff --git a/src/event/urls.py b/src/event/urls.py index 0f42aed1..d99a0ac0 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -4,7 +4,6 @@ import database import event.crud from event.models import ( - Event, Event, EventCreate, EventUpdate, @@ -23,7 +22,6 @@ "", description="Get all events", response_model=list[Event], - # responses={}, operation_id="get_all_events", # probably want it to be public so no dependecies? # dependecies=[Depends()] @@ -40,7 +38,6 @@ async def get_all_events( "/{year}", description="Get events that start OR end in this year", response_model=list[Event], - # responses= {} operation_id="get_events_for_this_year" ) async def get_events_for_this_year( @@ -56,7 +53,6 @@ async def get_events_for_this_year( "/{year}/{month}", description="Get events that start OR end in the given year and month", response_model=list[Event], - # responses= {} operation_id="get_events_for_this_year_month" ) async def get_events_for_this_year_month( From f7d8c6077b09943f6acbd59f87e9d347c565964c Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Wed, 20 May 2026 17:06:56 -0700 Subject: [PATCH 19/26] fix: create, update and delete now requires site admin permission --- src/event/urls.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event/urls.py b/src/event/urls.py index d99a0ac0..9a8f8f97 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -3,6 +3,7 @@ import database import event.crud +from dependencies import perm_admin from event.models import ( Event, EventCreate, @@ -23,8 +24,6 @@ description="Get all events", response_model=list[Event], 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, @@ -74,7 +73,7 @@ async def get_events_for_this_year_month( 500: {"description": "failed to fetch new event", "model": DetailModel}, }, operation_id="create_event", - # dependecies=[Depends()] + dependecies=[Depends(perm_admin)], ) async def create_event( db_session: database.DBSession, @@ -99,7 +98,8 @@ async def create_event( responses={ 404:{"description": "Event doesn't exist."} }, - operation_id="update_event" + operation_id="update_event", + dependecies=[Depends(perm_admin)], ) async def update_event( db_session: database.DBSession, @@ -164,7 +164,7 @@ async def update_event( 404:{"description": "Event doesn't exist."} }, operation_id="delete_event", - # dependecies=[Depends()], + dependecies=[Depends(perm_admin)], ) async def delete_event( db_session: database.DBSession, From 6b2eaa82712fcf200cb7c62ba4badc5d7c984c4c Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Wed, 20 May 2026 17:25:55 -0700 Subject: [PATCH 20/26] chore: cleanup code --- src/event/tables.py | 16 +--------------- src/event/urls.py | 6 +++--- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/event/tables.py b/src/event/tables.py index 5b9cac94..86c3a3b5 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -53,18 +53,4 @@ class EventDB(Base): 'repeat_start_date < repeat_end_date', name='check_repeat_start_date_before_repeat_end_date' ) - ) - - - def serialize(self) -> dict: - return{ - "eid": self.eid, - "name": self.name, - "description": self.description, - "start_time": self.start_time, - "end_time": self.end_time, - "repeat_start_date": self.repeat_start_date, - "repeat_end_date": self.repeat_end_date, - } - - \ No newline at end of file + ) \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py index 9a8f8f97..5659dd02 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -73,7 +73,7 @@ async def get_events_for_this_year_month( 500: {"description": "failed to fetch new event", "model": DetailModel}, }, operation_id="create_event", - dependecies=[Depends(perm_admin)], + dependencies=[Depends(perm_admin)], ) async def create_event( db_session: database.DBSession, @@ -99,7 +99,7 @@ async def create_event( 404:{"description": "Event doesn't exist."} }, operation_id="update_event", - dependecies=[Depends(perm_admin)], + dependencies=[Depends(perm_admin)], ) async def update_event( db_session: database.DBSession, @@ -164,7 +164,7 @@ async def update_event( 404:{"description": "Event doesn't exist."} }, operation_id="delete_event", - dependecies=[Depends(perm_admin)], + dependencies=[Depends(perm_admin)], ) async def delete_event( db_session: database.DBSession, From c9a319bf05e9acd4f2e0a0b83da4feec7686ac62 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 21 May 2026 11:45:16 -0700 Subject: [PATCH 21/26] refactor: validation logic now handled by pydantic model for patch --- src/event/models.py | 36 ++++++++++++------------------------ src/event/urls.py | 39 ++++++--------------------------------- 2 files changed, 18 insertions(+), 57 deletions(-) diff --git a/src/event/models.py b/src/event/models.py index ba3f3a07..e67ba460 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,18 +1,8 @@ from pydantic import BaseModel, ConfigDict, model_validator 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 - repeat_start_date: datetime.date | None = None - repeat_end_date: datetime.date | None = None -class EventCreate(BaseModel): +class BaseEvent(BaseModel): name: str start_time: datetime.datetime end_time: datetime.datetime @@ -22,7 +12,7 @@ class EventCreate(BaseModel): repeat_end_date: datetime.date | None = None @model_validator(mode="after") - def validate_time_range(self) -> "EventCreate": + def validate_time_range(self) -> "BaseEvent": if self.start_time >= self.end_time: raise ValueError("The event start must be before the event end") @@ -30,15 +20,20 @@ def validate_time_range(self) -> "EventCreate": if self.repeat_start_date > self.repeat_end_date: raise ValueError("The event repeat start date must be before the end date") - if self.repeat_start_date and not self.repeat_end_date: - raise ValueError("The event can't have start date but not end date") + if (self.repeat_start_date is None) != (self.repeat_end_date is None): + raise ValueError("The event must have both repeat start and repeat end or have neither.") + + return self - if not self.repeat_start_date and self.repeat_end_date: - raise ValueError("The event can't have end date but not start date") +class Event(BaseEvent): + model_config = ConfigDict(from_attributes=True) + eid: int - return self +class EventCreate(BaseEvent): + pass class EventUpdate(BaseModel): + model_config = ConfigDict(extra="forbid") name: str | None = None start_time: datetime.datetime | None = None end_time: datetime.datetime | None = None @@ -47,13 +42,6 @@ class EventUpdate(BaseModel): repeat_start_date: datetime.date | None = None repeat_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/urls.py b/src/event/urls.py index 5659dd02..83852246 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -113,40 +113,13 @@ async def update_event( 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 + db_data = Event.model_validate(db_event).model_dump() + patch_data = body.model_dump(exclude_unset=True) - 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" - ) - - if not body.repeat_start_date and body.repeat_end_date: - if not db_event.repeat_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.repeat_start_date > body.repeat_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.repeat_start_date and not body.repeat_end_date: - if not db_event.repeat_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.repeat_start_date > db_event.repeat_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(): + merged_data = { **db_data, **patch_data} + Event.model_validate(merged_data) + + for key, value in patch_data.model_dump().items(): setattr(db_event, key, value) await db_session.commit() From 240774eefa83193e42cd1e4519f0cf45f24a2f1e Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 21 May 2026 12:09:42 -0700 Subject: [PATCH 22/26] bug: caught validation error from pydantic and return proper status code --- src/event/urls.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/event/urls.py b/src/event/urls.py index 83852246..c4e7e7a8 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -13,6 +13,8 @@ from event.tables import EventDB from utils.shared_models import DetailModel, SuccessResponse from datetime import datetime, date +from pydantic import ValidationError +from fastapi.encoders import jsonable_encoder router = APIRouter( prefix="/event", @@ -73,7 +75,7 @@ async def get_events_for_this_year_month( 500: {"description": "failed to fetch new event", "model": DetailModel}, }, operation_id="create_event", - dependencies=[Depends(perm_admin)], + # dependencies=[Depends(perm_admin)], ) async def create_event( db_session: database.DBSession, @@ -99,7 +101,7 @@ async def create_event( 404:{"description": "Event doesn't exist."} }, operation_id="update_event", - dependencies=[Depends(perm_admin)], + # dependencies=[Depends(perm_admin)], ) async def update_event( db_session: database.DBSession, @@ -117,9 +119,15 @@ async def update_event( patch_data = body.model_dump(exclude_unset=True) merged_data = { **db_data, **patch_data} - Event.model_validate(merged_data) + try: + Event.model_validate(merged_data) + except ValidationError as e: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=jsonable_encoder(e.errors()) + ) - for key, value in patch_data.model_dump().items(): + for key, value in patch_data.items(): setattr(db_event, key, value) await db_session.commit() @@ -137,7 +145,7 @@ async def update_event( 404:{"description": "Event doesn't exist."} }, operation_id="delete_event", - dependencies=[Depends(perm_admin)], + # dependencies=[Depends(perm_admin)], ) async def delete_event( db_session: database.DBSession, From 7a06cacbedecb5856a74c4e1d43beacacad42c0f Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 21 May 2026 12:12:27 -0700 Subject: [PATCH 23/26] fix: uncommented dependencies --- src/event/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/urls.py b/src/event/urls.py index c4e7e7a8..1ba465b5 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -75,7 +75,7 @@ async def get_events_for_this_year_month( 500: {"description": "failed to fetch new event", "model": DetailModel}, }, operation_id="create_event", - # dependencies=[Depends(perm_admin)], + dependencies=[Depends(perm_admin)], ) async def create_event( db_session: database.DBSession, @@ -101,7 +101,7 @@ async def create_event( 404:{"description": "Event doesn't exist."} }, operation_id="update_event", - # dependencies=[Depends(perm_admin)], + dependencies=[Depends(perm_admin)], ) async def update_event( db_session: database.DBSession, @@ -145,7 +145,7 @@ async def update_event( 404:{"description": "Event doesn't exist."} }, operation_id="delete_event", - # dependencies=[Depends(perm_admin)], + dependencies=[Depends(perm_admin)], ) async def delete_event( db_session: database.DBSession, From b0d457b5cdd8713cc507fca9d05fcd89cd59a2f6 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Thu, 21 May 2026 12:33:11 -0700 Subject: [PATCH 24/26] chore: ran ruff fix --- src/event/crud.py | 57 ++++++++++---------------------- src/event/models.py | 15 ++++++--- src/event/tables.py | 62 +++++++++-------------------------- src/event/urls.py | 71 ++++++++++++---------------------------- src/main.py | 2 +- tests/wip/test_github.py | 2 ++ 6 files changed, 67 insertions(+), 142 deletions(-) diff --git a/src/event/crud.py b/src/event/crud.py index 323ddd2b..ece11243 100644 --- a/src/event/crud.py +++ b/src/event/crud.py @@ -1,16 +1,13 @@ from collections.abc import Sequence +from datetime import date, datetime -from sqlalchemy import select, or_, and_, extract, delete +from sqlalchemy import and_, delete, extract, or_, select 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]: +async def get_all_events(db_session: AsyncSession) -> Sequence[EventDB]: events = (await db_session.scalars(select(EventDB))).all() return events @@ -19,15 +16,16 @@ 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 + events = ( + await db_session.scalars( + select(EventDB).where( + or_(extract("year", EventDB.start_time) == year, extract("year", EventDB.end_time) == year) + ) ) - ))).all() + ).all() return events + async def get_events_for_this_year_month( db_session: AsyncSession, year: int, @@ -37,14 +35,8 @@ async def get_events_for_this_year_month( 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 - ) + 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), ) ) ) @@ -52,28 +44,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 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 -): +async def create_event(db_session: AsyncSession, info: EventDB): db_session.add(info) -async def delete_event( - db_session: AsyncSession, - eid: int -): - result = await db_session.execute(delete(EventDB).where( - EventDB.eid == eid - )) +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 + return result.rowcount diff --git a/src/event/models.py b/src/event/models.py index e67ba460..e0b4943d 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,6 +1,7 @@ -from pydantic import BaseModel, ConfigDict, model_validator import datetime +from pydantic import BaseModel, ConfigDict, model_validator + class BaseEvent(BaseModel): name: str @@ -15,23 +16,26 @@ class BaseEvent(BaseModel): def validate_time_range(self) -> "BaseEvent": if self.start_time >= self.end_time: raise ValueError("The event start must be before the event end") - + if self.repeat_start_date and self.repeat_end_date: if self.repeat_start_date > self.repeat_end_date: raise ValueError("The event repeat start date must be before the end date") - + if (self.repeat_start_date is None) != (self.repeat_end_date is None): raise ValueError("The event must have both repeat start and repeat end or have neither.") - + return self + class Event(BaseEvent): model_config = ConfigDict(from_attributes=True) eid: int + class EventCreate(BaseEvent): pass + class EventUpdate(BaseModel): model_config = ConfigDict(extra="forbid") name: str | None = None @@ -42,6 +46,7 @@ class EventUpdate(BaseModel): repeat_start_date: datetime.date | None = None repeat_end_date: datetime.date | None = None + class EventDelete(BaseModel): result: bool - eid: int \ No newline at end of file + eid: int diff --git a/src/event/tables.py b/src/event/tables.py index 86c3a3b5..fc496c75 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -1,56 +1,24 @@ -from sqlalchemy import ( - Integer, - String, - DateTime, - Text, - Date, - CheckConstraint -) +from datetime import date, datetime + +from sqlalchemy import CheckConstraint, Date, DateTime, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column from database import Base -from datetime import datetime, date + class EventDB(Base): __tablename__ = "event_info" - eid: Mapped[int] = mapped_column( - Integer, - primary_key=True, - autoincrement=True - ) - description: Mapped[str] = mapped_column( - 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) - ) - repeat_start_date: Mapped[date] = mapped_column( - Date, - nullable=True - ) - repeat_end_date: Mapped[date] = mapped_column( - Date, - nullable=True - ) + eid: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + description: Mapped[str] = mapped_column(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)) + repeat_start_date: Mapped[date] = mapped_column(Date, nullable=True) + repeat_end_date: Mapped[date] = mapped_column(Date, nullable=True) __table_args__ = ( - CheckConstraint( - 'start_time < end_time', - name='check_start_time_before_end_time' - ), - CheckConstraint( - 'repeat_start_date < repeat_end_date', - name='check_repeat_start_date_before_repeat_end_date' - ) - ) \ No newline at end of file + CheckConstraint("start_time < end_time", name="check_start_time_before_end_time"), + CheckConstraint("repeat_start_date < repeat_end_date", name="check_repeat_start_date_before_repeat_end_date"), + ) diff --git a/src/event/urls.py b/src/event/urls.py index 1ba465b5..76968d69 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -1,26 +1,23 @@ +from datetime import date, datetime + from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse +from pydantic import ValidationError import database import event.crud from dependencies import perm_admin -from event.models import ( - Event, - EventCreate, - EventUpdate, - EventDelete -) +from event.models import Event, EventCreate, EventDelete, EventUpdate from event.tables import EventDB from utils.shared_models import DetailModel, SuccessResponse -from datetime import datetime, date -from pydantic import ValidationError -from fastapi.encoders import jsonable_encoder router = APIRouter( prefix="/event", tags=["event"], ) + @router.get( "", description="Get all events", @@ -39,7 +36,7 @@ async def get_all_events( "/{year}", description="Get events that start OR end in this year", response_model=list[Event], - operation_id="get_events_for_this_year" + operation_id="get_events_for_this_year", ) async def get_events_for_this_year( db_session: database.DBSession, @@ -54,13 +51,9 @@ async def get_events_for_this_year( "/{year}/{month}", description="Get events that start OR end in the given year and month", response_model=list[Event], - operation_id="get_events_for_this_year_month" + operation_id="get_events_for_this_year_month", ) -async def get_events_for_this_year_month( - db_session: database.DBSession, - year: int, - month: int -): +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 @@ -77,10 +70,7 @@ async def get_events_for_this_year_month( operation_id="create_event", dependencies=[Depends(perm_admin)], ) -async def create_event( - db_session: database.DBSession, - body: EventCreate -): +async def create_event(db_session: database.DBSession, body: EventCreate): new_event = EventDB(**body.model_dump()) await event.crud.create_event( db_session, @@ -97,67 +87,48 @@ async def create_event( "/{eid}", description="Update an Event detail", response_model=Event, - responses={ - 404:{"description": "Event doesn't exist."} - }, + responses={404: {"description": "Event doesn't exist."}}, operation_id="update_event", dependencies=[Depends(perm_admin)], ) -async def update_event( - db_session: database.DBSession, - eid: int, - body: EventUpdate -): +async def update_event(db_session: database.DBSession, eid: int, body: EventUpdate): 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." - ) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Event doesn't exist.") db_data = Event.model_validate(db_event).model_dump() patch_data = body.model_dump(exclude_unset=True) - merged_data = { **db_data, **patch_data} + merged_data = {**db_data, **patch_data} try: Event.model_validate(merged_data) except ValidationError as e: raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=jsonable_encoder(e.errors()) - ) + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=jsonable_encoder(e.errors()) + ) from e for key, value in patch_data.items(): setattr(db_event, key, value) - + await db_session.commit() await db_session.refresh(db_event) return db_event - @router.delete( "/{eid}", description="Delete an event", response_model=EventDelete, - responses={ - 404:{"description": "Event doesn't exist."} - }, + responses={404: {"description": "Event doesn't exist."}}, operation_id="delete_event", dependencies=[Depends(perm_admin)], ) -async def delete_event( - db_session: database.DBSession, - eid: int -): +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." - ) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Event doesn't exist.") await db_session.commit() - return EventDelete(result=True, eid=eid) \ No newline at end of file + return EventDelete(result=True, eid=eid) diff --git a/src/main.py b/src/main.py index 720296fb..60bb5a74 100755 --- a/src/main.py +++ b/src/main.py @@ -10,10 +10,10 @@ import candidates.urls import database import elections.urls +import event.urls import nominees.urls import officers.urls import permission.urls -import event.urls from constants import IS_PROD logging.basicConfig(level=logging.DEBUG) diff --git a/tests/wip/test_github.py b/tests/wip/test_github.py index 671aefcb..054ca919 100644 --- a/tests/wip/test_github.py +++ b/tests/wip/test_github.py @@ -4,11 +4,13 @@ # NOTE: must export API key to use github api (mostly...) + @pytest.mark.asyncio async def test__list_users(): member_list = await github.internals.list_members() print(member_list) + @pytest.mark.asyncio async def test__get_user_by_name(): user = await github.internals.get_user_by_username("EarthenSky") From e52ccc29a83d2cb3f399bb88099f4abe1d6232d1 Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sun, 24 May 2026 18:58:27 -0700 Subject: [PATCH 25/26] refactor: changed the column name of repeat to frequency and the type from string to Enum in event_info table --- ...event_table.py => 4928dc3f0b07_create_event_table.py} | 9 +++++---- src/event/constants.py | 9 +++++++++ src/event/models.py | 6 +++--- src/event/tables.py | 6 ++++-- 4 files changed, 21 insertions(+), 9 deletions(-) rename src/alembic/versions/{87bca29af018_create_event_table.py => 4928dc3f0b07_create_event_table.py} (80%) create mode 100644 src/event/constants.py diff --git a/src/alembic/versions/87bca29af018_create_event_table.py b/src/alembic/versions/4928dc3f0b07_create_event_table.py similarity index 80% rename from src/alembic/versions/87bca29af018_create_event_table.py rename to src/alembic/versions/4928dc3f0b07_create_event_table.py index eca26e34..e2ffe3fd 100644 --- a/src/alembic/versions/87bca29af018_create_event_table.py +++ b/src/alembic/versions/4928dc3f0b07_create_event_table.py @@ -1,8 +1,8 @@ """create_event_table -Revision ID: 87bca29af018 +Revision ID: 4928dc3f0b07 Revises: 0a2c458d1ddd -Create Date: 2026-05-20 16:40:02.515549 +Create Date: 2026-05-24 17:39:22.538239 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ # revision identifiers, used by Alembic. -revision: str = '87bca29af018' +revision: str = '4928dc3f0b07' down_revision: Union[str, None] = '0a2c458d1ddd' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -26,9 +26,10 @@ def upgrade() -> None: 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('frequency', sa.String(length=64), server_default=sa.text("'NONE'"), nullable=True), sa.Column('repeat_start_date', sa.Date(), nullable=True), sa.Column('repeat_end_date', sa.Date(), nullable=True), + sa.CheckConstraint("frequency IN ('NONE', 'DAILY', 'WEEKLY', 'MONTHLY', 'SEMESTERLY', 'YEARLY')", name=op.f('ck_event_info_valid_frequency_value')), sa.CheckConstraint('repeat_start_date < repeat_end_date', name=op.f('ck_event_info_check_repeat_start_date_before_repeat_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')) diff --git a/src/event/constants.py b/src/event/constants.py new file mode 100644 index 00000000..2a573867 --- /dev/null +++ b/src/event/constants.py @@ -0,0 +1,9 @@ +from enum import StrEnum + +class EventFrequencyEnum(StrEnum): + NONE = "NONE" + DAILY = "DAILY" + WEEKLY = "WEEKLY" + MONTHLY = "MONTHLY" + SEMESTERLY = "SEMESTERLY" + YEARLY = "YEARLY" diff --git a/src/event/models.py b/src/event/models.py index e0b4943d..5086cab6 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,5 +1,5 @@ import datetime - +from event.constants import EventFrequencyEnum from pydantic import BaseModel, ConfigDict, model_validator @@ -8,7 +8,7 @@ class BaseEvent(BaseModel): start_time: datetime.datetime end_time: datetime.datetime description: str | None = None - repeat: str | None = None + frequency: EventFrequencyEnum | None = None repeat_start_date: datetime.date | None = None repeat_end_date: datetime.date | None = None @@ -42,7 +42,7 @@ class EventUpdate(BaseModel): start_time: datetime.datetime | None = None end_time: datetime.datetime | None = None description: str | None = None - repeat: str | None = None + frequency: EventFrequencyEnum | None = None repeat_start_date: datetime.date | None = None repeat_end_date: datetime.date | None = None diff --git a/src/event/tables.py b/src/event/tables.py index fc496c75..a273e249 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -1,6 +1,7 @@ from datetime import date, datetime +from event.constants import EventFrequencyEnum -from sqlalchemy import CheckConstraint, Date, DateTime, Integer, String, Text +from sqlalchemy import CheckConstraint, Date, DateTime, Integer, String, Text, text from sqlalchemy.orm import Mapped, mapped_column from database import Base @@ -14,11 +15,12 @@ class EventDB(Base): 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)) + frequency: Mapped[EventFrequencyEnum] = mapped_column(String(64), server_default=text("'NONE'"), nullable=True) repeat_start_date: Mapped[date] = mapped_column(Date, nullable=True) repeat_end_date: Mapped[date] = mapped_column(Date, nullable=True) __table_args__ = ( CheckConstraint("start_time < end_time", name="check_start_time_before_end_time"), CheckConstraint("repeat_start_date < repeat_end_date", name="check_repeat_start_date_before_repeat_end_date"), + CheckConstraint(frequency.in_([e.value for e in EventFrequencyEnum]), name="valid_frequency_value") ) From 1b064f65ecb750f42cb4e99e702a7dc23ed48a4b Mon Sep 17 00:00:00 2001 From: arunPdl02 Date: Sun, 24 May 2026 19:00:12 -0700 Subject: [PATCH 26/26] chore: ran ruff fix --- src/event/constants.py | 1 + src/event/models.py | 4 +++- src/event/tables.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event/constants.py b/src/event/constants.py index 2a573867..87640317 100644 --- a/src/event/constants.py +++ b/src/event/constants.py @@ -1,5 +1,6 @@ from enum import StrEnum + class EventFrequencyEnum(StrEnum): NONE = "NONE" DAILY = "DAILY" diff --git a/src/event/models.py b/src/event/models.py index 5086cab6..40c89770 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,7 +1,9 @@ import datetime -from event.constants import EventFrequencyEnum + from pydantic import BaseModel, ConfigDict, model_validator +from event.constants import EventFrequencyEnum + class BaseEvent(BaseModel): name: str diff --git a/src/event/tables.py b/src/event/tables.py index a273e249..2981cb2e 100644 --- a/src/event/tables.py +++ b/src/event/tables.py @@ -1,10 +1,10 @@ from datetime import date, datetime -from event.constants import EventFrequencyEnum from sqlalchemy import CheckConstraint, Date, DateTime, Integer, String, Text, text from sqlalchemy.orm import Mapped, mapped_column from database import Base +from event.constants import EventFrequencyEnum class EventDB(Base):