system-prompts-and-models-o.../salesflow-saas/backend/app/api/v1/auth.py
Claude f1852c1121
Add SalesMatic AI Sales SaaS Platform - Complete Foundation
Full-stack AI-powered sales automation platform for Saudi SMEs:

Backend (FastAPI + PostgreSQL):
- Multi-tenant architecture with row-level isolation
- JWT auth with RBAC (owner/manager/agent/admin)
- Lead, Customer, Deal, Pipeline, Activity, Message, Proposal models
- Dashboard analytics API (overview, pipeline, revenue)
- WhatsApp Business API, Email (SMTP/SendGrid), SMS (Unifonic) integrations
- Celery + Redis workers for automated follow-ups and scheduled messages
- Property model for Real Estate module (Riyadh districts)
- Hijri date utilities, Arabic/English localization

Frontend (Next.js + Tailwind):
- Professional Arabic RTL landing page with 10 sections
- Brand identity: SalesMatic (سيلزماتك) with custom SVG logo
- Color system: Trust Blue #0F4C81, Growth Teal #00BFA6, CTA Orange #FF6B35
- IBM Plex Sans Arabic + Inter typography
- Responsive design, dark hero section, pricing table, FAQ

Industry Templates:
- Healthcare/Clinics: pipeline stages, WhatsApp message templates, auto-workflows
- Real Estate Riyadh: 20 districts, property tours, payment plans, matching

Infrastructure:
- Docker Compose (PostgreSQL, Redis, Backend, Celery, Frontend, Nginx)
- Nginx reverse proxy config
- Makefile for common operations

https://claude.ai/code/session_01LLR7jzpyNRwDA9kojtT3CW
2026-03-28 03:06:53 +00:00

119 lines
4.1 KiB
Python

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,
)