Backend (replaces PHP api.php + auth.php): - FastAPI app with routers: jobs, auth, billing - Supabase JWT authentication in deps.py - Celery + Redis job queue (process_pdf_task) - MinIO S3-compatible storage service - PDF checker wrapper (delegates to enterprise_pdf_checker.py) - Stripe billing: checkout, portal, webhook handler Multi-tenancy (Phase 3): - Alembic migration 001: workspaces, workspace_members, jobs, usage_events - Row-Level Security on all tenant tables via app.workspace_id session var - Monthly quota enforcement per workspace (402 on exceeded) - Plan tiers: free(5) / pro(100) / business(unlimited) Config: - pydantic-settings based config.py (no hardcoded values) - docker-compose.yml rewritten: postgres, redis, minio, api, celery Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
38 lines
1.9 KiB
Python
38 lines
1.9 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
from sqlalchemy import String, Integer, DateTime, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from app.db import Base
|
|
|
|
|
|
class Workspace(Base):
|
|
__tablename__ = "workspaces"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
slug: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
|
|
plan_tier: Mapped[str] = mapped_column(String(50), default="free") # free|pro|business
|
|
monthly_quota: Mapped[int] = mapped_column(Integer, default=5)
|
|
stripe_customer_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
stripe_subscription_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class WorkspaceMember(Base):
|
|
__tablename__ = "workspace_members"
|
|
|
|
workspace_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True)
|
|
user_id: Mapped[str] = mapped_column(String(255), primary_key=True)
|
|
role: Mapped[str] = mapped_column(String(50), default="member") # owner|admin|member
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class UsageEvent(Base):
|
|
__tablename__ = "usage_events"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
workspace_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
|
|
event_type: Mapped[str] = mapped_column(String(100), nullable=False) # pdf_checked|remediated|api_call
|
|
job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|