Skip to content

Commit dd5ffec

Browse files
committed
clean & fix POST /term
1 parent bd96ceb commit dd5ffec

File tree

5 files changed

+54
-58
lines changed

5 files changed

+54
-58
lines changed

src/officers/crud.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
_logger = logging.getLogger(__name__)
1616

17+
# NOTE: this module should do any data validation; that should be done in the urls.py or higher layer
18+
1719
async def most_recent_officer_term(db_session: database.DBSession, computing_id: str) -> OfficerTerm | None:
1820
"""
1921
Returns the most recent OfficerTerm an exec has held
@@ -82,7 +84,7 @@ async def current_officers(
8284
async def all_officers(
8385
db_session: database.DBSession,
8486
include_private_data: bool,
85-
include_future_terms: bool,
87+
include_future_terms: bool
8688
) -> list[OfficerData]:
8789
"""
8890
This could be a lot of data, so be careful
@@ -138,19 +140,17 @@ async def get_officer_terms(
138140
)
139141
if not include_future_terms:
140142
query = utils.has_started_term(query)
141-
142143
if max_terms is not None:
143144
query.limit(max_terms)
144145

145146
return (await db_session.scalars(query)).all()
146147

147148
async def get_officer_term_by_id(db_session: database.DBSession, term_id: int) -> OfficerTerm:
148-
query = (
149+
officer_term = await db_session.scalar(
149150
sqlalchemy
150151
.select(OfficerTerm)
151152
.where(OfficerTerm.id == term_id)
152153
)
153-
officer_term = await db_session.scalar(query)
154154
if officer_term is None:
155155
raise HTTPException(status_code=400, detail=f"Could not find officer_term with id={term_id}")
156156
return officer_term
@@ -159,16 +159,13 @@ async def create_new_officer_info(
159159
db_session: database.DBSession,
160160
new_officer_info: OfficerInfo
161161
) -> bool:
162-
"""
163-
Return False if the officer already exists
164-
"""
165-
query = (
162+
"""Return False if the officer already exists & don't do anything."""
163+
existing_officer_info = await db_session.scalar(
166164
sqlalchemy
167165
.select(OfficerInfo)
168166
.where(OfficerInfo.computing_id == new_officer_info.computing_id)
169167
)
170-
stored_officer_info = await db_session.scalar(query)
171-
if stored_officer_info is not None:
168+
if existing_officer_info is not None:
172169
return False
173170

174171
db_session.add(new_officer_info)
@@ -178,13 +175,9 @@ async def create_new_officer_term(
178175
db_session: database.DBSession,
179176
new_officer_term: OfficerTerm
180177
):
181-
# TODO: does this check need to be here?
182-
# if new_officer_term.position not in OfficerPosition.position_list():
183-
# raise HTTPException(status_code=500)
184-
185-
# when creating a new position, assign a default end date if one exists
186178
position_length = OfficerPosition.length_in_semesters(new_officer_term.position)
187179
if position_length is not None:
180+
# when creating a new position, assign a default end date if one exists
188181
new_officer_term.end_date = semesters.step_semesters(
189182
semesters.current_semester_start(new_officer_term.start_date),
190183
position_length,

src/officers/tables.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
class OfficerTerm(Base):
2828
__tablename__ = "officer_term"
2929

30+
# TODO: change primary key to computing_id, position, start_date?
3031
id = Column(Integer, primary_key=True, autoincrement=True)
32+
3133
computing_id = Column(
3234
String(COMPUTING_ID_LEN),
3335
ForeignKey("site_user.computing_id"),

src/officers/types.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ class OfficerInfoUpload:
1818
github_username: None | str = None
1919
google_drive_email: None | str = None
2020

21-
def validate(self) -> None | HTTPException:
22-
if self.legal_name is not None and self.legal_name == "":
23-
return HTTPException(status_code=400, detail="legal name must not be empty")
21+
def valid_or_raise(self):
2422
# TODO: more checks
25-
else:
26-
return None
23+
if self.legal_name is not None and self.legal_name == "":
24+
raise HTTPException(status_code=400, detail="legal name must not be empty")
2725

2826
def to_officer_info(self, computing_id: str, discord_id: str | None, discord_nickname: str | None) -> OfficerInfo:
2927
return OfficerInfo(
@@ -58,8 +56,7 @@ class OfficerTermUpload:
5856
# NOTE: changing the name of this variable without changing all instances is breaking
5957
photo_url: None | str = None
6058

61-
def validate(self):
62-
"""input validation"""
59+
def valid_or_raise(self):
6360
# NOTE: An officer can change their own data for terms that are ongoing.
6461
if self.position not in OfficerPosition.position_list():
6562
raise HTTPException(status_code=400, detail=f"invalid new position={self.position}")

src/officers/urls.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -167,51 +167,66 @@ async def get_officer_info(
167167

168168
return JSONResponse(officer_info.serializable_dict())
169169

170+
# TODO: move this to types?
170171
@dataclass
171172
class InitialOfficerInfo:
172173
computing_id: str
173174
position: str
174175
start_date: date
175176

177+
def valid_or_raise(self):
178+
if len(self.computing_id) > COMPUTING_ID_MAX:
179+
raise HTTPException(status_code=400, detail=f"computing_id={self.computing_id} is too large")
180+
elif self.computing_id == "":
181+
raise HTTPException(status_code=400, detail="computing_id cannot be empty")
182+
elif self.position not in OfficerPosition.position_list():
183+
raise HTTPException(status_code=400, detail=f"invalid position={self.position}")
184+
176185
@router.post(
177186
"/term",
178-
description="Only the sysadmin, president, or DoA can submit this request. It will usually be the DoA. Updates the system with a new officer, and enables the user to login to the system to input their information.",
187+
description="""
188+
Only the sysadmin, president, or DoA can submit this request. It will usually be the DoA.
189+
Updates the system with a new officer, and enables the user to login to the system to input their information.
190+
""",
179191
)
180192
async def new_officer_term(
181193
request: Request,
182194
db_session: database.DBSession,
183-
officer_info_list: list[InitialOfficerInfo] = Body(), # noqa: B008
195+
officer_info_list: list[InitialOfficerInfo] = Body(), # noqa: B008
184196
):
185197
"""
186198
If the current computing_id is not already an officer, officer_info will be created for them.
187199
"""
188200
for officer_info in officer_info_list:
189-
if len(officer_info.computing_id) > COMPUTING_ID_MAX:
190-
raise HTTPException(status_code=400, detail=f"computing_id={officer_info.computing_id} is too large")
191-
elif officer_info.position not in OfficerPosition.position_list():
192-
raise HTTPException(status_code=400, detail=f"invalid position={officer_info.position}")
201+
officer_info.valid_or_raise()
193202

194-
WebsiteAdmin.validate_request(db_session, request)
203+
_, session_computing_id = logged_in_or_raise(request, db_session)
204+
WebsiteAdmin.has_permission_or_raise(db_session, session_computing_id)
195205

196206
for officer_info in officer_info_list:
197-
await officers.crud.create_new_officer_info(
198-
db_session,
199-
# TODO: do I need this object atm?
200-
OfficerInfoUpload(
201-
# TODO: use sfu api to get legal name
202-
legal_name = "default name",
203-
).to_officer_info(officer_info.computing_id, None, None),
204-
)
205-
# TODO: update create_new_officer_term to be the same as create_new_officer_info
207+
await officers.crud.create_new_officer_info(db_session, OfficerInfo(
208+
computing_id = officer_info.computing_id,
209+
# TODO: use sfu api to get legal name from officer_info.computing_id
210+
legal_name = "default name",
211+
phone_number = None,
212+
213+
discord_id = None,
214+
discord_name = None,
215+
discord_nickname = None,
216+
217+
google_drive_email = None,
218+
github_username = None,
219+
))
206220
await officers.crud.create_new_officer_term(db_session, OfficerTerm(
207221
computing_id = officer_info.computing_id,
208222
position = officer_info.position,
209223
# TODO: remove the hours & seconds (etc.) from start_date
224+
# TODO: start_date should be a Date, not a Datetime
210225
start_date = officer_info.start_date,
211226
))
212227

213228
await db_session.commit()
214-
return PlainTextResponse("ok")
229+
return PlainTextResponse("success")
215230

216231
@router.patch(
217232
"/info/{computing_id}",
@@ -227,9 +242,7 @@ async def update_info(
227242
computing_id: str,
228243
officer_info_upload: OfficerInfoUpload = Body(), # noqa: B008
229244
):
230-
http_exception = officer_info_upload.validate()
231-
if http_exception is not None:
232-
raise http_exception
245+
officer_info_upload.valid_or_raise()
233246

234247
session_id = request.cookies.get("session_id", None)
235248
if session_id is None:
@@ -320,7 +333,7 @@ async def update_term(
320333
term_id: int,
321334
officer_term_upload: OfficerTermUpload = Body(), # noqa: B008
322335
):
323-
officer_term_upload.validate()
336+
officer_term_upload.valid_or_raise()
324337

325338
# Refactor all of these gets & raises into small functions
326339
session_id = request.cookies.get("session_id", None)
@@ -370,9 +383,9 @@ async def update_term(
370383
"validation_failures": [], # none for now, but may be important later
371384
})
372385

373-
@router.post(
374-
"/remove/{term_id}",
375-
description="Remove the specified officer term. Only website admins can run this endpoint. BE CAREFUL WITH THIS!"
386+
@router.delete(
387+
"/term/{term_id}",
388+
description="Remove the specified officer term. Only website admins can run this endpoint. BE CAREFUL WITH THIS!",
376389
)
377390
async def remove_officer():
378391
# TODO: this

src/permission/types.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,6 @@ async def has_permission(db_session: database.DBSession, computing_id: str) -> b
5252
return False
5353

5454
@staticmethod
55-
async def validate_request(db_session: database.DBSession, request: Request) -> bool:
56-
"""
57-
Checks if the provided request satisfies these permissions, and raises the neccessary
58-
exceptions if not
59-
"""
60-
session_id = request.cookies.get("session_id", None)
61-
if session_id is None:
62-
raise HTTPException(status_code=401, detail="must be logged in")
63-
else:
64-
computing_id = await auth.crud.get_computing_id(db_session, session_id)
65-
if not await WebsiteAdmin.has_permission(db_session, computing_id):
66-
raise HTTPException(status_code=401, detail="must have website admin permissions")
55+
async def has_permission_or_raise(db_session: database.DBSession, computing_id: str) -> bool:
56+
if not await WebsiteAdmin.has_permission(db_session, computing_id):
57+
raise HTTPException(status_code=401, detail="must have website admin permissions")

0 commit comments

Comments
 (0)