feat: enhance admin setup with email validation and update workflows for fresh lib cloning
parent
290b149855
commit
41d8e25843
|
|
@ -13,14 +13,21 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
# Do NOT use submodules: recursive — the submodule pointers in this repo
|
||||||
submodules: recursive
|
# are stale and predate package.json. We clone the libs fresh below.
|
||||||
|
|
||||||
- name: Setup Node.js 22
|
- name: Setup Node.js 22
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
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
|
# Cache wokwi-libs node_modules to speed up repeated runs
|
||||||
- name: Cache wokwi-libs node_modules
|
- name: Cache wokwi-libs node_modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|
|
||||||
|
|
@ -46,20 +46,14 @@ async def setup_admin(body: AdminSetupRequest, db: AsyncSession = Depends(get_db
|
||||||
|
|
||||||
# Check uniqueness
|
# Check uniqueness
|
||||||
conflict = await db.execute(
|
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():
|
if conflict.scalar_one_or_none():
|
||||||
raise HTTPException(status_code=400, detail="Username already taken.")
|
raise HTTPException(status_code=400, detail="Username or email 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"
|
|
||||||
|
|
||||||
user = User(
|
user = User(
|
||||||
username=username,
|
username=username,
|
||||||
email=email,
|
email=body.email,
|
||||||
hashed_password=hash_password(body.password),
|
hashed_password=hash_password(body.password),
|
||||||
is_admin=True,
|
is_admin=True,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from pydantic import BaseModel, EmailStr
|
||||||
|
|
||||||
class AdminSetupRequest(BaseModel):
|
class AdminSetupRequest(BaseModel):
|
||||||
username: str
|
username: str
|
||||||
|
email: EmailStr
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ const modalStyles: Record<string, React.CSSProperties> = {
|
||||||
|
|
||||||
function SetupScreen({ onDone }: { onDone: () => void }) {
|
function SetupScreen({ onDone }: { onDone: () => void }) {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirm, setConfirm] = useState('');
|
const [confirm, setConfirm] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
@ -157,7 +158,7 @@ function SetupScreen({ onDone }: { onDone: () => void }) {
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await createFirstAdmin(username, password);
|
await createFirstAdmin(username, email, password);
|
||||||
onDone();
|
onDone();
|
||||||
navigate('/login?redirect=/admin');
|
navigate('/login?redirect=/admin');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
@ -183,6 +184,15 @@ function SetupScreen({ onDone }: { onDone: () => void }) {
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder="admin"
|
placeholder="admin"
|
||||||
/>
|
/>
|
||||||
|
<label style={s.label}>Email</label>
|
||||||
|
<input
|
||||||
|
style={s.input}
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
placeholder="admin@example.com"
|
||||||
|
/>
|
||||||
<label style={s.label}>Password</label>
|
<label style={s.label}>Password</label>
|
||||||
<input
|
<input
|
||||||
style={s.input}
|
style={s.input}
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ export async function getAdminSetupStatus(): Promise<{ has_admin: boolean }> {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createFirstAdmin(username: string, password: string): Promise<AdminUserResponse> {
|
export async function createFirstAdmin(username: string, email: string, password: string): Promise<AdminUserResponse> {
|
||||||
const { data } = await api.post('/admin/setup', { username, password });
|
const { data } = await api.post('/admin/setup', { username, email, password });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue