Skip to content

Commit 27907f7

Browse files
authored
Finalize Officer Terms (#86)
* add support for having multiple exec terms at once * officers that have not started yet are not considered active * add support for measuring exec term length * fix bugs * clean naming
1 parent 2379c2b commit 27907f7

File tree

14 files changed

+297
-171
lines changed

14 files changed

+297
-171
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ If you're planning to read through the source code, please check out this projec
2525
- `misc/` for anything that can't be easily categorized or is very small
2626
- `officers/` for officer contact information + photos
2727
- `test/` for html pages which interact with the backend's local api
28+
29+
## Linter
30+
31+
We use `ruff 0.6.9` as our linter, which you can run with `ruff check --fix`. If you use a different version, it may be inconsistent with our CI checks.

src/auth/crud.py

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,49 @@
77
from auth.tables import SiteUser, UserSession
88
from auth.types import SiteUserData
99

10+
_logger = logging.getLogger(__name__)
1011

1112
async def create_user_session(db_session: AsyncSession, session_id: str, computing_id: str):
1213
"""
1314
Updates the past user session if one exists, so no duplicate sessions can ever occur.
1415
1516
Also, adds the new user to the SiteUser table if it's their first time logging in.
1617
"""
17-
query = sqlalchemy.select(UserSession).where(UserSession.computing_id == computing_id)
18-
existing_user_session = (await db_session.scalars(query)).first()
19-
if existing_user_session:
18+
existing_user_session = await db_session.scalar(
19+
sqlalchemy
20+
.select(UserSession)
21+
.where(UserSession.computing_id == computing_id)
22+
)
23+
existing_user = await db_session.scalar(
24+
sqlalchemy
25+
.select(SiteUser)
26+
.where(SiteUser.computing_id == computing_id)
27+
)
28+
29+
if existing_user is None:
30+
if existing_user_session is not None:
31+
# log this strange case that shouldn't be possible
32+
_logger.warning(f"User session {session_id} exists for non-existent user {computing_id} ... !")
33+
34+
# add new user to User table if it's their first time logging in
35+
db_session.add(SiteUser(
36+
computing_id=computing_id,
37+
first_logged_in=datetime.now(),
38+
last_logged_in=datetime.now()
39+
))
40+
41+
if existing_user_session is not None:
2042
existing_user_session.issue_time = datetime.now()
2143
existing_user_session.session_id = session_id
22-
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == computing_id)
23-
existing_user = (await db_session.scalars(query)).first()
24-
if existing_user is None:
25-
# log this strange case
26-
_logger = logging.getLogger(__name__)
27-
_logger.warning(f"User session {session_id} exists for non-existent user {computing_id}!")
28-
# create a user for this session
29-
new_user = SiteUser(
30-
computing_id=computing_id,
31-
first_logged_in=datetime.now(),
32-
last_logged_in=datetime.now()
33-
)
34-
db_session.add(new_user)
35-
else:
44+
if existing_user is not None:
3645
# update the last time the user logged in to now
37-
existing_user.last_logged_in=datetime.now()
46+
existing_user.last_logged_in = datetime.now()
3847
else:
39-
# add new user to User table if it's their first time logging in
40-
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == computing_id)
41-
existing_user = (await db_session.scalars(query)).first()
42-
if existing_user is None:
43-
new_user = SiteUser(
44-
computing_id=computing_id,
45-
first_logged_in=datetime.now(),
46-
last_logged_in=datetime.now()
47-
)
48-
db_session.add(new_user)
49-
50-
new_user_session = UserSession(
51-
issue_time=datetime.now(),
48+
db_session.add(UserSession(
5249
session_id=session_id,
5350
computing_id=computing_id,
54-
)
55-
db_session.add(new_user_session)
51+
issue_time=datetime.now(),
52+
))
5653

5754

5855
async def remove_user_session(db_session: AsyncSession, session_id: str) -> dict:

src/blog/crud.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
import dataclasses
21
import logging
32
from datetime import date, datetime
4-
from typing import Optional
53

64
import sqlalchemy
75
from sqlalchemy import func
86

97
import database
108
from blog.models import BlogPosts
119

12-
_logger = logging.getLogger(__name__)
1310

1411
async def create_new_entry(
1512
db_session: database.DBSession,

src/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# TODO(future): replace new.sfucsss.org with sfucsss.org during migration
44
# TODO(far-future): branch-specific root IP addresses (e.g., devbranch.sfucsss.org)
5-
FRONTEND_ROOT_URL = "http://127.0.0.1:8000" if os.environ.get("LOCAL") == "true" else "https://new.sfucsss.org"
5+
FRONTEND_ROOT_URL = "http://localhost:8080" if os.environ.get("LOCAL") == "true" else "https://new.sfucsss.org"
66
GITHUB_ORG_NAME = "CSSS-Test-Organization" if os.environ.get("LOCAL") == "true" else "CSSS"
77

88
W3_GUILD_ID = "1260652618875797504"

src/cron/daily.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import google_api
88
import utils
99
from database import _db_session
10-
from officers.crud import all_officer_terms, get_user_by_username, officer_terms
10+
from officers.crud import all_officer_data, get_user_by_username
1111

1212
_logger = logging.getLogger(__name__)
1313

@@ -18,7 +18,7 @@ async def update_google_permissions(db_session):
1818

1919
# TODO: for performance, only include officers with recent end-date (1 yr)
2020
# but measure performance first
21-
for term in await all_officer_terms(db_session):
21+
for term in await all_officer_data(db_session):
2222
if utils.is_active(term):
2323
# TODO: if google drive permission is not active, update them
2424
pass
@@ -31,7 +31,7 @@ async def update_google_permissions(db_session):
3131
async def update_github_permissions(db_session):
3232
github_permissions, team_id_map = github.all_permissions()
3333

34-
for term in await all_officer_terms(db_session):
34+
for term in await all_officer_data(db_session):
3535
new_teams = (
3636
# move all active officers to their respective teams
3737
github.officer_teams(term.position)

src/load_test_db.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# python load_test_db.py
44

55
import asyncio
6-
from datetime import date, datetime, timedelta
6+
from datetime import date, timedelta
77

88
import sqlalchemy
99
from sqlalchemy.ext.asyncio import AsyncSession
@@ -60,6 +60,7 @@ async def reset_db(engine):
6060
async def load_test_auth_data(db_session: AsyncSession):
6161
await create_user_session(db_session, "temp_id_314", "abc314")
6262
await update_site_user(db_session, "temp_id_314", "www.my_profile_picture_url.ca/test")
63+
await db_session.commit()
6364

6465
async def load_test_officers_data(db_session: AsyncSession):
6566
print("login the 3 users, putting them in the site users table")
@@ -109,7 +110,6 @@ async def load_test_officers_data(db_session: AsyncSession):
109110
))
110111
await db_session.commit()
111112

112-
# TODO: will id autoincrement?
113113
await create_new_officer_term(db_session, OfficerTerm(
114114
computing_id="abc11",
115115

@@ -213,11 +213,11 @@ async def load_test_officers_data(db_session: AsyncSession):
213213
await db_session.commit()
214214

215215
async def load_sysadmin(db_session: AsyncSession):
216-
print("loading new sysadmin")
217216
# put your computing id here for testing purposes
218217
SYSADMIN_COMPUTING_ID = "gsa92"
219218

220-
await create_user_session(db_session, "temp_id_4", SYSADMIN_COMPUTING_ID)
219+
print(f"loading new sysadmin '{SYSADMIN_COMPUTING_ID}'")
220+
await create_user_session(db_session, f"temp_id_{SYSADMIN_COMPUTING_ID}", SYSADMIN_COMPUTING_ID)
221221
await create_new_officer_info(db_session, OfficerInfo(
222222
legal_name="Gabe Schulz",
223223
discord_id=None,
@@ -246,6 +246,40 @@ async def load_sysadmin(db_session: AsyncSession):
246246
biography="The systems are good o7",
247247
photo_url=None,
248248
))
249+
await create_new_officer_term(db_session, OfficerTerm(
250+
computing_id=SYSADMIN_COMPUTING_ID,
251+
252+
position=OfficerPosition.FIRST_YEAR_REPRESENTATIVE,
253+
start_date=date.today() - timedelta(days=(365*3)),
254+
end_date=date.today() - timedelta(days=(365*2)),
255+
256+
nickname="Gabe",
257+
favourite_course_0="MACM 101",
258+
favourite_course_1="CMPT 125",
259+
260+
favourite_pl_0="C#",
261+
favourite_pl_1="C++",
262+
263+
biography="o hey fellow kids \n\n\n I can newline",
264+
photo_url=None,
265+
))
266+
await create_new_officer_term(db_session, OfficerTerm(
267+
computing_id=SYSADMIN_COMPUTING_ID,
268+
269+
position=OfficerPosition.SYSTEM_ADMINISTRATOR,
270+
start_date=date.today() - timedelta(days=365),
271+
end_date=None,
272+
273+
nickname="Gabe",
274+
favourite_course_0="CMPT 379",
275+
favourite_course_1="CMPT 295",
276+
277+
favourite_pl_0="Rust",
278+
favourite_pl_1="C",
279+
280+
biography="The systems are good o7",
281+
photo_url=None,
282+
))
249283
await db_session.commit()
250284

251285
async def async_main(sessionmanager):

src/officers/constants.py

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
import logging
2-
from enum import Enum
3-
from typing import Self
4-
5-
_logger = logging.getLogger(__name__)
6-
71
class OfficerPosition:
82
PRESIDENT = "president"
93
VICE_PRESIDENT = "vice-president"
@@ -32,6 +26,13 @@ class OfficerPosition:
3226
def position_list() -> list[str]:
3327
return _OFFICER_POSITION_LIST
3428

29+
@staticmethod
30+
def length_in_semesters(position: str) -> int | None:
31+
# TODO: ask the committee to maintain a json file with all the important details from the constitution
32+
# (I can create the version version of the file)
33+
"""How many semester position is active for, according to the CSSS Constitution"""
34+
return _LENGTH_MAP[position]
35+
3536
@staticmethod
3637
def to_email(position: str) -> str | None:
3738
return _EMAIL_MAP.get(position, None)
@@ -43,13 +44,13 @@ def num_active(position: str) -> int | None:
4344
"""
4445
# None means there can be any number active
4546
if (
46-
position == OfficerPosition.ExecutiveAtLarge
47-
or position == OfficerPosition.FirstYearRepresentative
47+
position == OfficerPosition.EXECUTIVE_AT_LARGE
48+
or position == OfficerPosition.FIRST_YEAR_REPRESENTATIVE
4849
):
4950
return 2
5051
elif (
51-
position == OfficerPosition.FroshWeekChair
52-
or position == OfficerPosition.SocialMediaManager
52+
position == OfficerPosition.FROSH_WEEK_CHAIR
53+
or position == OfficerPosition.SOCIAL_MEDIA_MANAGER
5354
):
5455
return None
5556
else:
@@ -61,38 +62,38 @@ def is_signer(position: str) -> bool:
6162
If the officer is a signing authority of the CSSS
6263
"""
6364
return (
64-
position == OfficerPosition.President
65-
or position == OfficerPosition.VicePresident
66-
or position == OfficerPosition.Treasurer
67-
or position == OfficerPosition.DirectorOfResources
68-
or position == OfficerPosition.DirectorOfEvents
65+
position == OfficerPosition.PRESIDENT
66+
or position == OfficerPosition.VICE_PRESIDENT
67+
or position == OfficerPosition.TREASURER
68+
or position == OfficerPosition.DIRECTOR_OF_RESOURCES
69+
or position == OfficerPosition.DIRECTOR_OF_EVENTS
6970
)
7071

7172
@staticmethod
7273
def expected_positions() -> list[str]:
7374
return [
74-
OfficerPosition.President,
75-
OfficerPosition.VicePresident,
76-
OfficerPosition.Treasurer,
77-
78-
OfficerPosition.DirectorOfResources,
79-
OfficerPosition.DirectorOfEvents,
80-
OfficerPosition.DirectorOfEducationalEvents,
81-
OfficerPosition.AssistantDirectorOfEvents,
82-
OfficerPosition.DirectorOfCommunications,
83-
#DirectorOfOutreach, # TODO: when https://github.com/CSSS/documents/pull/9/files merged
84-
OfficerPosition.DirectorOfMultimedia,
85-
OfficerPosition.DirectorOfArchives,
86-
OfficerPosition.ExecutiveAtLarge,
75+
OfficerPosition.PRESIDENT,
76+
OfficerPosition.VICE_PRESIDENT,
77+
OfficerPosition.TREASURER,
78+
79+
OfficerPosition.DIRECTOR_OF_RESOURCES,
80+
OfficerPosition.DIRECTOR_OF_EVENTS,
81+
OfficerPosition.DIRECTOR_OF_EDUCATIONAL_EVENTS,
82+
OfficerPosition.ASSISTANT_DIRECTOR_OF_EVENTS,
83+
OfficerPosition.DIRECTOR_OF_COMMUNICATIONS,
84+
#OfficerPosition.DIRECTOR_OF_OUTREACH, # TODO: when https://github.com/CSSS/documents/pull/9/files merged
85+
OfficerPosition.DIRECTOR_OF_MULTIMEDIA,
86+
OfficerPosition.DIRECTOR_OF_ARCHIVES,
87+
OfficerPosition.EXECUTIVE_AT_LARGE,
8788
# TODO: expect these only during fall & spring semesters. Also, TODO: this todo is correct...
88-
#FirstYearRepresentative,
89+
#OfficerPosition.FIRST_YEAR_REPRESENTATIVE,
8990

9091
#ElectionsOfficer,
91-
OfficerPosition.SFSSCouncilRepresentative,
92-
OfficerPosition.FroshWeekChair,
92+
OfficerPosition.SFSS_COUNCIL_REPRESENTATIVE,
93+
OfficerPosition.FROSH_WEEK_CHAIR,
9394

94-
OfficerPosition.SystemAdministrator,
95-
OfficerPosition.Webmaster,
95+
OfficerPosition.SYSTEM_ADMINISTRATOR,
96+
OfficerPosition.WEBMASTER,
9697
]
9798

9899
_EMAIL_MAP = {
@@ -120,6 +121,34 @@ def expected_positions() -> list[str]:
120121
OfficerPosition.SOCIAL_MEDIA_MANAGER: "N/A",
121122
}
122123

124+
# TODO: when an officer's start date is modified, update the end date as well if it's defined in this list
125+
# a number of semesters (a semester begins on the 1st of each four month period, starting january)
126+
# None, means that the length of the position does not have a set length in semesters
127+
_LENGTH_MAP = {
128+
OfficerPosition.PRESIDENT: 3,
129+
OfficerPosition.VICE_PRESIDENT: 3,
130+
OfficerPosition.TREASURER: 3,
131+
132+
OfficerPosition.DIRECTOR_OF_RESOURCES: 3,
133+
OfficerPosition.DIRECTOR_OF_EVENTS: 3,
134+
OfficerPosition.DIRECTOR_OF_EDUCATIONAL_EVENTS: 3,
135+
OfficerPosition.ASSISTANT_DIRECTOR_OF_EVENTS: 3,
136+
OfficerPosition.DIRECTOR_OF_COMMUNICATIONS: 3,
137+
#OfficerPosition.DIRECTOR_OF_OUTREACH: 3,
138+
OfficerPosition.DIRECTOR_OF_MULTIMEDIA: 3,
139+
OfficerPosition.DIRECTOR_OF_ARCHIVES: 3,
140+
OfficerPosition.EXECUTIVE_AT_LARGE: 1,
141+
OfficerPosition.FIRST_YEAR_REPRESENTATIVE: 2,
142+
143+
OfficerPosition.ELECTIONS_OFFICER: None,
144+
OfficerPosition.SFSS_COUNCIL_REPRESENTATIVE: 3,
145+
OfficerPosition.FROSH_WEEK_CHAIR: None,
146+
147+
OfficerPosition.SYSTEM_ADMINISTRATOR: None,
148+
OfficerPosition.WEBMASTER: None,
149+
OfficerPosition.SOCIAL_MEDIA_MANAGER: None,
150+
}
151+
123152
_OFFICER_POSITION_LIST = [
124153
OfficerPosition.PRESIDENT,
125154
OfficerPosition.VICE_PRESIDENT,

0 commit comments

Comments
 (0)