Tab 1 Admin overview (/): aggregate stats + DATE FILTER (?from&to), hardest personas, admin quick actions (Users / + Add product). Tab 2 My dashboard (/my/board): per-user win/lose/session summary (works for admin too). Tab 3 Training (/training): product list w/ status + persona count; admins see all groups (draft->analyze/edit), trainees see ready->personas; persona list shows DIFFICULTY (1-5 stars); admins get a Manage-personas button. Settings (/settings): profile (name/email) via new PATCH /api/auth/profile + change password + language. Backend: analytics date filter; profile endpoint; /api/me/board + /api/chat/sessions opened to any authed user. Rebuilt frontend/dist.
85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
"""Auth routes: login, current user, first-time admin setup. No self-registration."""
|
|
from __future__ import annotations
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
|
|
from ..auth.users import AuthError
|
|
from .helpers import ApiError, current_user, require_auth
|
|
|
|
auth_bp = Blueprint("auth", __name__)
|
|
|
|
|
|
def _store():
|
|
from flask import current_app
|
|
|
|
return current_app.extensions["user_store"]
|
|
|
|
|
|
def _login_body(data: dict) -> str:
|
|
# Accept `username` (primary) or `email` (fallback), lower-cased.
|
|
return (data.get("username") or data.get("email") or "").strip().lower()
|
|
|
|
|
|
@auth_bp.post("/login")
|
|
def login():
|
|
data = request.get_json(silent=True) or {}
|
|
username = _login_body(data)
|
|
password = data.get("password") or ""
|
|
if not username or not password:
|
|
raise ApiError("username and password are required")
|
|
try:
|
|
user = _store().verify(username, password)
|
|
token = _store().issue_token(user)
|
|
except AuthError as exc:
|
|
raise ApiError(str(exc), 401)
|
|
return jsonify({
|
|
"token": token,
|
|
"user": _store().public_user(user),
|
|
"must_setup": bool(user.get("must_setup")),
|
|
})
|
|
|
|
|
|
@auth_bp.get("/me")
|
|
@require_auth
|
|
def me():
|
|
return jsonify({"user": _store().public_user(current_user())})
|
|
|
|
|
|
@auth_bp.post("/setup")
|
|
@require_auth
|
|
def setup():
|
|
"""First-time admin setup: set email + change password, then clear must_setup."""
|
|
user = current_user()
|
|
data = request.get_json(silent=True) or {}
|
|
username = (data.get("username") or user.get("username") or user.get("id") or "").strip().lower()
|
|
email = (data.get("email") or "").strip()
|
|
new_password = data.get("password") or ""
|
|
if not email or not new_password:
|
|
raise ApiError("email and new password are required")
|
|
try:
|
|
updated = _store().complete_setup(username, email, new_password)
|
|
except AuthError as exc:
|
|
raise ApiError(str(exc), 400)
|
|
return jsonify({
|
|
"ok": True,
|
|
"user": _store().public_user(updated),
|
|
"must_setup": False,
|
|
})
|
|
|
|
|
|
@auth_bp.patch("/profile")
|
|
@require_auth
|
|
def profile():
|
|
"""Self-service profile update: name (and optional email). Any authenticated user."""
|
|
user = current_user()
|
|
data = request.get_json(silent=True) or {}
|
|
username = user.get("username") or user.get("id")
|
|
try:
|
|
if "name" in data:
|
|
_store().set_name(username, data.get("name"))
|
|
if "email" in data:
|
|
_store().set_email(username, data.get("email"))
|
|
except AuthError as exc:
|
|
raise ApiError(str(exc), 400)
|
|
return jsonify({"user": _store().public_user(_store().get_user(username))})
|