User avatar (Google picture), avatar in mobile You tab, /account page
- Capture the Google profile picture (picture claim) into users.avatar_url; an Avatar component shows it, falling back to the initial. Used in the desktop header and the mobile "You" tab (which now shows the user when signed in). - Move account/settings to its own route /account (robust + scrolls to top), reached by the desktop avatar and the mobile You tab; drop the inline "You" sheet. AccountPanel gains a Sign out action; the page links to Saved/History/ Boundaries via home intent params (?view= / ?open=). - db: users.avatar_url (schema + idempotent migration). 118 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -104,6 +104,15 @@ def _require_user(conn: sqlite3.Connection, request: Request) -> sqlite3.Row:
|
||||
return user
|
||||
|
||||
|
||||
def _user_out(user: sqlite3.Row) -> dict:
|
||||
return {
|
||||
"id": user["id"],
|
||||
"email": user["email"],
|
||||
"display_name": user["display_name"],
|
||||
"avatar_url": user["avatar_url"],
|
||||
}
|
||||
|
||||
|
||||
def _send_link_safe(email: str, link: str) -> None:
|
||||
"""Send the magic link, swallowing failures (runs off the request path)."""
|
||||
try:
|
||||
@@ -284,6 +293,7 @@ class UserOut(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
display_name: str | None = None
|
||||
avatar_url: str | None = None
|
||||
|
||||
|
||||
class SessionOut(BaseModel):
|
||||
@@ -370,18 +380,13 @@ def create_app() -> FastAPI:
|
||||
conn.commit()
|
||||
user = auth.get_user(conn, user_id)
|
||||
_set_session_cookie(response, token)
|
||||
return SessionOut(
|
||||
user=UserOut(id=user["id"], email=user["email"], display_name=user["display_name"]),
|
||||
token=token,
|
||||
)
|
||||
return SessionOut(user=UserOut(**_user_out(user)), token=token)
|
||||
|
||||
@app.get("/api/auth/me", response_model=UserOut | None)
|
||||
def auth_me(request: Request) -> UserOut | None:
|
||||
with get_conn() as conn:
|
||||
user = _current_user(conn, request)
|
||||
if not user:
|
||||
return None
|
||||
return UserOut(id=user["id"], email=user["email"], display_name=user["display_name"])
|
||||
return UserOut(**_user_out(user)) if user else None
|
||||
|
||||
@app.post("/api/auth/logout")
|
||||
def auth_logout(request: Request, response: Response) -> dict:
|
||||
@@ -431,7 +436,8 @@ def create_app() -> FastAPI:
|
||||
return fail
|
||||
with get_conn() as conn:
|
||||
user_id = auth.find_or_create_user(
|
||||
conn, info["email"], "google", info["sub"], display_name=info.get("name")
|
||||
conn, info["email"], "google", info["sub"],
|
||||
display_name=info.get("name"), avatar_url=info.get("picture"),
|
||||
)
|
||||
token = auth.create_session(conn, user_id, user_agent=request.headers.get("User-Agent"))
|
||||
conn.commit()
|
||||
|
||||
Reference in New Issue
Block a user