Skip to content

Commit dcd3dbd

Browse files
authored
Merge pull request #214 from hack-duke/bro-i-dont-wanna-do-ts
bruh
2 parents 0e30dc2 + f923368 commit dcd3dbd

File tree

3 files changed

+356
-1
lines changed

3 files changed

+356
-1
lines changed

portal-backend-python/routers/admin.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from models.admin_user import AdminUser
1414
from models.application import Application, ApplicationStatus
1515
from models.response import Response
16+
from models.form import Form as Form1
1617
from pydantic import BaseModel
1718
from services.google_sheets import export_applicants_to_sheets
1819

@@ -842,3 +843,128 @@ async def export_to_sheets(
842843
return ExportResponse(**result)
843844
except Exception as e:
844845
raise HTTPException(status_code=500, detail=f"Failed to export to Google Sheets: {str(e)}")
846+
847+
848+
class ExceptionListResponse(BaseModel):
849+
form_key: str
850+
exception_emails: list[str]
851+
852+
853+
class AddExceptionRequest(BaseModel):
854+
email: str
855+
856+
857+
class RemoveExceptionRequest(BaseModel):
858+
email: str
859+
860+
861+
@router.get("/exceptions", response_model=ExceptionListResponse)
862+
async def get_exception_list(
863+
session_id: str,
864+
auth_payload: Dict[str, Any] = Security(auth.verify),
865+
db: Session = Depends(get_db),
866+
):
867+
"""Get the list of emails with form submission exceptions."""
868+
auth0_id = auth_payload.get("sub")
869+
if not auth0_id:
870+
raise HTTPException(status_code=401, detail="Auth0 ID not found in token")
871+
872+
user = db.query(User).filter(User.auth0_id == auth0_id).first()
873+
if not user:
874+
raise HTTPException(status_code=401, detail="User not found")
875+
876+
admin_user = db.query(AdminUser).filter(AdminUser.user_id == user.id).first()
877+
if not admin_user:
878+
raise HTTPException(status_code=403, detail="Not an admin")
879+
880+
if not _validate_session(db, user.id, session_id):
881+
raise HTTPException(status_code=403, detail="Session invalidated")
882+
883+
form = db.query(Form1).filter(Form1.form_key == CURRENT_FORM_KEY).first()
884+
if not form:
885+
raise HTTPException(status_code=404, detail="Form not found")
886+
887+
return ExceptionListResponse(
888+
form_key=form.form_key,
889+
exception_emails=form.exception_emails or []
890+
)
891+
892+
893+
@router.post("/exceptions/add", response_model=ExceptionListResponse)
894+
async def add_exception_email(
895+
request: AddExceptionRequest,
896+
session_id: str,
897+
auth_payload: Dict[str, Any] = Security(auth.verify),
898+
db: Session = Depends(get_db),
899+
):
900+
"""Add an email to the exception list."""
901+
auth0_id = auth_payload.get("sub")
902+
if not auth0_id:
903+
raise HTTPException(status_code=401, detail="Auth0 ID not found in token")
904+
905+
user = db.query(User).filter(User.auth0_id == auth0_id).first()
906+
if not user:
907+
raise HTTPException(status_code=401, detail="User not found")
908+
909+
admin_user = db.query(AdminUser).filter(AdminUser.user_id == user.id).first()
910+
if not admin_user:
911+
raise HTTPException(status_code=403, detail="Not an admin")
912+
913+
if not _validate_session(db, user.id, session_id):
914+
raise HTTPException(status_code=403, detail="Session invalidated")
915+
916+
form = db.query(Form1).filter(Form1.form_key == CURRENT_FORM_KEY).first()
917+
if not form:
918+
raise HTTPException(status_code=404, detail="Form not found")
919+
920+
email = request.email.strip().lower()
921+
if not email:
922+
raise HTTPException(status_code=400, detail="Email cannot be empty")
923+
924+
current_emails = form.exception_emails or []
925+
if email not in [e.lower() for e in current_emails]:
926+
form.exception_emails = current_emails + [email]
927+
db.commit()
928+
929+
return ExceptionListResponse(
930+
form_key=form.form_key,
931+
exception_emails=form.exception_emails or []
932+
)
933+
934+
935+
@router.post("/exceptions/remove", response_model=ExceptionListResponse)
936+
async def remove_exception_email(
937+
request: RemoveExceptionRequest,
938+
session_id: str,
939+
auth_payload: Dict[str, Any] = Security(auth.verify),
940+
db: Session = Depends(get_db),
941+
):
942+
"""Remove an email from the exception list."""
943+
auth0_id = auth_payload.get("sub")
944+
if not auth0_id:
945+
raise HTTPException(status_code=401, detail="Auth0 ID not found in token")
946+
947+
user = db.query(User).filter(User.auth0_id == auth0_id).first()
948+
if not user:
949+
raise HTTPException(status_code=401, detail="User not found")
950+
951+
admin_user = db.query(AdminUser).filter(AdminUser.user_id == user.id).first()
952+
if not admin_user:
953+
raise HTTPException(status_code=403, detail="Not an admin")
954+
955+
if not _validate_session(db, user.id, session_id):
956+
raise HTTPException(status_code=403, detail="Session invalidated")
957+
958+
form = db.query(Form1).filter(Form1.form_key == CURRENT_FORM_KEY).first()
959+
if not form:
960+
raise HTTPException(status_code=404, detail="Form not found")
961+
962+
email = request.email.strip().lower()
963+
current_emails = form.exception_emails or []
964+
form.exception_emails = [e for e in current_emails if e.lower() != email]
965+
db.commit()
966+
967+
return ExceptionListResponse(
968+
form_key=form.form_key,
969+
exception_emails=form.exception_emails or []
970+
)

portal-frontend/src/pages/AdminPage.css

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,112 @@
214214
.export-result p:first-child {
215215
font-weight: 600;
216216
}
217+
218+
/* Exceptions Section */
219+
.admin-exceptions-section {
220+
margin-top: 64px;
221+
padding-top: 48px;
222+
border-top: 1px solid #e0e0e0;
223+
text-align: center;
224+
}
225+
226+
.exceptions-title {
227+
font-size: 1.5rem;
228+
font-weight: 600;
229+
color: #333;
230+
margin-bottom: 8px;
231+
}
232+
233+
.exceptions-description {
234+
font-size: 1rem;
235+
color: #666;
236+
margin-bottom: 24px;
237+
}
238+
239+
.exception-add-form {
240+
display: flex;
241+
justify-content: center;
242+
gap: 12px;
243+
margin-bottom: 24px;
244+
flex-wrap: wrap;
245+
}
246+
247+
.exception-input {
248+
padding: 12px 16px;
249+
font-size: 1rem;
250+
border: 2px solid #e0e0e0;
251+
border-radius: 8px;
252+
min-width: 300px;
253+
outline: none;
254+
transition: border-color 0.2s ease;
255+
}
256+
257+
.exception-input:focus {
258+
border-color: #1031d0;
259+
}
260+
261+
.exception-add-btn {
262+
padding: 12px 24px;
263+
font-size: 1rem;
264+
font-weight: 600;
265+
}
266+
267+
.exception-list {
268+
list-style: none;
269+
padding: 0;
270+
margin: 0;
271+
max-width: 500px;
272+
margin: 0 auto;
273+
}
274+
275+
.exception-item {
276+
display: flex;
277+
justify-content: space-between;
278+
align-items: center;
279+
padding: 12px 16px;
280+
background: #f8f9fa;
281+
border-radius: 8px;
282+
margin-bottom: 8px;
283+
border: 1px solid #e8e8e8;
284+
}
285+
286+
.exception-email {
287+
font-size: 0.95rem;
288+
color: #333;
289+
font-family: monospace;
290+
}
291+
292+
.exception-remove-btn {
293+
background: none;
294+
border: none;
295+
color: #ff5252;
296+
font-size: 1.5rem;
297+
cursor: pointer;
298+
padding: 0 8px;
299+
line-height: 1;
300+
transition: color 0.2s ease;
301+
}
302+
303+
.exception-remove-btn:hover {
304+
color: #c62828;
305+
}
306+
307+
.no-exceptions {
308+
color: #999;
309+
font-style: italic;
310+
}
311+
312+
@media (max-width: 768px) {
313+
.exception-input {
314+
min-width: 100%;
315+
}
316+
317+
.exception-add-form {
318+
flex-direction: column;
319+
align-items: stretch;
320+
}
321+
322+
.exception-add-btn {
323+
width: 100%;
324+
}
325+
}

0 commit comments

Comments
 (0)