From d67b0002e6e708b54fc53f76b89c8bf90cca95b8 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Fri, 9 Aug 2024 11:12:35 +0000 Subject: [PATCH 01/32] Added elections model to alembic/env.py --- src/alembic/env.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/alembic/env.py b/src/alembic/env.py index 3eab2bc..b9f4e11 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -3,6 +3,7 @@ import auth.models import database +import elections.models import officers.models from alembic import context from sqlalchemy import pool From 905170b1282c72b1facb82095bd4094454629ce4 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Fri, 9 Aug 2024 11:13:14 +0000 Subject: [PATCH 02/32] Added election tables revision to alembic --- .../cb5df398547c_create_election_tables.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/alembic/versions/cb5df398547c_create_election_tables.py diff --git a/src/alembic/versions/cb5df398547c_create_election_tables.py b/src/alembic/versions/cb5df398547c_create_election_tables.py new file mode 100644 index 0000000..8ff98e3 --- /dev/null +++ b/src/alembic/versions/cb5df398547c_create_election_tables.py @@ -0,0 +1,59 @@ +"""create election tables + +Revision ID: cb5df398547c +Revises: 43f71e4bd6fc +Create Date: 2024-08-09 09:36:22.745234 + +""" +from collections.abc import Sequence +from typing import Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "cb5df398547c" +down_revision: str | None = "43f71e4bd6fc" +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("election", + sa.Column("slug", sa.String(length=32), nullable=False), + sa.Column("name", sa.String(length=32), nullable=False), + sa.Column("type", sa.String(length=64), nullable=True), + sa.Column("date", sa.DateTime(), nullable=True), + sa.Column("end_date", sa.DateTime(), nullable=True), + sa.Column("websurvey", sa.String(length=300), nullable=True), + sa.PrimaryKeyConstraint("slug") + ) + op.create_table("election_nominee", + sa.Column("computing_id", sa.String(length=32), nullable=False), + sa.Column("full_name", sa.String(length=64), nullable=False), + sa.Column("facebook", sa.String(length=64), nullable=True), + sa.Column("instagram", sa.String(length=64), nullable=True), + sa.Column("email", sa.String(length=64), nullable=True), + sa.Column("discord", sa.String(length=32), nullable=True), + sa.Column("discord_id", sa.String(length=18), nullable=True), + sa.Column("discord_username", sa.String(length=32), nullable=True), + sa.PrimaryKeyConstraint("computing_id") + ) + op.create_table("nominee_speech", + sa.Column("computing_id", sa.String(length=32), nullable=False), + sa.Column("nominee_election", sa.String(length=32), nullable=False), + sa.Column("speech", sa.Text(), nullable=True), + sa.ForeignKeyConstraint(["computing_id"], ["election_nominee.computing_id"], ), + sa.ForeignKeyConstraint(["nominee_election"], ["election.slug"], ), + sa.PrimaryKeyConstraint("computing_id", "nominee_election") + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("nominee_speech") + op.drop_table("election_nominee") + op.drop_table("election") + # ### end Alembic commands ### From 7cb496c013d2fbb000a1919245765be1b16e7390 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Fri, 9 Aug 2024 11:13:55 +0000 Subject: [PATCH 03/32] Initial elections model implementation --- src/elections/models.py | 62 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/elections/models.py diff --git a/src/elections/models.py b/src/elections/models.py new file mode 100644 index 0000000..7bdcfc8 --- /dev/null +++ b/src/elections/models.py @@ -0,0 +1,62 @@ +from datetime import datetime + +from constants import ( + COMPUTING_ID_LEN, + DISCORD_ID_LEN, + DISCORD_NAME_LEN, + DISCORD_NICKNAME_LEN, +) +from database import Base +from sqlalchemy import ( + Column, + DateTime, + ForeignKeyConstraint, + PrimaryKeyConstraint, + String, + Text, +) + + +# Each row represents an instance of an election +class Election(Base): + __tablename__ = "election" + + # Slugs are unique identifiers + slug = Column(String(32), primary_key=True) + name = Column(String(32), nullable=False) + # Can be one of (general_election: General Election, by_election: By-Election, council_rep_election: Council Rep Election) + type = Column(String(64), default="general_election") + date = Column(DateTime, default=datetime.now()) + end_date = Column(DateTime) + websurvey = Column(String(300)) + +# Each row represents a nominee of a given election +class Nominee(Base): + __tablename__ = "election_nominee" + + # Previously named sfuid + computing_id = Column(String(COMPUTING_ID_LEN), primary_key=True) + full_name = Column(String(64), nullable=False) + facebook = Column(String(64)) + instagram = Column(String(64)) + email = Column(String(64)) + discord = Column(String(DISCORD_NAME_LEN)) + discord_id = Column(String(DISCORD_ID_LEN)) + discord_username = Column(String(DISCORD_NICKNAME_LEN)) + +class NomineeSpeech(Base): + __tablename__ = "nominee_speech" + + computing_id = Column(String(COMPUTING_ID_LEN), primary_key=True) + nominee_election = Column(String(32), primary_key=True) + speech = Column(Text) + + __table_args__ = ( + PrimaryKeyConstraint(computing_id, nominee_election), + ForeignKeyConstraint( + ["computing_id"], ["election_nominee.computing_id"] + ), + ForeignKeyConstraint( + ["nominee_election"], ["election.slug"] + ) + ) From 8b06ddae1d53fe8ed636c67cf00262925261b698 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Sat, 10 Aug 2024 08:37:09 +0000 Subject: [PATCH 04/32] Redid revision, added position column to NomineeAplication, renamed NomineeSepech -> NomineeApplication, changed ForeignKeyConstraints to ForeignKey --- ...y => 243190df5588_create_election_tables.py} | 11 ++++++----- src/elections/models.py | 17 ++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) rename src/alembic/versions/{cb5df398547c_create_election_tables.py => 243190df5588_create_election_tables.py} (89%) diff --git a/src/alembic/versions/cb5df398547c_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py similarity index 89% rename from src/alembic/versions/cb5df398547c_create_election_tables.py rename to src/alembic/versions/243190df5588_create_election_tables.py index 8ff98e3..adea237 100644 --- a/src/alembic/versions/cb5df398547c_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -1,8 +1,8 @@ """create election tables -Revision ID: cb5df398547c +Revision ID: 243190df5588 Revises: 43f71e4bd6fc -Create Date: 2024-08-09 09:36:22.745234 +Create Date: 2024-08-10 08:32:54.037614 """ from collections.abc import Sequence @@ -12,7 +12,7 @@ from alembic import op # revision identifiers, used by Alembic. -revision: str = "cb5df398547c" +revision: str = "243190df5588" down_revision: str | None = "43f71e4bd6fc" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None @@ -40,10 +40,11 @@ def upgrade() -> None: sa.Column("discord_username", sa.String(length=32), nullable=True), sa.PrimaryKeyConstraint("computing_id") ) - op.create_table("nominee_speech", + op.create_table("nominee_application", sa.Column("computing_id", sa.String(length=32), nullable=False), sa.Column("nominee_election", sa.String(length=32), nullable=False), sa.Column("speech", sa.Text(), nullable=True), + sa.Column("position", sa.String(length=64), nullable=False), sa.ForeignKeyConstraint(["computing_id"], ["election_nominee.computing_id"], ), sa.ForeignKeyConstraint(["nominee_election"], ["election.slug"], ), sa.PrimaryKeyConstraint("computing_id", "nominee_election") @@ -53,7 +54,7 @@ def upgrade() -> None: def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("nominee_speech") + op.drop_table("nominee_application") op.drop_table("election_nominee") op.drop_table("election") # ### end Alembic commands ### diff --git a/src/elections/models.py b/src/elections/models.py index 7bdcfc8..afab99c 100644 --- a/src/elections/models.py +++ b/src/elections/models.py @@ -10,7 +10,7 @@ from sqlalchemy import ( Column, DateTime, - ForeignKeyConstraint, + ForeignKey, PrimaryKeyConstraint, String, Text, @@ -44,19 +44,14 @@ class Nominee(Base): discord_id = Column(String(DISCORD_ID_LEN)) discord_username = Column(String(DISCORD_NICKNAME_LEN)) -class NomineeSpeech(Base): - __tablename__ = "nominee_speech" +class NomineeApplication(Base): + __tablename__ = "nominee_application" - computing_id = Column(String(COMPUTING_ID_LEN), primary_key=True) - nominee_election = Column(String(32), primary_key=True) + computing_id = Column(ForeignKey("election_nominee.computing_id"), primary_key=True) + nominee_election = Column(ForeignKey("election.slug"), primary_key=True) speech = Column(Text) + position = Column(String(64), nullable=False) __table_args__ = ( PrimaryKeyConstraint(computing_id, nominee_election), - ForeignKeyConstraint( - ["computing_id"], ["election_nominee.computing_id"] - ), - ForeignKeyConstraint( - ["nominee_election"], ["election.slug"] - ) ) From b5103cabb126012e54171fac79475c9b5c68871b Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 12 Aug 2024 08:26:20 +0000 Subject: [PATCH 05/32] Added ElectionOfficer class, created has_permission() to check whether a user is a current elections officer --- src/permission/types.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/permission/types.py b/src/permission/types.py index a29aaf5..7bf190c 100644 --- a/src/permission/types.py +++ b/src/permission/types.py @@ -1,6 +1,8 @@ from datetime import UTC, datetime, timezone import database +import elections.crud +import officers.constants import officers.crud from data.semesters import current_semester_start, step_semesters @@ -23,3 +25,18 @@ async def has_permission(db_session: database.DBSession, computing_id: str) -> b cutoff_date = step_semesters(semester_start, -NUM_SEMESTERS) return most_recent_exec_term > cutoff_date + +class ElectionOfficer: + @staticmethod + async def has_permission(db_session: database.DBSession, computing_id: str) -> bool: + """ + An current elections officer has access to all elections, prior elections officers have no access. + """ + officer_terms = await officers.crud.current_executive_team(db_session, True) + current_election_officer = officer_terms.get(officers.constants.OfficerPosition.ElectionsOfficer.value)[0] + if current_election_officer is not None: + # no need to verify if position is election officer, we do so above + if current_election_officer.private_data.computing_id == computing_id and current_election_officer.is_current_officer is True: + return True + + return False From b579e08ff9854f451ae1d1bc6dd696ebf12970f3 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Tue, 13 Aug 2024 03:23:46 +0000 Subject: [PATCH 06/32] Changed date to be nonnull, officer_id in alembic revision --- src/alembic/versions/243190df5588_create_election_tables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index adea237..ee1feca 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -23,8 +23,9 @@ def upgrade() -> None: op.create_table("election", sa.Column("slug", sa.String(length=32), nullable=False), sa.Column("name", sa.String(length=32), nullable=False), + sa.Column("officer_id", sa.String(length=32), nullable=False, unique=True), sa.Column("type", sa.String(length=64), nullable=True), - sa.Column("date", sa.DateTime(), nullable=True), + sa.Column("date", sa.DateTime(), nullable=False), sa.Column("end_date", sa.DateTime(), nullable=True), sa.Column("websurvey", sa.String(length=300), nullable=True), sa.PrimaryKeyConstraint("slug") From c89e659522ac12336503fed7b75ca515829a50dc Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Tue, 13 Aug 2024 03:25:06 +0000 Subject: [PATCH 07/32] Removed unique constraint on officer_id --- src/alembic/versions/243190df5588_create_election_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index ee1feca..be19df0 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -23,7 +23,7 @@ def upgrade() -> None: op.create_table("election", sa.Column("slug", sa.String(length=32), nullable=False), sa.Column("name", sa.String(length=32), nullable=False), - sa.Column("officer_id", sa.String(length=32), nullable=False, unique=True), + sa.Column("officer_id", sa.String(length=32), nullable=False), sa.Column("type", sa.String(length=64), nullable=True), sa.Column("date", sa.DateTime(), nullable=False), sa.Column("end_date", sa.DateTime(), nullable=True), From 117611aa2f136f649161fb2186eeae72bbfd8f51 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Tue, 13 Aug 2024 03:25:33 +0000 Subject: [PATCH 08/32] Removed unique constraint on officer_id, removed nullability from date --- src/elections/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/elections/models.py b/src/elections/models.py index afab99c..783bc2f 100644 --- a/src/elections/models.py +++ b/src/elections/models.py @@ -17,16 +17,17 @@ ) -# Each row represents an instance of an election +# Each row represents an instance of an class Election(Base): __tablename__ = "election" # Slugs are unique identifiers slug = Column(String(32), primary_key=True) name = Column(String(32), nullable=False) + officer_id = Column(String(COMPUTING_ID_LEN), nullable=False) # Can be one of (general_election: General Election, by_election: By-Election, council_rep_election: Council Rep Election) type = Column(String(64), default="general_election") - date = Column(DateTime, default=datetime.now()) + date = Column(DateTime, nullable=False) end_date = Column(DateTime) websurvey = Column(String(300)) From 3aded13afbf03f23358223357da4cb3fc1c1d1d1 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Thu, 29 Aug 2024 20:42:28 +0000 Subject: [PATCH 09/32] properly handled session validation on create_elections --- src/elections/urls.py | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/elections/urls.py diff --git a/src/elections/urls.py b/src/elections/urls.py new file mode 100644 index 0000000..0de34df --- /dev/null +++ b/src/elections/urls.py @@ -0,0 +1,101 @@ +import base64 +import logging +import os +import re +import urllib.parse +from datetime import datetime +from enum import Enum + +import auth +import auth.crud +import database +import elections +import requests # TODO: make this async +import xmltodict +from constants import root_ip_address +from fastapi import APIRouter, BackgroundTasks, FastAPI, HTTPException, Request, status +from fastapi.exceptions import RequestValidationError +from permission import types + +_logger = logging.getLogger(__name__) + +router = APIRouter( + prefix="/elections", + tags=["elections"], +) + +class ElectionType(Enum): + GENERAL_ELECTION = "general_election" + BY_ELECTION = "by_election" + COUNCIL_REP_ELECTION = "council_rep_election" + +def _slugify( + text: str +) -> str: + """ + Creates a unique slug based on text passed in. Assumes non-unicode text. + """ + return re.sub(r"[\W_]+", "-", text) + +async def _validate_user( + db_session: database.DBSession, + session_id: str +) -> dict: + computing_id = await auth.crud.get_computing_id(db_session, session_id) + # Assuming now user is validated + result = await types.ElectionOfficer.has_permission(db_session, computing_id) + return result + +@router.get( + "/create_election", + description="asdfasfasdf", +) +async def create_election( + request: Request, + db_session: database.DBSession, + name: str, + election_type: str, + date: datetime | None = None, + end_date: datetime | None = None, + websurvey: str | None = None +): + """ + aaa + """ + session_id = request.cookies.get("session_id", None) + user_auth = await _validate_user(db_session, session_id) + if user_auth is False: + # let's workshop how we actually wanna handle this + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="You do not have permission to access this resource", + headers={"WWW-Authenticate": "Basic"}, + ) + + # Default start time should be now unless specified otherwise + if date is None: + date = datetime.now() + + if election_type not in [e.value for e in ElectionType]: + raise RequestValidationError() + + params = { + "slug" : _slugify(name), + "name": name, + "officer_id" : await auth.crud.get_computing_id(db_session, session_id), + "type": election_type, + "date": date, + "end_date": end_date, + "websurvey": websurvey + } + + result = await elections.crud.create_election(params, db_session) + + #print(result) + return {} + +@router.get( + "/test" +) +async def test(): + return {"error": "lol"} From fc8def56580d2cd3e01d11db6b09f004ae600ce7 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Thu, 29 Aug 2024 20:43:23 +0000 Subject: [PATCH 10/32] created create_election stub --- src/elections/crud.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/elections/crud.py diff --git a/src/elections/crud.py b/src/elections/crud.py new file mode 100644 index 0000000..cdf4c07 --- /dev/null +++ b/src/elections/crud.py @@ -0,0 +1,32 @@ +import dataclasses +import logging +from datetime import datetime + +import database +import sqlalchemy +from elections.models import Election +from officers.constants import OfficerPosition +from officers.models import OfficerInfo, OfficerTerm +from officers.schemas import ( + OfficerData, + OfficerInfoData, + OfficerPrivateData, + OfficerTermData, +) + +_logger = logging.getLogger(__name__) + +async def get_election(db_session: database.DBSession, election_slug: str) -> Election | None: + query = sqlalchemy.select(Election) + query = query.where(Election.slug == election_slug) + + return (await db_session.execute(query)).scalar() + +async def create_election(params: dict[str, datetime], db_session: database.DBSession) -> None: + """ + Does not validate if an election _already_ exists + """ + # TODO: actually insert stuff + print(params) + +#async def update_election(params: ) From d9b8e52eb7eba2bbb1d0758936a983bcdbb3319f Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Thu, 29 Aug 2024 20:43:41 +0000 Subject: [PATCH 11/32] added elections router to main --- src/main.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 1497a6e..688c94f 100755 --- a/src/main.py +++ b/src/main.py @@ -2,8 +2,12 @@ import auth.urls import database +import elections.urls import officers.urls -from fastapi import FastAPI +from fastapi import FastAPI, Request, status +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse, RedirectResponse import tests.urls @@ -15,7 +19,23 @@ app.include_router(auth.urls.router) app.include_router(officers.urls.router) app.include_router(tests.urls.router) +app.include_router(elections.urls.router) @app.get("/") async def read_root(): return {"message": "Hello! You might be lost, this is actually the sfucsss.org's backend api."} + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler( + request: Request, + exception: RequestValidationError +): + return JSONResponse( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + content=jsonable_encoder( + { + "detail": exception.errors(), + "body": exception.body + } + ) + ) From e239b9bdeceeb1ef1cfafb442f82ae9a844b58b6 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Fri, 27 Sep 2024 00:38:43 +0000 Subject: [PATCH 12/32] Working commit containing fns to create, delete, and update elections --- src/elections/crud.py | 52 ++++++++++++++++++++++++++++++---- src/elections/urls.py | 65 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index cdf4c07..d3515a0 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -19,14 +19,56 @@ async def get_election(db_session: database.DBSession, election_slug: str) -> Election | None: query = sqlalchemy.select(Election) query = query.where(Election.slug == election_slug) - - return (await db_session.execute(query)).scalar() + result = (await db_session.execute(query)).scalar() + db_session.commit() + return result async def create_election(params: dict[str, datetime], db_session: database.DBSession) -> None: """ + Creates a new election with given parameters. Does not validate if an election _already_ exists """ - # TODO: actually insert stuff - print(params) + election = Election(slug=params["slug"], + name=params["name"], + officer_id=params["officer_id"], + type=params["type"], + date=params["date"], + end_date=params["end_date"], + websurvey=params["websurvey"]) + db_session.add(election) + await db_session.commit() + +async def delete_election(slug: str, db_session: database.DBSession) -> None: + """ + Deletes a given election by its slug. + Does not validate if an election exists + """ + query = sqlalchemy.delete(Election).where(Election.slug == slug) + await db_session.execute(query) + await db_session.commit() + +async def update_election(params: dict[str, datetime], db_session: database.DBSession) -> None: + """ + Updates an election with the provided parameters. + Take care as this will replace values with None if not populated. + You _cannot_ change the name or slug, you should instead delete and create a new election. + Does not validate if an election _already_ exists + """ + + election = (await db_session.execute(sqlalchemy.select(Election).filter_by(slug=params["slug"]))).scalar_one() + + if params["date"] is not None: + election.date = params["date"] + if params["type"] is not None: + election.type = params["type"] + if params["end_date"] is not None: + election.end_date = params["end_date"] + if params["websurvey"] is not None: + election.websurvey = params["websurvey"] + + await db_session.commit() + + + # query = sqlalchemy.update(Election).where(Election.slug == params["slug"]).values(election) + # await db_session.execute(query) -#async def update_election(params: ) diff --git a/src/elections/urls.py b/src/elections/urls.py index 0de34df..ec3acdb 100644 --- a/src/elections/urls.py +++ b/src/elections/urls.py @@ -48,7 +48,7 @@ async def _validate_user( @router.get( "/create_election", - description="asdfasfasdf", + description="Creates an election and places it in the database", ) async def create_election( request: Request, @@ -89,11 +89,70 @@ async def create_election( "websurvey": websurvey } - result = await elections.crud.create_election(params, db_session) + await elections.crud.create_election(params, db_session) - #print(result) + # TODO: create a suitable json response return {} +@router.get( + "/delete_election", + description="Deletes an election from the database" +) +async def delete_election( + request: Request, + db_session: database.DBSession, + slug: str +): + session_id = request.cookies.get("session_id", None) + user_auth = await _validate_user(db_session, session_id) + if user_auth is False: + # let's workshop how we actually wanna handle this + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="You do not have permission to access this resource", + headers={"WWW-Authenticate": "Basic"}, + ) + + if slug is not None: + await elections.crud.delete_election(slug, db_session) + +@router.get( + "/update_election", + description="""Updates an election in the database. + Note that this does not allow you to change the _name_ of an election as this would generate a new slug.""" +) +async def update_election( + request: Request, + db_session: database.DBSession, + slug: str, + name: str, + election_type: str, + date: datetime | None = None, + end_date: datetime | None = None, + websurvey: str | None = None +): + session_id = request.cookies.get("session_id", None) + user_auth = await _validate_user(db_session, session_id) + if user_auth is False: + # let's workshop how we actually wanna handle this + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="You do not have permission to access this resource", + headers={"WWW-Authenticate": "Basic"}, + ) + if slug is not None: + params = { + "slug" : slug, + "name" : name, + "officer_id" : await auth.crud.get_computing_id(db_session, session_id), + "type": election_type, + "date": date, + "end_date": end_date, + "websurvey": websurvey + } + await elections.crud.update_election(params, db_session) + + @router.get( "/test" ) From 06fe816df7523209b0f5f65bbcc63214ec433bc0 Mon Sep 17 00:00:00 2001 From: Gabe WSL Debian <24978329+EarthenSky@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:49:33 -0700 Subject: [PATCH 13/32] switch from models to tables --- src/alembic/env.py | 2 +- src/elections/crud.py | 6 +++--- src/elections/{models.py => tables.py} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/elections/{models.py => tables.py} (100%) diff --git a/src/alembic/env.py b/src/alembic/env.py index f256851..db0a52d 100644 --- a/src/alembic/env.py +++ b/src/alembic/env.py @@ -3,7 +3,7 @@ import auth.tables import database -import elections.models # TODO: update to tables +import elections.tables import officers.tables from alembic import context from sqlalchemy import pool diff --git a/src/elections/crud.py b/src/elections/crud.py index d3515a0..c758402 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -4,10 +4,10 @@ import database import sqlalchemy -from elections.models import Election +from elections.tables import Election from officers.constants import OfficerPosition -from officers.models import OfficerInfo, OfficerTerm -from officers.schemas import ( +from officers.tables import OfficerInfo, OfficerTerm +from officers.types import ( OfficerData, OfficerInfoData, OfficerPrivateData, diff --git a/src/elections/models.py b/src/elections/tables.py similarity index 100% rename from src/elections/models.py rename to src/elections/tables.py From 82685efc668285e14e73fcaa5bbfd51f77592a02 Mon Sep 17 00:00:00 2001 From: Gabe WSL Debian <24978329+EarthenSky@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:50:55 -0700 Subject: [PATCH 14/32] fix small import bug --- src/elections/crud.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index c758402..28beeee 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -7,12 +7,6 @@ from elections.tables import Election from officers.constants import OfficerPosition from officers.tables import OfficerInfo, OfficerTerm -from officers.types import ( - OfficerData, - OfficerInfoData, - OfficerPrivateData, - OfficerTermData, -) _logger = logging.getLogger(__name__) From 0c299d2115830fee74e3c6d80e16a473097bc510 Mon Sep 17 00:00:00 2001 From: Gabe WSL Debian <24978329+EarthenSky@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:55:54 -0700 Subject: [PATCH 15/32] fix past alembic migration --- src/alembic/versions/243190df5588_create_election_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index be19df0..0cf8a81 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -13,7 +13,7 @@ # revision identifiers, used by Alembic. revision: str = "243190df5588" -down_revision: str | None = "43f71e4bd6fc" +down_revision: str | None = "166f3772fce7" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None From 5a2b8863575b606809c3c646627c71b8dfd47f6a Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 04:39:17 +0000 Subject: [PATCH 16/32] Removed enum in urls.py, satisfied linter for tables.py --- src/elections/tables.py | 15 ++++++++------- src/elections/urls.py | 15 +++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/elections/tables.py b/src/elections/tables.py index 783bc2f..146c280 100644 --- a/src/elections/tables.py +++ b/src/elections/tables.py @@ -1,12 +1,5 @@ from datetime import datetime -from constants import ( - COMPUTING_ID_LEN, - DISCORD_ID_LEN, - DISCORD_NAME_LEN, - DISCORD_NICKNAME_LEN, -) -from database import Base from sqlalchemy import ( Column, DateTime, @@ -16,6 +9,14 @@ Text, ) +from constants import ( + COMPUTING_ID_LEN, + DISCORD_ID_LEN, + DISCORD_NAME_LEN, + DISCORD_NICKNAME_LEN, +) +from database import Base + # Each row represents an instance of an class Election(Base): diff --git a/src/elections/urls.py b/src/elections/urls.py index ec3acdb..ab12446 100644 --- a/src/elections/urls.py +++ b/src/elections/urls.py @@ -4,17 +4,17 @@ import re import urllib.parse from datetime import datetime -from enum import Enum + +import requests # TODO: make this async +import xmltodict +from fastapi import APIRouter, BackgroundTasks, FastAPI, HTTPException, Request, status +from fastapi.exceptions import RequestValidationError import auth import auth.crud import database import elections -import requests # TODO: make this async -import xmltodict from constants import root_ip_address -from fastapi import APIRouter, BackgroundTasks, FastAPI, HTTPException, Request, status -from fastapi.exceptions import RequestValidationError from permission import types _logger = logging.getLogger(__name__) @@ -24,11 +24,6 @@ tags=["elections"], ) -class ElectionType(Enum): - GENERAL_ELECTION = "general_election" - BY_ELECTION = "by_election" - COUNCIL_REP_ELECTION = "council_rep_election" - def _slugify( text: str ) -> str: From 5faad3538b57a74c1cde225c02b34b6a25574ce1 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 04:43:02 +0000 Subject: [PATCH 17/32] Changed old ElectionTypes enum into a string array --- src/elections/tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elections/tables.py b/src/elections/tables.py index 146c280..42336ba 100644 --- a/src/elections/tables.py +++ b/src/elections/tables.py @@ -17,6 +17,7 @@ ) from database import Base +election_types = ["general_election", "by_election", "council_rep_election"] # Each row represents an instance of an class Election(Base): @@ -26,7 +27,6 @@ class Election(Base): slug = Column(String(32), primary_key=True) name = Column(String(32), nullable=False) officer_id = Column(String(COMPUTING_ID_LEN), nullable=False) - # Can be one of (general_election: General Election, by_election: By-Election, council_rep_election: Council Rep Election) type = Column(String(64), default="general_election") date = Column(DateTime, nullable=False) end_date = Column(DateTime) From 7b465b59f8084e901288542394ec7731833246e1 Mon Sep 17 00:00:00 2001 From: Sean Chan <56064980+DerpyWasHere@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:46:21 -0800 Subject: [PATCH 18/32] Changed query in get_election() to be more SQL-like. Co-authored-by: Gabe <24978329+EarthenSky@users.noreply.github.com> --- src/elections/crud.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index 28beeee..6ce176d 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -11,9 +11,12 @@ _logger = logging.getLogger(__name__) async def get_election(db_session: database.DBSession, election_slug: str) -> Election | None: - query = sqlalchemy.select(Election) - query = query.where(Election.slug == election_slug) - result = (await db_session.execute(query)).scalar() + query = ( + sqlalchemy + .select(Election) + .where(Election.slug == election_slug) + ) + result = await db_session.scalar(query) db_session.commit() return result From 44d60f397dfe06a062a29bc28ebce50f3180c3b2 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 04:51:07 +0000 Subject: [PATCH 19/32] Changed list comprehension in create_election() to just a 'if not in' style check --- src/elections/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/elections/urls.py b/src/elections/urls.py index ab12446..66006f1 100644 --- a/src/elections/urls.py +++ b/src/elections/urls.py @@ -9,6 +9,7 @@ import xmltodict from fastapi import APIRouter, BackgroundTasks, FastAPI, HTTPException, Request, status from fastapi.exceptions import RequestValidationError +from tables import election_types import auth import auth.crud @@ -71,7 +72,7 @@ async def create_election( if date is None: date = datetime.now() - if election_type not in [e.value for e in ElectionType]: + if election_type not in election_types: raise RequestValidationError() params = { From 40eedd7420de2e53143ba6b650a9eb939c5b5a3c Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 04:52:34 +0000 Subject: [PATCH 20/32] Made change referenced in pr 63 wrt committing transactions in get_election() --- src/elections/crud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index 6ce176d..ebcfcc1 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -2,8 +2,9 @@ import logging from datetime import datetime -import database import sqlalchemy + +import database from elections.tables import Election from officers.constants import OfficerPosition from officers.tables import OfficerInfo, OfficerTerm @@ -17,7 +18,6 @@ async def get_election(db_session: database.DBSession, election_slug: str) -> El .where(Election.slug == election_slug) ) result = await db_session.scalar(query) - db_session.commit() return result async def create_election(params: dict[str, datetime], db_session: database.DBSession) -> None: From e2bb4db03880bdf77cc203fcf40688ecea53dd5e Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 04:59:29 +0000 Subject: [PATCH 21/32] Removed commits from crud.py and added commits to endpoints in urls.py --- src/elections/crud.py | 9 ++------- src/elections/urls.py | 3 +++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index ebcfcc1..a01ceda 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -33,7 +33,6 @@ async def create_election(params: dict[str, datetime], db_session: database.DBSe end_date=params["end_date"], websurvey=params["websurvey"]) db_session.add(election) - await db_session.commit() async def delete_election(slug: str, db_session: database.DBSession) -> None: """ @@ -42,7 +41,6 @@ async def delete_election(slug: str, db_session: database.DBSession) -> None: """ query = sqlalchemy.delete(Election).where(Election.slug == slug) await db_session.execute(query) - await db_session.commit() async def update_election(params: dict[str, datetime], db_session: database.DBSession) -> None: """ @@ -63,9 +61,6 @@ async def update_election(params: dict[str, datetime], db_session: database.DBSe if params["websurvey"] is not None: election.websurvey = params["websurvey"] - await db_session.commit() - - - # query = sqlalchemy.update(Election).where(Election.slug == params["slug"]).values(election) - # await db_session.execute(query) + query = sqlalchemy.update(Election).where(Election.slug == params["slug"]).values(election) + await db_session.execute(query) diff --git a/src/elections/urls.py b/src/elections/urls.py index 66006f1..da65484 100644 --- a/src/elections/urls.py +++ b/src/elections/urls.py @@ -86,6 +86,7 @@ async def create_election( } await elections.crud.create_election(params, db_session) + await db_session.commit() # TODO: create a suitable json response return {} @@ -111,6 +112,7 @@ async def delete_election( if slug is not None: await elections.crud.delete_election(slug, db_session) + await db_session.commit() @router.get( "/update_election", @@ -147,6 +149,7 @@ async def update_election( "websurvey": websurvey } await elections.crud.update_election(params, db_session) + await db_session.commit() @router.get( From 50302b1ee67a2ae6b17a2c46bc62bd241d622888 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 05:05:40 +0000 Subject: [PATCH 22/32] Changed occurrences of websurvey to survey_link to match that websurvey has been deprecated --- src/alembic/versions/243190df5588_create_election_tables.py | 3 ++- src/elections/tables.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index 0cf8a81..bb9e1b1 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -9,6 +9,7 @@ from typing import Union import sqlalchemy as sa + from alembic import op # revision identifiers, used by Alembic. @@ -27,7 +28,7 @@ def upgrade() -> None: sa.Column("type", sa.String(length=64), nullable=True), sa.Column("date", sa.DateTime(), nullable=False), sa.Column("end_date", sa.DateTime(), nullable=True), - sa.Column("websurvey", sa.String(length=300), nullable=True), + sa.Column("survey_link", sa.String(length=300), nullable=True), sa.PrimaryKeyConstraint("slug") ) op.create_table("election_nominee", diff --git a/src/elections/tables.py b/src/elections/tables.py index 42336ba..4236728 100644 --- a/src/elections/tables.py +++ b/src/elections/tables.py @@ -30,7 +30,7 @@ class Election(Base): type = Column(String(64), default="general_election") date = Column(DateTime, nullable=False) end_date = Column(DateTime) - websurvey = Column(String(300)) + survey_link = Column(String(300)) # Each row represents a nominee of a given election class Nominee(Base): From 82ccc2414f96bc616ed7546ae2f1bbcea4a70f21 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 05:15:58 +0000 Subject: [PATCH 23/32] Changed election parameters from a list to a dedicated dataclass, reflected changes across urls.py --- src/elections/crud.py | 55 +++++++++++++++++++++++++++---------------- src/elections/urls.py | 41 ++++++++++++++++---------------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index a01ceda..5106271 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -1,5 +1,5 @@ -import dataclasses import logging +from dataclasses import dataclass from datetime import datetime import sqlalchemy @@ -9,6 +9,21 @@ from officers.constants import OfficerPosition from officers.tables import OfficerInfo, OfficerTerm + +@dataclass +class ElectionParameters: + """ + Dataclass encompassing the data that can go into an Election. + """ + slug: str + name: str + officer_id: str + type: str + date: datetime + end_date: datetime + survey_link: str + + _logger = logging.getLogger(__name__) async def get_election(db_session: database.DBSession, election_slug: str) -> Election | None: @@ -20,18 +35,18 @@ async def get_election(db_session: database.DBSession, election_slug: str) -> El result = await db_session.scalar(query) return result -async def create_election(params: dict[str, datetime], db_session: database.DBSession) -> None: +async def create_election(params: ElectionParameters, db_session: database.DBSession) -> None: """ Creates a new election with given parameters. Does not validate if an election _already_ exists """ - election = Election(slug=params["slug"], - name=params["name"], - officer_id=params["officer_id"], - type=params["type"], - date=params["date"], - end_date=params["end_date"], - websurvey=params["websurvey"]) + election = Election(slug=params.slug, + name=params.name, + officer_id=params.officer_id, + type=params.type, + date=params.date, + end_date=params.end_date, + survey_link=params.survey_link) db_session.add(election) async def delete_election(slug: str, db_session: database.DBSession) -> None: @@ -42,7 +57,7 @@ async def delete_election(slug: str, db_session: database.DBSession) -> None: query = sqlalchemy.delete(Election).where(Election.slug == slug) await db_session.execute(query) -async def update_election(params: dict[str, datetime], db_session: database.DBSession) -> None: +async def update_election(params: ElectionParameters, db_session: database.DBSession) -> None: """ Updates an election with the provided parameters. Take care as this will replace values with None if not populated. @@ -50,17 +65,17 @@ async def update_election(params: dict[str, datetime], db_session: database.DBSe Does not validate if an election _already_ exists """ - election = (await db_session.execute(sqlalchemy.select(Election).filter_by(slug=params["slug"]))).scalar_one() + election = (await db_session.execute(sqlalchemy.select(Election).filter_by(slug=params.slug))).scalar_one() - if params["date"] is not None: - election.date = params["date"] - if params["type"] is not None: - election.type = params["type"] - if params["end_date"] is not None: - election.end_date = params["end_date"] - if params["websurvey"] is not None: - election.websurvey = params["websurvey"] + if params.date is not None: + election.date = params.date + if params.type is not None: + election.type = params.type + if params.end_date is not None: + election.end_date = params.end_date + if params.survey_link is not None: + election.survey_link = params.survey_link - query = sqlalchemy.update(Election).where(Election.slug == params["slug"]).values(election) + query = sqlalchemy.update(Election).where(Election.slug == params.slug).values(election) await db_session.execute(query) diff --git a/src/elections/urls.py b/src/elections/urls.py index da65484..f7d2d19 100644 --- a/src/elections/urls.py +++ b/src/elections/urls.py @@ -7,6 +7,7 @@ import requests # TODO: make this async import xmltodict +from crud import ElectionParameters from fastapi import APIRouter, BackgroundTasks, FastAPI, HTTPException, Request, status from fastapi.exceptions import RequestValidationError from tables import election_types @@ -53,7 +54,7 @@ async def create_election( election_type: str, date: datetime | None = None, end_date: datetime | None = None, - websurvey: str | None = None + survey_link: str | None = None ): """ aaa @@ -75,15 +76,15 @@ async def create_election( if election_type not in election_types: raise RequestValidationError() - params = { - "slug" : _slugify(name), - "name": name, - "officer_id" : await auth.crud.get_computing_id(db_session, session_id), - "type": election_type, - "date": date, - "end_date": end_date, - "websurvey": websurvey - } + params = ElectionParameters( + _slugify(name), + name, + await auth.crud.get_computing_id(db_session, session_id), + election_type, + date, + end_date, + survey_link + ) await elections.crud.create_election(params, db_session) await db_session.commit() @@ -127,7 +128,7 @@ async def update_election( election_type: str, date: datetime | None = None, end_date: datetime | None = None, - websurvey: str | None = None + survey_link: str | None = None ): session_id = request.cookies.get("session_id", None) user_auth = await _validate_user(db_session, session_id) @@ -139,15 +140,15 @@ async def update_election( headers={"WWW-Authenticate": "Basic"}, ) if slug is not None: - params = { - "slug" : slug, - "name" : name, - "officer_id" : await auth.crud.get_computing_id(db_session, session_id), - "type": election_type, - "date": date, - "end_date": end_date, - "websurvey": websurvey - } + params = ElectionParameters( + _slugify(name), + name, + await auth.crud.get_computing_id(db_session, session_id), + election_type, + date, + end_date, + survey_link + ) await elections.crud.update_election(params, db_session) await db_session.commit() From a36eef63b81220c9b737c0bb99a5dfb9e752b31e Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 05:32:35 +0000 Subject: [PATCH 24/32] Changed parameter orders to be consistent with other crud functions in the project. Changed database.DBSession -> AsyncSession to match other crud functions. --- src/elections/crud.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/elections/crud.py b/src/elections/crud.py index 5106271..f6c5f28 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -3,6 +3,7 @@ from datetime import datetime import sqlalchemy +from sqlalchemy.ext.asyncio import AsyncSession import database from elections.tables import Election @@ -26,7 +27,7 @@ class ElectionParameters: _logger = logging.getLogger(__name__) -async def get_election(db_session: database.DBSession, election_slug: str) -> Election | None: +async def get_election(db_session: AsyncSession, election_slug: str) -> Election | None: query = ( sqlalchemy .select(Election) @@ -35,7 +36,7 @@ async def get_election(db_session: database.DBSession, election_slug: str) -> El result = await db_session.scalar(query) return result -async def create_election(params: ElectionParameters, db_session: database.DBSession) -> None: +async def create_election(db_session: AsyncSession, params: ElectionParameters) -> None: """ Creates a new election with given parameters. Does not validate if an election _already_ exists @@ -49,7 +50,7 @@ async def create_election(params: ElectionParameters, db_session: database.DBSes survey_link=params.survey_link) db_session.add(election) -async def delete_election(slug: str, db_session: database.DBSession) -> None: +async def delete_election(db_session: AsyncSession, slug: str) -> None: """ Deletes a given election by its slug. Does not validate if an election exists @@ -57,7 +58,7 @@ async def delete_election(slug: str, db_session: database.DBSession) -> None: query = sqlalchemy.delete(Election).where(Election.slug == slug) await db_session.execute(query) -async def update_election(params: ElectionParameters, db_session: database.DBSession) -> None: +async def update_election(db_session: AsyncSession, params: ElectionParameters) -> None: """ Updates an election with the provided parameters. Take care as this will replace values with None if not populated. From ff2951cccf96035527bcc6fab7020b247ad48ab9 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 05:43:42 +0000 Subject: [PATCH 25/32] Appeased linter --- src/main.py | 10 ++++------ src/permission/types.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main.py b/src/main.py index f9b5bc2..7c63844 100755 --- a/src/main.py +++ b/src/main.py @@ -1,17 +1,15 @@ import logging -from fastapi import FastAPI +from fastapi import FastAPI, Request, status +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse import auth.urls import database import elections.urls import officers.urls import permission.urls -from fastapi import FastAPI, Request, status -from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError -from fastapi.responses import JSONResponse - import tests.urls logging.basicConfig(level=logging.DEBUG) diff --git a/src/permission/types.py b/src/permission/types.py index 03e37ac..6c32d96 100644 --- a/src/permission/types.py +++ b/src/permission/types.py @@ -72,4 +72,4 @@ async def has_permission_or_raise( errmsg:str = "must have website admin permissions" ) -> bool: if not await WebsiteAdmin.has_permission(db_session, computing_id): - raise HTTPException(status_code=401, detail=errmsg) \ No newline at end of file + raise HTTPException(status_code=401, detail=errmsg) From 8d0e267a7b600387dc1c9daff12335cada29ce8f Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 05:53:21 +0000 Subject: [PATCH 26/32] Reintroduced elections router into main.py --- src/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.py b/src/main.py index 7c63844..af44dda 100755 --- a/src/main.py +++ b/src/main.py @@ -21,6 +21,8 @@ app.include_router(officers.urls.router) app.include_router(permission.urls.router) +app.include_router(elections.urls.router) + @app.get("/") async def read_root(): return {"message": "Hello! You might be lost, this is actually the sfucsss.org's backend api."} From 40041ad391bb502e8435ffa6bf22d9eb7790454e Mon Sep 17 00:00:00 2001 From: Gabe WSL Debian <24978329+EarthenSky@users.noreply.github.com> Date: Sun, 12 Jan 2025 22:02:00 -0800 Subject: [PATCH 27/32] update down revision to be blog posts --- .../243190df5588_create_election_tables.py | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index bb9e1b1..ce2685a 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = "243190df5588" -down_revision: str | None = "166f3772fce7" +down_revision: str | None = "2a6ea95342dc" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None @@ -22,34 +22,34 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table("election", - sa.Column("slug", sa.String(length=32), nullable=False), - sa.Column("name", sa.String(length=32), nullable=False), - sa.Column("officer_id", sa.String(length=32), nullable=False), - sa.Column("type", sa.String(length=64), nullable=True), - sa.Column("date", sa.DateTime(), nullable=False), - sa.Column("end_date", sa.DateTime(), nullable=True), - sa.Column("survey_link", sa.String(length=300), nullable=True), - sa.PrimaryKeyConstraint("slug") + sa.Column("slug", sa.String(length=32), nullable=False), + sa.Column("name", sa.String(length=32), nullable=False), + sa.Column("officer_id", sa.String(length=32), nullable=False), + sa.Column("type", sa.String(length=64), nullable=True), + sa.Column("date", sa.DateTime(), nullable=False), + sa.Column("end_date", sa.DateTime(), nullable=True), + sa.Column("survey_link", sa.String(length=300), nullable=True), + sa.PrimaryKeyConstraint("slug") ) op.create_table("election_nominee", - sa.Column("computing_id", sa.String(length=32), nullable=False), - sa.Column("full_name", sa.String(length=64), nullable=False), - sa.Column("facebook", sa.String(length=64), nullable=True), - sa.Column("instagram", sa.String(length=64), nullable=True), - sa.Column("email", sa.String(length=64), nullable=True), - sa.Column("discord", sa.String(length=32), nullable=True), - sa.Column("discord_id", sa.String(length=18), nullable=True), - sa.Column("discord_username", sa.String(length=32), nullable=True), - sa.PrimaryKeyConstraint("computing_id") + sa.Column("computing_id", sa.String(length=32), nullable=False), + sa.Column("full_name", sa.String(length=64), nullable=False), + sa.Column("facebook", sa.String(length=64), nullable=True), + sa.Column("instagram", sa.String(length=64), nullable=True), + sa.Column("email", sa.String(length=64), nullable=True), + sa.Column("discord", sa.String(length=32), nullable=True), + sa.Column("discord_id", sa.String(length=18), nullable=True), + sa.Column("discord_username", sa.String(length=32), nullable=True), + sa.PrimaryKeyConstraint("computing_id") ) op.create_table("nominee_application", - sa.Column("computing_id", sa.String(length=32), nullable=False), - sa.Column("nominee_election", sa.String(length=32), nullable=False), - sa.Column("speech", sa.Text(), nullable=True), - sa.Column("position", sa.String(length=64), nullable=False), - sa.ForeignKeyConstraint(["computing_id"], ["election_nominee.computing_id"], ), - sa.ForeignKeyConstraint(["nominee_election"], ["election.slug"], ), - sa.PrimaryKeyConstraint("computing_id", "nominee_election") + sa.Column("computing_id", sa.String(length=32), nullable=False), + sa.Column("nominee_election", sa.String(length=32), nullable=False), + sa.Column("speech", sa.Text(), nullable=True), + sa.Column("position", sa.String(length=64), nullable=False), + sa.ForeignKeyConstraint(["computing_id"], ["election_nominee.computing_id"], ), + sa.ForeignKeyConstraint(["nominee_election"], ["election.slug"], ), + sa.PrimaryKeyConstraint("computing_id", "nominee_election") ) # ### end Alembic commands ### From 853038d29fd44ce131fde467bce0fdac2f855241 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 06:25:07 +0000 Subject: [PATCH 28/32] Added lost default param in elections table migration --- src/alembic/versions/243190df5588_create_election_tables.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index ce2685a..73d466b 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -20,12 +20,11 @@ def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### op.create_table("election", sa.Column("slug", sa.String(length=32), nullable=False), sa.Column("name", sa.String(length=32), nullable=False), sa.Column("officer_id", sa.String(length=32), nullable=False), - sa.Column("type", sa.String(length=64), nullable=True), + sa.Column("type", sa.String(length=64), nullable=True, default="general_election"), sa.Column("date", sa.DateTime(), nullable=False), sa.Column("end_date", sa.DateTime(), nullable=True), sa.Column("survey_link", sa.String(length=300), nullable=True), @@ -51,12 +50,9 @@ def upgrade() -> None: sa.ForeignKeyConstraint(["nominee_election"], ["election.slug"], ), sa.PrimaryKeyConstraint("computing_id", "nominee_election") ) - # ### end Alembic commands ### def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### op.drop_table("nominee_application") op.drop_table("election_nominee") op.drop_table("election") - # ### end Alembic commands ### From 2dee83a24b8858017ed260933a0767f60141a36e Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 06:50:56 +0000 Subject: [PATCH 29/32] Changed discord id length in election_nominee table from 18 to 32. --- src/alembic/versions/243190df5588_create_election_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index 73d466b..0718599 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -37,7 +37,7 @@ def upgrade() -> None: sa.Column("instagram", sa.String(length=64), nullable=True), sa.Column("email", sa.String(length=64), nullable=True), sa.Column("discord", sa.String(length=32), nullable=True), - sa.Column("discord_id", sa.String(length=18), nullable=True), + sa.Column("discord_id", sa.String(length=32), nullable=True), sa.Column("discord_username", sa.String(length=32), nullable=True), sa.PrimaryKeyConstraint("computing_id") ) From 945fb29c52b81337eb40d37fdc2dfc6d4335f087 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 06:52:24 +0000 Subject: [PATCH 30/32] Changed date -> start_date in elections table --- src/elections/tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elections/tables.py b/src/elections/tables.py index 4236728..5f26004 100644 --- a/src/elections/tables.py +++ b/src/elections/tables.py @@ -28,7 +28,7 @@ class Election(Base): name = Column(String(32), nullable=False) officer_id = Column(String(COMPUTING_ID_LEN), nullable=False) type = Column(String(64), default="general_election") - date = Column(DateTime, nullable=False) + start_date = Column(DateTime, nullable=False) end_date = Column(DateTime) survey_link = Column(String(300)) From 57f4b2a7064cddb79560da80990507a2f3adf2b2 Mon Sep 17 00:00:00 2001 From: DerpyWasHere Date: Mon, 13 Jan 2025 06:58:19 +0000 Subject: [PATCH 31/32] Changed date -> start_datetime, start_date -> start_datetime, end_date -> end_datetime in elections module. --- .../243190df5588_create_election_tables.py | 4 ++-- src/elections/crud.py | 16 ++++++++-------- src/elections/tables.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/alembic/versions/243190df5588_create_election_tables.py b/src/alembic/versions/243190df5588_create_election_tables.py index 0718599..b5f3f8e 100644 --- a/src/alembic/versions/243190df5588_create_election_tables.py +++ b/src/alembic/versions/243190df5588_create_election_tables.py @@ -25,8 +25,8 @@ def upgrade() -> None: sa.Column("name", sa.String(length=32), nullable=False), sa.Column("officer_id", sa.String(length=32), nullable=False), sa.Column("type", sa.String(length=64), nullable=True, default="general_election"), - sa.Column("date", sa.DateTime(), nullable=False), - sa.Column("end_date", sa.DateTime(), nullable=True), + sa.Column("start_datetime", sa.DateTime(), nullable=False), + sa.Column("end_datetime", sa.DateTime(), nullable=True), sa.Column("survey_link", sa.String(length=300), nullable=True), sa.PrimaryKeyConstraint("slug") ) diff --git a/src/elections/crud.py b/src/elections/crud.py index f6c5f28..9de73d5 100644 --- a/src/elections/crud.py +++ b/src/elections/crud.py @@ -20,8 +20,8 @@ class ElectionParameters: name: str officer_id: str type: str - date: datetime - end_date: datetime + start_datetime: datetime + end_datetime: datetime survey_link: str @@ -45,8 +45,8 @@ async def create_election(db_session: AsyncSession, params: ElectionParameters) name=params.name, officer_id=params.officer_id, type=params.type, - date=params.date, - end_date=params.end_date, + start_datetime=params.start_datetime, + end_datetime=params.end_datetime, survey_link=params.survey_link) db_session.add(election) @@ -68,12 +68,12 @@ async def update_election(db_session: AsyncSession, params: ElectionParameters) election = (await db_session.execute(sqlalchemy.select(Election).filter_by(slug=params.slug))).scalar_one() - if params.date is not None: - election.date = params.date + if params.start_datetime is not None: + election.start_datetime = params.start_datetime if params.type is not None: election.type = params.type - if params.end_date is not None: - election.end_date = params.end_date + if params.end_datetime is not None: + election.end_datetime = params.end_datetime if params.survey_link is not None: election.survey_link = params.survey_link diff --git a/src/elections/tables.py b/src/elections/tables.py index 5f26004..5bf74e4 100644 --- a/src/elections/tables.py +++ b/src/elections/tables.py @@ -28,8 +28,8 @@ class Election(Base): name = Column(String(32), nullable=False) officer_id = Column(String(COMPUTING_ID_LEN), nullable=False) type = Column(String(64), default="general_election") - start_date = Column(DateTime, nullable=False) - end_date = Column(DateTime) + start_datetime = Column(DateTime, nullable=False) + end_datetime = Column(DateTime) survey_link = Column(String(300)) # Each row represents a nominee of a given election From 1c61134dbceaf229d7ff81d54bba4cacf89e44a8 Mon Sep 17 00:00:00 2001 From: Sean Chan <56064980+DerpyWasHere@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:46:05 -0800 Subject: [PATCH 32/32] Changed date -> start_datetime, end_date -> end_datetime --- src/elections/urls.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/elections/urls.py b/src/elections/urls.py index f7d2d19..b3c0c9f 100644 --- a/src/elections/urls.py +++ b/src/elections/urls.py @@ -52,8 +52,8 @@ async def create_election( db_session: database.DBSession, name: str, election_type: str, - date: datetime | None = None, - end_date: datetime | None = None, + start_datetime: datetime | None = None, + end_datetime: datetime | None = None, survey_link: str | None = None ): """ @@ -70,8 +70,8 @@ async def create_election( ) # Default start time should be now unless specified otherwise - if date is None: - date = datetime.now() + if start_datetime is None: + start_datetime = datetime.now() if election_type not in election_types: raise RequestValidationError() @@ -81,8 +81,8 @@ async def create_election( name, await auth.crud.get_computing_id(db_session, session_id), election_type, - date, - end_date, + start_datetime, + end_datetime, survey_link ) @@ -126,8 +126,8 @@ async def update_election( slug: str, name: str, election_type: str, - date: datetime | None = None, - end_date: datetime | None = None, + start_datetime: datetime | None = None, + end_datetime: datetime | None = None, survey_link: str | None = None ): session_id = request.cookies.get("session_id", None) @@ -145,8 +145,8 @@ async def update_election( name, await auth.crud.get_computing_id(db_session, session_id), election_type, - date, - end_date, + start_datetime, + end_datetime, survey_link ) await elections.crud.update_election(params, db_session)