feat(server): clean up dirty data from legacy version (#15078)
#### PR Dependency Tree * **PR #15078** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Persist and replay incoming payment webhooks for reliability. * Track provider-level subscriptions, payment events, and per-target trial usage across providers. * Nightly replay job to reprocess stuck payment events. * Shadow backfill mode and emit-suppression options to control projection/backfill side effects. * Subscriptions now derived from entitlements + provider facts. * **Bug Fixes** * Improved error propagation, retry tracking, and safer owner-grant projection handling. * **Tests** * Added webhook failure/replay, provider integration, entitlement projection, and trial/checkout tests. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -637,6 +637,18 @@ BEGIN
|
||||
RAISE EXCEPTION 'Cannot project unknown doc role % for %.% user %', NEW.type, NEW.workspace_id, NEW.page_id, NEW.user_id;
|
||||
END IF;
|
||||
|
||||
IF projected_role = 'owner' AND EXISTS (
|
||||
SELECT 1
|
||||
FROM doc_grants
|
||||
WHERE workspace_id = NEW.workspace_id
|
||||
AND doc_id = NEW.page_id
|
||||
AND principal_type = 'user'
|
||||
AND role = 'owner'
|
||||
AND principal_id <> NEW.user_id
|
||||
) THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
INSERT INTO doc_grants (
|
||||
workspace_id,
|
||||
doc_id,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "provider_subscriptions" (
|
||||
"id" VARCHAR NOT NULL,
|
||||
"provider" "Provider" NOT NULL,
|
||||
"target_type" TEXT NOT NULL,
|
||||
"target_id" VARCHAR NOT NULL,
|
||||
"plan" VARCHAR(20) NOT NULL,
|
||||
"recurring" VARCHAR(20),
|
||||
"status" VARCHAR(20) NOT NULL,
|
||||
"external_customer_id" VARCHAR,
|
||||
"external_subscription_id" VARCHAR,
|
||||
"external_product_id" VARCHAR,
|
||||
"external_price_id" VARCHAR,
|
||||
"iap_store" "IapStore",
|
||||
"external_ref" VARCHAR,
|
||||
"currency" VARCHAR(3),
|
||||
"amount" INTEGER,
|
||||
"quantity" INTEGER,
|
||||
"period_start" TIMESTAMPTZ(3),
|
||||
"period_end" TIMESTAMPTZ(3),
|
||||
"trial_start" TIMESTAMPTZ(3),
|
||||
"trial_end" TIMESTAMPTZ(3),
|
||||
"canceled_at" TIMESTAMPTZ(3),
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "provider_subscriptions_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "provider_subscriptions_target_type_check" CHECK ("target_type" IN ('user', 'workspace', 'instance')),
|
||||
CONSTRAINT "provider_subscriptions_stripe_identity_check" CHECK ("provider" <> 'stripe' OR "external_subscription_id" IS NOT NULL),
|
||||
CONSTRAINT "provider_subscriptions_revenuecat_identity_check" CHECK ("provider" <> 'revenuecat' OR ("iap_store" IS NOT NULL AND "external_ref" IS NOT NULL AND "external_product_id" IS NOT NULL AND "external_customer_id" IS NOT NULL))
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "payment_events" (
|
||||
"id" VARCHAR NOT NULL,
|
||||
"provider" "Provider" NOT NULL,
|
||||
"event_type" VARCHAR NOT NULL,
|
||||
"external_event_id" VARCHAR NOT NULL,
|
||||
"target_type" TEXT,
|
||||
"target_id" VARCHAR,
|
||||
"external_invoice_id" VARCHAR,
|
||||
"external_payment_id" VARCHAR,
|
||||
"plan" VARCHAR(20),
|
||||
"amount" INTEGER,
|
||||
"currency" VARCHAR(3),
|
||||
"occurred_at" TIMESTAMPTZ(3),
|
||||
"processing_status" VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
"processing_attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"processed_at" TIMESTAMPTZ(3),
|
||||
"last_error" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "payment_events_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "payment_events_target_type_check" CHECK ("target_type" IS NULL OR "target_type" IN ('user', 'workspace', 'instance')),
|
||||
CONSTRAINT "payment_events_processing_status_check" CHECK ("processing_status" IN ('pending', 'processing', 'processed', 'failed'))
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "subscription_trial_usages" (
|
||||
"id" VARCHAR NOT NULL,
|
||||
"target_type" TEXT NOT NULL,
|
||||
"target_id" VARCHAR NOT NULL,
|
||||
"plan" VARCHAR(20) NOT NULL,
|
||||
"provider" "Provider" NOT NULL,
|
||||
"external_ref" VARCHAR,
|
||||
"first_used_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "subscription_trial_usages_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "subscription_trial_usages_target_type_check" CHECK ("target_type" IN ('user', 'workspace', 'instance'))
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "provider_subscriptions_target_type_target_id_plan_status_idx" ON "provider_subscriptions"("target_type", "target_id", "plan", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "provider_subscriptions_provider_external_customer_id_idx" ON "provider_subscriptions"("provider", "external_customer_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "provider_subscriptions_provider_external_subscription_id_key" ON "provider_subscriptions"("provider", "external_subscription_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "provider_subscriptions_revenuecat_external_identity_key" ON "provider_subscriptions"("provider", "iap_store", "external_ref", "external_product_id", "external_customer_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "payment_events_provider_external_event_id_key" ON "payment_events"("provider", "external_event_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payment_events_processing_status_updated_at_idx" ON "payment_events"("processing_status", "updated_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payment_events_target_type_target_id_idx" ON "payment_events"("target_type", "target_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "subscription_trial_usages_target_type_target_id_plan_key" ON "subscription_trial_usages"("target_type", "target_id", "plan");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_trial_usages_provider_external_ref_idx" ON "subscription_trial_usages"("provider", "external_ref");
|
||||
Reference in New Issue
Block a user