-
Notifications
You must be signed in to change notification settings - Fork 0
Added animations and an official onboarding process for new users. #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| """ | ||
|
|
||
| import json | ||
| import logging | ||
| from datetime import datetime, timezone, timedelta | ||
|
|
||
| from fastapi import APIRouter, File, Form, HTTPException, Query, UploadFile | ||
|
|
@@ -29,6 +30,7 @@ | |
| GOOGLE_AVAILABLE = False | ||
|
|
||
| router = APIRouter() | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| # ── Helpers ─────────────────────────────────────────────────────────────────── | ||
|
|
@@ -115,13 +117,21 @@ def save_assignments(body: SaveAssignmentsBody): | |
| @router.get("/upcoming/{user_id}") | ||
| def get_upcoming(user_id: str): | ||
| today = datetime.utcnow().strftime("%Y-%m-%d") | ||
| rows = table("assignments").select( | ||
| "*", | ||
| filters={"user_id": f"eq.{user_id}", "due_date": f"gte.{today}"}, | ||
| order="due_date.asc", | ||
| limit=20, | ||
| ) | ||
| return {"assignments": rows} | ||
| try: | ||
| rows = table("assignments").select( | ||
| "*", | ||
| filters={"user_id": f"eq.{user_id}", "due_date": f"gte.{today}"}, | ||
| order="due_date.asc", | ||
| limit=20, | ||
| ) | ||
| return {"assignments": rows or []} | ||
| except Exception: | ||
| logger.exception( | ||
| "Failed to fetch upcoming assignments for user_id=%s today=%s", | ||
| user_id, | ||
| today, | ||
| ) | ||
| return {"assignments": []} | ||
|
Comment on lines
+120
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify blind catches and missing telemetry in these route handlers.
rg -n -C2 'except Exception' backend/routes/calendar.py backend/routes/graph.py
rg -n 'logger\.(exception|error|warning)\(' backend/routes/calendar.py backend/routes/graph.pyRepository: SaplingLearn/Sapling Length of output: 1279 🏁 Script executed: # Find test files for calendar routes
find . -type f -name "*test*calendar*" -o -name "*calendar*test*" | head -20Repository: SaplingLearn/Sapling Length of output: 103 🏁 Script executed: # Check if logging is configured in the backend
rg -n "import logging" backend/ | head -10
rg -n "^logger = " backend/ | head -10Repository: SaplingLearn/Sapling Length of output: 176 🏁 Script executed: # Look for pytest/test configuration
fd -t f "(conftest|pytest)" backend/Repository: SaplingLearn/Sapling Length of output: 89 🏁 Script executed: cat -n backend/tests/test_calendar_routes.pyRepository: SaplingLearn/Sapling Length of output: 8933 🏁 Script executed: # Check calendar.py to see the full context of the get_upcoming function
cat -n backend/routes/calendar.py | head -150Repository: SaplingLearn/Sapling Length of output: 5870 🏁 Script executed: # Look at how documents.py handles exceptions (shows logging pattern)
rg -B5 -A5 'except Exception' backend/routes/documents.pyRepository: SaplingLearn/Sapling Length of output: 922 Add logging and exception path test coverage to Line 126–127 silently swallows all exceptions, hiding real database/service failures and making incident detection impossible. Tests also lack coverage for the exception path. Specific issues
The logging pattern is already established in Suggested minimal fix+import logging
+
+logger = logging.getLogger(__name__)
+
`@router.get`("/upcoming/{user_id}")
def get_upcoming(user_id: str):
try:
today = datetime.utcnow().strftime("%Y-%m-%d")
rows = table("assignments").select(
"*",
filters={"user_id": f"eq.{user_id}", "due_date": f"gte.{today}"},
order="due_date.asc",
limit=20,
)
return {"assignments": rows or []}
- except Exception:
- return {"assignments": []}
+ except HTTPException:
+ raise
+ except Exception:
+ logger.exception("calendar.get_upcoming_failed", extra={"user_id": user_id})
+ return {"assignments": []}Add test case in def test_returns_empty_on_db_failure(self):
with patch("routes.calendar.table") as t:
t.return_value.select.side_effect = Exception("DB connection error")
r = client.get("/api/calendar/upcoming/user_andres")
assert r.status_code == 200
assert r.json()["assignments"] == []🧰 Tools🪛 Ruff (0.15.10)[warning] 126-126: Do not catch blind exception: (BLE001) 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @router.post("/suggest-study-blocks") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| from fastapi import APIRouter | ||
| import logging | ||
|
|
||
| from fastapi import APIRouter, HTTPException | ||
| from pydantic import BaseModel | ||
| from typing import Optional | ||
|
|
||
|
|
@@ -8,16 +10,25 @@ | |
| ) | ||
|
|
||
| router = APIRouter() | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| @router.get("/{user_id}") | ||
| def get_user_graph(user_id: str): | ||
| return get_graph(user_id) | ||
| try: | ||
| return get_graph(user_id) | ||
| except Exception as e: | ||
| logger.exception("get_graph failed for user_id=%s", user_id) | ||
| raise HTTPException(status_code=500, detail=str(e) or "Failed to fetch graph") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid exposing raw backend exception text in API responses. Using Also applies to: 31-31 🧰 Tools🪛 Ruff (0.15.10)[warning] 22-22: Within an (B904) 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @router.get("/{user_id}/recommendations") | ||
| def get_user_recommendations(user_id: str): | ||
| return {"recommendations": get_recommendations(user_id)} | ||
| try: | ||
| return {"recommendations": get_recommendations(user_id)} | ||
| except Exception as e: | ||
| logger.exception("get_recommendations failed for user_id=%s", user_id) | ||
| raise HTTPException(status_code=500, detail=str(e) or "Failed to fetch recommendations") | ||
|
|
||
|
|
||
| # ── Course endpoints ────────────────────────────────────────────────────────── | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| """ | ||
| Unit tests for routes/graph.py | ||
| """ | ||
|
|
||
| from unittest.mock import patch | ||
|
|
||
| from fastapi.testclient import TestClient | ||
|
|
||
| from main import app | ||
|
|
||
| client = TestClient(app) | ||
|
|
||
|
|
||
| class TestGraphRoutes: | ||
| def test_get_user_graph_returns_500_on_failure(self): | ||
| with patch("routes.graph.get_graph", side_effect=Exception("graph failed")): | ||
| r = client.get("/api/graph/user_andres") | ||
|
|
||
| assert r.status_code == 500 | ||
| assert r.json() == {"detail": "graph failed"} | ||
|
|
||
| def test_get_user_recommendations_returns_500_on_failure(self): | ||
| with patch("routes.graph.get_recommendations", side_effect=Exception("recommendations failed")): | ||
| r = client.get("/api/graph/user_andres/recommendations") | ||
|
|
||
| assert r.status_code == 500 | ||
| assert r.json() == {"detail": "recommendations failed"} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export const dynamic = 'force-static'; | ||
|
|
||
| export async function GET() { | ||
| return new Response('Static export build: Google auth callback is handled by the backend redirect.', { | ||
| status: 410, | ||
| headers: { | ||
| 'Content-Type': 'text/plain; charset=utf-8', | ||
| }, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export const dynamic = 'force-static'; | ||
|
|
||
| export async function GET() { | ||
| return new Response('Static export build: use the frontend Google sign-in button instead.', { | ||
| status: 410, | ||
| headers: { | ||
| 'Content-Type': 'text/plain; charset=utf-8', | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent
auth_providerupdate across branches.When a user is found by
google_idmatch, the update (lines 136-139) does not setauth_provider, while all other code paths explicitly setauth_provider="google"(lines 146, 156, 169). This could leave legacy users without the field populated.🔧 Proposed fix for consistency
table("users").update( - {"name": name, "avatar_url": avatar_url, "email": email}, + {"name": name, "avatar_url": avatar_url, "email": email, "auth_provider": "google"}, filters={"id": f"eq.{user_id}"}, )🤖 Prompt for AI Agents