from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, EmailStr from sqlalchemy.orm import Session from core.db import get_db from core.events import event_bus from core.security import ( decode_token, get_current_user_id, hash_password, make_access_token, make_refresh_token, verify_password, ) from .models import User router = APIRouter() class RegisterIn(BaseModel): email: EmailStr password: str name: str = "" class LoginIn(BaseModel): email: EmailStr password: str class TokenOut(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" role: str user_id: int class UserOut(BaseModel): id: int email: str name: str role: str locale: str class UpdateMeIn(BaseModel): name: str | None = None locale: str | None = None class ChangePasswordIn(BaseModel): old_password: str new_password: str @router.post("/register", response_model=TokenOut) def register(body: RegisterIn, db: Session = Depends(get_db)): if db.query(User).filter_by(email=body.email.lower()).first(): raise HTTPException(400, "Email already registered") user = User( email=body.email.lower(), password_hash=hash_password(body.password), name=body.name, role="customer", ) db.add(user) db.commit() db.refresh(user) event_bus.publish( "user.registered", {"user_id": user.id, "email": user.email}, db=db, ) return TokenOut( access_token=make_access_token(user.id, user.role), refresh_token=make_refresh_token(user.id, user.role), role=user.role, user_id=user.id, ) @router.post("/login", response_model=TokenOut) def login(body: LoginIn, db: Session = Depends(get_db)): user = db.query(User).filter_by(email=body.email.lower()).first() if not user or not verify_password(body.password, user.password_hash): raise HTTPException(401, "Invalid credentials") event_bus.publish("user.logged_in", {"user_id": user.id}, db=db) return TokenOut( access_token=make_access_token(user.id, user.role), refresh_token=make_refresh_token(user.id, user.role), role=user.role, user_id=user.id, ) class RefreshIn(BaseModel): refresh_token: str @router.post("/refresh", response_model=TokenOut) def refresh(body: RefreshIn, db: Session = Depends(get_db)): claims = decode_token(body.refresh_token) if claims.get("type") != "refresh": raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Not a refresh token") user_id = int(claims["sub"]) user = db.get(User, user_id) if not user: raise HTTPException(401, "User gone") return TokenOut( access_token=make_access_token(user.id, user.role), refresh_token=make_refresh_token(user.id, user.role), role=user.role, user_id=user.id, ) @router.get("/me", response_model=UserOut) def me(user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db)): user = db.get(User, user_id) if not user: raise HTTPException(404, "User not found") return UserOut(id=user.id, email=user.email, name=user.name, role=user.role, locale=user.locale) @router.put("/me", response_model=UserOut) def update_me( body: UpdateMeIn, user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db), ): user = db.get(User, user_id) if not user: raise HTTPException(404, "User not found") if body.name is not None: user.name = body.name if body.locale is not None: user.locale = body.locale db.commit() return UserOut(id=user.id, email=user.email, name=user.name, role=user.role, locale=user.locale) @router.post("/change-password") def change_password( body: ChangePasswordIn, user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db), ): user = db.get(User, user_id) if not user or not verify_password(body.old_password, user.password_hash): raise HTTPException(400, "Old password wrong") user.password_hash = hash_password(body.new_password) db.commit() return {"ok": True}