From 41d8e25843d66a0bbfbe31ec42797422039aec67 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Sat, 7 Mar 2026 00:00:22 -0300 Subject: [PATCH] feat: enhance admin setup with email validation and update workflows for fresh lib cloning --- .github/workflows/frontend-tests.yml | 11 +++++++++-- backend/app/api/routes/admin.py | 12 +++--------- backend/app/schemas/admin.py | 1 + frontend/src/pages/AdminPage.tsx | 12 +++++++++++- frontend/src/services/adminService.ts | 4 ++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index ec26e79..a5660df 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -13,14 +13,21 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - with: - submodules: recursive + # Do NOT use submodules: recursive — the submodule pointers in this repo + # are stale and predate package.json. We clone the libs fresh below. - name: Setup Node.js 22 uses: actions/setup-node@v4 with: node-version: '22' + # Clone wokwi-libs fresh (stale submodule pointers can't be used) + - name: Clone wokwi-libs + run: | + git clone --depth=1 https://github.com/wokwi/avr8js.git wokwi-libs/avr8js + git clone --depth=1 https://github.com/wokwi/rp2040js.git wokwi-libs/rp2040js + git clone --depth=1 https://github.com/wokwi/wokwi-elements.git wokwi-libs/wokwi-elements + # Cache wokwi-libs node_modules to speed up repeated runs - name: Cache wokwi-libs node_modules uses: actions/cache@v4 diff --git a/backend/app/api/routes/admin.py b/backend/app/api/routes/admin.py index bcc4724..0156d14 100644 --- a/backend/app/api/routes/admin.py +++ b/backend/app/api/routes/admin.py @@ -46,20 +46,14 @@ async def setup_admin(body: AdminSetupRequest, db: AsyncSession = Depends(get_db # Check uniqueness conflict = await db.execute( - select(User).where(User.username == username) + select(User).where((User.username == username) | (User.email == body.email)) ) if conflict.scalar_one_or_none(): - raise HTTPException(status_code=400, detail="Username already taken.") - - # Generate a placeholder email for the admin setup account - email = f"{username}@admin.local" - email_conflict = await db.execute(select(User).where(User.email == email)) - if email_conflict.scalar_one_or_none(): - email = f"{username}.admin@admin.local" + raise HTTPException(status_code=400, detail="Username or email already taken.") user = User( username=username, - email=email, + email=body.email, hashed_password=hash_password(body.password), is_admin=True, is_active=True, diff --git a/backend/app/schemas/admin.py b/backend/app/schemas/admin.py index 2135bdd..4a51149 100644 --- a/backend/app/schemas/admin.py +++ b/backend/app/schemas/admin.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, EmailStr class AdminSetupRequest(BaseModel): username: str + email: EmailStr password: str diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index 011769b..7f08822 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -142,6 +142,7 @@ const modalStyles: Record = { function SetupScreen({ onDone }: { onDone: () => void }) { const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirm, setConfirm] = useState(''); const [error, setError] = useState(''); @@ -157,7 +158,7 @@ function SetupScreen({ onDone }: { onDone: () => void }) { } setLoading(true); try { - await createFirstAdmin(username, password); + await createFirstAdmin(username, email, password); onDone(); navigate('/login?redirect=/admin'); } catch (err: any) { @@ -183,6 +184,15 @@ function SetupScreen({ onDone }: { onDone: () => void }) { autoFocus placeholder="admin" /> + + setEmail(e.target.value)} + required + placeholder="admin@example.com" + /> { return data; } -export async function createFirstAdmin(username: string, password: string): Promise { - const { data } = await api.post('/admin/setup', { username, password }); +export async function createFirstAdmin(username: string, email: string, password: string): Promise { + const { data } = await api.post('/admin/setup', { username, email, password }); return data; }