mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
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
119 lines
4.1 KiB
Python
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,
|
|
)
|