mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-11 16:52:38 +02:00
feat(story-3.5): add cloud-mode LLM model selection with token quota enforcement
Implement system-managed model catalog, subscription tier enforcement, atomic token quota tracking, and frontend cloud/self-hosted conditional rendering. Apply all 20 BMAD code review patches including security fixes (cross-user API key hijack), race condition mitigation (atomic SQL UPDATE), and SSE mid-stream quota error handling. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
parent
e7382b26de
commit
c1776b3ec8
19 changed files with 1003 additions and 34 deletions
|
|
@ -0,0 +1,76 @@
|
|||
"""124_add_subscription_token_quota_columns
|
||||
|
||||
Revision ID: 124
|
||||
Revises: 123
|
||||
Create Date: 2026-04-14
|
||||
|
||||
Adds subscription and token quota columns to the user table for
|
||||
cloud-mode LLM billing (Story 3.5).
|
||||
|
||||
Columns added:
|
||||
- monthly_token_limit (Integer, default 100000)
|
||||
- tokens_used_this_month (Integer, default 0)
|
||||
- token_reset_date (Date, nullable)
|
||||
- subscription_status (Enum: free/active/canceled/past_due, default 'free')
|
||||
- plan_id (String(50), default 'free')
|
||||
- stripe_customer_id (String(255), nullable, unique)
|
||||
- stripe_subscription_id (String(255), nullable, unique)
|
||||
|
||||
Also creates the 'subscriptionstatus' PostgreSQL enum type.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "124"
|
||||
down_revision: str | None = "123"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
# Create the enum type so SQLAlchemy's create_type=False works at runtime
|
||||
subscriptionstatus_enum = sa.Enum(
|
||||
"free", "active", "canceled", "past_due",
|
||||
name="subscriptionstatus",
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create the PostgreSQL enum type first
|
||||
subscriptionstatus_enum.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
op.add_column("user", sa.Column("monthly_token_limit", sa.Integer(), nullable=False, server_default="100000"))
|
||||
op.add_column("user", sa.Column("tokens_used_this_month", sa.Integer(), nullable=False, server_default="0"))
|
||||
op.add_column("user", sa.Column("token_reset_date", sa.Date(), nullable=True))
|
||||
op.add_column(
|
||||
"user",
|
||||
sa.Column(
|
||||
"subscription_status",
|
||||
subscriptionstatus_enum,
|
||||
nullable=False,
|
||||
server_default="free",
|
||||
),
|
||||
)
|
||||
op.add_column("user", sa.Column("plan_id", sa.String(50), nullable=False, server_default="free"))
|
||||
op.add_column("user", sa.Column("stripe_customer_id", sa.String(255), nullable=True))
|
||||
op.add_column("user", sa.Column("stripe_subscription_id", sa.String(255), nullable=True))
|
||||
|
||||
op.create_unique_constraint("uq_user_stripe_customer_id", "user", ["stripe_customer_id"])
|
||||
op.create_unique_constraint("uq_user_stripe_subscription_id", "user", ["stripe_subscription_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint("uq_user_stripe_subscription_id", "user", type_="unique")
|
||||
op.drop_constraint("uq_user_stripe_customer_id", "user", type_="unique")
|
||||
op.drop_column("user", "stripe_subscription_id")
|
||||
op.drop_column("user", "stripe_customer_id")
|
||||
op.drop_column("user", "plan_id")
|
||||
op.drop_column("user", "subscription_status")
|
||||
op.drop_column("user", "token_reset_date")
|
||||
op.drop_column("user", "tokens_used_this_month")
|
||||
op.drop_column("user", "monthly_token_limit")
|
||||
|
||||
subscriptionstatus_enum.drop(op.get_bind(), checkfirst=True)
|
||||
Loading…
Add table
Add a link
Reference in a new issue