Repo for HooHacks 2026.
See DEPLOY.md for deploying the frontend/ app on Vercel and wiring env vars, CORS, and Firebase.
Working directory: Run backend commands with your shell’s current directory set to backend/ (the folder that contains requirements.txt and the app/ package). If you run uvicorn from the repo root, Python will not find the app module—cd backend first.
From the project root:
cd backend
bash setup_backend.shThis creates backend/.venv and installs pinned dependencies from requirements.txt.
From the project root:
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtTo activate the virtual environment later in a new terminal:
cd backend
source .venv/bin/activateWith the venv activated and current directory backend/:
uvicorn app.main:app --reload --host 127.0.0.1 --port 8000Then open http://127.0.0.1:8000/health in a browser or use the interactive docs at http://127.0.0.1:8000/docs.
StudySync stores imports under backend/studysync.db (SQLite file; gitignored). Tables are created automatically on API startup (create_all). To use a different database (e.g. in tests), set:
export STUDYSYNC_DATABASE_URL="sqlite:////absolute/path/to/file.db"Stop the server before deleting studysync.db if you want a clean reset (avoid file locks).
Browsers allow your Vite/Next dev app to call this API when its origin is listed. Defaults: http://localhost:5173, http://localhost:3000. Override with a comma-separated list:
export STUDYSYNC_CORS_ORIGINS="http://localhost:5173,http://localhost:3000,http://127.0.0.1:5173"- Copy
.env.exampleto.env. Never commit.env— it is gitignored. - Put
.envin either place (both are loaded): the repository root (next toREADME.md) orbackend/.env(next torequirements.txt). If the key was only inbackend/.envbefore, it was previously ignored — that is fixed now. - Set
GOOGLE_API_KEYfor the Gemini developer API, orGEMINI_API_KEY(same value; both names work). - Restart Uvicorn after changing
.env(env is loaded at app startup).
Optional: STUDYSYNC_GEMINI_MODEL (default gemini-2.0-flash).
The API verifies Firebase ID tokens only; profile rows live in SQLite (see Phase 2 — user profiles below). Configure the Admin SDK with one of:
| Variable | Purpose |
|---|---|
GOOGLE_APPLICATION_CREDENTIALS |
Absolute path to the service account JSON (Firebase Console → Project settings → Service accounts → Generate new private key). |
STUDYSYNC_FIREBASE_CREDENTIALS_JSON |
The same JSON pasted as a single-line string (useful when a file path is awkward). |
If neither is set, the app still starts; protected routes respond with 503 until you add credentials. Store local keys under e.g. backend/secrets/ (gitignored).
GET /auth/me— requires headerAuthorization: Bearer <Firebase ID token>. Returns JSON includinguidandemailfrom verified claims. Missing/invalid token → 401; Firebase not initialized → 503.
See backend/.env.example for copy-paste names.
Environment: Same as Part B (no new env vars). The users table is created on startup with the rest of the schema.
Clients must send Authorization: Bearer <Firebase ID token> on profile routes.
| Method | Path | Description |
|---|---|---|
| GET | /users/me |
Returns the signed-in user’s SQLite profile: internal id, firebase_uid, email, display_name, college, canvas_ics_url, created_at, updated_at. On each call, the server upserts the row for claims["uid"] and refreshes email when the token includes it. |
| PATCH | /users/me |
JSON body with any of display_name, college, canvas_ics_url (all optional). Omitted fields are unchanged; send null to clear a nullable field. canvas_ics_url is validated with the same rules as POST /import-canvas-ics (invalid URL → 422). |
Auth errors match Part B (401 / 503). Passwords are not stored; Firebase remains auth-only.
POST /sessions/{session_id}/ai-plan loads all assignments and courses for that session, computes same-day “load” in your planning window (using the same date precedence as elsewhere: personal_target → official_due → official_start → official_end), and asks Gemini for a structured JSON plan. The response is suggestions only — nothing is written to the DB (use existing PATCH routes to apply your own changes).
Request JSON:
window_start,window_end— ISO dates (YYYY-MM-DD). End must be ≥ start (422if not).user_goal(optional) — short free text (availability, finals focus, etc.).
Response JSON: summary, suggestions (list), warnings (list; may include overload / missing-date notes), truncated_input (true when more than 80 assignments were trimmed for the prompt).
Without a configured API key (GOOGLE_API_KEY or GEMINI_API_KEY), the endpoint returns 503 with a clear message (no stack trace).
Try in /docs: expand POST /sessions/{session_id}/ai-plan, paste a real session_id from an import, body e.g. {"window_start":"2025-01-06","window_end":"2025-01-12","user_goal":"Study after 5pm weekdays"}.
curl example (set SID after an import):
curl -s -X POST "http://127.0.0.1:8000/sessions/$SID/ai-plan" \
-H "Content-Type: application/json" \
-d '{"window_start":"2025-01-06","window_end":"2025-01-12","user_goal":"Light week — prioritize high-priority courses"}' | jq .POST /import-canvas-ics accepts JSON { "ics_url": "..." }, persists a new session plus courses and assignments, and returns:
session_id— use this for later GET/PATCH routes.count_assignments,count_courses— rows created for this import.countandevents— same shape as before (immediate preview for the UI). Duplicate ICSUIDs in one feed are deduped (first wins). Optional query paramsdate_from/date_to(ISO dates) still filter before persistence.
Course dedupe rule: Within a session, one Course row per inferred name (case-insensitive). We infer from, in order: first iCal CATEGORIES entry; else a [CODE] prefix in SUMMARY; else text after an em dash in SUMMARY; else a shared bucket Uncategorized.
Example: import → read session_id from JSON (here shown with jq):
RESP=$(curl -s -X POST "http://127.0.0.1:8000/import-canvas-ics" \
-H "Content-Type: application/json" \
-d '{"ics_url":"https://example.com/calendar.ics"}')
echo "$RESP" | jq .
SID=$(echo "$RESP" | jq -r .session_id)
curl -s "http://127.0.0.1:8000/sessions/$SID/assignments" | jq .
curl -s "http://127.0.0.1:8000/sessions/$SID/courses" | jq .
# Update personal study target and notes (replace ASSIGNMENT_UUID from assignments list)
curl -s -X PATCH "http://127.0.0.1:8000/sessions/$SID/assignments/ASSIGNMENT_UUID" \
-H "Content-Type: application/json" \
-d '{"personal_target":"2025-06-10T18:00:00Z","notes":"Review chapter 3"}' | jq .
# Optional: download merged personal calendar (personal_target if set, else official due/start)
curl -s "http://127.0.0.1:8000/sessions/$SID/calendar.ics" -o studysync.icsAssignment date filter: GET .../assignments?date_from=&date_to= uses, per row, the first available of personal_target, official_due, official_start, official_end (calendar day, inclusive). Rows with none of these are omitted when either query param is set.
Fetching: The server uses requests with a 10s connect / 30s read timeout so a dead host fails quickly while a slow but valid download can still finish. Errors: invalid URL → 422; bad iCal body → 400; upstream non-2xx → 502; timeout → 504.
Response time fields: Timed events use UTC ISO 8601 with a Z suffix (YYYY-MM-DDTHH:MM:SSZ). All-day events use a date-only string (YYYY-MM-DD) with no timezone, per iCalendar DATE semantics. description is capped at 8,000 characters to keep payloads bounded (full text in feed may be longer).
Run this from the project root:
cd frontend
npm installCopy frontend/.env.example to frontend/.env and set VITE_STUDYSYNC_API_URL (FastAPI base, e.g. http://127.0.0.1:8000) plus Firebase Web App keys from the Firebase Console (VITE_FIREBASE_API_KEY, VITE_FIREBASE_AUTH_DOMAIN, VITE_FIREBASE_PROJECT_ID, etc.). Restart Vite after changes.
The UI uses Firebase Auth (email/password) for sign-in; the app sends Authorization: Bearer <Firebase ID token> on API calls. Profile data comes from GET/PATCH /users/me (SQLite). Passwords never go to FastAPI. ICS import (POST /import-canvas-ics) does not require auth on the backend. Manual check: sign up → open Settings and save display name / college / Canvas URL → import calendar (modal or /onboarding) → confirm requests in DevTools include Authorization on /users/me and session routes.
Start the frontend dev server:
npm run devRun this to confirm the verions
node checkVersions.jsOptional useful commands (no need now)
npm run build
npm run preview