import re from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.database import get_db from app.models.tenant import Tenant from app.models.user import User from app.models.subscription import Subscription from app.schemas.auth import RegisterRequest, LoginRequest, TokenResponse, RefreshRequest from app.utils.security import hash_password, verify_password, create_access_token, create_refresh_token, decode_token router = APIRouter() def _make_slug(name: str) -> str: slug = re.sub(r"[^\w\s-]", "", name.lower().strip()) return re.sub(r"[\s_]+", "-", slug) @router.post("/register", response_model=TokenResponse) async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)): existing = await db.execute(select(User).where(User.email == data.email)) if existing.scalar_one_or_none(): raise HTTPException(status_code=400, detail="Email already registered") slug = _make_slug(data.company_name) existing_tenant = await db.execute(select(Tenant).where(Tenant.slug == slug)) if existing_tenant.scalar_one_or_none(): slug = f"{slug}-{int(datetime.now(timezone.utc).timestamp()) % 10000}" tenant = Tenant( name=data.company_name, name_ar=data.company_name_ar, slug=slug, industry=data.industry, email=data.email, phone=data.phone, ) db.add(tenant) await db.flush() user = User( tenant_id=tenant.id, email=data.email, password_hash=hash_password(data.password), full_name=data.full_name, phone=data.phone, role="owner", ) db.add(user) subscription = Subscription( tenant_id=tenant.id, plan="basic", status="trial", price_monthly=0, currency="SAR", ) db.add(subscription) await db.flush() access_token = create_access_token({"sub": str(user.id), "tenant_id": str(tenant.id), "role": user.role}) refresh_token = create_refresh_token({"sub": str(user.id)}) return TokenResponse( access_token=access_token, refresh_token=refresh_token, user_id=str(user.id), tenant_id=str(tenant.id), role=user.role, ) @router.post("/login", response_model=TokenResponse) async def login(data: LoginRequest, db: AsyncSession = Depends(get_db)): result = await db.execute(select(User).where(User.email == data.email, User.is_active == True)) user = result.scalar_one_or_none() if not user or not verify_password(data.password, user.password_hash): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password") user.last_login = datetime.now(timezone.utc) await db.flush() access_token = create_access_token({"sub": str(user.id), "tenant_id": str(user.tenant_id), "role": user.role}) refresh_token = create_refresh_token({"sub": str(user.id)}) return TokenResponse( access_token=access_token, refresh_token=refresh_token, user_id=str(user.id), tenant_id=str(user.tenant_id), role=user.role, ) @router.post("/refresh", response_model=TokenResponse) async def refresh_token(data: RefreshRequest, db: AsyncSession = Depends(get_db)): payload = decode_token(data.refresh_token) if not payload or payload.get("type") != "refresh": raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token") result = await db.execute(select(User).where(User.id == payload["sub"], User.is_active == True)) user = result.scalar_one_or_none() if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found") access_token = create_access_token({"sub": str(user.id), "tenant_id": str(user.tenant_id), "role": user.role}) new_refresh = create_refresh_token({"sub": str(user.id)}) return TokenResponse( access_token=access_token, refresh_token=new_refresh, user_id=str(user.id), tenant_id=str(user.tenant_id), role=user.role, )