vibecarbon 0.1.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 (495) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +188 -0
  3. package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
  4. package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
  5. package/carbon/.claude/settings.json +29 -0
  6. package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
  7. package/carbon/.dockerignore +57 -0
  8. package/carbon/.env.example +219 -0
  9. package/carbon/.github/copilot-instructions.md +3 -0
  10. package/carbon/.github/workflows/deploy.yml +346 -0
  11. package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
  12. package/carbon/.windsurfrules +74 -0
  13. package/carbon/AGENTS.md +422 -0
  14. package/carbon/CLAUDE.md +3 -0
  15. package/carbon/DEVELOPMENT.md +187 -0
  16. package/carbon/Dockerfile +98 -0
  17. package/carbon/LICENSE +21 -0
  18. package/carbon/PRODUCTION.md +364 -0
  19. package/carbon/README.md +193 -0
  20. package/carbon/TESTING.md +138 -0
  21. package/carbon/backup/Dockerfile +75 -0
  22. package/carbon/backup/backup.sh +95 -0
  23. package/carbon/backup/compose-backup.sh +140 -0
  24. package/carbon/biome.json +83 -0
  25. package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
  26. package/carbon/cloud-init/k3s/master-init.sh +215 -0
  27. package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
  28. package/carbon/cloud-init/k3s/worker-init.sh +147 -0
  29. package/carbon/components.json +24 -0
  30. package/carbon/content/blog/authentication-guide.mdx +34 -0
  31. package/carbon/content/blog/getting-started.mdx +41 -0
  32. package/carbon/content/changelog/v0-1-0.mdx +40 -0
  33. package/carbon/content/docs/analytics.mdx +90 -0
  34. package/carbon/content/docs/authentication.mdx +231 -0
  35. package/carbon/content/docs/background-jobs.mdx +116 -0
  36. package/carbon/content/docs/cli.mdx +630 -0
  37. package/carbon/content/docs/database.mdx +236 -0
  38. package/carbon/content/docs/deployment.mdx +227 -0
  39. package/carbon/content/docs/development.mdx +238 -0
  40. package/carbon/content/docs/environments.mdx +84 -0
  41. package/carbon/content/docs/getting-started.mdx +112 -0
  42. package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
  43. package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
  44. package/carbon/content/docs/optional-services.mdx +160 -0
  45. package/carbon/db/Dockerfile +23 -0
  46. package/carbon/docker-compose.dev-init.yml +5 -0
  47. package/carbon/docker-compose.metabase.override.yml +14 -0
  48. package/carbon/docker-compose.metabase.prod.yml +28 -0
  49. package/carbon/docker-compose.metabase.yml +84 -0
  50. package/carbon/docker-compose.n8n.override.yml +14 -0
  51. package/carbon/docker-compose.n8n.prod.yml +31 -0
  52. package/carbon/docker-compose.n8n.yml +97 -0
  53. package/carbon/docker-compose.observability.override.yml +18 -0
  54. package/carbon/docker-compose.observability.prod.yml +28 -0
  55. package/carbon/docker-compose.observability.yml +125 -0
  56. package/carbon/docker-compose.override.yml +28 -0
  57. package/carbon/docker-compose.prod.yml +294 -0
  58. package/carbon/docker-compose.yml +508 -0
  59. package/carbon/docker-entrypoint.sh +12 -0
  60. package/carbon/ha/activate-standby.sh +52 -0
  61. package/carbon/ha/primary-init.sql +36 -0
  62. package/carbon/ha/standby-init.sh +74 -0
  63. package/carbon/k8s/LICENSE +99 -0
  64. package/carbon/k8s/README.md +272 -0
  65. package/carbon/k8s/base/app/deployment.yaml +130 -0
  66. package/carbon/k8s/base/app/hpa.yaml +44 -0
  67. package/carbon/k8s/base/app/kustomization.yaml +10 -0
  68. package/carbon/k8s/base/app/network-policy.yaml +124 -0
  69. package/carbon/k8s/base/app/pdb.yaml +14 -0
  70. package/carbon/k8s/base/app/rbac.yaml +36 -0
  71. package/carbon/k8s/base/app/service.yaml +16 -0
  72. package/carbon/k8s/base/backup/cronjob.yaml +120 -0
  73. package/carbon/k8s/base/backup/kustomization.yaml +7 -0
  74. package/carbon/k8s/base/backup/network-policy.yaml +31 -0
  75. package/carbon/k8s/base/backup/rbac.yaml +7 -0
  76. package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
  77. package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
  78. package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
  79. package/carbon/k8s/base/config/configmap.yaml +48 -0
  80. package/carbon/k8s/base/config/kustomization.yaml +8 -0
  81. package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
  82. package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
  83. package/carbon/k8s/base/kustomization.yaml +43 -0
  84. package/carbon/k8s/base/namespace.yaml +7 -0
  85. package/carbon/k8s/base/network-policies.yaml +95 -0
  86. package/carbon/k8s/base/registry/kustomization.yaml +5 -0
  87. package/carbon/k8s/base/registry/local-registry.yaml +193 -0
  88. package/carbon/k8s/base/traefik/certificate.yaml +21 -0
  89. package/carbon/k8s/base/traefik/configmap.yaml +13 -0
  90. package/carbon/k8s/base/traefik/deployment.yaml +165 -0
  91. package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
  92. package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
  93. package/carbon/k8s/base/traefik/middleware.yaml +109 -0
  94. package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
  95. package/carbon/k8s/base/traefik/service.yaml +38 -0
  96. package/carbon/k8s/flux/README.md +49 -0
  97. package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
  98. package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
  99. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
  100. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
  101. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
  102. package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
  103. package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
  104. package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
  105. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
  106. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
  107. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
  108. package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
  109. package/carbon/k8s/infra/kustomization.yaml +21 -0
  110. package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
  111. package/carbon/k8s/overlays/local/configmap.yaml +23 -0
  112. package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
  113. package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
  114. package/carbon/k8s/overlays/local/namespace.yaml +8 -0
  115. package/carbon/k8s/overlays/local/secrets.yaml +32 -0
  116. package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
  117. package/carbon/k8s/test-local.sh +318 -0
  118. package/carbon/k8s/values/supabase.values.yaml +133 -0
  119. package/carbon/package.json +154 -0
  120. package/carbon/runtime.Dockerfile +17 -0
  121. package/carbon/scripts/_dev-jwt.mjs +45 -0
  122. package/carbon/scripts/check-rls.ts +53 -0
  123. package/carbon/scripts/dev-init.js +191 -0
  124. package/carbon/scripts/dev.js +191 -0
  125. package/carbon/scripts/docker-down.js +63 -0
  126. package/carbon/scripts/docker-logs.js +59 -0
  127. package/carbon/scripts/docker-up.js +222 -0
  128. package/carbon/scripts/generate-dev-configs.sh +131 -0
  129. package/carbon/scripts/generate-rss.ts +116 -0
  130. package/carbon/scripts/generate-sitemap.ts +102 -0
  131. package/carbon/scripts/k8s-apply.js +71 -0
  132. package/carbon/scripts/k8s-delete.js +75 -0
  133. package/carbon/scripts/lib/manifest.js +176 -0
  134. package/carbon/scripts/secret-scan.mjs +278 -0
  135. package/carbon/scripts/validate-dev-configs.sh +101 -0
  136. package/carbon/src/client/App.tsx +202 -0
  137. package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
  138. package/carbon/src/client/assets/hyperformant-light.svg +29 -0
  139. package/carbon/src/client/assets/logos/aider.svg +1 -0
  140. package/carbon/src/client/assets/logos/antigravity.svg +1 -0
  141. package/carbon/src/client/assets/logos/bolt.svg +1 -0
  142. package/carbon/src/client/assets/logos/claude-code.svg +1 -0
  143. package/carbon/src/client/assets/logos/copilot.svg +1 -0
  144. package/carbon/src/client/assets/logos/cursor.svg +1 -0
  145. package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
  146. package/carbon/src/client/assets/logos/lovable.svg +1 -0
  147. package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
  148. package/carbon/src/client/assets/logos/v0.svg +1 -0
  149. package/carbon/src/client/assets/logos/windsurf.svg +1 -0
  150. package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
  151. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
  152. package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
  153. package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
  154. package/carbon/src/client/components/AppSidebar.tsx +760 -0
  155. package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
  156. package/carbon/src/client/components/CTAFooter.tsx +59 -0
  157. package/carbon/src/client/components/ComparisonSection.tsx +132 -0
  158. package/carbon/src/client/components/ContentPanel.tsx +66 -0
  159. package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
  160. package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
  161. package/carbon/src/client/components/FAQSection.tsx +76 -0
  162. package/carbon/src/client/components/FileUpload.tsx +210 -0
  163. package/carbon/src/client/components/HeaderActions.tsx +17 -0
  164. package/carbon/src/client/components/Hero.tsx +608 -0
  165. package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
  166. package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
  167. package/carbon/src/client/components/Logo.tsx +87 -0
  168. package/carbon/src/client/components/LogoStrip.tsx +76 -0
  169. package/carbon/src/client/components/MetricsStrip.tsx +48 -0
  170. package/carbon/src/client/components/Nav.tsx +195 -0
  171. package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
  172. package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
  173. package/carbon/src/client/components/PageHeader.tsx +24 -0
  174. package/carbon/src/client/components/PlanGate.tsx +36 -0
  175. package/carbon/src/client/components/PricingSection.tsx +371 -0
  176. package/carbon/src/client/components/SEO.tsx +34 -0
  177. package/carbon/src/client/components/SmoothScroll.tsx +45 -0
  178. package/carbon/src/client/components/TechStackSection.tsx +165 -0
  179. package/carbon/src/client/components/WorkflowSection.tsx +604 -0
  180. package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
  181. package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
  182. package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
  183. package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
  184. package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
  185. package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
  186. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
  187. package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
  188. package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
  189. package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
  190. package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
  191. package/carbon/src/client/components/effects/index.ts +2 -0
  192. package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
  193. package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
  194. package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
  195. package/carbon/src/client/components/scroll/index.ts +2 -0
  196. package/carbon/src/client/components/ui/accordion.tsx +71 -0
  197. package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
  198. package/carbon/src/client/components/ui/alert.tsx +73 -0
  199. package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
  200. package/carbon/src/client/components/ui/avatar.tsx +91 -0
  201. package/carbon/src/client/components/ui/badge.tsx +50 -0
  202. package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
  203. package/carbon/src/client/components/ui/button-group.tsx +78 -0
  204. package/carbon/src/client/components/ui/button.tsx +120 -0
  205. package/carbon/src/client/components/ui/calendar.tsx +182 -0
  206. package/carbon/src/client/components/ui/card.tsx +85 -0
  207. package/carbon/src/client/components/ui/carousel.tsx +227 -0
  208. package/carbon/src/client/components/ui/chart.tsx +357 -0
  209. package/carbon/src/client/components/ui/checkbox.tsx +27 -0
  210. package/carbon/src/client/components/ui/collapsible.tsx +15 -0
  211. package/carbon/src/client/components/ui/command.tsx +178 -0
  212. package/carbon/src/client/components/ui/context-menu.tsx +233 -0
  213. package/carbon/src/client/components/ui/dialog.tsx +132 -0
  214. package/carbon/src/client/components/ui/drawer.tsx +118 -0
  215. package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
  216. package/carbon/src/client/components/ui/empty.tsx +94 -0
  217. package/carbon/src/client/components/ui/field.tsx +226 -0
  218. package/carbon/src/client/components/ui/hover-card.tsx +44 -0
  219. package/carbon/src/client/components/ui/input-group.tsx +146 -0
  220. package/carbon/src/client/components/ui/input-otp.tsx +83 -0
  221. package/carbon/src/client/components/ui/input.tsx +20 -0
  222. package/carbon/src/client/components/ui/item.tsx +188 -0
  223. package/carbon/src/client/components/ui/kbd.tsx +26 -0
  224. package/carbon/src/client/components/ui/label.tsx +21 -0
  225. package/carbon/src/client/components/ui/menubar.tsx +250 -0
  226. package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
  227. package/carbon/src/client/components/ui/pagination.tsx +102 -0
  228. package/carbon/src/client/components/ui/popover.tsx +75 -0
  229. package/carbon/src/client/components/ui/progress.tsx +66 -0
  230. package/carbon/src/client/components/ui/radio-group.tsx +36 -0
  231. package/carbon/src/client/components/ui/resizable.tsx +46 -0
  232. package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
  233. package/carbon/src/client/components/ui/select.tsx +186 -0
  234. package/carbon/src/client/components/ui/separator.tsx +21 -0
  235. package/carbon/src/client/components/ui/sheet.tsx +124 -0
  236. package/carbon/src/client/components/ui/sidebar.tsx +702 -0
  237. package/carbon/src/client/components/ui/skeleton.tsx +13 -0
  238. package/carbon/src/client/components/ui/slider.tsx +57 -0
  239. package/carbon/src/client/components/ui/sonner.tsx +45 -0
  240. package/carbon/src/client/components/ui/spinner.tsx +15 -0
  241. package/carbon/src/client/components/ui/switch.tsx +30 -0
  242. package/carbon/src/client/components/ui/table.tsx +89 -0
  243. package/carbon/src/client/components/ui/tabs.tsx +73 -0
  244. package/carbon/src/client/components/ui/textarea.tsx +18 -0
  245. package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
  246. package/carbon/src/client/components/ui/toggle.tsx +44 -0
  247. package/carbon/src/client/components/ui/tooltip.tsx +56 -0
  248. package/carbon/src/client/hooks/api/index.ts +14 -0
  249. package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
  250. package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
  251. package/carbon/src/client/hooks/use-mobile.ts +21 -0
  252. package/carbon/src/client/hooks/useMousePosition.ts +91 -0
  253. package/carbon/src/client/hooks/useNotifications.tsx +124 -0
  254. package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
  255. package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
  256. package/carbon/src/client/hooks/usePackageManager.ts +69 -0
  257. package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
  258. package/carbon/src/client/hooks/useRunningServices.ts +114 -0
  259. package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
  260. package/carbon/src/client/index.css +467 -0
  261. package/carbon/src/client/index.html +56 -0
  262. package/carbon/src/client/lib/admin-services.ts +151 -0
  263. package/carbon/src/client/lib/api.ts +32 -0
  264. package/carbon/src/client/lib/blog.ts +35 -0
  265. package/carbon/src/client/lib/changelog.ts +33 -0
  266. package/carbon/src/client/lib/docs-search.ts +101 -0
  267. package/carbon/src/client/lib/docs.ts +37 -0
  268. package/carbon/src/client/lib/i18n.ts +32 -0
  269. package/carbon/src/client/lib/supabase.ts +72 -0
  270. package/carbon/src/client/lib/tailwind-colors.ts +357 -0
  271. package/carbon/src/client/lib/theme.ts +117 -0
  272. package/carbon/src/client/lib/utils.ts +22 -0
  273. package/carbon/src/client/locales/de.json +529 -0
  274. package/carbon/src/client/locales/en.json +461 -0
  275. package/carbon/src/client/locales/es.json +529 -0
  276. package/carbon/src/client/locales/fr.json +529 -0
  277. package/carbon/src/client/locales/pt.json +529 -0
  278. package/carbon/src/client/main.tsx +56 -0
  279. package/carbon/src/client/mdx.d.ts +13 -0
  280. package/carbon/src/client/pages/ApiDocs.tsx +76 -0
  281. package/carbon/src/client/pages/AuthCallback.tsx +34 -0
  282. package/carbon/src/client/pages/Blog.tsx +167 -0
  283. package/carbon/src/client/pages/Changelog.tsx +171 -0
  284. package/carbon/src/client/pages/Charts.tsx +388 -0
  285. package/carbon/src/client/pages/Checkout.tsx +227 -0
  286. package/carbon/src/client/pages/Contact.tsx +174 -0
  287. package/carbon/src/client/pages/Dashboard.tsx +368 -0
  288. package/carbon/src/client/pages/Docs.tsx +372 -0
  289. package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
  290. package/carbon/src/client/pages/Home.tsx +187 -0
  291. package/carbon/src/client/pages/Legal.tsx +100 -0
  292. package/carbon/src/client/pages/Login.tsx +408 -0
  293. package/carbon/src/client/pages/MFAVerify.tsx +156 -0
  294. package/carbon/src/client/pages/NotFound.tsx +21 -0
  295. package/carbon/src/client/pages/Onboarding.tsx +246 -0
  296. package/carbon/src/client/pages/ResetPassword.tsx +200 -0
  297. package/carbon/src/client/pages/UIComponents.tsx +390 -0
  298. package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
  299. package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
  300. package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
  301. package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
  302. package/carbon/src/client/pages/admin/Logs.tsx +18 -0
  303. package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
  304. package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
  305. package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
  306. package/carbon/src/client/pages/admin/Settings.tsx +314 -0
  307. package/carbon/src/client/pages/admin/Theme.tsx +465 -0
  308. package/carbon/src/client/pages/admin/Users.tsx +365 -0
  309. package/carbon/src/client/pages/api-docs.css +156 -0
  310. package/carbon/src/client/pages/organizations/Details.tsx +200 -0
  311. package/carbon/src/client/pages/organizations/Members.tsx +402 -0
  312. package/carbon/src/client/pages/settings/Billing.tsx +473 -0
  313. package/carbon/src/client/pages/settings/Profile.tsx +160 -0
  314. package/carbon/src/client/pages/settings/Security.tsx +341 -0
  315. package/carbon/src/client/public/favicon.svg +19 -0
  316. package/carbon/src/client/public/robots.txt +8 -0
  317. package/carbon/src/server/billing/index.ts +104 -0
  318. package/carbon/src/server/billing/provider.ts +81 -0
  319. package/carbon/src/server/billing/providers/paddle.ts +314 -0
  320. package/carbon/src/server/billing/providers/polar.ts +325 -0
  321. package/carbon/src/server/billing/providers/stripe.ts +233 -0
  322. package/carbon/src/server/emails/templates.ts +116 -0
  323. package/carbon/src/server/index.ts +554 -0
  324. package/carbon/src/server/lib/auth.ts +6 -0
  325. package/carbon/src/server/lib/email.ts +64 -0
  326. package/carbon/src/server/lib/env.ts +112 -0
  327. package/carbon/src/server/lib/errors.ts +21 -0
  328. package/carbon/src/server/lib/logger.ts +17 -0
  329. package/carbon/src/server/lib/rate-limiter.ts +288 -0
  330. package/carbon/src/server/lib/request.ts +34 -0
  331. package/carbon/src/server/lib/stripe.ts +42 -0
  332. package/carbon/src/server/lib/supabase.ts +65 -0
  333. package/carbon/src/server/middleware/requirePlan.ts +80 -0
  334. package/carbon/src/server/routes/_internal/services-status.ts +958 -0
  335. package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
  336. package/carbon/src/server/routes/health.ts +48 -0
  337. package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
  338. package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
  339. package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
  340. package/carbon/src/server/routes/v1/auth.ts +390 -0
  341. package/carbon/src/server/routes/v1/billing.ts +718 -0
  342. package/carbon/src/server/routes/v1/contact.ts +93 -0
  343. package/carbon/src/server/routes/v1/index.ts +1333 -0
  344. package/carbon/src/server/routes/v1/newsletter.ts +181 -0
  345. package/carbon/src/server/routes/v1/performance.ts +157 -0
  346. package/carbon/src/server/routes/v1/stats.ts +170 -0
  347. package/carbon/src/server/routes/v1/theme.ts +106 -0
  348. package/carbon/src/server/routes/webhooks/billing.ts +376 -0
  349. package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
  350. package/carbon/src/server/types.ts +11 -0
  351. package/carbon/src/shared/pricing.ts +155 -0
  352. package/carbon/src/shared/types.ts +338 -0
  353. package/carbon/supabase/migrations/00001_init.sql +717 -0
  354. package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
  355. package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
  356. package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
  357. package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
  358. package/carbon/supabase/seed.sql +16 -0
  359. package/carbon/tests/_helpers/app.ts +45 -0
  360. package/carbon/tests/_helpers/env.ts +37 -0
  361. package/carbon/tests/_helpers/factories.ts +69 -0
  362. package/carbon/tests/_helpers/jwt.ts +23 -0
  363. package/carbon/tests/_helpers/setup-integration.ts +20 -0
  364. package/carbon/tests/_helpers/setup-rtl.ts +12 -0
  365. package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
  366. package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
  367. package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
  368. package/carbon/tests/integration/server/routes/health.test.ts +61 -0
  369. package/carbon/tests/structural/i18n-parity.test.ts +42 -0
  370. package/carbon/tests/unit/client/utils.test.ts +49 -0
  371. package/carbon/tests/unit/shared/pricing.test.ts +93 -0
  372. package/carbon/tsconfig.json +27 -0
  373. package/carbon/tsconfig.server.json +27 -0
  374. package/carbon/tsconfig.test.json +9 -0
  375. package/carbon/vite.config.ts +110 -0
  376. package/carbon/vitest.config.ts +74 -0
  377. package/carbon/volumes/db/jwt.sql +57 -0
  378. package/carbon/volumes/db/metabase-init.sh +29 -0
  379. package/carbon/volumes/db/n8n-init.sh +25 -0
  380. package/carbon/volumes/db/realtime.sql +33 -0
  381. package/carbon/volumes/db/roles.sql +93 -0
  382. package/carbon/volumes/db/set-passwords.sh +12 -0
  383. package/carbon/volumes/db/super-admin.dev.sql +113 -0
  384. package/carbon/volumes/db/super-admin.generated.sql +113 -0
  385. package/carbon/volumes/db/super-admin.sql +114 -0
  386. package/carbon/volumes/grafana/dashboards/logs.json +179 -0
  387. package/carbon/volumes/grafana/dashboards/overview.json +523 -0
  388. package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
  389. package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
  390. package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
  391. package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
  392. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
  393. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
  394. package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
  395. package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
  396. package/carbon/volumes/kong/kong.yml +208 -0
  397. package/carbon/volumes/loki/loki-config.yml +58 -0
  398. package/carbon/volumes/n8n/hooks.js +131 -0
  399. package/carbon/volumes/n8n/scripts/setup.sh +66 -0
  400. package/carbon/volumes/prometheus/prometheus.yml +43 -0
  401. package/carbon/volumes/promtail/promtail-config.yml +64 -0
  402. package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
  403. package/carbon/volumes/traefik/middlewares.yml +95 -0
  404. package/carbon/volumes/traefik/vite-dev.yml +20 -0
  405. package/package.json +95 -0
  406. package/src/access.js +354 -0
  407. package/src/activate.js +187 -0
  408. package/src/add.js +718 -0
  409. package/src/backup.js +786 -0
  410. package/src/cli.js +350 -0
  411. package/src/configure.js +967 -0
  412. package/src/console.js +155 -0
  413. package/src/create.js +1499 -0
  414. package/src/deploy.js +311 -0
  415. package/src/destroy.js +2033 -0
  416. package/src/diagnose.js +735 -0
  417. package/src/down.js +80 -0
  418. package/src/failover.js +1032 -0
  419. package/src/lib/backup-s3.js +179 -0
  420. package/src/lib/build.js +33 -0
  421. package/src/lib/checksum.js +28 -0
  422. package/src/lib/ci-setup.js +666 -0
  423. package/src/lib/cli/help.js +129 -0
  424. package/src/lib/cli/parse-flags.js +160 -0
  425. package/src/lib/cli/select-action.js +65 -0
  426. package/src/lib/cli/select-environment.js +108 -0
  427. package/src/lib/cli/tty-guard.js +75 -0
  428. package/src/lib/cloudflare.js +447 -0
  429. package/src/lib/colors.js +52 -0
  430. package/src/lib/command.js +361 -0
  431. package/src/lib/config.js +359 -0
  432. package/src/lib/cost.js +103 -0
  433. package/src/lib/deploy/bundle.js +231 -0
  434. package/src/lib/deploy/compose/acme-verify.js +121 -0
  435. package/src/lib/deploy/compose/build-args.js +78 -0
  436. package/src/lib/deploy/compose/ha.js +1874 -0
  437. package/src/lib/deploy/compose/index.js +1294 -0
  438. package/src/lib/deploy/compose/reconcile.js +74 -0
  439. package/src/lib/deploy/github.js +382 -0
  440. package/src/lib/deploy/image.js +191 -0
  441. package/src/lib/deploy/index.js +119 -0
  442. package/src/lib/deploy/k8s/LICENSE +99 -0
  443. package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
  444. package/src/lib/deploy/k8s/ha/index.js +1000 -0
  445. package/src/lib/deploy/k8s/index.js +62 -0
  446. package/src/lib/deploy/k8s/k3s.js +2515 -0
  447. package/src/lib/deploy/orchestrator.js +1401 -0
  448. package/src/lib/deploy/prompts.js +545 -0
  449. package/src/lib/deploy/remote-build.js +130 -0
  450. package/src/lib/deploy/state.js +120 -0
  451. package/src/lib/deploy/utils.js +328 -0
  452. package/src/lib/deploy-logger.js +93 -0
  453. package/src/lib/dns-propagation.js +48 -0
  454. package/src/lib/environment.js +58 -0
  455. package/src/lib/fetch-retry.js +103 -0
  456. package/src/lib/github-environments.js +335 -0
  457. package/src/lib/hetzner-dns.js +377 -0
  458. package/src/lib/hetzner-guided-setup.js +275 -0
  459. package/src/lib/host-keys.js +37 -0
  460. package/src/lib/iac/cloud-init.js +52 -0
  461. package/src/lib/iac/index.js +325 -0
  462. package/src/lib/iac/programs/hetzner-compose.js +130 -0
  463. package/src/lib/iac/programs/hetzner-k8s.js +320 -0
  464. package/src/lib/kubectl.js +81 -0
  465. package/src/lib/licensing/index.js +259 -0
  466. package/src/lib/licensing/tiers.js +181 -0
  467. package/src/lib/licensing/validator.js +171 -0
  468. package/src/lib/merge-package-json.js +90 -0
  469. package/src/lib/operator-ip.js +381 -0
  470. package/src/lib/package-manager.js +111 -0
  471. package/src/lib/perf.js +71 -0
  472. package/src/lib/project-guard.js +39 -0
  473. package/src/lib/project.js +334 -0
  474. package/src/lib/providers/base.js +276 -0
  475. package/src/lib/providers/hetzner-s3.js +656 -0
  476. package/src/lib/providers/hetzner.js +755 -0
  477. package/src/lib/providers/index.js +164 -0
  478. package/src/lib/s3.js +510 -0
  479. package/src/lib/secret-scan.js +583 -0
  480. package/src/lib/secrets.js +63 -0
  481. package/src/lib/server-types.js +195 -0
  482. package/src/lib/shell.js +91 -0
  483. package/src/lib/ssh.js +241 -0
  484. package/src/lib/tracker.js +242 -0
  485. package/src/lib/upgrade-policy.js +170 -0
  486. package/src/lib/validators.js +105 -0
  487. package/src/lib/version.js +5 -0
  488. package/src/remove.js +292 -0
  489. package/src/reset.js +97 -0
  490. package/src/restore.js +871 -0
  491. package/src/scale.js +1734 -0
  492. package/src/shell.js +222 -0
  493. package/src/status.js +981 -0
  494. package/src/up.js +264 -0
  495. package/src/upgrade.js +721 -0
@@ -0,0 +1,717 @@
1
+ -- Vibecarbon Initial Migration
2
+ -- Complete database schema with multi-tenancy, billing, notifications, security, and storage
3
+ --
4
+ -- Tables:
5
+ -- - organizations: Multi-tenant organization management
6
+ -- - memberships: User-organization relationships
7
+ -- - customers: Stripe billing customers
8
+ -- - subscriptions: Stripe subscriptions
9
+ -- - notifications: System-wide notifications (with visibility controls)
10
+ -- - notification_dismissals: User dismissal tracking
11
+ -- - failed_login_attempts: Brute force protection
12
+ -- - app_settings: Global application settings
13
+ --
14
+ -- Storage Buckets:
15
+ -- - avatars: Public bucket for user profile pictures (2MB, images only)
16
+ -- - uploads: Private bucket for general file uploads (10MB)
17
+
18
+ -- ============================================================================
19
+ -- UPDATED_AT TRIGGER FUNCTION (used by multiple tables)
20
+ -- ============================================================================
21
+
22
+ CREATE OR REPLACE FUNCTION update_updated_at()
23
+ RETURNS TRIGGER
24
+ LANGUAGE plpgsql
25
+ SET search_path = ''
26
+ AS $$
27
+ BEGIN
28
+ NEW.updated_at = NOW();
29
+ RETURN NEW;
30
+ END;
31
+ $$;
32
+
33
+ -- ============================================================================
34
+ -- ORGANIZATIONS TABLE
35
+ -- ============================================================================
36
+
37
+ CREATE TABLE IF NOT EXISTS organizations (
38
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
39
+ name TEXT NOT NULL,
40
+ slug TEXT UNIQUE NOT NULL,
41
+ plan TEXT DEFAULT 'FREE' CHECK (plan IN ('FREE', 'STARTER', 'PRO', 'ENTERPRISE')),
42
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
43
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
44
+ );
45
+
46
+ -- No index needed for slug — the UNIQUE constraint already creates one.
47
+
48
+ -- ============================================================================
49
+ -- MEMBERSHIPS TABLE
50
+ -- ============================================================================
51
+
52
+ CREATE TABLE IF NOT EXISTS memberships (
53
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
54
+ role TEXT DEFAULT 'MEMBER' CHECK (role IN ('OWNER', 'ADMIN', 'MEMBER')),
55
+ user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
56
+ organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
57
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
58
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
59
+ UNIQUE(user_id, organization_id)
60
+ );
61
+
62
+ CREATE INDEX IF NOT EXISTS idx_memberships_user_id ON memberships(user_id);
63
+ CREATE INDEX IF NOT EXISTS idx_memberships_organization_id ON memberships(organization_id);
64
+
65
+ -- ============================================================================
66
+ -- BILLING TABLES (Stripe integration)
67
+ -- ============================================================================
68
+
69
+ CREATE TABLE IF NOT EXISTS customers (
70
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
71
+ user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
72
+ organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
73
+ stripe_customer_id TEXT NOT NULL UNIQUE,
74
+ email TEXT,
75
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
76
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
77
+ CONSTRAINT customer_type CHECK (
78
+ (user_id IS NOT NULL AND organization_id IS NULL) OR
79
+ (user_id IS NULL AND organization_id IS NOT NULL)
80
+ )
81
+ );
82
+
83
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_user_id ON customers(user_id) WHERE user_id IS NOT NULL;
84
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_organization_id ON customers(organization_id) WHERE organization_id IS NOT NULL;
85
+ CREATE INDEX IF NOT EXISTS idx_customers_stripe_customer_id ON customers(stripe_customer_id);
86
+
87
+ CREATE TABLE IF NOT EXISTS subscriptions (
88
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
89
+ customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
90
+ stripe_subscription_id TEXT NOT NULL UNIQUE,
91
+ stripe_price_id TEXT NOT NULL,
92
+ status TEXT NOT NULL CHECK (status IN ('active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'trialing', 'unpaid', 'paused')),
93
+ current_period_start TIMESTAMPTZ,
94
+ current_period_end TIMESTAMPTZ,
95
+ cancel_at_period_end BOOLEAN DEFAULT FALSE NOT NULL,
96
+ canceled_at TIMESTAMPTZ,
97
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
98
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
99
+ );
100
+
101
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_customer_id ON subscriptions(customer_id);
102
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_subscription_id ON subscriptions(stripe_subscription_id);
103
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
104
+
105
+ -- ============================================================================
106
+ -- NOTIFICATIONS TABLES
107
+ -- ============================================================================
108
+
109
+ CREATE TABLE IF NOT EXISTS notifications (
110
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
111
+ title TEXT NOT NULL,
112
+ message TEXT,
113
+ type TEXT DEFAULT 'info' CHECK (type IN ('info', 'warning', 'error', 'success')),
114
+ visibility TEXT DEFAULT 'all' CHECK (visibility IN ('all', 'authenticated', 'public')),
115
+ dismissible BOOLEAN DEFAULT true,
116
+ organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
117
+ starts_at TIMESTAMPTZ DEFAULT NOW(),
118
+ ends_at TIMESTAMPTZ,
119
+ action_label TEXT,
120
+ action_url TEXT,
121
+ is_active BOOLEAN DEFAULT true,
122
+ created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
123
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
124
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
125
+ );
126
+
127
+ CREATE INDEX IF NOT EXISTS idx_notifications_active
128
+ ON notifications(is_active, starts_at, ends_at)
129
+ WHERE is_active = true;
130
+
131
+ CREATE INDEX IF NOT EXISTS idx_notifications_org
132
+ ON notifications(organization_id)
133
+ WHERE organization_id IS NOT NULL;
134
+
135
+ CREATE TABLE IF NOT EXISTS notification_dismissals (
136
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
137
+ notification_id UUID NOT NULL REFERENCES notifications(id) ON DELETE CASCADE,
138
+ user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
139
+ dismissed_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
140
+ UNIQUE(notification_id, user_id)
141
+ );
142
+
143
+ CREATE INDEX IF NOT EXISTS idx_notification_dismissals_user
144
+ ON notification_dismissals(user_id, notification_id);
145
+
146
+ -- ============================================================================
147
+ -- AUTH SECURITY TABLES
148
+ -- ============================================================================
149
+
150
+ CREATE TABLE IF NOT EXISTS failed_login_attempts (
151
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
152
+ email TEXT NOT NULL,
153
+ ip_address INET NOT NULL,
154
+ attempted_at TIMESTAMPTZ DEFAULT NOW()
155
+ );
156
+
157
+ CREATE INDEX IF NOT EXISTS idx_failed_login_attempts_lookup
158
+ ON failed_login_attempts(email, ip_address, attempted_at DESC);
159
+
160
+ CREATE INDEX IF NOT EXISTS idx_failed_login_attempts_time
161
+ ON failed_login_attempts(attempted_at);
162
+
163
+ CREATE TABLE IF NOT EXISTS app_settings (
164
+ key TEXT PRIMARY KEY,
165
+ value JSONB NOT NULL DEFAULT '{}'::jsonb,
166
+ updated_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
167
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
168
+ );
169
+
170
+ -- WARNING: This table is publicly readable by the anonymous role to support
171
+ -- pre-authentication flows (e.g. checking if MFA is enforced).
172
+ -- DO NOT STORE SECRETS, TOKENS, OR SENSITIVE ENVIRONMENT VARIABLES HERE.
173
+ -- Public access is strictly controlled via RLS policy below.
174
+
175
+ -- Grant access to anon since we removed the default global grant
176
+ GRANT SELECT ON app_settings TO anon;
177
+
178
+ INSERT INTO app_settings (key, value)
179
+ VALUES ('mfa_enabled', '{"enabled": false}'::jsonb)
180
+ ON CONFLICT (key) DO NOTHING;
181
+
182
+ -- ============================================================================
183
+ -- ROW LEVEL SECURITY (RLS) - Enable on all tables
184
+ -- ============================================================================
185
+
186
+ ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
187
+ ALTER TABLE memberships ENABLE ROW LEVEL SECURITY;
188
+ ALTER TABLE customers ENABLE ROW LEVEL SECURITY;
189
+ ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
190
+ ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
191
+ ALTER TABLE notification_dismissals ENABLE ROW LEVEL SECURITY;
192
+ ALTER TABLE failed_login_attempts ENABLE ROW LEVEL SECURITY;
193
+ ALTER TABLE app_settings ENABLE ROW LEVEL SECURITY;
194
+
195
+ -- ============================================================================
196
+ -- HELPER FUNCTIONS (SECURITY DEFINER to bypass RLS for policy checks)
197
+ -- ============================================================================
198
+
199
+ -- Get organization IDs for current user
200
+ CREATE OR REPLACE FUNCTION get_user_org_ids()
201
+ RETURNS SETOF UUID
202
+ LANGUAGE sql
203
+ SECURITY DEFINER
204
+ STABLE
205
+ SET search_path = ''
206
+ AS $$
207
+ SELECT organization_id FROM public.memberships WHERE user_id = auth.uid();
208
+ $$;
209
+
210
+ -- Get organization IDs where user is owner/admin
211
+ CREATE OR REPLACE FUNCTION get_user_admin_org_ids()
212
+ RETURNS SETOF UUID
213
+ LANGUAGE sql
214
+ SECURITY DEFINER
215
+ STABLE
216
+ SET search_path = ''
217
+ AS $$
218
+ SELECT organization_id FROM public.memberships
219
+ WHERE user_id = auth.uid() AND role IN ('OWNER', 'ADMIN');
220
+ $$;
221
+
222
+ -- Get organization IDs where user is owner
223
+ CREATE OR REPLACE FUNCTION get_user_owner_org_ids()
224
+ RETURNS SETOF UUID
225
+ LANGUAGE sql
226
+ SECURITY DEFINER
227
+ STABLE
228
+ SET search_path = ''
229
+ AS $$
230
+ SELECT organization_id FROM public.memberships
231
+ WHERE user_id = auth.uid() AND role = 'OWNER';
232
+ $$;
233
+
234
+ -- Get customer IDs for the current user
235
+ CREATE OR REPLACE FUNCTION get_user_customer_ids()
236
+ RETURNS SETOF UUID
237
+ LANGUAGE sql
238
+ STABLE
239
+ SECURITY DEFINER
240
+ SET search_path = ''
241
+ AS $$
242
+ SELECT c.id FROM public.customers c
243
+ WHERE c.user_id = (SELECT auth.uid())
244
+ UNION
245
+ SELECT c.id FROM public.customers c
246
+ WHERE c.organization_id IN (SELECT public.get_user_org_ids());
247
+ $$;
248
+
249
+ -- Check if user is a super admin
250
+ CREATE OR REPLACE FUNCTION is_super_admin()
251
+ RETURNS BOOLEAN
252
+ LANGUAGE sql
253
+ SECURITY DEFINER
254
+ STABLE
255
+ SET search_path = ''
256
+ AS $$
257
+ SELECT COALESCE(
258
+ (SELECT auth.jwt() -> 'app_metadata' ->> 'role') = 'super_admin',
259
+ false
260
+ );
261
+ $$;
262
+
263
+ -- ============================================================================
264
+ -- ORGANIZATIONS POLICIES
265
+ -- ============================================================================
266
+
267
+ DROP POLICY IF EXISTS "Users can view their organizations" ON organizations;
268
+ CREATE POLICY "Users can view their organizations"
269
+ ON organizations FOR SELECT
270
+ USING (id IN (SELECT get_user_org_ids()));
271
+
272
+ DROP POLICY IF EXISTS "Authenticated users can create organizations" ON organizations;
273
+ CREATE POLICY "Authenticated users can create organizations"
274
+ ON organizations FOR INSERT
275
+ WITH CHECK ((SELECT auth.uid()) IS NOT NULL);
276
+
277
+ DROP POLICY IF EXISTS "Owners and admins can update organizations" ON organizations;
278
+ CREATE POLICY "Owners and admins can update organizations"
279
+ ON organizations FOR UPDATE
280
+ USING (id IN (SELECT get_user_admin_org_ids()));
281
+
282
+ DROP POLICY IF EXISTS "Owners can delete organizations" ON organizations;
283
+ CREATE POLICY "Owners can delete organizations"
284
+ ON organizations FOR DELETE
285
+ USING (id IN (SELECT get_user_owner_org_ids()));
286
+
287
+ -- ============================================================================
288
+ -- MEMBERSHIPS POLICIES
289
+ -- ============================================================================
290
+
291
+ DROP POLICY IF EXISTS "Users can view memberships in their organizations" ON memberships;
292
+ CREATE POLICY "Users can view memberships in their organizations"
293
+ ON memberships FOR SELECT
294
+ USING (organization_id IN (SELECT get_user_org_ids()));
295
+
296
+ DROP POLICY IF EXISTS "Owners and admins can add members" ON memberships;
297
+ DROP POLICY IF EXISTS "Admins can add non-owner members" ON memberships;
298
+ CREATE POLICY "Admins can add non-owner members"
299
+ ON memberships FOR INSERT
300
+ WITH CHECK (
301
+ organization_id IN (SELECT get_user_admin_org_ids())
302
+ AND role IN ('ADMIN', 'MEMBER')
303
+ );
304
+
305
+ DROP POLICY IF EXISTS "Owners can add any member" ON memberships;
306
+ CREATE POLICY "Owners can add any member"
307
+ ON memberships FOR INSERT
308
+ WITH CHECK (organization_id IN (SELECT get_user_owner_org_ids()));
309
+
310
+ DROP POLICY IF EXISTS "Owners and admins can update memberships" ON memberships;
311
+ DROP POLICY IF EXISTS "Admins can update non-owner memberships" ON memberships;
312
+ CREATE POLICY "Admins can update non-owner memberships"
313
+ ON memberships FOR UPDATE
314
+ USING (
315
+ organization_id IN (SELECT get_user_admin_org_ids())
316
+ AND role <> 'OWNER'
317
+ )
318
+ WITH CHECK (role <> 'OWNER');
319
+
320
+ DROP POLICY IF EXISTS "Owners can update any membership" ON memberships;
321
+ CREATE POLICY "Owners can update any membership"
322
+ ON memberships FOR UPDATE
323
+ USING (organization_id IN (SELECT get_user_owner_org_ids()));
324
+
325
+ DROP POLICY IF EXISTS "Users can leave or be removed by owners/admins" ON memberships;
326
+ CREATE POLICY "Users can leave or be removed by owners/admins"
327
+ ON memberships FOR DELETE
328
+ USING (
329
+ user_id = (SELECT auth.uid())
330
+ OR organization_id IN (SELECT get_user_admin_org_ids())
331
+ );
332
+
333
+ -- ============================================================================
334
+ -- CUSTOMERS POLICIES (Billing)
335
+ -- ============================================================================
336
+
337
+ DROP POLICY IF EXISTS "Users can view their own customer records" ON customers;
338
+ CREATE POLICY "Users can view their own customer records"
339
+ ON customers FOR SELECT
340
+ USING (
341
+ user_id = (SELECT auth.uid()) OR
342
+ organization_id IN (SELECT get_user_org_ids())
343
+ );
344
+
345
+ DROP POLICY IF EXISTS "Users can create customer records for themselves" ON customers;
346
+ CREATE POLICY "Users can create customer records for themselves"
347
+ ON customers FOR INSERT
348
+ WITH CHECK (
349
+ user_id = (SELECT auth.uid()) OR
350
+ organization_id IN (SELECT get_user_admin_org_ids())
351
+ );
352
+
353
+ -- ============================================================================
354
+ -- SUBSCRIPTIONS POLICIES
355
+ -- ============================================================================
356
+
357
+ DROP POLICY IF EXISTS "Users can view their own subscriptions" ON subscriptions;
358
+ CREATE POLICY "Users can view their own subscriptions"
359
+ ON subscriptions FOR SELECT
360
+ USING (customer_id IN (SELECT get_user_customer_ids()));
361
+
362
+ -- Note: Subscriptions are created/updated via webhooks using service role key
363
+
364
+ -- ============================================================================
365
+ -- NOTIFICATIONS POLICIES
366
+ -- ============================================================================
367
+
368
+ DROP POLICY IF EXISTS "Authenticated users can view relevant notifications" ON notifications;
369
+ CREATE POLICY "Authenticated users can view relevant notifications"
370
+ ON notifications FOR SELECT
371
+ TO authenticated
372
+ USING (
373
+ is_active = true
374
+ AND (starts_at IS NULL OR starts_at <= NOW())
375
+ AND (ends_at IS NULL OR ends_at > NOW())
376
+ AND (visibility IN ('all', 'authenticated'))
377
+ AND (
378
+ organization_id IS NULL
379
+ OR organization_id IN (SELECT get_user_org_ids())
380
+ )
381
+ );
382
+
383
+ DROP POLICY IF EXISTS "Anonymous users can view public notifications" ON notifications;
384
+ CREATE POLICY "Anonymous users can view public notifications"
385
+ ON notifications FOR SELECT
386
+ TO anon
387
+ USING (
388
+ is_active = true
389
+ AND (starts_at IS NULL OR starts_at <= NOW())
390
+ AND (ends_at IS NULL OR ends_at > NOW())
391
+ AND (visibility IN ('all', 'public'))
392
+ AND organization_id IS NULL
393
+ );
394
+
395
+ DROP POLICY IF EXISTS "Super admins can create notifications" ON notifications;
396
+ CREATE POLICY "Super admins can create notifications"
397
+ ON notifications FOR INSERT
398
+ WITH CHECK (is_super_admin());
399
+
400
+ DROP POLICY IF EXISTS "Super admins can update notifications" ON notifications;
401
+ CREATE POLICY "Super admins can update notifications"
402
+ ON notifications FOR UPDATE
403
+ USING (is_super_admin());
404
+
405
+ DROP POLICY IF EXISTS "Super admins can delete notifications" ON notifications;
406
+ CREATE POLICY "Super admins can delete notifications"
407
+ ON notifications FOR DELETE
408
+ USING (is_super_admin());
409
+
410
+ -- ============================================================================
411
+ -- NOTIFICATION DISMISSALS POLICIES
412
+ -- ============================================================================
413
+
414
+ DROP POLICY IF EXISTS "Users can view their dismissals" ON notification_dismissals;
415
+ CREATE POLICY "Users can view their dismissals"
416
+ ON notification_dismissals FOR SELECT
417
+ USING (user_id = (SELECT auth.uid()));
418
+
419
+ DROP POLICY IF EXISTS "Users can dismiss notifications" ON notification_dismissals;
420
+ CREATE POLICY "Users can dismiss notifications"
421
+ ON notification_dismissals FOR INSERT
422
+ WITH CHECK (user_id = (SELECT auth.uid()));
423
+
424
+ DROP POLICY IF EXISTS "Users can remove their dismissals" ON notification_dismissals;
425
+ CREATE POLICY "Users can remove their dismissals"
426
+ ON notification_dismissals FOR DELETE
427
+ USING (user_id = (SELECT auth.uid()));
428
+
429
+ -- ============================================================================
430
+ -- APP SETTINGS POLICIES
431
+ -- ============================================================================
432
+
433
+ -- Public access is limited to a whitelist of safe keys. Add new keys here as needed.
434
+ DROP POLICY IF EXISTS "Anyone can read app settings" ON app_settings;
435
+ DROP POLICY IF EXISTS "Public can read whitelisted settings" ON app_settings;
436
+ CREATE POLICY "Public can read whitelisted settings"
437
+ ON app_settings FOR SELECT
438
+ USING (key IN ('mfa_enabled'));
439
+
440
+ DROP POLICY IF EXISTS "Super admins can read all settings" ON app_settings;
441
+ CREATE POLICY "Super admins can read all settings"
442
+ ON app_settings FOR SELECT
443
+ USING (is_super_admin());
444
+
445
+ DROP POLICY IF EXISTS "Super admins can update app settings" ON app_settings;
446
+ CREATE POLICY "Super admins can update app settings"
447
+ ON app_settings FOR UPDATE
448
+ USING (is_super_admin());
449
+
450
+ DROP POLICY IF EXISTS "Super admins can insert app settings" ON app_settings;
451
+ CREATE POLICY "Super admins can insert app settings"
452
+ ON app_settings FOR INSERT
453
+ WITH CHECK (is_super_admin());
454
+
455
+ -- ============================================================================
456
+ -- SECURITY HELPER FUNCTIONS
457
+ -- ============================================================================
458
+
459
+ -- Check if an account is locked due to too many failed attempts
460
+ CREATE OR REPLACE FUNCTION check_account_lockout(
461
+ p_email TEXT,
462
+ p_ip_address INET,
463
+ p_max_attempts INTEGER DEFAULT 5,
464
+ p_lockout_minutes INTEGER DEFAULT 15
465
+ )
466
+ RETURNS JSONB
467
+ LANGUAGE plpgsql
468
+ SECURITY DEFINER
469
+ STABLE
470
+ SET search_path = ''
471
+ AS $$
472
+ DECLARE
473
+ attempt_count INTEGER;
474
+ oldest_relevant TIMESTAMPTZ;
475
+ minutes_remaining INTEGER;
476
+ BEGIN
477
+ SELECT COUNT(*), MIN(attempted_at)
478
+ INTO attempt_count, oldest_relevant
479
+ FROM public.failed_login_attempts
480
+ WHERE email = LOWER(p_email)
481
+ AND ip_address = p_ip_address
482
+ AND attempted_at > NOW() - (p_lockout_minutes || ' minutes')::INTERVAL;
483
+
484
+ IF attempt_count >= p_max_attempts THEN
485
+ minutes_remaining := GREATEST(
486
+ 0,
487
+ p_lockout_minutes - EXTRACT(EPOCH FROM (NOW() - oldest_relevant))::INTEGER / 60
488
+ );
489
+ RETURN jsonb_build_object('locked', true, 'remaining_minutes', minutes_remaining);
490
+ END IF;
491
+
492
+ RETURN jsonb_build_object('locked', false, 'remaining_minutes', 0);
493
+ END;
494
+ $$;
495
+
496
+ -- Record a failed login attempt
497
+ CREATE OR REPLACE FUNCTION record_failed_login(
498
+ p_email TEXT,
499
+ p_ip_address INET
500
+ )
501
+ RETURNS VOID
502
+ LANGUAGE sql
503
+ SECURITY DEFINER
504
+ SET search_path = ''
505
+ AS $$
506
+ INSERT INTO public.failed_login_attempts (email, ip_address)
507
+ VALUES (LOWER(p_email), p_ip_address);
508
+ $$;
509
+
510
+ -- Clear failed login attempts on successful login
511
+ CREATE OR REPLACE FUNCTION clear_failed_logins(
512
+ p_email TEXT,
513
+ p_ip_address INET
514
+ )
515
+ RETURNS VOID
516
+ LANGUAGE sql
517
+ SECURITY DEFINER
518
+ SET search_path = ''
519
+ AS $$
520
+ DELETE FROM public.failed_login_attempts
521
+ WHERE email = LOWER(p_email) AND ip_address = p_ip_address;
522
+ $$;
523
+
524
+ -- Admin function to unlock an account
525
+ CREATE OR REPLACE FUNCTION admin_unlock_account(p_email TEXT)
526
+ RETURNS VOID
527
+ LANGUAGE sql
528
+ SECURITY DEFINER
529
+ SET search_path = ''
530
+ AS $$
531
+ DELETE FROM public.failed_login_attempts WHERE email = LOWER(p_email);
532
+ $$;
533
+
534
+ -- Cleanup old failed login attempts
535
+ CREATE OR REPLACE FUNCTION cleanup_old_login_attempts(
536
+ p_retention_hours INTEGER DEFAULT 24
537
+ )
538
+ RETURNS INTEGER
539
+ LANGUAGE plpgsql
540
+ SECURITY DEFINER
541
+ SET search_path = ''
542
+ AS $$
543
+ DECLARE
544
+ deleted_count INTEGER;
545
+ BEGIN
546
+ DELETE FROM public.failed_login_attempts
547
+ WHERE attempted_at < NOW() - (p_retention_hours || ' hours')::INTERVAL;
548
+
549
+ GET DIAGNOSTICS deleted_count = ROW_COUNT;
550
+ RETURN deleted_count;
551
+ END;
552
+ $$;
553
+
554
+ -- Get lockout status for admin view
555
+ CREATE OR REPLACE FUNCTION get_locked_accounts(
556
+ p_max_attempts INTEGER DEFAULT 5,
557
+ p_lockout_minutes INTEGER DEFAULT 15
558
+ )
559
+ RETURNS TABLE (
560
+ email TEXT,
561
+ attempt_count BIGINT,
562
+ first_attempt TIMESTAMPTZ,
563
+ last_attempt TIMESTAMPTZ
564
+ )
565
+ LANGUAGE sql
566
+ SECURITY DEFINER
567
+ STABLE
568
+ SET search_path = ''
569
+ AS $$
570
+ SELECT
571
+ email,
572
+ COUNT(*) as attempt_count,
573
+ MIN(attempted_at) as first_attempt,
574
+ MAX(attempted_at) as last_attempt
575
+ FROM public.failed_login_attempts
576
+ WHERE attempted_at > NOW() - (p_lockout_minutes || ' minutes')::INTERVAL
577
+ GROUP BY email
578
+ HAVING COUNT(*) >= p_max_attempts
579
+ ORDER BY last_attempt DESC;
580
+ $$;
581
+
582
+ -- These functions are called server-side via the service role key (adminDb).
583
+ -- Revoke direct execution by end users to prevent abuse.
584
+ REVOKE EXECUTE ON FUNCTION check_account_lockout(TEXT, INET, INTEGER, INTEGER) FROM PUBLIC, anon, authenticated;
585
+ REVOKE EXECUTE ON FUNCTION record_failed_login(TEXT, INET) FROM PUBLIC, anon, authenticated;
586
+ REVOKE EXECUTE ON FUNCTION clear_failed_logins(TEXT, INET) FROM PUBLIC, anon, authenticated;
587
+ REVOKE EXECUTE ON FUNCTION admin_unlock_account(TEXT) FROM PUBLIC, anon, authenticated;
588
+ REVOKE EXECUTE ON FUNCTION cleanup_old_login_attempts(INTEGER) FROM PUBLIC, anon, authenticated;
589
+ REVOKE EXECUTE ON FUNCTION get_locked_accounts(INTEGER, INTEGER) FROM PUBLIC, anon, authenticated;
590
+
591
+ -- ============================================================================
592
+ -- TRIGGERS
593
+ -- ============================================================================
594
+
595
+ DROP TRIGGER IF EXISTS organizations_updated_at ON organizations;
596
+ CREATE TRIGGER organizations_updated_at
597
+ BEFORE UPDATE ON organizations
598
+ FOR EACH ROW
599
+ EXECUTE FUNCTION update_updated_at();
600
+
601
+ DROP TRIGGER IF EXISTS memberships_updated_at ON memberships;
602
+ CREATE TRIGGER memberships_updated_at
603
+ BEFORE UPDATE ON memberships
604
+ FOR EACH ROW
605
+ EXECUTE FUNCTION update_updated_at();
606
+
607
+ DROP TRIGGER IF EXISTS update_customers_updated_at ON customers;
608
+ CREATE TRIGGER update_customers_updated_at
609
+ BEFORE UPDATE ON customers
610
+ FOR EACH ROW
611
+ EXECUTE FUNCTION update_updated_at();
612
+
613
+ DROP TRIGGER IF EXISTS update_subscriptions_updated_at ON subscriptions;
614
+ CREATE TRIGGER update_subscriptions_updated_at
615
+ BEFORE UPDATE ON subscriptions
616
+ FOR EACH ROW
617
+ EXECUTE FUNCTION update_updated_at();
618
+
619
+ DROP TRIGGER IF EXISTS notifications_updated_at ON notifications;
620
+ CREATE TRIGGER notifications_updated_at
621
+ BEFORE UPDATE ON notifications
622
+ FOR EACH ROW
623
+ EXECUTE FUNCTION update_updated_at();
624
+
625
+ DROP TRIGGER IF EXISTS app_settings_updated_at ON app_settings;
626
+ CREATE TRIGGER app_settings_updated_at
627
+ BEFORE UPDATE ON app_settings
628
+ FOR EACH ROW
629
+ EXECUTE FUNCTION update_updated_at();
630
+
631
+ -- ============================================================================
632
+ -- STORAGE BUCKETS
633
+ -- ============================================================================
634
+
635
+ -- Avatars bucket - public, for user profile pictures
636
+ INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
637
+ VALUES (
638
+ 'avatars',
639
+ 'avatars',
640
+ true,
641
+ 2097152, -- 2MB
642
+ ARRAY['image/jpeg', 'image/png', 'image/gif', 'image/webp']
643
+ )
644
+ ON CONFLICT (id) DO NOTHING;
645
+
646
+ -- Uploads bucket - private, for general file uploads
647
+ INSERT INTO storage.buckets (id, name, public, file_size_limit)
648
+ VALUES (
649
+ 'uploads',
650
+ 'uploads',
651
+ false,
652
+ 10485760 -- 10MB
653
+ )
654
+ ON CONFLICT (id) DO NOTHING;
655
+
656
+ -- RLS policies for avatars bucket
657
+ DROP POLICY IF EXISTS "Anyone can view avatars" ON storage.objects;
658
+ CREATE POLICY "Anyone can view avatars"
659
+ ON storage.objects FOR SELECT
660
+ USING (bucket_id = 'avatars');
661
+
662
+ DROP POLICY IF EXISTS "Users can upload their own avatar" ON storage.objects;
663
+ CREATE POLICY "Users can upload their own avatar"
664
+ ON storage.objects FOR INSERT
665
+ WITH CHECK (
666
+ bucket_id = 'avatars'
667
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
668
+ );
669
+
670
+ DROP POLICY IF EXISTS "Users can update their own avatar" ON storage.objects;
671
+ CREATE POLICY "Users can update their own avatar"
672
+ ON storage.objects FOR UPDATE
673
+ USING (
674
+ bucket_id = 'avatars'
675
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
676
+ );
677
+
678
+ DROP POLICY IF EXISTS "Users can delete their own avatar" ON storage.objects;
679
+ CREATE POLICY "Users can delete their own avatar"
680
+ ON storage.objects FOR DELETE
681
+ USING (
682
+ bucket_id = 'avatars'
683
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
684
+ );
685
+
686
+ -- RLS policies for uploads bucket
687
+ DROP POLICY IF EXISTS "Users can view their own uploads" ON storage.objects;
688
+ CREATE POLICY "Users can view their own uploads"
689
+ ON storage.objects FOR SELECT
690
+ USING (
691
+ bucket_id = 'uploads'
692
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
693
+ );
694
+
695
+ DROP POLICY IF EXISTS "Users can upload their own files" ON storage.objects;
696
+ CREATE POLICY "Users can upload their own files"
697
+ ON storage.objects FOR INSERT
698
+ WITH CHECK (
699
+ bucket_id = 'uploads'
700
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
701
+ );
702
+
703
+ DROP POLICY IF EXISTS "Users can update their own files" ON storage.objects;
704
+ CREATE POLICY "Users can update their own files"
705
+ ON storage.objects FOR UPDATE
706
+ USING (
707
+ bucket_id = 'uploads'
708
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
709
+ );
710
+
711
+ DROP POLICY IF EXISTS "Users can delete their own files" ON storage.objects;
712
+ CREATE POLICY "Users can delete their own files"
713
+ ON storage.objects FOR DELETE
714
+ USING (
715
+ bucket_id = 'uploads'
716
+ AND (SELECT auth.uid())::text = (storage.foldername(name))[1]
717
+ );