@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.
Files changed (271) hide show
  1. package/README.md +78 -38
  2. package/dist/core/createApp.d.ts +1 -1
  3. package/dist/core/createApp.d.ts.map +1 -1
  4. package/dist/core/createApp.js +77 -2
  5. package/dist/core/createApp.js.map +1 -1
  6. package/dist/core/dualWriteProxy.d.ts +7 -2
  7. package/dist/core/dualWriteProxy.d.ts.map +1 -1
  8. package/dist/core/dualWriteProxy.js +16 -5
  9. package/dist/core/dualWriteProxy.js.map +1 -1
  10. package/dist/core/routeContext.d.ts +24 -0
  11. package/dist/core/routeContext.d.ts.map +1 -1
  12. package/dist/core/routeContext.js +31 -4
  13. package/dist/core/routeContext.js.map +1 -1
  14. package/dist/core/systemDb.d.ts +2 -2
  15. package/dist/core/systemDb.js +2 -2
  16. package/dist/generators/rls-checker.d.ts +1 -1
  17. package/dist/generators/rls-checker.js +1 -1
  18. package/dist/generators/rls-exec-sql.d.ts +1 -1
  19. package/dist/generators/rls-exec-sql.js +1 -1
  20. package/dist/generators/rpc/index.d.ts +1 -1
  21. package/dist/generators/rpc/index.js +1 -1
  22. package/dist/index.d.ts +3 -31
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -32
  25. package/dist/index.js.map +1 -1
  26. package/dist/middleware/securityMiddleware.d.ts +1 -1
  27. package/dist/middleware/securityMiddleware.d.ts.map +1 -1
  28. package/dist/middleware/validateBody.d.ts.map +1 -1
  29. package/dist/middleware/validateBody.js +51 -8
  30. package/dist/middleware/validateBody.js.map +1 -1
  31. package/dist/shared/rfc7807ErrorResponse.d.ts +7 -0
  32. package/dist/shared/rfc7807ErrorResponse.d.ts.map +1 -1
  33. package/dist/shared/rfc7807ErrorResponse.js +19 -5
  34. package/dist/shared/rfc7807ErrorResponse.js.map +1 -1
  35. package/dist/utils/logger.d.ts.map +1 -1
  36. package/dist/utils/logger.js +16 -1
  37. package/dist/utils/logger.js.map +1 -1
  38. package/package.json +33 -77
  39. package/dist/affiliate.d.ts +0 -11
  40. package/dist/affiliate.d.ts.map +0 -1
  41. package/dist/affiliate.js +0 -10
  42. package/dist/affiliate.js.map +0 -1
  43. package/dist/billing.d.ts +0 -8
  44. package/dist/billing.d.ts.map +0 -1
  45. package/dist/billing.js +0 -7
  46. package/dist/billing.js.map +0 -1
  47. package/dist/email.d.ts +0 -9
  48. package/dist/email.d.ts.map +0 -1
  49. package/dist/email.js +0 -8
  50. package/dist/email.js.map +0 -1
  51. package/dist/generators/rls-exec-sql.sql +0 -57
  52. package/dist/generators.d.ts +0 -15
  53. package/dist/generators.d.ts.map +0 -1
  54. package/dist/generators.js +0 -12
  55. package/dist/generators.js.map +0 -1
  56. package/dist/mcp.d.ts +0 -8
  57. package/dist/mcp.d.ts.map +0 -1
  58. package/dist/mcp.js +0 -7
  59. package/dist/mcp.js.map +0 -1
  60. package/dist/planner.d.ts +0 -8
  61. package/dist/planner.d.ts.map +0 -1
  62. package/dist/planner.js +0 -7
  63. package/dist/planner.js.map +0 -1
  64. package/dist/shared/affiliate/AffiliateAttributionService.d.ts +0 -47
  65. package/dist/shared/affiliate/AffiliateAttributionService.d.ts.map +0 -1
  66. package/dist/shared/affiliate/AffiliateAttributionService.js +0 -308
  67. package/dist/shared/affiliate/AffiliateAttributionService.js.map +0 -1
  68. package/dist/shared/affiliate/AffiliateClickService.d.ts +0 -35
  69. package/dist/shared/affiliate/AffiliateClickService.d.ts.map +0 -1
  70. package/dist/shared/affiliate/AffiliateClickService.js +0 -87
  71. package/dist/shared/affiliate/AffiliateClickService.js.map +0 -1
  72. package/dist/shared/affiliate/affiliateFeatureConfig.d.ts +0 -11
  73. package/dist/shared/affiliate/affiliateFeatureConfig.d.ts.map +0 -1
  74. package/dist/shared/affiliate/affiliateFeatureConfig.js +0 -242
  75. package/dist/shared/affiliate/affiliateFeatureConfig.js.map +0 -1
  76. package/dist/shared/affiliate/index.d.ts +0 -11
  77. package/dist/shared/affiliate/index.d.ts.map +0 -1
  78. package/dist/shared/affiliate/index.js +0 -13
  79. package/dist/shared/affiliate/index.js.map +0 -1
  80. package/dist/shared/affiliate/routes.d.ts +0 -87
  81. package/dist/shared/affiliate/routes.d.ts.map +0 -1
  82. package/dist/shared/affiliate/routes.js +0 -404
  83. package/dist/shared/affiliate/routes.js.map +0 -1
  84. package/dist/shared/affiliate/types.d.ts +0 -170
  85. package/dist/shared/affiliate/types.d.ts.map +0 -1
  86. package/dist/shared/affiliate/types.js +0 -11
  87. package/dist/shared/affiliate/types.js.map +0 -1
  88. package/dist/shared/billing/BillingService.d.ts +0 -56
  89. package/dist/shared/billing/BillingService.d.ts.map +0 -1
  90. package/dist/shared/billing/BillingService.js +0 -588
  91. package/dist/shared/billing/BillingService.js.map +0 -1
  92. package/dist/shared/billing/SeatBillingService.d.ts +0 -106
  93. package/dist/shared/billing/SeatBillingService.d.ts.map +0 -1
  94. package/dist/shared/billing/SeatBillingService.js +0 -292
  95. package/dist/shared/billing/SeatBillingService.js.map +0 -1
  96. package/dist/shared/billing/index.d.ts +0 -30
  97. package/dist/shared/billing/index.d.ts.map +0 -1
  98. package/dist/shared/billing/index.js +0 -27
  99. package/dist/shared/billing/index.js.map +0 -1
  100. package/dist/shared/billing/routes.d.ts +0 -45
  101. package/dist/shared/billing/routes.d.ts.map +0 -1
  102. package/dist/shared/billing/routes.js +0 -184
  103. package/dist/shared/billing/routes.js.map +0 -1
  104. package/dist/shared/billing/seat-pricing.d.ts +0 -53
  105. package/dist/shared/billing/seat-pricing.d.ts.map +0 -1
  106. package/dist/shared/billing/seat-pricing.js +0 -81
  107. package/dist/shared/billing/seat-pricing.js.map +0 -1
  108. package/dist/shared/billing/types.d.ts +0 -109
  109. package/dist/shared/billing/types.d.ts.map +0 -1
  110. package/dist/shared/billing/types.js +0 -8
  111. package/dist/shared/billing/types.js.map +0 -1
  112. package/dist/shared/email/EmailService.d.ts +0 -64
  113. package/dist/shared/email/EmailService.d.ts.map +0 -1
  114. package/dist/shared/email/EmailService.js +0 -300
  115. package/dist/shared/email/EmailService.js.map +0 -1
  116. package/dist/shared/email/adminRoutes.d.ts +0 -30
  117. package/dist/shared/email/adminRoutes.d.ts.map +0 -1
  118. package/dist/shared/email/adminRoutes.js +0 -227
  119. package/dist/shared/email/adminRoutes.js.map +0 -1
  120. package/dist/shared/email/gmail.d.ts +0 -208
  121. package/dist/shared/email/gmail.d.ts.map +0 -1
  122. package/dist/shared/email/gmail.js +0 -626
  123. package/dist/shared/email/gmail.js.map +0 -1
  124. package/dist/shared/email/index.d.ts +0 -15
  125. package/dist/shared/email/index.d.ts.map +0 -1
  126. package/dist/shared/email/index.js +0 -18
  127. package/dist/shared/email/index.js.map +0 -1
  128. package/dist/shared/email/mailgun.d.ts +0 -18
  129. package/dist/shared/email/mailgun.d.ts.map +0 -1
  130. package/dist/shared/email/mailgun.js +0 -76
  131. package/dist/shared/email/mailgun.js.map +0 -1
  132. package/dist/shared/email/sanitize.d.ts +0 -25
  133. package/dist/shared/email/sanitize.d.ts.map +0 -1
  134. package/dist/shared/email/sanitize.js +0 -39
  135. package/dist/shared/email/sanitize.js.map +0 -1
  136. package/dist/shared/email/smtp.d.ts +0 -20
  137. package/dist/shared/email/smtp.d.ts.map +0 -1
  138. package/dist/shared/email/smtp.js +0 -53
  139. package/dist/shared/email/smtp.js.map +0 -1
  140. package/dist/shared/email/types.d.ts +0 -113
  141. package/dist/shared/email/types.d.ts.map +0 -1
  142. package/dist/shared/email/types.js +0 -7
  143. package/dist/shared/email/types.js.map +0 -1
  144. package/dist/shared/email/webhookRoutes.d.ts +0 -29
  145. package/dist/shared/email/webhookRoutes.d.ts.map +0 -1
  146. package/dist/shared/email/webhookRoutes.js +0 -125
  147. package/dist/shared/email/webhookRoutes.js.map +0 -1
  148. package/dist/shared/mcp/index.d.ts +0 -51
  149. package/dist/shared/mcp/index.d.ts.map +0 -1
  150. package/dist/shared/mcp/index.js +0 -51
  151. package/dist/shared/mcp/index.js.map +0 -1
  152. package/dist/shared/mcp/mcp-auth-routes.d.ts +0 -26
  153. package/dist/shared/mcp/mcp-auth-routes.d.ts.map +0 -1
  154. package/dist/shared/mcp/mcp-auth-routes.js +0 -141
  155. package/dist/shared/mcp/mcp-auth-routes.js.map +0 -1
  156. package/dist/shared/mcp/mcp-db.d.ts +0 -99
  157. package/dist/shared/mcp/mcp-db.d.ts.map +0 -1
  158. package/dist/shared/mcp/mcp-db.js +0 -106
  159. package/dist/shared/mcp/mcp-db.js.map +0 -1
  160. package/dist/shared/mcp/mcp-routes.d.ts +0 -29
  161. package/dist/shared/mcp/mcp-routes.d.ts.map +0 -1
  162. package/dist/shared/mcp/mcp-routes.js +0 -171
  163. package/dist/shared/mcp/mcp-routes.js.map +0 -1
  164. package/dist/shared/mcp/mcp-tokens-routes.d.ts +0 -35
  165. package/dist/shared/mcp/mcp-tokens-routes.d.ts.map +0 -1
  166. package/dist/shared/mcp/mcp-tokens-routes.js +0 -94
  167. package/dist/shared/mcp/mcp-tokens-routes.js.map +0 -1
  168. package/dist/shared/mcp/mcp-usage-routes.d.ts +0 -17
  169. package/dist/shared/mcp/mcp-usage-routes.d.ts.map +0 -1
  170. package/dist/shared/mcp/mcp-usage-routes.js +0 -81
  171. package/dist/shared/mcp/mcp-usage-routes.js.map +0 -1
  172. package/dist/shared/mcp/tenant-context.d.ts +0 -59
  173. package/dist/shared/mcp/tenant-context.d.ts.map +0 -1
  174. package/dist/shared/mcp/tenant-context.js +0 -136
  175. package/dist/shared/mcp/tenant-context.js.map +0 -1
  176. package/dist/shared/mcp/types.d.ts +0 -74
  177. package/dist/shared/mcp/types.d.ts.map +0 -1
  178. package/dist/shared/mcp/types.js +0 -7
  179. package/dist/shared/mcp/types.js.map +0 -1
  180. package/dist/shared/planner/GoogleCalendarService.d.ts +0 -137
  181. package/dist/shared/planner/GoogleCalendarService.d.ts.map +0 -1
  182. package/dist/shared/planner/GoogleCalendarService.js +0 -525
  183. package/dist/shared/planner/GoogleCalendarService.js.map +0 -1
  184. package/dist/shared/planner/PlannerService.d.ts +0 -264
  185. package/dist/shared/planner/PlannerService.d.ts.map +0 -1
  186. package/dist/shared/planner/PlannerService.js +0 -1393
  187. package/dist/shared/planner/PlannerService.js.map +0 -1
  188. package/dist/shared/planner/index.d.ts +0 -37
  189. package/dist/shared/planner/index.d.ts.map +0 -1
  190. package/dist/shared/planner/index.js +0 -35
  191. package/dist/shared/planner/index.js.map +0 -1
  192. package/dist/shared/planner/intervals.d.ts +0 -60
  193. package/dist/shared/planner/intervals.d.ts.map +0 -1
  194. package/dist/shared/planner/intervals.js +0 -141
  195. package/dist/shared/planner/intervals.js.map +0 -1
  196. package/dist/shared/planner/routes.d.ts +0 -69
  197. package/dist/shared/planner/routes.d.ts.map +0 -1
  198. package/dist/shared/planner/routes.js +0 -770
  199. package/dist/shared/planner/routes.js.map +0 -1
  200. package/dist/shared/planner/types.d.ts +0 -328
  201. package/dist/shared/planner/types.d.ts.map +0 -1
  202. package/dist/shared/planner/types.js +0 -9
  203. package/dist/shared/planner/types.js.map +0 -1
  204. package/dist/shared/storage/ImageProcessingService.d.ts +0 -32
  205. package/dist/shared/storage/ImageProcessingService.d.ts.map +0 -1
  206. package/dist/shared/storage/ImageProcessingService.js +0 -127
  207. package/dist/shared/storage/ImageProcessingService.js.map +0 -1
  208. package/dist/shared/storage/StorageProxyService.d.ts +0 -47
  209. package/dist/shared/storage/StorageProxyService.d.ts.map +0 -1
  210. package/dist/shared/storage/StorageProxyService.js +0 -196
  211. package/dist/shared/storage/StorageProxyService.js.map +0 -1
  212. package/dist/shared/storage/StorageUploadService.d.ts +0 -126
  213. package/dist/shared/storage/StorageUploadService.d.ts.map +0 -1
  214. package/dist/shared/storage/StorageUploadService.js +0 -206
  215. package/dist/shared/storage/StorageUploadService.js.map +0 -1
  216. package/dist/shared/storage/creative-urls.d.ts +0 -14
  217. package/dist/shared/storage/creative-urls.d.ts.map +0 -1
  218. package/dist/shared/storage/creative-urls.js +0 -30
  219. package/dist/shared/storage/creative-urls.js.map +0 -1
  220. package/dist/shared/storage/index.d.ts +0 -28
  221. package/dist/shared/storage/index.d.ts.map +0 -1
  222. package/dist/shared/storage/index.js +0 -27
  223. package/dist/shared/storage/index.js.map +0 -1
  224. package/dist/shared/storage/routes.d.ts +0 -42
  225. package/dist/shared/storage/routes.d.ts.map +0 -1
  226. package/dist/shared/storage/routes.js +0 -160
  227. package/dist/shared/storage/routes.js.map +0 -1
  228. package/dist/shared/storage/types.d.ts +0 -53
  229. package/dist/shared/storage/types.d.ts.map +0 -1
  230. package/dist/shared/storage/types.js +0 -2
  231. package/dist/shared/storage/types.js.map +0 -1
  232. package/dist/shared/telegram/index.d.ts +0 -4
  233. package/dist/shared/telegram/index.d.ts.map +0 -1
  234. package/dist/shared/telegram/index.js +0 -3
  235. package/dist/shared/telegram/index.js.map +0 -1
  236. package/dist/shared/telegram/routes.d.ts +0 -43
  237. package/dist/shared/telegram/routes.d.ts.map +0 -1
  238. package/dist/shared/telegram/routes.js +0 -868
  239. package/dist/shared/telegram/routes.js.map +0 -1
  240. package/dist/shared/telegram/types.d.ts +0 -168
  241. package/dist/shared/telegram/types.d.ts.map +0 -1
  242. package/dist/shared/telegram/types.js +0 -7
  243. package/dist/shared/telegram/types.js.map +0 -1
  244. package/dist/shared/telegram/utils.d.ts +0 -44
  245. package/dist/shared/telegram/utils.d.ts.map +0 -1
  246. package/dist/shared/telegram/utils.js +0 -121
  247. package/dist/shared/telegram/utils.js.map +0 -1
  248. package/dist/storage.d.ts +0 -9
  249. package/dist/storage.d.ts.map +0 -1
  250. package/dist/storage.js +0 -8
  251. package/dist/storage.js.map +0 -1
  252. package/dist/telemetry.d.ts +0 -9
  253. package/dist/telemetry.d.ts.map +0 -1
  254. package/dist/telemetry.js +0 -8
  255. package/dist/telemetry.js.map +0 -1
  256. package/scripts/postinstall.js +0 -79
  257. package/src/shared/affiliate/migrations/001_create_affiliates.sql +0 -49
  258. package/src/shared/affiliate/migrations/002_create_affiliate_commissions.sql +0 -31
  259. package/src/shared/affiliate/migrations/003_create_affiliate_clicks.sql +0 -26
  260. package/src/shared/affiliate/migrations/004_create_affiliate_payments.sql +0 -34
  261. package/src/shared/affiliate/migrations/005_create_affiliate_tier_history.sql +0 -19
  262. package/src/shared/affiliate/migrations/006_create_affiliate_rpc_functions.sql +0 -209
  263. package/src/shared/affiliate/migrations/007_create_affiliate_rls_policies.sql +0 -123
  264. package/src/shared/billing/migrations/00000000000001_billing.sql +0 -114
  265. package/src/shared/email/migrations/000_create_email_logs.sql +0 -27
  266. package/src/shared/email/migrations/001_create_email_templates.sql +0 -27
  267. package/src/shared/email/migrations/002_add_rls_baseline_policies.sql +0 -37
  268. package/src/shared/email/migrations/003_create_gmail_accounts.sql +0 -82
  269. package/src/shared/email/migrations/004_add_email_logs_tracking_columns.sql +0 -15
  270. package/src/shared/mcp/migrations/001_mcp_api_tokens.sql +0 -21
  271. 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);