From 1ff09e09bf4c9b076326e8a72cc1dcd586d4c960 Mon Sep 17 00:00:00 2001 From: Amraj Koonar Date: Mon, 29 Jun 2026 15:16:15 -0700 Subject: [PATCH 1/4] Fix nominee route database session type --- src/nominees/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nominees/urls.py b/src/nominees/urls.py index da3bd2db..913a9877 100644 --- a/src/nominees/urls.py +++ b/src/nominees/urls.py @@ -84,7 +84,7 @@ async def get_nominee_info(db_session: database.DBSession, computing_id: str): operation_id="delete_nominee", dependencies=[Depends(perm_election)], ) -async def delete_nominee_info(db_session: database.tDBSession, computing_id: str): +async def delete_nominee_info(db_session: database.DBSession, computing_id: str): try: await nominees.crud.delete_nominee_info(db_session, computing_id) await db_session.commit() From f13ba818ac45eed859a237b755ee06e799ceb91f Mon Sep 17 00:00:00 2001 From: Amraj Koonar Date: Mon, 29 Jun 2026 16:00:06 -0700 Subject: [PATCH 2/4] Add honourary member endpoints --- src/alembic/env.py | 1 + ...00f28c26e2_create_honorary_member_table.py | 36 ++++++ src/honourary/__init__.py | 0 src/honourary/crud.py | 63 ++++++++++ src/honourary/models.py | 28 +++++ src/honourary/tables.py | 15 +++ src/honourary/urls.py | 117 ++++++++++++++++++ src/main.py | 2 + 8 files changed, 262 insertions(+) create mode 100644 src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py create mode 100644 src/honourary/__init__.py create mode 100644 src/honourary/crud.py create mode 100644 src/honourary/models.py create mode 100644 src/honourary/tables.py create mode 100644 src/honourary/urls.py diff --git a/src/alembic/env.py b/src/alembic/env.py index 3693ae0e..5fd39561 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -13,6 +13,7 @@ import officers.tables import candidates.tables import event.tables +import honourary.tables from alembic import context # this is the Alembic Config object, which provides diff --git a/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py b/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py new file mode 100644 index 00000000..09356285 --- /dev/null +++ b/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py @@ -0,0 +1,36 @@ +"""create honorary member table + +Revision ID: 3c00f28c26e2 +Revises: 42f855bec532 +Create Date: 2026-06-29 15:43:07.361827 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '3c00f28c26e2' +down_revision: Union[str, None] = '42f855bec532' +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('honorary_member', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('name', sa.String(length=128), nullable=False), + sa.Column('start_date', sa.Date(), nullable=False), + sa.Column('end_date', sa.Date(), nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('pk_honorary_member')) + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('honorary_member') + # ### end Alembic commands ### diff --git a/src/honourary/__init__.py b/src/honourary/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/honourary/crud.py b/src/honourary/crud.py new file mode 100644 index 00000000..4b6eedb1 --- /dev/null +++ b/src/honourary/crud.py @@ -0,0 +1,63 @@ +from datetime import date + +from fastapi import HTTPException, status +from sqlalchemy import delete, select + +import database +from honourary.tables import HonoraryMemberDB + + +async def get_all_honorary_members(db_session: database.DBSession) -> list[HonoraryMemberDB]: + query = select(HonoraryMemberDB).order_by(HonoraryMemberDB.start_date.desc()) + return list((await db_session.scalars(query)).all()) + + +async def get_current_honorary_members(db_session: database.DBSession) -> list[HonoraryMemberDB]: + today = date.today() + query = ( + select(HonoraryMemberDB) + .where( + (HonoraryMemberDB.start_date <= today) + & ((HonoraryMemberDB.end_date >= today) | HonoraryMemberDB.end_date.is_(None)) + ) + .order_by(HonoraryMemberDB.start_date.desc()) + ) + return list((await db_session.scalars(query)).all()) + + +async def get_honorary_member_by_id_or_raise( + db_session: database.DBSession, + term_id: int, +) -> HonoraryMemberDB: + honorary_member = await db_session.scalar(select(HonoraryMemberDB).where(HonoraryMemberDB.id == term_id)) + if honorary_member is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"honorary member term with id={term_id} does not exist", + ) + return honorary_member + + +def ensure_term_has_not_ended(honorary_member: HonoraryMemberDB) -> None: + if honorary_member.end_date is not None and honorary_member.end_date < date.today(): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="cannot update or delete a term that has already ended", + ) + + +async def create_honorary_members( + db_session: database.DBSession, + honorary_members: list[HonoraryMemberDB], +) -> list[HonoraryMemberDB]: + db_session.add_all(honorary_members) + await db_session.flush() + + return honorary_members + + +async def delete_honorary_member( + db_session: database.DBSession, + honorary_member: HonoraryMemberDB, +) -> None: + await db_session.execute(delete(HonoraryMemberDB).where(HonoraryMemberDB.id == honorary_member.id)) diff --git a/src/honourary/models.py b/src/honourary/models.py new file mode 100644 index 00000000..84a179bc --- /dev/null +++ b/src/honourary/models.py @@ -0,0 +1,28 @@ +from datetime import date + +from pydantic import BaseModel, ConfigDict, Field, computed_field + +from utils import is_active_term + + +class HonoraryMemberCreate(BaseModel): + name: str = Field(..., max_length=128) + start_date: date + end_date: date | None = None + + +class HonoraryMember(HonoraryMemberCreate): + model_config = ConfigDict(from_attributes=True) + + id: int + + @computed_field + @property + def is_active(self) -> bool: + return is_active_term(start_date=self.start_date, end_date=self.end_date) + + +class HonoraryMemberUpdate(BaseModel): + name: str | None = Field(None, max_length=128) + start_date: date | None = None + end_date: date | None = None diff --git a/src/honourary/tables.py b/src/honourary/tables.py new file mode 100644 index 00000000..6534f669 --- /dev/null +++ b/src/honourary/tables.py @@ -0,0 +1,15 @@ +from datetime import date + +from sqlalchemy import Date, Integer, String +from sqlalchemy.orm import Mapped, mapped_column + +from database import Base + + +class HonoraryMemberDB(Base): + __tablename__ = "honorary_member" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(128), nullable=False) + start_date: Mapped[date] = mapped_column(Date, nullable=False) + end_date: Mapped[date] = mapped_column(Date, nullable=True) diff --git a/src/honourary/urls.py b/src/honourary/urls.py new file mode 100644 index 00000000..041723a2 --- /dev/null +++ b/src/honourary/urls.py @@ -0,0 +1,117 @@ +from fastapi import APIRouter, Depends +from fastapi.responses import JSONResponse + +import database +import honourary.crud +from dependencies import perm_admin +from honourary.models import HonoraryMember, HonoraryMemberCreate, HonoraryMemberUpdate +from honourary.tables import HonoraryMemberDB +from utils.shared_models import SuccessResponse + +router = APIRouter( + prefix="/honourary", + tags=["honourary"], +) + + +@router.get( + "", + description="Get all honourary member terms", + response_model=list[HonoraryMember], + operation_id="get_all_honourary_members", + dependencies=[Depends(perm_admin)], +) +async def get_all_honourary_members(db_session: database.DBSession): + honorary_members = await honourary.crud.get_all_honorary_members(db_session) + return JSONResponse( + [ + HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) + for member in honorary_members + ] + ) + + +@router.get( + "/current", + description="Get all active honourary member terms", + response_model=list[HonoraryMember], + operation_id="get_current_honourary_members", + dependencies=[Depends(perm_admin)], +) +async def get_current_honourary_members(db_session: database.DBSession): + honorary_members = await honourary.crud.get_current_honorary_members(db_session) + return JSONResponse( + [ + HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) + for member in honorary_members + ] + ) + + +@router.post( + "", + description="Create honourary member terms", + response_model=list[HonoraryMember], + operation_id="create_honourary_members", + dependencies=[Depends(perm_admin)], +) +async def create_honourary_members( + db_session: database.DBSession, + body: list[HonoraryMemberCreate], +): + new_members = [HonoraryMemberDB(**member.model_dump()) for member in body] + created_members = await honourary.crud.create_honorary_members(db_session, new_members) + + await db_session.commit() + + return JSONResponse( + [ + HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) + for member in created_members + ] + ) + + +@router.patch( + "/{term_id}", + description="Update an honourary member term. Ended terms cannot be updated.", + response_model=HonoraryMember, + operation_id="update_honourary_member", + dependencies=[Depends(perm_admin)], +) +async def update_honourary_member( + db_session: database.DBSession, + term_id: int, + body: HonoraryMemberUpdate, +): + honorary_member = await honourary.crud.get_honorary_member_by_id_or_raise(db_session, term_id) + honourary.crud.ensure_term_has_not_ended(honorary_member) + + update_data = body.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(honorary_member, key, value) + + await db_session.commit() + await db_session.refresh(honorary_member) + + return JSONResponse(HonoraryMember.model_validate(honorary_member).model_dump(mode="json", exclude_unset=True)) + + +@router.delete( + "/{term_id}", + description="Delete an honourary member term. Ended terms cannot be deleted.", + response_model=SuccessResponse, + operation_id="delete_honourary_member", + dependencies=[Depends(perm_admin)], +) +async def delete_honourary_member( + db_session: database.DBSession, + term_id: int, +): + honorary_member = await honourary.crud.get_honorary_member_by_id_or_raise(db_session, term_id) + honourary.crud.ensure_term_has_not_ended(honorary_member) + + await honourary.crud.delete_honorary_member(db_session, honorary_member) + await db_session.commit() + + return SuccessResponse(success=True) diff --git a/src/main.py b/src/main.py index 60bb5a74..b3021ab1 100755 --- a/src/main.py +++ b/src/main.py @@ -11,6 +11,7 @@ import database import elections.urls import event.urls +import honourary.urls import nominees.urls import officers.urls import permission.urls @@ -60,6 +61,7 @@ app.include_router(officers.urls.router) app.include_router(permission.urls.router) app.include_router(event.urls.router) +app.include_router(honourary.urls.router) @app.get("/") From be40db2bd06f891821acd820e005b1302de2cdc8 Mon Sep 17 00:00:00 2001 From: Amraj Koonar Date: Tue, 30 Jun 2026 16:05:59 -0700 Subject: [PATCH 3/4] Fixed honorary spelling, added constant value, and other changes per PR feedback --- src/alembic/env.py | 6 +- ...00f28c26e2_create_honorary_member_table.py | 30 ++-- src/honorary/constants.py | 1 + src/{honourary => honorary}/crud.py | 25 +-- src/{honourary => honorary}/models.py | 5 +- src/{honourary => honorary}/tables.py | 3 +- src/honorary/urls.py | 154 ++++++++++++++++++ src/honourary/__init__.py | 0 src/honourary/urls.py | 117 ------------- src/main.py | 4 +- 10 files changed, 188 insertions(+), 157 deletions(-) create mode 100644 src/honorary/constants.py rename src/{honourary => honorary}/crud.py (59%) rename src/{honourary => honorary}/models.py (74%) rename src/{honourary => honorary}/tables.py (74%) create mode 100644 src/honorary/urls.py delete mode 100644 src/honourary/__init__.py delete mode 100644 src/honourary/urls.py diff --git a/src/alembic/env.py b/src/alembic/env.py index 5fd39561..8a998926 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -7,13 +7,13 @@ import auth.tables import blog.tables +import candidates.tables import database import elections.tables +import event.tables +import honorary.tables import nominees.tables import officers.tables -import candidates.tables -import event.tables -import honourary.tables from alembic import context # this is the Alembic Config object, which provides diff --git a/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py b/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py index 09356285..f8dd4c33 100644 --- a/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py +++ b/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py @@ -5,32 +5,36 @@ Create Date: 2026-06-29 15:43:07.361827 """ -from typing import Sequence, Union -from alembic import op +from collections.abc import Sequence +from typing import Union + import sqlalchemy as sa +from alembic import op +from honorary.constants import HONORARY_MEMBER_MAX_LENGTH # revision identifiers, used by Alembic. -revision: str = '3c00f28c26e2' -down_revision: Union[str, None] = '42f855bec532' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None +revision: str = "3c00f28c26e2" +down_revision: str | None = "42f855bec532" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('honorary_member', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('start_date', sa.Date(), nullable=False), - sa.Column('end_date', sa.Date(), nullable=True), - sa.PrimaryKeyConstraint('id', name=op.f('pk_honorary_member')) + op.create_table( + "honorary_member", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("name", sa.String(length=HONORARY_MEMBER_MAX_LENGTH), nullable=False), + sa.Column("start_date", sa.Date(), nullable=False), + sa.Column("end_date", sa.Date(), nullable=True), + sa.PrimaryKeyConstraint("id", name=op.f("pk_honorary_member")), ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('honorary_member') + op.drop_table("honorary_member") # ### end Alembic commands ### diff --git a/src/honorary/constants.py b/src/honorary/constants.py new file mode 100644 index 00000000..619e968b --- /dev/null +++ b/src/honorary/constants.py @@ -0,0 +1 @@ +HONORARY_MEMBER_MAX_LENGTH = 128 diff --git a/src/honourary/crud.py b/src/honorary/crud.py similarity index 59% rename from src/honourary/crud.py rename to src/honorary/crud.py index 4b6eedb1..3fdf257b 100644 --- a/src/honourary/crud.py +++ b/src/honorary/crud.py @@ -1,10 +1,9 @@ from datetime import date -from fastapi import HTTPException, status from sqlalchemy import delete, select import database -from honourary.tables import HonoraryMemberDB +from honorary.tables import HonoraryMemberDB async def get_all_honorary_members(db_session: database.DBSession) -> list[HonoraryMemberDB]: @@ -25,25 +24,15 @@ async def get_current_honorary_members(db_session: database.DBSession) -> list[H return list((await db_session.scalars(query)).all()) -async def get_honorary_member_by_id_or_raise( +async def get_honorary_member_by_id( db_session: database.DBSession, term_id: int, -) -> HonoraryMemberDB: - honorary_member = await db_session.scalar(select(HonoraryMemberDB).where(HonoraryMemberDB.id == term_id)) - if honorary_member is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"honorary member term with id={term_id} does not exist", - ) - return honorary_member +) -> HonoraryMemberDB | None: + return await db_session.scalar(select(HonoraryMemberDB).where(HonoraryMemberDB.id == term_id)) -def ensure_term_has_not_ended(honorary_member: HonoraryMemberDB) -> None: - if honorary_member.end_date is not None and honorary_member.end_date < date.today(): - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="cannot update or delete a term that has already ended", - ) +def has_term_ended(honorary_member: HonoraryMemberDB) -> bool: + return honorary_member.end_date is not None and honorary_member.end_date < date.today() async def create_honorary_members( @@ -51,8 +40,6 @@ async def create_honorary_members( honorary_members: list[HonoraryMemberDB], ) -> list[HonoraryMemberDB]: db_session.add_all(honorary_members) - await db_session.flush() - return honorary_members diff --git a/src/honourary/models.py b/src/honorary/models.py similarity index 74% rename from src/honourary/models.py rename to src/honorary/models.py index 84a179bc..642d0b42 100644 --- a/src/honourary/models.py +++ b/src/honorary/models.py @@ -2,11 +2,12 @@ from pydantic import BaseModel, ConfigDict, Field, computed_field +from honorary.constants import HONORARY_MEMBER_MAX_LENGTH from utils import is_active_term class HonoraryMemberCreate(BaseModel): - name: str = Field(..., max_length=128) + name: str = Field(..., max_length=HONORARY_MEMBER_MAX_LENGTH) start_date: date end_date: date | None = None @@ -23,6 +24,6 @@ def is_active(self) -> bool: class HonoraryMemberUpdate(BaseModel): - name: str | None = Field(None, max_length=128) + name: str | None = Field(None, max_length=HONORARY_MEMBER_MAX_LENGTH) start_date: date | None = None end_date: date | None = None diff --git a/src/honourary/tables.py b/src/honorary/tables.py similarity index 74% rename from src/honourary/tables.py rename to src/honorary/tables.py index 6534f669..369aa505 100644 --- a/src/honourary/tables.py +++ b/src/honorary/tables.py @@ -4,12 +4,13 @@ from sqlalchemy.orm import Mapped, mapped_column from database import Base +from honorary.constants import HONORARY_MEMBER_MAX_LENGTH class HonoraryMemberDB(Base): __tablename__ = "honorary_member" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - name: Mapped[str] = mapped_column(String(128), nullable=False) + name: Mapped[str] = mapped_column(String(HONORARY_MEMBER_MAX_LENGTH), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=True) diff --git a/src/honorary/urls.py b/src/honorary/urls.py new file mode 100644 index 00000000..0a822358 --- /dev/null +++ b/src/honorary/urls.py @@ -0,0 +1,154 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.responses import JSONResponse + +import database +import honorary.crud +from dependencies import perm_admin +from honorary.models import HonoraryMember, HonoraryMemberCreate, HonoraryMemberUpdate +from honorary.tables import HonoraryMemberDB +from utils.shared_models import DetailModel, SuccessResponse + +router = APIRouter( + prefix="/honorary", + tags=["honorary"], +) + + +@router.get( + "", + description="Get all honorary member terms", + response_model=list[HonoraryMember], + responses={403: {"description": "must be a website admin", "model": DetailModel}}, + operation_id="get_all_honorary_members", + dependencies=[Depends(perm_admin)], +) +async def get_all_honorary_members(db_session: database.DBSession): + honorary_members = await honorary.crud.get_all_honorary_members(db_session) + return JSONResponse( + [ + HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) + for member in honorary_members + ] + ) + + +@router.get( + "/current", + description="Get all active honorary member terms", + response_model=list[HonoraryMember], + responses={403: {"description": "must be a website admin", "model": DetailModel}}, + operation_id="get_current_honorary_members", + dependencies=[Depends(perm_admin)], +) +async def get_current_honorary_members(db_session: database.DBSession): + honorary_members = await honorary.crud.get_current_honorary_members(db_session) + return JSONResponse( + [ + HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) + for member in honorary_members + ] + ) + + +@router.post( + "", + description="Create honorary member terms", + response_model=list[HonoraryMember], + responses={ + 403: {"description": "must be a website admin", "model": DetailModel}, + 500: {"description": "failed to create honorary member terms", "model": DetailModel}, + }, + operation_id="create_honorary_members", + dependencies=[Depends(perm_admin)], +) +async def create_honorary_members( + db_session: database.DBSession, + body: list[HonoraryMemberCreate], +): + new_members = [HonoraryMemberDB(**member.model_dump()) for member in body] + created_members = await honorary.crud.create_honorary_members(db_session, new_members) + + await db_session.flush() + + content = [ + HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) for member in created_members + ] + + await db_session.commit() + + return JSONResponse(content) + + +@router.patch( + "/{term_id}", + description="Update an honorary member term. Ended terms cannot be updated.", + response_model=HonoraryMember, + responses={ + 403: {"description": "must be a website admin", "model": DetailModel}, + 404: {"description": "honorary member term does not exist", "model": DetailModel}, + 409: {"description": "honorary member term has already ended", "model": DetailModel}, + }, + operation_id="update_honorary_member", + dependencies=[Depends(perm_admin)], +) +async def update_honorary_member( + db_session: database.DBSession, + term_id: int, + body: HonoraryMemberUpdate, +): + honorary_member = await honorary.crud.get_honorary_member_by_id(db_session, term_id) + if honorary_member is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"honorary member term with id={term_id} does not exist", + ) + + if honorary.crud.has_term_ended(honorary_member): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="cannot update a term that has already ended", + ) + + update_data = body.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(honorary_member, key, value) + + await db_session.commit() + await db_session.refresh(honorary_member) + + return JSONResponse(HonoraryMember.model_validate(honorary_member).model_dump(mode="json", exclude_unset=True)) + + +@router.delete( + "/{term_id}", + description="Delete an honorary member term. Ended terms cannot be deleted.", + response_model=SuccessResponse, + responses={ + 403: {"description": "must be a website admin", "model": DetailModel}, + 404: {"description": "honorary member term does not exist", "model": DetailModel}, + 409: {"description": "honorary member term has already ended", "model": DetailModel}, + }, + operation_id="delete_honorary_member", + dependencies=[Depends(perm_admin)], +) +async def delete_honorary_member( + db_session: database.DBSession, + term_id: int, +): + honorary_member = await honorary.crud.get_honorary_member_by_id(db_session, term_id) + if honorary_member is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"honorary member term with id={term_id} does not exist", + ) + + if honorary.crud.has_term_ended(honorary_member): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="cannot delete a term that has already ended", + ) + + await honorary.crud.delete_honorary_member(db_session, honorary_member) + await db_session.commit() + + return SuccessResponse(success=True) diff --git a/src/honourary/__init__.py b/src/honourary/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/honourary/urls.py b/src/honourary/urls.py deleted file mode 100644 index 041723a2..00000000 --- a/src/honourary/urls.py +++ /dev/null @@ -1,117 +0,0 @@ -from fastapi import APIRouter, Depends -from fastapi.responses import JSONResponse - -import database -import honourary.crud -from dependencies import perm_admin -from honourary.models import HonoraryMember, HonoraryMemberCreate, HonoraryMemberUpdate -from honourary.tables import HonoraryMemberDB -from utils.shared_models import SuccessResponse - -router = APIRouter( - prefix="/honourary", - tags=["honourary"], -) - - -@router.get( - "", - description="Get all honourary member terms", - response_model=list[HonoraryMember], - operation_id="get_all_honourary_members", - dependencies=[Depends(perm_admin)], -) -async def get_all_honourary_members(db_session: database.DBSession): - honorary_members = await honourary.crud.get_all_honorary_members(db_session) - return JSONResponse( - [ - HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) - for member in honorary_members - ] - ) - - -@router.get( - "/current", - description="Get all active honourary member terms", - response_model=list[HonoraryMember], - operation_id="get_current_honourary_members", - dependencies=[Depends(perm_admin)], -) -async def get_current_honourary_members(db_session: database.DBSession): - honorary_members = await honourary.crud.get_current_honorary_members(db_session) - return JSONResponse( - [ - HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) - for member in honorary_members - ] - ) - - -@router.post( - "", - description="Create honourary member terms", - response_model=list[HonoraryMember], - operation_id="create_honourary_members", - dependencies=[Depends(perm_admin)], -) -async def create_honourary_members( - db_session: database.DBSession, - body: list[HonoraryMemberCreate], -): - new_members = [HonoraryMemberDB(**member.model_dump()) for member in body] - created_members = await honourary.crud.create_honorary_members(db_session, new_members) - - await db_session.commit() - - return JSONResponse( - [ - HonoraryMember.model_validate(member).model_dump(mode="json", exclude_unset=True) - for member in created_members - ] - ) - - -@router.patch( - "/{term_id}", - description="Update an honourary member term. Ended terms cannot be updated.", - response_model=HonoraryMember, - operation_id="update_honourary_member", - dependencies=[Depends(perm_admin)], -) -async def update_honourary_member( - db_session: database.DBSession, - term_id: int, - body: HonoraryMemberUpdate, -): - honorary_member = await honourary.crud.get_honorary_member_by_id_or_raise(db_session, term_id) - honourary.crud.ensure_term_has_not_ended(honorary_member) - - update_data = body.model_dump(exclude_unset=True) - for key, value in update_data.items(): - setattr(honorary_member, key, value) - - await db_session.commit() - await db_session.refresh(honorary_member) - - return JSONResponse(HonoraryMember.model_validate(honorary_member).model_dump(mode="json", exclude_unset=True)) - - -@router.delete( - "/{term_id}", - description="Delete an honourary member term. Ended terms cannot be deleted.", - response_model=SuccessResponse, - operation_id="delete_honourary_member", - dependencies=[Depends(perm_admin)], -) -async def delete_honourary_member( - db_session: database.DBSession, - term_id: int, -): - honorary_member = await honourary.crud.get_honorary_member_by_id_or_raise(db_session, term_id) - honourary.crud.ensure_term_has_not_ended(honorary_member) - - await honourary.crud.delete_honorary_member(db_session, honorary_member) - await db_session.commit() - - return SuccessResponse(success=True) diff --git a/src/main.py b/src/main.py index b3021ab1..7cc53b69 100755 --- a/src/main.py +++ b/src/main.py @@ -11,7 +11,7 @@ import database import elections.urls import event.urls -import honourary.urls +import honorary.urls import nominees.urls import officers.urls import permission.urls @@ -61,7 +61,7 @@ app.include_router(officers.urls.router) app.include_router(permission.urls.router) app.include_router(event.urls.router) -app.include_router(honourary.urls.router) +app.include_router(honorary.urls.router) @app.get("/") From 3fa10aa7a98f7b85584d8768997412030927a648 Mon Sep 17 00:00:00 2001 From: Amraj Koonar Date: Fri, 3 Jul 2026 12:28:37 -0700 Subject: [PATCH 4/4] Fixed migration and constant value issue --- .../versions/3c00f28c26e2_create_honorary_member_table.py | 5 ++--- src/load_test_db.py | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py b/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py index f8dd4c33..068f7da8 100644 --- a/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py +++ b/src/alembic/versions/3c00f28c26e2_create_honorary_member_table.py @@ -12,11 +12,10 @@ import sqlalchemy as sa from alembic import op -from honorary.constants import HONORARY_MEMBER_MAX_LENGTH # revision identifiers, used by Alembic. revision: str = "3c00f28c26e2" -down_revision: str | None = "42f855bec532" +down_revision: str | None = "f0c99d0db277" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None @@ -26,7 +25,7 @@ def upgrade() -> None: op.create_table( "honorary_member", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("name", sa.String(length=HONORARY_MEMBER_MAX_LENGTH), nullable=False), + sa.Column("name", sa.String(length=128), nullable=False), sa.Column("start_date", sa.Date(), nullable=False), sa.Column("end_date", sa.Date(), nullable=True), sa.PrimaryKeyConstraint("id", name=op.f("pk_honorary_member")), diff --git a/src/load_test_db.py b/src/load_test_db.py index 024d1830..e4cc79c9 100644 --- a/src/load_test_db.py +++ b/src/load_test_db.py @@ -8,6 +8,8 @@ import sqlalchemy from sqlalchemy.ext.asyncio import AsyncSession +import honorary.tables + # NOTE: make sure you import from a file in your module which (at least) indirectly contains those # tables, or the current python context will not be able to find them & they won't be loaded from auth.crud import create_user_session, update_site_user