Add tier-gated RAG and Drive document sources (#42)
CI / test (pull_request) Successful in 10s
Unit Tests / test (pull_request) Successful in 10s

Ship allows_rag entitlement (founders/backer/pro/business), enforce it on
document APIs and RAG chat, harden ingest/delete/active lifecycle, and add
Google/Microsoft Drive connect + sync for personal and company knowledge bases.

Closes #43 #44 #45 #46 #47 #48 #49 #50 #51 #52 #53
Parent epic: #42
Related: #11
This commit is contained in:
2026-08-01 15:33:55 -05:00
parent 2e9e95e16c
commit c5efe60e0b
38 changed files with 3166 additions and 98 deletions
@@ -7,9 +7,113 @@ from django.db import migrations, models
def seed_plans(apps, schema_editor):
from finance.services.plans import seed_subscription_plans
"""Seed the original 5-plan catalog frozen at this migration's schema.
seed_subscription_plans(update_existing=True)
Deliberately does NOT import ``finance.services.plans`` — that module's
``PLAN_SEED``/model class reflect the *current* code, so a later required
field (e.g. ``allows_rag`` added in #43) would make this historical
RunPython try to write a column that doesn't exist yet when a fresh
database replays migrations in order. Live code re-seeds (and adds any
new fields) via ``seed_subscription_plans()`` calls elsewhere (app
startup, quota checks, test setUp), so this only needs to create the
original rows.
"""
SubscriptionPlan = apps.get_model("finance", "SubscriptionPlan")
seed = [
{
"slug": "founders",
"name": "Founders",
"description": (
"Unlimited product access for early supporters: text plus all future "
"capabilities as they ship. $10/mo."
),
"price_cents": 1000,
"is_public": True,
"is_selectable": True,
"allows_text_generation": True,
"allows_image_generation": True,
"allows_all_future_features": True,
"prompt_quota_per_window": 300,
"prompt_window_hours": 6,
"monthly_token_quota": None,
"sort_order": 10,
},
{
"slug": "standard",
"name": "Standard",
"description": (
"Secure conversational chat and coding assistance for developers "
"and privacy-conscious individuals."
),
"price_cents": 1500,
"is_public": False,
"is_selectable": False,
"allows_text_generation": True,
"allows_image_generation": False,
"allows_all_future_features": False,
"prompt_quota_per_window": 100,
"prompt_window_hours": 6,
"monthly_token_quota": 1_000_000,
"sort_order": 20,
},
{
"slug": "pro",
"name": "Pro / Creator",
"description": (
"Higher message caps and multi-modal workflows for heavy users, "
"including image generation when available."
),
"price_cents": 4000,
"is_public": False,
"is_selectable": False,
"allows_text_generation": True,
"allows_image_generation": True,
"allows_all_future_features": False,
"prompt_quota_per_window": 200,
"prompt_window_hours": 6,
"monthly_token_quota": 3_000_000,
"sort_order": 30,
},
{
"slug": "business",
"name": "Business Team",
"description": (
"Team seats, centralized auth, priority support, and absolute data "
"privacy for local companies handling sensitive data."
),
"price_cents": 9900,
"is_public": False,
"is_selectable": False,
"allows_text_generation": True,
"allows_image_generation": True,
"allows_all_future_features": False,
"prompt_quota_per_window": 300,
"prompt_window_hours": 6,
"monthly_token_quota": 5_000_000,
"sort_order": 40,
},
{
"slug": "backer",
"name": "Backer",
"description": (
"Complimentary Founders-level access for pre-approved emails. "
"Not shown at checkout."
),
"price_cents": 0,
"is_public": False,
"is_selectable": False,
"allows_text_generation": True,
"allows_image_generation": True,
"allows_all_future_features": True,
"prompt_quota_per_window": 300,
"prompt_window_hours": 6,
"monthly_token_quota": None,
"sort_order": 5,
},
]
for row in seed:
slug = row.pop("slug")
SubscriptionPlan.objects.get_or_create(slug=slug, defaults=row)
class Migration(migrations.Migration):
@@ -0,0 +1,21 @@
# Generated by Django 6.0 on 2026-08-01 20:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("finance", "0003_subscription_cancel_period_fields"),
]
operations = [
migrations.AddField(
model_name="subscriptionplan",
name="allows_rag",
field=models.BooleanField(
default=False,
help_text="Drive/RAG document sync (Google Drive, OneDrive, SharePoint).",
),
),
]