feat: implement initial admin setup (Story 1.1)

Add complete initial admin setup functionality including:
- SetupForm with email, password, and password confirmation fields
- Password validation (minimum 12 characters) and confirmation matching
- Email format validation
- Setup route with GET (display form) and POST (process setup) handlers
- bcrypt password hashing before storing admin credentials
- Auto-login after successful setup with session management
- First-run detection middleware that redirects to /setup if no admin exists
- Setup page returns 404 after admin account is created
- Base HTML template with Pico CSS integration
- Admin dashboard placeholder template
- 404 error template

All tests pass with 90.09% code coverage (exceeds 80% requirement).
Code passes ruff linting and mypy type checking.

Story: 1.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-22 11:40:38 -07:00
parent b077112aba
commit 6a2ac7a8a7
15 changed files with 536 additions and 15 deletions

View File

@@ -55,12 +55,12 @@ def create_app(config_name: str | None = None) -> Flask:
app.config["SESSION_SQLALCHEMY"] = db
session.init_app(app)
# Register blueprints (will be created later)
# from src.routes import admin, auth, participant, public
# app.register_blueprint(admin.bp)
# app.register_blueprint(auth.bp)
# app.register_blueprint(participant.bp)
# app.register_blueprint(public.bp)
# Register blueprints
from src.routes.admin import admin_bp
from src.routes.setup import setup_bp
app.register_blueprint(setup_bp)
app.register_blueprint(admin_bp)
# Register error handlers
register_error_handlers(app)
@@ -127,17 +127,15 @@ def register_setup_check(app: Flask) -> None:
has been set up with an admin account.
"""
# Skip check for certain endpoints
if request.endpoint in ["setup", "static", "health"]:
if request.endpoint in ["setup.setup", "static", "health"]:
return
# Check if we've already determined setup is required
if not hasattr(app, "_setup_checked"):
from src.models.admin import Admin
# Check if admin exists (always check in testing mode)
from src.models.admin import Admin
admin_count = db.session.query(Admin).count()
app.config["REQUIRES_SETUP"] = admin_count == 0
app._setup_checked = True
admin_count = db.session.query(Admin).count()
requires_setup = admin_count == 0
# Redirect to setup if needed
if app.config.get("REQUIRES_SETUP") and request.endpoint != "setup":
return redirect(url_for("setup"))
if requires_setup and request.endpoint != "setup.setup":
return redirect(url_for("setup.setup"))