Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2a6d0ba
feat: extract avatar circle from user avatar so that the same style c…
ethnjs Jul 24, 2026
ae9dce5
feat(profile): defined profile header section that shows the user's a…
ethnjs Jul 24, 2026
c99258a
feat: move auth provider to root layout.tsx so that other dir in root…
ethnjs Jul 24, 2026
14567c4
feat(profile): profile view page skeleton
ethnjs Jul 24, 2026
127109b
feat(profile): add profile option in user avatar dropdown; add IconUs…
ethnjs Jul 24, 2026
ebfde05
feat: nest event schema into experience responses for more info
ethnjs Jul 24, 2026
4044e98
feat(profile): add education/career section
ethnjs Jul 24, 2026
bf0ffd1
feat(profile): add competition and volunteer experience sections
ethnjs Jul 24, 2026
02e248a
fix: auto-create nexus_test database on fresh Postgres volume
ethnjs Jul 24, 2026
2dd663b
chore(tests): update event, user experience, and user tests to reflec…
ethnjs Jul 24, 2026
401bf30
feat(profile): add logistics section and extract experience sections …
ethnjs Jul 24, 2026
ab9c8ac
fix(frontend): wire up Tailwind v4 theme tokens and fix cascade-layer…
ethnjs Jul 24, 2026
dc67df7
fix(auth): use useAuth() instead of removed authApi.me() in verify-em…
ethnjs Jul 24, 2026
0d7e710
feat(profile): add floating edit button; update edit icon
ethnjs Jul 24, 2026
2f55645
feat(profile): add email and phone number to profile header
ethnjs Jul 24, 2026
f2f28a4
refactor(sign-up): extract fields for reuse for profile edit
ethnjs Jul 24, 2026
f9d9cff
refactor(sign-up): redesign experience tables
ethnjs Jul 24, 2026
ed86cac
fix: combobox bugs when existing option is fully typed but not picked…
ethnjs Jul 24, 2026
a584f48
fix(profile): add background behind check and x buttons that show up …
ethnjs Jul 25, 2026
8706a25
feat(profile): remove pronouns as a required profile field
ethnjs Jul 25, 2026
98c6c16
feat(profile): add edit button to profile header that will eventually…
ethnjs Jul 25, 2026
0546569
feat(profile): profile edit page; added disable flag to radio option
ethnjs Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,8 @@ pnpm dev

## Running Tests

Tests run against a dedicated `nexus_test` Postgres database on the same Docker container as dev — this keeps test transaction/rollback semantics identical to prod (SQLite doesn't support the savepoints the test fixtures rely on). Create it once:
Tests run against a dedicated `nexus_test` Postgres database on the same Docker container as dev — this keeps test transaction/rollback semantics identical to prod (SQLite doesn't support the savepoints the test fixtures rely on). `nexus_test` is created automatically by `db-init/01-create-test-db.sql` the first time the container starts on a fresh `postgres_data` volume (Postgres's `docker-entrypoint-initdb.d` convention) — no manual step needed, including after `docker-compose down -v`.

```bash
docker exec backend-db-1 createdb -U nexus nexus_test
```

Then run:
```bash
cd backend
pytest
Expand Down
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ credentials.json
# Database
*.db
*.sqlite
*.dump
.db-backup/

# Testing
.pytest_cache/
Expand Down
9 changes: 6 additions & 3 deletions backend/app/api/routes/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from app.core.users import check_if_email_exists, find_user_by_id
from app.core.profile_status import compute_missing_profile_fields, is_profile_complete
from app.db.session import get_db
from app.models.models import User
from app.models.models import User, UserCompetitionExperience, UserVolunteerExperience, Event
from app.schemas.user import (
UserFullResponse, UserMeFullResponse, UserSlimResponse,
UserMeSlimResponse, UserUpdate, AdminUserUpdate
Expand Down Expand Up @@ -108,12 +108,15 @@ def get_me(
if full:
user = (
db.query(User)
.options(selectinload(User.competition_experience), selectinload(User.volunteer_experience))
.options(
selectinload(User.competition_experience).selectinload(UserCompetitionExperience.event).selectinload(Event.category),
selectinload(User.volunteer_experience).selectinload(UserVolunteerExperience.event).selectinload(Event.category),
)
.filter(User.id == current_user.id)
.first()
)
response = UserMeFullResponse.model_validate(user)
response.missing_profile_fields = compute_missing_profile_fields(user) # relationships loaded, no db needed
response.missing_profile_fields = compute_missing_profile_fields(user)
return response

response = UserMeSlimResponse.model_validate(current_user)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/core/profile_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def _has_volunteer_rows(user: User, db: Optional[Session]) -> bool:


def compute_missing_profile_fields(user: User, *, db: Optional[Session] = None) -> list[str]:
always_required = ["phone", "date_of_birth", "pronouns", "shirt_size", "dietary_restriction"]
always_required = ["phone", "date_of_birth", "shirt_size", "dietary_restriction"]
missing = [f for f in always_required if not getattr(user, f)]

if not user.student_status:
Expand Down
2 changes: 1 addition & 1 deletion backend/app/schemas/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class EventCategoryUpdate(BaseModel):
class EventResponse(BaseModel):
id: int
name: str
category_id: int
category: EventCategoryResponse

model_config = {"from_attributes": True}

Expand Down
6 changes: 4 additions & 2 deletions backend/app/schemas/user_experience.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from pydantic import BaseModel, model_validator
from typing import Optional

from app.schemas.event import EventResponse


class CompetitionExperienceCreate(BaseModel):
event_id: int
Expand All @@ -14,7 +16,7 @@ class CompetitionExperienceUpdate(BaseModel):

class CompetitionExperienceResponse(BaseModel):
id: int
event_id: int
event: EventResponse
school: str
notes: Optional[str] = None

Expand Down Expand Up @@ -50,7 +52,7 @@ class VolunteerExperienceResponse(BaseModel):

tournament_name: str
year: int
event_id: Optional[int] = None
event: Optional[EventResponse] = None
role: str

notes: Optional[VolunteerExperienceNotes] = None
Expand Down
5 changes: 5 additions & 0 deletions backend/db-init/01-create-test-db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Runs automatically on first init of a fresh postgres_data volume
-- (docker-entrypoint-initdb.d convention). Creates the dedicated test
-- database so `pytest` works without a manual `createdb` step, even
-- after the volume has been dropped and recreated.
CREATE DATABASE nexus_test;
1 change: 1 addition & 0 deletions backend/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db-init:/docker-entrypoint-initdb.d

volumes:
postgres_data:
4 changes: 2 additions & 2 deletions backend/tests/api/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_admin_can_create_event(self, client, admin_user, event_category):
assert res.status_code == 201
data = res.json()
assert data["name"] == "Boomilever"
assert data["category_id"] == event_category.id
assert data["category"]["id"] == event_category.id

def test_non_admin_forbidden(self, client, td_user, event_category):
login(client, "td@test.com", "tdpass")
Expand Down Expand Up @@ -67,7 +67,7 @@ def test_update_category_id(self, client, admin_user, event_category_factory, ev
login(client, "admin@test.com", "adminpass")
res = client.patch(f"/events/{event.id}/", json={"category_id": other_category.id})
assert res.status_code == 200
assert res.json()["category_id"] == other_category.id
assert res.json()["category"]["id"] == other_category.id

def test_missing_event_404(self, client, admin_user):
login(client, "admin@test.com", "adminpass")
Expand Down
10 changes: 5 additions & 5 deletions backend/tests/api/test_user_experience.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_valid_event_and_school(self, client, td_user, event):
})
assert res.status_code == 201
data = res.json()
assert data["event_id"] == event.id
assert data["event"]["id"] == event.id
assert data["school"] == "MIT"

def test_invalid_event_id_404(self, client, td_user):
Expand Down Expand Up @@ -49,7 +49,7 @@ def test_partial_update(self, client, db, td_user, event):
assert res.status_code == 200
data = res.json()
assert data["school"] == "Caltech"
assert data["event_id"] == event.id # untouched
assert data["event"]["id"] == event.id # untouched

def test_missing_entry_404(self, client, td_user):
login(client, "td@test.com", "tdpass")
Expand Down Expand Up @@ -116,7 +116,7 @@ def test_minimal_fields(self, client, td_user):
assert data["tournament_name"] == "Regionals"
assert data["year"] == 2025
assert data["role"] == "Event Supervisor"
assert data["event_id"] is None
assert data["event"] is None
assert data["notes"] is None

def test_with_event_id_no_notes_event(self, client, td_user, event):
Expand All @@ -128,7 +128,7 @@ def test_with_event_id_no_notes_event(self, client, td_user, event):
"event_id": event.id,
})
assert res.status_code == 201
assert res.json()["event_id"] == event.id
assert res.json()["event"]["id"] == event.id

def test_with_notes_event_no_event_id(self, client, td_user):
login(client, "td@test.com", "tdpass")
Expand All @@ -140,7 +140,7 @@ def test_with_notes_event_no_event_id(self, client, td_user):
})
assert res.status_code == 201
data = res.json()
assert data["event_id"] is None
assert data["event"] is None
assert data["notes"]["event"] == "Custom Event Name"

def test_event_id_and_notes_event_mutually_exclusive(self, client, td_user, event):
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/api/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def test_competition_and_volunteer_experience_populated(self, client, td_user, d

assert len(data["competition_experience"]) == 1
assert data["competition_experience"][0]["school"] == "MIT"
assert data["competition_experience"][0]["event_id"] == event.id
assert data["competition_experience"][0]["event"]["id"] == event.id

assert len(data["volunteer_experience"]) == 1
assert data["volunteer_experience"][0]["tournament_name"] == "Regionals"
Expand Down
Loading
Loading