@soulbatical/tetra-core 0.10.4 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -38
- package/dist/core/createApp.d.ts +1 -1
- package/dist/core/createApp.d.ts.map +1 -1
- package/dist/core/createApp.js +77 -2
- package/dist/core/createApp.js.map +1 -1
- package/dist/core/dualWriteProxy.d.ts +7 -2
- package/dist/core/dualWriteProxy.d.ts.map +1 -1
- package/dist/core/dualWriteProxy.js +16 -5
- package/dist/core/dualWriteProxy.js.map +1 -1
- package/dist/core/routeContext.d.ts +24 -0
- package/dist/core/routeContext.d.ts.map +1 -1
- package/dist/core/routeContext.js +31 -4
- package/dist/core/routeContext.js.map +1 -1
- package/dist/core/systemDb.d.ts +2 -2
- package/dist/core/systemDb.js +2 -2
- package/dist/generators/rls-checker.d.ts +1 -1
- package/dist/generators/rls-checker.js +1 -1
- package/dist/generators/rls-exec-sql.d.ts +1 -1
- package/dist/generators/rls-exec-sql.js +1 -1
- package/dist/generators/rpc/index.d.ts +1 -1
- package/dist/generators/rpc/index.js +1 -1
- package/dist/index.d.ts +3 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -32
- package/dist/index.js.map +1 -1
- package/dist/middleware/securityMiddleware.d.ts +1 -1
- package/dist/middleware/securityMiddleware.d.ts.map +1 -1
- package/dist/middleware/validateBody.d.ts.map +1 -1
- package/dist/middleware/validateBody.js +51 -8
- package/dist/middleware/validateBody.js.map +1 -1
- package/dist/shared/rfc7807ErrorResponse.d.ts +7 -0
- package/dist/shared/rfc7807ErrorResponse.d.ts.map +1 -1
- package/dist/shared/rfc7807ErrorResponse.js +19 -5
- package/dist/shared/rfc7807ErrorResponse.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +16 -1
- package/dist/utils/logger.js.map +1 -1
- package/package.json +33 -77
- package/dist/affiliate.d.ts +0 -11
- package/dist/affiliate.d.ts.map +0 -1
- package/dist/affiliate.js +0 -10
- package/dist/affiliate.js.map +0 -1
- package/dist/billing.d.ts +0 -8
- package/dist/billing.d.ts.map +0 -1
- package/dist/billing.js +0 -7
- package/dist/billing.js.map +0 -1
- package/dist/email.d.ts +0 -9
- package/dist/email.d.ts.map +0 -1
- package/dist/email.js +0 -8
- package/dist/email.js.map +0 -1
- package/dist/generators/rls-exec-sql.sql +0 -57
- package/dist/generators.d.ts +0 -15
- package/dist/generators.d.ts.map +0 -1
- package/dist/generators.js +0 -12
- package/dist/generators.js.map +0 -1
- package/dist/mcp.d.ts +0 -8
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js +0 -7
- package/dist/mcp.js.map +0 -1
- package/dist/planner.d.ts +0 -8
- package/dist/planner.d.ts.map +0 -1
- package/dist/planner.js +0 -7
- package/dist/planner.js.map +0 -1
- package/dist/shared/affiliate/AffiliateAttributionService.d.ts +0 -47
- package/dist/shared/affiliate/AffiliateAttributionService.d.ts.map +0 -1
- package/dist/shared/affiliate/AffiliateAttributionService.js +0 -308
- package/dist/shared/affiliate/AffiliateAttributionService.js.map +0 -1
- package/dist/shared/affiliate/AffiliateClickService.d.ts +0 -35
- package/dist/shared/affiliate/AffiliateClickService.d.ts.map +0 -1
- package/dist/shared/affiliate/AffiliateClickService.js +0 -87
- package/dist/shared/affiliate/AffiliateClickService.js.map +0 -1
- package/dist/shared/affiliate/affiliateFeatureConfig.d.ts +0 -11
- package/dist/shared/affiliate/affiliateFeatureConfig.d.ts.map +0 -1
- package/dist/shared/affiliate/affiliateFeatureConfig.js +0 -242
- package/dist/shared/affiliate/affiliateFeatureConfig.js.map +0 -1
- package/dist/shared/affiliate/index.d.ts +0 -11
- package/dist/shared/affiliate/index.d.ts.map +0 -1
- package/dist/shared/affiliate/index.js +0 -13
- package/dist/shared/affiliate/index.js.map +0 -1
- package/dist/shared/affiliate/routes.d.ts +0 -87
- package/dist/shared/affiliate/routes.d.ts.map +0 -1
- package/dist/shared/affiliate/routes.js +0 -404
- package/dist/shared/affiliate/routes.js.map +0 -1
- package/dist/shared/affiliate/types.d.ts +0 -170
- package/dist/shared/affiliate/types.d.ts.map +0 -1
- package/dist/shared/affiliate/types.js +0 -11
- package/dist/shared/affiliate/types.js.map +0 -1
- package/dist/shared/billing/BillingService.d.ts +0 -56
- package/dist/shared/billing/BillingService.d.ts.map +0 -1
- package/dist/shared/billing/BillingService.js +0 -588
- package/dist/shared/billing/BillingService.js.map +0 -1
- package/dist/shared/billing/SeatBillingService.d.ts +0 -106
- package/dist/shared/billing/SeatBillingService.d.ts.map +0 -1
- package/dist/shared/billing/SeatBillingService.js +0 -292
- package/dist/shared/billing/SeatBillingService.js.map +0 -1
- package/dist/shared/billing/index.d.ts +0 -30
- package/dist/shared/billing/index.d.ts.map +0 -1
- package/dist/shared/billing/index.js +0 -27
- package/dist/shared/billing/index.js.map +0 -1
- package/dist/shared/billing/routes.d.ts +0 -45
- package/dist/shared/billing/routes.d.ts.map +0 -1
- package/dist/shared/billing/routes.js +0 -184
- package/dist/shared/billing/routes.js.map +0 -1
- package/dist/shared/billing/seat-pricing.d.ts +0 -53
- package/dist/shared/billing/seat-pricing.d.ts.map +0 -1
- package/dist/shared/billing/seat-pricing.js +0 -81
- package/dist/shared/billing/seat-pricing.js.map +0 -1
- package/dist/shared/billing/types.d.ts +0 -109
- package/dist/shared/billing/types.d.ts.map +0 -1
- package/dist/shared/billing/types.js +0 -8
- package/dist/shared/billing/types.js.map +0 -1
- package/dist/shared/email/EmailService.d.ts +0 -64
- package/dist/shared/email/EmailService.d.ts.map +0 -1
- package/dist/shared/email/EmailService.js +0 -300
- package/dist/shared/email/EmailService.js.map +0 -1
- package/dist/shared/email/adminRoutes.d.ts +0 -30
- package/dist/shared/email/adminRoutes.d.ts.map +0 -1
- package/dist/shared/email/adminRoutes.js +0 -227
- package/dist/shared/email/adminRoutes.js.map +0 -1
- package/dist/shared/email/gmail.d.ts +0 -208
- package/dist/shared/email/gmail.d.ts.map +0 -1
- package/dist/shared/email/gmail.js +0 -626
- package/dist/shared/email/gmail.js.map +0 -1
- package/dist/shared/email/index.d.ts +0 -15
- package/dist/shared/email/index.d.ts.map +0 -1
- package/dist/shared/email/index.js +0 -18
- package/dist/shared/email/index.js.map +0 -1
- package/dist/shared/email/mailgun.d.ts +0 -18
- package/dist/shared/email/mailgun.d.ts.map +0 -1
- package/dist/shared/email/mailgun.js +0 -76
- package/dist/shared/email/mailgun.js.map +0 -1
- package/dist/shared/email/sanitize.d.ts +0 -25
- package/dist/shared/email/sanitize.d.ts.map +0 -1
- package/dist/shared/email/sanitize.js +0 -39
- package/dist/shared/email/sanitize.js.map +0 -1
- package/dist/shared/email/smtp.d.ts +0 -20
- package/dist/shared/email/smtp.d.ts.map +0 -1
- package/dist/shared/email/smtp.js +0 -53
- package/dist/shared/email/smtp.js.map +0 -1
- package/dist/shared/email/types.d.ts +0 -113
- package/dist/shared/email/types.d.ts.map +0 -1
- package/dist/shared/email/types.js +0 -7
- package/dist/shared/email/types.js.map +0 -1
- package/dist/shared/email/webhookRoutes.d.ts +0 -29
- package/dist/shared/email/webhookRoutes.d.ts.map +0 -1
- package/dist/shared/email/webhookRoutes.js +0 -125
- package/dist/shared/email/webhookRoutes.js.map +0 -1
- package/dist/shared/mcp/index.d.ts +0 -51
- package/dist/shared/mcp/index.d.ts.map +0 -1
- package/dist/shared/mcp/index.js +0 -51
- package/dist/shared/mcp/index.js.map +0 -1
- package/dist/shared/mcp/mcp-auth-routes.d.ts +0 -26
- package/dist/shared/mcp/mcp-auth-routes.d.ts.map +0 -1
- package/dist/shared/mcp/mcp-auth-routes.js +0 -141
- package/dist/shared/mcp/mcp-auth-routes.js.map +0 -1
- package/dist/shared/mcp/mcp-db.d.ts +0 -99
- package/dist/shared/mcp/mcp-db.d.ts.map +0 -1
- package/dist/shared/mcp/mcp-db.js +0 -106
- package/dist/shared/mcp/mcp-db.js.map +0 -1
- package/dist/shared/mcp/mcp-routes.d.ts +0 -29
- package/dist/shared/mcp/mcp-routes.d.ts.map +0 -1
- package/dist/shared/mcp/mcp-routes.js +0 -171
- package/dist/shared/mcp/mcp-routes.js.map +0 -1
- package/dist/shared/mcp/mcp-tokens-routes.d.ts +0 -35
- package/dist/shared/mcp/mcp-tokens-routes.d.ts.map +0 -1
- package/dist/shared/mcp/mcp-tokens-routes.js +0 -94
- package/dist/shared/mcp/mcp-tokens-routes.js.map +0 -1
- package/dist/shared/mcp/mcp-usage-routes.d.ts +0 -17
- package/dist/shared/mcp/mcp-usage-routes.d.ts.map +0 -1
- package/dist/shared/mcp/mcp-usage-routes.js +0 -81
- package/dist/shared/mcp/mcp-usage-routes.js.map +0 -1
- package/dist/shared/mcp/tenant-context.d.ts +0 -59
- package/dist/shared/mcp/tenant-context.d.ts.map +0 -1
- package/dist/shared/mcp/tenant-context.js +0 -136
- package/dist/shared/mcp/tenant-context.js.map +0 -1
- package/dist/shared/mcp/types.d.ts +0 -74
- package/dist/shared/mcp/types.d.ts.map +0 -1
- package/dist/shared/mcp/types.js +0 -7
- package/dist/shared/mcp/types.js.map +0 -1
- package/dist/shared/planner/GoogleCalendarService.d.ts +0 -137
- package/dist/shared/planner/GoogleCalendarService.d.ts.map +0 -1
- package/dist/shared/planner/GoogleCalendarService.js +0 -525
- package/dist/shared/planner/GoogleCalendarService.js.map +0 -1
- package/dist/shared/planner/PlannerService.d.ts +0 -264
- package/dist/shared/planner/PlannerService.d.ts.map +0 -1
- package/dist/shared/planner/PlannerService.js +0 -1393
- package/dist/shared/planner/PlannerService.js.map +0 -1
- package/dist/shared/planner/index.d.ts +0 -37
- package/dist/shared/planner/index.d.ts.map +0 -1
- package/dist/shared/planner/index.js +0 -35
- package/dist/shared/planner/index.js.map +0 -1
- package/dist/shared/planner/intervals.d.ts +0 -60
- package/dist/shared/planner/intervals.d.ts.map +0 -1
- package/dist/shared/planner/intervals.js +0 -141
- package/dist/shared/planner/intervals.js.map +0 -1
- package/dist/shared/planner/routes.d.ts +0 -69
- package/dist/shared/planner/routes.d.ts.map +0 -1
- package/dist/shared/planner/routes.js +0 -770
- package/dist/shared/planner/routes.js.map +0 -1
- package/dist/shared/planner/types.d.ts +0 -328
- package/dist/shared/planner/types.d.ts.map +0 -1
- package/dist/shared/planner/types.js +0 -9
- package/dist/shared/planner/types.js.map +0 -1
- package/dist/shared/storage/ImageProcessingService.d.ts +0 -32
- package/dist/shared/storage/ImageProcessingService.d.ts.map +0 -1
- package/dist/shared/storage/ImageProcessingService.js +0 -127
- package/dist/shared/storage/ImageProcessingService.js.map +0 -1
- package/dist/shared/storage/StorageProxyService.d.ts +0 -47
- package/dist/shared/storage/StorageProxyService.d.ts.map +0 -1
- package/dist/shared/storage/StorageProxyService.js +0 -196
- package/dist/shared/storage/StorageProxyService.js.map +0 -1
- package/dist/shared/storage/StorageUploadService.d.ts +0 -126
- package/dist/shared/storage/StorageUploadService.d.ts.map +0 -1
- package/dist/shared/storage/StorageUploadService.js +0 -206
- package/dist/shared/storage/StorageUploadService.js.map +0 -1
- package/dist/shared/storage/creative-urls.d.ts +0 -14
- package/dist/shared/storage/creative-urls.d.ts.map +0 -1
- package/dist/shared/storage/creative-urls.js +0 -30
- package/dist/shared/storage/creative-urls.js.map +0 -1
- package/dist/shared/storage/index.d.ts +0 -28
- package/dist/shared/storage/index.d.ts.map +0 -1
- package/dist/shared/storage/index.js +0 -27
- package/dist/shared/storage/index.js.map +0 -1
- package/dist/shared/storage/routes.d.ts +0 -42
- package/dist/shared/storage/routes.d.ts.map +0 -1
- package/dist/shared/storage/routes.js +0 -160
- package/dist/shared/storage/routes.js.map +0 -1
- package/dist/shared/storage/types.d.ts +0 -53
- package/dist/shared/storage/types.d.ts.map +0 -1
- package/dist/shared/storage/types.js +0 -2
- package/dist/shared/storage/types.js.map +0 -1
- package/dist/shared/telegram/index.d.ts +0 -4
- package/dist/shared/telegram/index.d.ts.map +0 -1
- package/dist/shared/telegram/index.js +0 -3
- package/dist/shared/telegram/index.js.map +0 -1
- package/dist/shared/telegram/routes.d.ts +0 -43
- package/dist/shared/telegram/routes.d.ts.map +0 -1
- package/dist/shared/telegram/routes.js +0 -868
- package/dist/shared/telegram/routes.js.map +0 -1
- package/dist/shared/telegram/types.d.ts +0 -168
- package/dist/shared/telegram/types.d.ts.map +0 -1
- package/dist/shared/telegram/types.js +0 -7
- package/dist/shared/telegram/types.js.map +0 -1
- package/dist/shared/telegram/utils.d.ts +0 -44
- package/dist/shared/telegram/utils.d.ts.map +0 -1
- package/dist/shared/telegram/utils.js +0 -121
- package/dist/shared/telegram/utils.js.map +0 -1
- package/dist/storage.d.ts +0 -9
- package/dist/storage.d.ts.map +0 -1
- package/dist/storage.js +0 -8
- package/dist/storage.js.map +0 -1
- package/dist/telemetry.d.ts +0 -9
- package/dist/telemetry.d.ts.map +0 -1
- package/dist/telemetry.js +0 -8
- package/dist/telemetry.js.map +0 -1
- package/scripts/postinstall.js +0 -79
- package/src/shared/affiliate/migrations/001_create_affiliates.sql +0 -49
- package/src/shared/affiliate/migrations/002_create_affiliate_commissions.sql +0 -31
- package/src/shared/affiliate/migrations/003_create_affiliate_clicks.sql +0 -26
- package/src/shared/affiliate/migrations/004_create_affiliate_payments.sql +0 -34
- package/src/shared/affiliate/migrations/005_create_affiliate_tier_history.sql +0 -19
- package/src/shared/affiliate/migrations/006_create_affiliate_rpc_functions.sql +0 -209
- package/src/shared/affiliate/migrations/007_create_affiliate_rls_policies.sql +0 -123
- package/src/shared/billing/migrations/00000000000001_billing.sql +0 -114
- package/src/shared/email/migrations/000_create_email_logs.sql +0 -27
- package/src/shared/email/migrations/001_create_email_templates.sql +0 -27
- package/src/shared/email/migrations/002_add_rls_baseline_policies.sql +0 -37
- package/src/shared/email/migrations/003_create_gmail_accounts.sql +0 -82
- package/src/shared/email/migrations/004_add_email_logs_tracking_columns.sql +0 -15
- package/src/shared/mcp/migrations/001_mcp_api_tokens.sql +0 -21
- package/src/shared/mcp/migrations/002_mcp_audit_log.sql +0 -16
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- Affiliate Module: affiliate_commissions table
|
|
3
|
-
-- Template from @soulbatical/tetra-core
|
|
4
|
-
-- ============================================================================
|
|
5
|
-
|
|
6
|
-
CREATE TABLE IF NOT EXISTS public.affiliate_commissions (
|
|
7
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
-
organization_id uuid NOT NULL REFERENCES public.organizations(id),
|
|
9
|
-
affiliate_id uuid NOT NULL REFERENCES public.affiliates(id) ON DELETE CASCADE,
|
|
10
|
-
order_id uuid NOT NULL,
|
|
11
|
-
affiliate_source text NOT NULL DEFAULT 'internal',
|
|
12
|
-
external_click_ref text,
|
|
13
|
-
product_name text NOT NULL DEFAULT 'Order',
|
|
14
|
-
order_amount_excl_vat numeric NOT NULL,
|
|
15
|
-
commission_percentage numeric NOT NULL,
|
|
16
|
-
commission_amount numeric NOT NULL,
|
|
17
|
-
status text NOT NULL DEFAULT 'pending',
|
|
18
|
-
approved_at timestamptz,
|
|
19
|
-
approved_by uuid REFERENCES public.users_public(id),
|
|
20
|
-
paid_at timestamptz,
|
|
21
|
-
payment_id uuid,
|
|
22
|
-
notes text,
|
|
23
|
-
created_at timestamptz DEFAULT now(),
|
|
24
|
-
updated_at timestamptz DEFAULT now()
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
-- Indexes
|
|
28
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_commissions_org_id ON public.affiliate_commissions(organization_id);
|
|
29
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_commissions_affiliate ON public.affiliate_commissions(affiliate_id);
|
|
30
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_commissions_order ON public.affiliate_commissions(order_id);
|
|
31
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_commissions_status ON public.affiliate_commissions(status);
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- Affiliate Module: affiliate_clicks table
|
|
3
|
-
-- Template from @soulbatical/tetra-core
|
|
4
|
-
-- ============================================================================
|
|
5
|
-
|
|
6
|
-
CREATE TABLE IF NOT EXISTS public.affiliate_clicks (
|
|
7
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
-
organization_id uuid REFERENCES public.organizations(id),
|
|
9
|
-
affiliate_id uuid REFERENCES public.affiliates(id) ON DELETE SET NULL,
|
|
10
|
-
visitor_ip text,
|
|
11
|
-
visitor_country text,
|
|
12
|
-
user_agent text,
|
|
13
|
-
referrer_url text,
|
|
14
|
-
landing_page text,
|
|
15
|
-
affiliate_source text NOT NULL DEFAULT 'internal',
|
|
16
|
-
external_click_ref text,
|
|
17
|
-
converted boolean DEFAULT false,
|
|
18
|
-
order_id uuid,
|
|
19
|
-
conversion_time_hours integer,
|
|
20
|
-
clicked_at timestamptz DEFAULT now()
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
-- Indexes
|
|
24
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_clicks_org_id ON public.affiliate_clicks(organization_id);
|
|
25
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_clicks_affiliate ON public.affiliate_clicks(affiliate_id);
|
|
26
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_clicks_converted ON public.affiliate_clicks(converted);
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- Affiliate Module: affiliate_payments table
|
|
3
|
-
-- Template from @soulbatical/tetra-core
|
|
4
|
-
-- ============================================================================
|
|
5
|
-
|
|
6
|
-
CREATE TABLE IF NOT EXISTS public.affiliate_payments (
|
|
7
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
-
organization_id uuid NOT NULL REFERENCES public.organizations(id),
|
|
9
|
-
affiliate_id uuid NOT NULL REFERENCES public.affiliates(id) ON DELETE CASCADE,
|
|
10
|
-
payment_amount numeric NOT NULL,
|
|
11
|
-
payment_date date NOT NULL,
|
|
12
|
-
payment_method text,
|
|
13
|
-
payment_reference text,
|
|
14
|
-
commission_ids uuid[] NOT NULL,
|
|
15
|
-
notes text,
|
|
16
|
-
created_at timestamptz DEFAULT now()
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
-- Add FK from commissions to payments (after payments table exists)
|
|
20
|
-
DO $$
|
|
21
|
-
BEGIN
|
|
22
|
-
IF NOT EXISTS (
|
|
23
|
-
SELECT 1 FROM information_schema.table_constraints
|
|
24
|
-
WHERE constraint_name = 'affiliate_commissions_payment_id_fkey'
|
|
25
|
-
) THEN
|
|
26
|
-
ALTER TABLE public.affiliate_commissions
|
|
27
|
-
ADD CONSTRAINT affiliate_commissions_payment_id_fkey
|
|
28
|
-
FOREIGN KEY (payment_id) REFERENCES public.affiliate_payments(id);
|
|
29
|
-
END IF;
|
|
30
|
-
END $$;
|
|
31
|
-
|
|
32
|
-
-- Indexes
|
|
33
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_payments_org_id ON public.affiliate_payments(organization_id);
|
|
34
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_payments_affiliate ON public.affiliate_payments(affiliate_id);
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- Affiliate Module: affiliate_tier_history table
|
|
3
|
-
-- Template from @soulbatical/tetra-core
|
|
4
|
-
-- ============================================================================
|
|
5
|
-
|
|
6
|
-
CREATE TABLE IF NOT EXISTS public.affiliate_tier_history (
|
|
7
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
8
|
-
affiliate_id uuid NOT NULL REFERENCES public.affiliates(id) ON DELETE CASCADE,
|
|
9
|
-
old_tier text NOT NULL,
|
|
10
|
-
new_tier text NOT NULL,
|
|
11
|
-
old_commission_percentage numeric NOT NULL,
|
|
12
|
-
new_commission_percentage numeric NOT NULL,
|
|
13
|
-
reason text,
|
|
14
|
-
triggered_by_sale_count integer,
|
|
15
|
-
changed_at timestamptz DEFAULT now()
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
-- Indexes
|
|
19
|
-
CREATE INDEX IF NOT EXISTS idx_affiliate_tier_history_affiliate ON public.affiliate_tier_history(affiliate_id);
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- Affiliate Module: RPC functions
|
|
3
|
-
-- Template from @soulbatical/tetra-core
|
|
4
|
-
--
|
|
5
|
-
-- NOTE: These are template functions. Projects should generate their own
|
|
6
|
-
-- using `npm run generate:rpc affiliates` from the featureConfig, which
|
|
7
|
-
-- produces optimized SQL matching the project's exact filter/count setup.
|
|
8
|
-
--
|
|
9
|
-
-- These templates provide a working baseline.
|
|
10
|
-
-- ============================================================================
|
|
11
|
-
|
|
12
|
-
-- ─── Results Function ─────────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
DROP FUNCTION IF EXISTS public.get_affiliates_results;
|
|
15
|
-
|
|
16
|
-
CREATE OR REPLACE FUNCTION public.get_affiliates_results(
|
|
17
|
-
p_org_id uuid DEFAULT NULL,
|
|
18
|
-
p_ids uuid[] DEFAULT NULL,
|
|
19
|
-
p_status text DEFAULT NULL,
|
|
20
|
-
p_tier text DEFAULT NULL,
|
|
21
|
-
p_search text DEFAULT NULL,
|
|
22
|
-
p_has_user text DEFAULT NULL,
|
|
23
|
-
p_time_period text DEFAULT NULL,
|
|
24
|
-
p_limit int DEFAULT 20,
|
|
25
|
-
p_offset int DEFAULT 0,
|
|
26
|
-
p_sort_by text DEFAULT 'date-desc'
|
|
27
|
-
)
|
|
28
|
-
RETURNS TABLE (
|
|
29
|
-
data jsonb,
|
|
30
|
-
total_count bigint
|
|
31
|
-
)
|
|
32
|
-
LANGUAGE plpgsql
|
|
33
|
-
STABLE
|
|
34
|
-
SECURITY DEFINER
|
|
35
|
-
SET search_path = ''
|
|
36
|
-
AS $$
|
|
37
|
-
DECLARE
|
|
38
|
-
v_is_service_role boolean := (
|
|
39
|
-
auth.role() = 'service_role'
|
|
40
|
-
OR session_user = 'postgres'
|
|
41
|
-
);
|
|
42
|
-
BEGIN
|
|
43
|
-
IF NOT v_is_service_role THEN
|
|
44
|
-
IF NOT EXISTS (SELECT 1 FROM public.auth_admin_organizations()) THEN
|
|
45
|
-
RAISE EXCEPTION 'Access denied: authentication required'
|
|
46
|
-
USING ERRCODE = '42501';
|
|
47
|
-
END IF;
|
|
48
|
-
IF p_org_id IS NOT NULL AND p_org_id NOT IN (SELECT public.auth_admin_organizations()) THEN
|
|
49
|
-
RAISE EXCEPTION 'Access denied: not authorized for organization %', p_org_id
|
|
50
|
-
USING ERRCODE = '42501';
|
|
51
|
-
END IF;
|
|
52
|
-
END IF;
|
|
53
|
-
|
|
54
|
-
RETURN QUERY
|
|
55
|
-
WITH results AS (
|
|
56
|
-
SELECT
|
|
57
|
-
a.*,
|
|
58
|
-
COUNT(*) OVER() as total_count
|
|
59
|
-
FROM public.affiliates a
|
|
60
|
-
WHERE (p_org_id IS NULL OR a.organization_id = p_org_id OR a.organization_id IS NULL)
|
|
61
|
-
AND (p_ids IS NULL OR a.id = ANY(p_ids))
|
|
62
|
-
AND (p_status IS NULL OR p_status = 'all' OR a.status::text = p_status)
|
|
63
|
-
AND (p_tier IS NULL OR p_tier = 'all' OR a.tier::text = p_tier)
|
|
64
|
-
AND (
|
|
65
|
-
p_search IS NULL OR p_search = '' OR p_search = 'all'
|
|
66
|
-
OR a.contact_name ILIKE '%' || p_search || '%'
|
|
67
|
-
OR a.email ILIKE '%' || p_search || '%'
|
|
68
|
-
OR a.company_name ILIKE '%' || p_search || '%'
|
|
69
|
-
OR a.referral_code ILIKE '%' || p_search || '%'
|
|
70
|
-
)
|
|
71
|
-
AND (
|
|
72
|
-
p_has_user IS NULL OR p_has_user = 'all'
|
|
73
|
-
OR (p_has_user = 'with' AND a.user_id IS NOT NULL)
|
|
74
|
-
OR (p_has_user = 'without' AND a.user_id IS NULL)
|
|
75
|
-
)
|
|
76
|
-
AND (
|
|
77
|
-
p_time_period IS NULL OR p_time_period = 'all'
|
|
78
|
-
OR (p_time_period = 'today' AND a.created_at >= CURRENT_DATE)
|
|
79
|
-
OR (p_time_period = 'this_week' AND a.created_at >= CURRENT_DATE - INTERVAL '7 days')
|
|
80
|
-
OR (p_time_period = 'this_month' AND a.created_at >= CURRENT_DATE - INTERVAL '30 days')
|
|
81
|
-
OR (p_time_period = 'older' AND a.created_at >= CURRENT_DATE - INTERVAL '90 days')
|
|
82
|
-
)
|
|
83
|
-
AND (
|
|
84
|
-
v_is_service_role
|
|
85
|
-
OR a.organization_id IN (SELECT public.auth_admin_organizations())
|
|
86
|
-
OR a.organization_id IS NULL
|
|
87
|
-
)
|
|
88
|
-
ORDER BY
|
|
89
|
-
CASE WHEN p_sort_by = 'date-desc' THEN a.created_at END DESC NULLS LAST,
|
|
90
|
-
CASE WHEN p_sort_by = 'date-asc' THEN a.created_at END ASC NULLS LAST,
|
|
91
|
-
CASE WHEN p_sort_by = 'name-asc' THEN LOWER(a.contact_name) END ASC NULLS LAST,
|
|
92
|
-
CASE WHEN p_sort_by = 'name-desc' THEN LOWER(a.contact_name) END DESC NULLS LAST,
|
|
93
|
-
CASE WHEN p_sort_by = 'sales-desc' THEN a.total_sales END DESC NULLS LAST,
|
|
94
|
-
CASE WHEN p_sort_by = 'sales-asc' THEN a.total_sales END ASC NULLS LAST,
|
|
95
|
-
CASE WHEN p_sort_by = 'revenue-desc' THEN a.total_revenue END DESC NULLS LAST,
|
|
96
|
-
CASE WHEN p_sort_by = 'revenue-asc' THEN a.total_revenue END ASC NULLS LAST,
|
|
97
|
-
CASE WHEN p_sort_by = 'commission-desc' THEN a.total_commission_earned END DESC NULLS LAST,
|
|
98
|
-
CASE WHEN p_sort_by = 'commission-asc' THEN a.total_commission_earned END ASC NULLS LAST,
|
|
99
|
-
a.created_at DESC
|
|
100
|
-
LIMIT p_limit
|
|
101
|
-
OFFSET p_offset
|
|
102
|
-
)
|
|
103
|
-
SELECT
|
|
104
|
-
to_jsonb(results.*) - 'total_count' as data,
|
|
105
|
-
results.total_count
|
|
106
|
-
FROM results;
|
|
107
|
-
END;
|
|
108
|
-
$$;
|
|
109
|
-
|
|
110
|
-
-- ─── Counts Function ──────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
DROP FUNCTION IF EXISTS public.get_affiliates_counts;
|
|
113
|
-
|
|
114
|
-
CREATE OR REPLACE FUNCTION public.get_affiliates_counts(
|
|
115
|
-
p_org_id uuid DEFAULT NULL,
|
|
116
|
-
p_ids uuid[] DEFAULT NULL,
|
|
117
|
-
p_status text DEFAULT NULL,
|
|
118
|
-
p_tier text DEFAULT NULL,
|
|
119
|
-
p_search text DEFAULT NULL,
|
|
120
|
-
p_has_user text DEFAULT NULL,
|
|
121
|
-
p_time_period text DEFAULT NULL
|
|
122
|
-
)
|
|
123
|
-
RETURNS jsonb
|
|
124
|
-
LANGUAGE plpgsql
|
|
125
|
-
STABLE
|
|
126
|
-
SECURITY DEFINER
|
|
127
|
-
SET search_path = ''
|
|
128
|
-
AS $$
|
|
129
|
-
DECLARE
|
|
130
|
-
result jsonb;
|
|
131
|
-
v_is_service_role boolean := (
|
|
132
|
-
auth.role() = 'service_role'
|
|
133
|
-
OR session_user = 'postgres'
|
|
134
|
-
);
|
|
135
|
-
BEGIN
|
|
136
|
-
IF NOT v_is_service_role THEN
|
|
137
|
-
IF NOT EXISTS (SELECT 1 FROM public.auth_admin_organizations()) THEN
|
|
138
|
-
RAISE EXCEPTION 'Access denied: authentication required'
|
|
139
|
-
USING ERRCODE = '42501';
|
|
140
|
-
END IF;
|
|
141
|
-
IF p_org_id IS NOT NULL AND p_org_id NOT IN (SELECT public.auth_admin_organizations()) THEN
|
|
142
|
-
RAISE EXCEPTION 'Access denied: not authorized for organization %', p_org_id
|
|
143
|
-
USING ERRCODE = '42501';
|
|
144
|
-
END IF;
|
|
145
|
-
END IF;
|
|
146
|
-
|
|
147
|
-
WITH filtered_items AS (
|
|
148
|
-
SELECT a.*
|
|
149
|
-
FROM public.affiliates a
|
|
150
|
-
WHERE (p_org_id IS NULL OR a.organization_id = p_org_id OR a.organization_id IS NULL)
|
|
151
|
-
AND (p_ids IS NULL OR a.id = ANY(p_ids))
|
|
152
|
-
AND (p_status IS NULL OR p_status = 'all' OR a.status::text = p_status)
|
|
153
|
-
AND (p_tier IS NULL OR p_tier = 'all' OR a.tier::text = p_tier)
|
|
154
|
-
AND (
|
|
155
|
-
p_search IS NULL OR p_search = '' OR p_search = 'all'
|
|
156
|
-
OR a.contact_name ILIKE '%' || p_search || '%'
|
|
157
|
-
OR a.email ILIKE '%' || p_search || '%'
|
|
158
|
-
OR a.company_name ILIKE '%' || p_search || '%'
|
|
159
|
-
OR a.referral_code ILIKE '%' || p_search || '%'
|
|
160
|
-
)
|
|
161
|
-
AND (
|
|
162
|
-
p_has_user IS NULL OR p_has_user = 'all'
|
|
163
|
-
OR (p_has_user = 'with' AND a.user_id IS NOT NULL)
|
|
164
|
-
OR (p_has_user = 'without' AND a.user_id IS NULL)
|
|
165
|
-
)
|
|
166
|
-
AND (
|
|
167
|
-
p_time_period IS NULL OR p_time_period = 'all'
|
|
168
|
-
OR (p_time_period = 'today' AND a.created_at >= CURRENT_DATE)
|
|
169
|
-
OR (p_time_period = 'this_week' AND a.created_at >= CURRENT_DATE - INTERVAL '7 days')
|
|
170
|
-
OR (p_time_period = 'this_month' AND a.created_at >= CURRENT_DATE - INTERVAL '30 days')
|
|
171
|
-
OR (p_time_period = 'older' AND a.created_at >= CURRENT_DATE - INTERVAL '90 days')
|
|
172
|
-
)
|
|
173
|
-
AND (
|
|
174
|
-
v_is_service_role
|
|
175
|
-
OR a.organization_id IN (SELECT public.auth_admin_organizations())
|
|
176
|
-
OR a.organization_id IS NULL
|
|
177
|
-
)
|
|
178
|
-
)
|
|
179
|
-
SELECT jsonb_build_object(
|
|
180
|
-
'total', (SELECT COUNT(*)::int FROM filtered_items),
|
|
181
|
-
'byStatus', (
|
|
182
|
-
SELECT jsonb_object_agg(status_value, cnt) FROM (
|
|
183
|
-
SELECT 'pending' as status_value, COUNT(*)::int as cnt FROM filtered_items WHERE status::text = 'pending'
|
|
184
|
-
UNION ALL SELECT 'active', COUNT(*)::int FROM filtered_items WHERE status::text = 'active'
|
|
185
|
-
UNION ALL SELECT 'inactive', COUNT(*)::int FROM filtered_items WHERE status::text = 'inactive'
|
|
186
|
-
UNION ALL SELECT 'rejected', COUNT(*)::int FROM filtered_items WHERE status::text = 'rejected'
|
|
187
|
-
) subquery
|
|
188
|
-
),
|
|
189
|
-
'byTier', (
|
|
190
|
-
SELECT jsonb_object_agg(tier_value, cnt) FROM (
|
|
191
|
-
SELECT 'starter' as tier_value, COUNT(*)::int as cnt FROM filtered_items WHERE tier::text = 'starter'
|
|
192
|
-
UNION ALL SELECT 'active', COUNT(*)::int FROM filtered_items WHERE tier::text = 'active'
|
|
193
|
-
) subquery
|
|
194
|
-
),
|
|
195
|
-
'byUserAccount', jsonb_build_object(
|
|
196
|
-
'with', (SELECT COUNT(*)::int FROM filtered_items WHERE user_id IS NOT NULL),
|
|
197
|
-
'without', (SELECT COUNT(*)::int FROM filtered_items WHERE user_id IS NULL)
|
|
198
|
-
),
|
|
199
|
-
'byTimePeriod', jsonb_build_object(
|
|
200
|
-
'today', (SELECT COUNT(*)::int FROM filtered_items WHERE created_at >= CURRENT_DATE),
|
|
201
|
-
'this_week', (SELECT COUNT(*)::int FROM filtered_items WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'),
|
|
202
|
-
'this_month', (SELECT COUNT(*)::int FROM filtered_items WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'),
|
|
203
|
-
'older', (SELECT COUNT(*)::int FROM filtered_items WHERE created_at >= CURRENT_DATE - INTERVAL '90 days')
|
|
204
|
-
)
|
|
205
|
-
) INTO result;
|
|
206
|
-
|
|
207
|
-
RETURN result;
|
|
208
|
-
END;
|
|
209
|
-
$$;
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
-- ============================================================================
|
|
2
|
-
-- Affiliate Module: RLS Policies
|
|
3
|
-
-- Template from @soulbatical/tetra-core
|
|
4
|
-
-- Multi-tenant: all policies filter by organization_id
|
|
5
|
-
-- ============================================================================
|
|
6
|
-
|
|
7
|
-
-- Enable RLS on all affiliate tables
|
|
8
|
-
ALTER TABLE public.affiliates ENABLE ROW LEVEL SECURITY;
|
|
9
|
-
ALTER TABLE public.affiliate_commissions ENABLE ROW LEVEL SECURITY;
|
|
10
|
-
ALTER TABLE public.affiliate_clicks ENABLE ROW LEVEL SECURITY;
|
|
11
|
-
ALTER TABLE public.affiliate_payments ENABLE ROW LEVEL SECURITY;
|
|
12
|
-
ALTER TABLE public.affiliate_tier_history ENABLE ROW LEVEL SECURITY;
|
|
13
|
-
|
|
14
|
-
-- ─── affiliates ───────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
-- Admin: full access within organization
|
|
17
|
-
CREATE POLICY "Admin: full access to affiliates"
|
|
18
|
-
ON public.affiliates
|
|
19
|
-
FOR ALL
|
|
20
|
-
TO authenticated
|
|
21
|
-
USING (organization_id IN (SELECT public.auth_admin_organizations()))
|
|
22
|
-
WITH CHECK (organization_id IN (SELECT public.auth_admin_organizations()));
|
|
23
|
-
|
|
24
|
-
-- User: read own affiliate record
|
|
25
|
-
CREATE POLICY "User: read own affiliate"
|
|
26
|
-
ON public.affiliates
|
|
27
|
-
FOR SELECT
|
|
28
|
-
TO authenticated
|
|
29
|
-
USING (user_id = auth.uid());
|
|
30
|
-
|
|
31
|
-
-- Service role: bypass RLS
|
|
32
|
-
CREATE POLICY "Service role: full access to affiliates"
|
|
33
|
-
ON public.affiliates
|
|
34
|
-
FOR ALL
|
|
35
|
-
TO service_role
|
|
36
|
-
USING (true)
|
|
37
|
-
WITH CHECK (true);
|
|
38
|
-
|
|
39
|
-
-- ─── affiliate_commissions ────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
CREATE POLICY "Admin: full access to affiliate_commissions"
|
|
42
|
-
ON public.affiliate_commissions
|
|
43
|
-
FOR ALL
|
|
44
|
-
TO authenticated
|
|
45
|
-
USING (organization_id IN (SELECT public.auth_admin_organizations()))
|
|
46
|
-
WITH CHECK (organization_id IN (SELECT public.auth_admin_organizations()));
|
|
47
|
-
|
|
48
|
-
-- User: read own commissions (via affiliate)
|
|
49
|
-
CREATE POLICY "User: read own commissions"
|
|
50
|
-
ON public.affiliate_commissions
|
|
51
|
-
FOR SELECT
|
|
52
|
-
TO authenticated
|
|
53
|
-
USING (
|
|
54
|
-
affiliate_id IN (
|
|
55
|
-
SELECT id FROM public.affiliates WHERE user_id = auth.uid()
|
|
56
|
-
)
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
CREATE POLICY "Service role: full access to affiliate_commissions"
|
|
60
|
-
ON public.affiliate_commissions
|
|
61
|
-
FOR ALL
|
|
62
|
-
TO service_role
|
|
63
|
-
USING (true)
|
|
64
|
-
WITH CHECK (true);
|
|
65
|
-
|
|
66
|
-
-- ─── affiliate_clicks ─────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
CREATE POLICY "Admin: full access to affiliate_clicks"
|
|
69
|
-
ON public.affiliate_clicks
|
|
70
|
-
FOR ALL
|
|
71
|
-
TO authenticated
|
|
72
|
-
USING (organization_id IN (SELECT public.auth_admin_organizations()))
|
|
73
|
-
WITH CHECK (organization_id IN (SELECT public.auth_admin_organizations()));
|
|
74
|
-
|
|
75
|
-
-- Public: insert clicks (anonymous tracking)
|
|
76
|
-
CREATE POLICY "Public: insert affiliate clicks"
|
|
77
|
-
ON public.affiliate_clicks
|
|
78
|
-
FOR INSERT
|
|
79
|
-
TO anon
|
|
80
|
-
WITH CHECK (true);
|
|
81
|
-
|
|
82
|
-
CREATE POLICY "Service role: full access to affiliate_clicks"
|
|
83
|
-
ON public.affiliate_clicks
|
|
84
|
-
FOR ALL
|
|
85
|
-
TO service_role
|
|
86
|
-
USING (true)
|
|
87
|
-
WITH CHECK (true);
|
|
88
|
-
|
|
89
|
-
-- ─── affiliate_payments ───────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
CREATE POLICY "Admin: full access to affiliate_payments"
|
|
92
|
-
ON public.affiliate_payments
|
|
93
|
-
FOR ALL
|
|
94
|
-
TO authenticated
|
|
95
|
-
USING (organization_id IN (SELECT public.auth_admin_organizations()))
|
|
96
|
-
WITH CHECK (organization_id IN (SELECT public.auth_admin_organizations()));
|
|
97
|
-
|
|
98
|
-
CREATE POLICY "Service role: full access to affiliate_payments"
|
|
99
|
-
ON public.affiliate_payments
|
|
100
|
-
FOR ALL
|
|
101
|
-
TO service_role
|
|
102
|
-
USING (true)
|
|
103
|
-
WITH CHECK (true);
|
|
104
|
-
|
|
105
|
-
-- ─── affiliate_tier_history ───────────────────────────────────
|
|
106
|
-
|
|
107
|
-
CREATE POLICY "Admin: read affiliate tier history"
|
|
108
|
-
ON public.affiliate_tier_history
|
|
109
|
-
FOR SELECT
|
|
110
|
-
TO authenticated
|
|
111
|
-
USING (
|
|
112
|
-
affiliate_id IN (
|
|
113
|
-
SELECT id FROM public.affiliates
|
|
114
|
-
WHERE organization_id IN (SELECT public.auth_admin_organizations())
|
|
115
|
-
)
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
CREATE POLICY "Service role: full access to affiliate_tier_history"
|
|
119
|
-
ON public.affiliate_tier_history
|
|
120
|
-
FOR ALL
|
|
121
|
-
TO service_role
|
|
122
|
-
USING (true)
|
|
123
|
-
WITH CHECK (true);
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
-- Tetra Billing Module — Subscriptions + Events
|
|
2
|
-
-- Copy this migration into your project's supabase/migrations/ directory.
|
|
3
|
-
--
|
|
4
|
-
-- Prerequisites:
|
|
5
|
-
-- - organizations table with: id (uuid PK), plan (text), stripe_customer_id (text)
|
|
6
|
-
-- - update_updated_at() trigger function
|
|
7
|
-
|
|
8
|
-
-- ─── Add billing columns to organizations (if not present) ──
|
|
9
|
-
DO $$ BEGIN
|
|
10
|
-
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS plan text NOT NULL DEFAULT 'free';
|
|
11
|
-
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS stripe_customer_id text UNIQUE;
|
|
12
|
-
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS mollie_customer_id text UNIQUE;
|
|
13
|
-
EXCEPTION WHEN OTHERS THEN NULL;
|
|
14
|
-
END $$;
|
|
15
|
-
|
|
16
|
-
-- ─── Subscriptions ──────────────────────────────────────────
|
|
17
|
-
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
18
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
-
organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
20
|
-
provider text NOT NULL CHECK (provider IN ('stripe', 'mollie')),
|
|
21
|
-
external_subscription_id text,
|
|
22
|
-
external_customer_id text,
|
|
23
|
-
plan text NOT NULL,
|
|
24
|
-
billing_cycle text NOT NULL DEFAULT 'monthly' CHECK (billing_cycle IN ('monthly', 'yearly')),
|
|
25
|
-
price_amount_cents integer NOT NULL DEFAULT 0,
|
|
26
|
-
status text NOT NULL DEFAULT 'active'
|
|
27
|
-
CHECK (status IN ('trialing', 'active', 'past_due', 'canceled', 'unpaid')),
|
|
28
|
-
trial_end timestamptz,
|
|
29
|
-
current_period_start timestamptz NOT NULL DEFAULT now(),
|
|
30
|
-
current_period_end timestamptz NOT NULL DEFAULT now() + interval '30 days',
|
|
31
|
-
cancel_at timestamptz,
|
|
32
|
-
canceled_at timestamptz,
|
|
33
|
-
ended_at timestamptz,
|
|
34
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
35
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
-- Unique constraint: max 1 active subscription per org
|
|
39
|
-
CREATE UNIQUE INDEX IF NOT EXISTS subscriptions_org_active_idx
|
|
40
|
-
ON subscriptions (organization_id)
|
|
41
|
-
WHERE status NOT IN ('canceled');
|
|
42
|
-
|
|
43
|
-
CREATE INDEX IF NOT EXISTS subscriptions_org_id_idx ON subscriptions(organization_id);
|
|
44
|
-
CREATE INDEX IF NOT EXISTS subscriptions_external_id_idx ON subscriptions(external_subscription_id);
|
|
45
|
-
|
|
46
|
-
-- ─── Subscription Events (audit log) ────────────────────────
|
|
47
|
-
CREATE TABLE IF NOT EXISTS subscription_events (
|
|
48
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
49
|
-
organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
50
|
-
subscription_id uuid REFERENCES subscriptions(id) ON DELETE SET NULL,
|
|
51
|
-
event_type text NOT NULL,
|
|
52
|
-
stripe_event_id text,
|
|
53
|
-
provider text NOT NULL CHECK (provider IN ('stripe', 'mollie')),
|
|
54
|
-
data jsonb NOT NULL DEFAULT '{}',
|
|
55
|
-
source text NOT NULL DEFAULT 'webhook',
|
|
56
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
-- Idempotency: Stripe events are unique
|
|
60
|
-
CREATE UNIQUE INDEX IF NOT EXISTS subscription_events_stripe_event_idx
|
|
61
|
-
ON subscription_events (stripe_event_id)
|
|
62
|
-
WHERE stripe_event_id IS NOT NULL;
|
|
63
|
-
|
|
64
|
-
CREATE INDEX IF NOT EXISTS subscription_events_org_idx ON subscription_events(organization_id);
|
|
65
|
-
|
|
66
|
-
-- ─── RLS ────────────────────────────────────────────────────
|
|
67
|
-
ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
|
|
68
|
-
ALTER TABLE subscription_events ENABLE ROW LEVEL SECURITY;
|
|
69
|
-
|
|
70
|
-
-- FORCE RLS for service role too (webhook handlers use webhookDB which bypasses differently)
|
|
71
|
-
ALTER TABLE subscriptions FORCE ROW LEVEL SECURITY;
|
|
72
|
-
ALTER TABLE subscription_events FORCE ROW LEVEL SECURITY;
|
|
73
|
-
|
|
74
|
-
-- Admin read access (org members)
|
|
75
|
-
DO $$ BEGIN
|
|
76
|
-
CREATE POLICY subscriptions_org_read ON subscriptions
|
|
77
|
-
FOR SELECT TO authenticated
|
|
78
|
-
USING (organization_id IN (
|
|
79
|
-
SELECT organization_id FROM organization_members
|
|
80
|
-
WHERE user_id = auth.uid() AND status = 'active'
|
|
81
|
-
));
|
|
82
|
-
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
83
|
-
END $$;
|
|
84
|
-
|
|
85
|
-
DO $$ BEGIN
|
|
86
|
-
CREATE POLICY subscription_events_org_read ON subscription_events
|
|
87
|
-
FOR SELECT TO authenticated
|
|
88
|
-
USING (organization_id IN (
|
|
89
|
-
SELECT organization_id FROM organization_members
|
|
90
|
-
WHERE user_id = auth.uid() AND status = 'active'
|
|
91
|
-
));
|
|
92
|
-
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
93
|
-
END $$;
|
|
94
|
-
|
|
95
|
-
-- Service role bypass for webhook handlers
|
|
96
|
-
DO $$ BEGIN
|
|
97
|
-
CREATE POLICY subscriptions_service ON subscriptions
|
|
98
|
-
FOR ALL TO service_role USING (true) WITH CHECK (true);
|
|
99
|
-
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
100
|
-
END $$;
|
|
101
|
-
|
|
102
|
-
DO $$ BEGIN
|
|
103
|
-
CREATE POLICY subscription_events_service ON subscription_events
|
|
104
|
-
FOR ALL TO service_role USING (true) WITH CHECK (true);
|
|
105
|
-
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
106
|
-
END $$;
|
|
107
|
-
|
|
108
|
-
-- ─── Updated_at trigger ─────────────────────────────────────
|
|
109
|
-
DO $$ BEGIN
|
|
110
|
-
CREATE TRIGGER subscriptions_updated_at
|
|
111
|
-
BEFORE UPDATE ON subscriptions
|
|
112
|
-
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
113
|
-
EXCEPTION WHEN OTHERS THEN NULL;
|
|
114
|
-
END $$;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
-- ============================================
|
|
2
|
-
-- Email Logs — track all outgoing emails
|
|
3
|
-
-- Part of @soulbatical/tetra-core email module
|
|
4
|
-
-- ============================================
|
|
5
|
-
-- No FK constraints — consuming project adds its own
|
|
6
|
-
-- (e.g. organization_id → organizations, feedback_id → feedback)
|
|
7
|
-
|
|
8
|
-
CREATE TABLE IF NOT EXISTS email_logs (
|
|
9
|
-
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
10
|
-
organization_id UUID,
|
|
11
|
-
template_id UUID,
|
|
12
|
-
to_email VARCHAR(320) NOT NULL,
|
|
13
|
-
subject VARCHAR(500) NOT NULL,
|
|
14
|
-
template_slug VARCHAR(100),
|
|
15
|
-
variables_used JSONB DEFAULT '{}',
|
|
16
|
-
status VARCHAR(50) DEFAULT 'pending',
|
|
17
|
-
error_message TEXT,
|
|
18
|
-
mailgun_message_id VARCHAR(255),
|
|
19
|
-
sent_at TIMESTAMPTZ,
|
|
20
|
-
created_at TIMESTAMPTZ DEFAULT now()
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
CREATE INDEX IF NOT EXISTS idx_email_logs_org ON email_logs(organization_id);
|
|
24
|
-
CREATE INDEX IF NOT EXISTS idx_email_logs_status ON email_logs(status);
|
|
25
|
-
CREATE INDEX IF NOT EXISTS idx_email_logs_created_at ON email_logs(created_at);
|
|
26
|
-
|
|
27
|
-
ALTER TABLE email_logs ENABLE ROW LEVEL SECURITY;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
-- ============================================
|
|
2
|
-
-- Email Templates — DB-driven email templates
|
|
3
|
-
-- Part of @soulbatical/tetra-core email module
|
|
4
|
-
-- ============================================
|
|
5
|
-
-- No FK constraints — consuming project adds its own
|
|
6
|
-
|
|
7
|
-
CREATE TABLE IF NOT EXISTS email_templates (
|
|
8
|
-
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
9
|
-
slug VARCHAR(100) NOT NULL,
|
|
10
|
-
name VARCHAR(255) NOT NULL,
|
|
11
|
-
subject VARCHAR(500) NOT NULL,
|
|
12
|
-
body_html TEXT NOT NULL,
|
|
13
|
-
body_text TEXT,
|
|
14
|
-
variables JSONB DEFAULT '[]',
|
|
15
|
-
language VARCHAR(10) DEFAULT 'nl' NOT NULL,
|
|
16
|
-
category VARCHAR(100) DEFAULT 'transactional',
|
|
17
|
-
is_active BOOLEAN DEFAULT true,
|
|
18
|
-
organization_id UUID,
|
|
19
|
-
created_at TIMESTAMPTZ DEFAULT now(),
|
|
20
|
-
updated_at TIMESTAMPTZ DEFAULT now()
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
-- Unique per slug + language + org (NULL org uses sentinel UUID)
|
|
24
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_email_templates_slug_lang_org
|
|
25
|
-
ON email_templates(slug, language, COALESCE(organization_id, '00000000-0000-0000-0000-000000000000'));
|
|
26
|
-
|
|
27
|
-
ALTER TABLE email_templates ENABLE ROW LEVEL SECURITY;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
-- ============================================
|
|
2
|
-
-- RLS Baseline Policies — deny-all defaults
|
|
3
|
-
-- Part of @soulbatical/tetra-core email module
|
|
4
|
-
-- ============================================
|
|
5
|
-
-- These deny all access for anon and authenticated roles.
|
|
6
|
-
-- Service role bypasses RLS automatically.
|
|
7
|
-
-- Consuming projects should add their own grant policies as needed.
|
|
8
|
-
|
|
9
|
-
-- email_logs: no direct access for any non-service role
|
|
10
|
-
CREATE POLICY "email_logs_deny_anon"
|
|
11
|
-
ON email_logs FOR ALL TO anon
|
|
12
|
-
USING (false) WITH CHECK (false);
|
|
13
|
-
|
|
14
|
-
CREATE POLICY "email_logs_deny_authenticated"
|
|
15
|
-
ON email_logs FOR ALL TO authenticated
|
|
16
|
-
USING (false) WITH CHECK (false);
|
|
17
|
-
|
|
18
|
-
-- email_templates: read-only for authenticated (active templates only), no write
|
|
19
|
-
CREATE POLICY "email_templates_deny_anon"
|
|
20
|
-
ON email_templates FOR ALL TO anon
|
|
21
|
-
USING (false) WITH CHECK (false);
|
|
22
|
-
|
|
23
|
-
CREATE POLICY "email_templates_read_active_authenticated"
|
|
24
|
-
ON email_templates FOR SELECT TO authenticated
|
|
25
|
-
USING (is_active = true);
|
|
26
|
-
|
|
27
|
-
CREATE POLICY "email_templates_deny_write_authenticated"
|
|
28
|
-
ON email_templates FOR INSERT TO authenticated
|
|
29
|
-
WITH CHECK (false);
|
|
30
|
-
|
|
31
|
-
CREATE POLICY "email_templates_deny_update_authenticated"
|
|
32
|
-
ON email_templates FOR UPDATE TO authenticated
|
|
33
|
-
USING (false) WITH CHECK (false);
|
|
34
|
-
|
|
35
|
-
CREATE POLICY "email_templates_deny_delete_authenticated"
|
|
36
|
-
ON email_templates FOR DELETE TO authenticated
|
|
37
|
-
USING (false);
|