feat: expose admin status to frontend via user API
Add is_admin field to UserResponse schema and update AuthContext to fetch user profile after login, storing and exposing isAdmin boolean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-5svj
|
# nuzlocke-tracker-5svj
|
||||||
title: Expose admin status to frontend via user API
|
title: Expose admin status to frontend via user API
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-03-21T10:06:20Z
|
created_at: 2026-03-21T10:06:20Z
|
||||||
updated_at: 2026-03-21T10:06:24Z
|
updated_at: 2026-03-21T10:18:26Z
|
||||||
parent: nuzlocke-tracker-ce4o
|
parent: nuzlocke-tracker-ce4o
|
||||||
blocked_by:
|
blocked_by:
|
||||||
- nuzlocke-tracker-dwah
|
- nuzlocke-tracker-dwah
|
||||||
@@ -15,13 +15,21 @@ The frontend needs to know if the current user is an admin so it can show/hide t
|
|||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Add `is_admin` field to the user response schema (`/api/users/me` endpoint)
|
- [x] Add `is_admin` field to the user response schema (`/api/users/me` endpoint)
|
||||||
- [ ] Update `AuthContext` to fetch `/api/users/me` after login and store `isAdmin` in context
|
- [x] Update `AuthContext` to fetch `/api/users/me` after login and store `isAdmin` in context
|
||||||
- [ ] Expose `isAdmin` boolean from `useAuth()` hook
|
- [x] Expose `isAdmin` boolean from `useAuth()` hook
|
||||||
- [ ] Handle edge case: user exists in Supabase but not yet in local DB (first login creates user row with `is_admin=false`)
|
- [x] Handle edge case: user exists in Supabase but not yet in local DB (first login creates user row with `is_admin=false`)
|
||||||
|
|
||||||
## Files to change
|
## Files to change
|
||||||
|
|
||||||
- `backend/src/app/schemas/user.py` or equivalent — add `is_admin` to response
|
- `backend/src/app/schemas/user.py` or equivalent — add `is_admin` to response
|
||||||
- `backend/src/app/api/users.py` — ensure `/me` returns `is_admin`
|
- `backend/src/app/api/users.py` — ensure `/me` returns `is_admin`
|
||||||
- `frontend/src/contexts/AuthContext.tsx` — fetch and store admin status
|
- `frontend/src/contexts/AuthContext.tsx` — fetch and store admin status
|
||||||
|
|
||||||
|
## Summary of Changes
|
||||||
|
|
||||||
|
Added `isAdmin` field to frontend auth system:
|
||||||
|
|
||||||
|
- **Backend**: Added `is_admin: bool = False` to `UserResponse` schema in `backend/src/app/api/users.py`
|
||||||
|
- **Frontend**: Updated `AuthContext` to fetch `/api/users/me` after login and expose `isAdmin` boolean
|
||||||
|
- Edge case handled: `syncUserProfile` returns `false` if API call fails (new user auto-created with `is_admin=false` by backend)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class UserResponse(CamelModel):
|
|||||||
id: UUID
|
id: UUID
|
||||||
email: str
|
email: str
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
|
is_admin: bool = False
|
||||||
|
|
||||||
|
|
||||||
@router.post("/me", response_model=UserResponse)
|
@router.post("/me", response_model=UserResponse)
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
|
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react'
|
||||||
import type { User, Session, AuthError } from '@supabase/supabase-js'
|
import type { User, Session, AuthError } from '@supabase/supabase-js'
|
||||||
import { supabase } from '../lib/supabase'
|
import { supabase } from '../lib/supabase'
|
||||||
|
import { api } from '../api/client'
|
||||||
|
|
||||||
|
interface UserProfile {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
displayName: string | null
|
||||||
|
isAdmin: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
user: User | null
|
user: User | null
|
||||||
session: Session | null
|
session: Session | null
|
||||||
loading: boolean
|
loading: boolean
|
||||||
|
isAdmin: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthContextValue extends AuthState {
|
interface AuthContextValue extends AuthState {
|
||||||
@@ -18,22 +27,35 @@ interface AuthContextValue extends AuthState {
|
|||||||
|
|
||||||
const AuthContext = createContext<AuthContextValue | null>(null)
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
||||||
|
|
||||||
|
async function syncUserProfile(session: Session | null): Promise<boolean> {
|
||||||
|
if (!session) return false
|
||||||
|
try {
|
||||||
|
const profile = await api.post<UserProfile>('/users/me', {})
|
||||||
|
return profile.isAdmin
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [state, setState] = useState<AuthState>({
|
const [state, setState] = useState<AuthState>({
|
||||||
user: null,
|
user: null,
|
||||||
session: null,
|
session: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
isAdmin: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
supabase.auth.getSession().then(async ({ data: { session } }) => {
|
||||||
setState({ user: session?.user ?? null, session, loading: false })
|
const isAdmin = await syncUserProfile(session)
|
||||||
|
setState({ user: session?.user ?? null, session, loading: false, isAdmin })
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { subscription },
|
data: { subscription },
|
||||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
} = supabase.auth.onAuthStateChange(async (_event, session) => {
|
||||||
setState({ user: session?.user ?? null, session, loading: false })
|
const isAdmin = await syncUserProfile(session)
|
||||||
|
setState({ user: session?.user ?? null, session, loading: false, isAdmin })
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => subscription.unsubscribe()
|
return () => subscription.unsubscribe()
|
||||||
|
|||||||
Reference in New Issue
Block a user