first prototype commit

This commit is contained in:
2026-08-09 21:45:06 -04:00
commit 40613a00d5
36 changed files with 4371 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
from fastapi import APIRouter, Depends, HTTPException, status, Header
from sqlalchemy.orm import Session
from backend.database import get_db
from backend.models import User, UserRole
from backend.schemas import UserOut, GoogleAuthRequest, DemoLoginRequest
from backend.config import create_access_token, decode_access_token, GOOGLE_CLIENT_ID
from typing import Optional
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
router = APIRouter(prefix="/api/auth", tags=["auth"])
def get_current_user(authorization: Optional[str] = Header(None), db: Session = Depends(get_db)) -> User:
if not authorization:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing authorization header")
parts = authorization.split()
if len(parts) != 2 or parts[0].lower() != "bearer":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authorization header format")
token = parts[1]
payload = decode_access_token(token)
if not payload or "sub" not in payload:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token")
user_id = int(payload["sub"])
user = db.query(User).filter(User.id == user_id, User.is_active == True).first()
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or inactive")
return user
def get_admin_user(current_user: User = Depends(get_current_user)) -> User:
if current_user.role != UserRole.ADMIN.value:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required")
return current_user
@router.post("/google")
def google_auth(req: GoogleAuthRequest, db: Session = Depends(get_db)):
email = req.email
name = req.name or "Google User"
avatar_url = req.avatar_url or f"https://api.dicebear.com/7.x/bottts/svg?seed={email}"
google_sub = None
# Try verifying real Google token if client ID configured and credential provided
if req.credential and len(req.credential) > 50:
try:
if GOOGLE_CLIENT_ID:
id_info = id_token.verify_oauth2_token(req.credential, google_requests.Request(), GOOGLE_CLIENT_ID)
else:
id_info = id_token.verify_oauth2_token(req.credential, google_requests.Request())
email = id_info.get("email", email)
name = id_info.get("name", name)
avatar_url = id_info.get("picture", avatar_url)
google_sub = id_info.get("sub")
except Exception as e:
# Fallback to provided details if token decode fails in dev
if not email:
raise HTTPException(status_code=400, detail=f"Google token verification failed: {str(e)}")
if not email:
raise HTTPException(status_code=400, detail="Email is required")
user = db.query(User).filter(User.email == email).first()
# Check if this is the first user ever registered
total_users = db.query(User).count()
initial_role = UserRole.ADMIN.value if total_users == 0 else UserRole.REGULAR.value
if not user:
user = User(
email=email,
name=name,
avatar_url=avatar_url,
google_id=google_sub,
role=initial_role,
weekly_star_quota=15
)
db.add(user)
db.commit()
db.refresh(user)
else:
# Update existing user google_id or default avatar if missing
if google_sub and not user.google_id:
user.google_id = google_sub
if avatar_url and not user.avatar_url:
user.avatar_url = avatar_url
db.commit()
db.refresh(user)
token = create_access_token({"sub": str(user.id), "email": user.email, "role": user.role})
return {
"access_token": token,
"token_type": "bearer",
"user": UserOut.model_validate(user)
}
@router.post("/demo-login")
def demo_login(req: DemoLoginRequest, db: Session = Depends(get_db)):
user = db.query(User).filter(User.email == req.email).first()
if not user:
raise HTTPException(status_code=404, detail="Demo user not found. Run backend initialization first.")
token = create_access_token({"sub": str(user.id), "email": user.email, "role": user.role})
return {
"access_token": token,
"token_type": "bearer",
"user": UserOut.model_validate(user)
}
@router.get("/me", response_model=UserOut)
def get_me(current_user: User = Depends(get_current_user)):
return current_user