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.
- package/LICENSE +663 -0
- package/README.md +188 -0
- package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
- package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
- package/carbon/.claude/settings.json +29 -0
- package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
- package/carbon/.dockerignore +57 -0
- package/carbon/.env.example +219 -0
- package/carbon/.github/copilot-instructions.md +3 -0
- package/carbon/.github/workflows/deploy.yml +346 -0
- package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
- package/carbon/.windsurfrules +74 -0
- package/carbon/AGENTS.md +422 -0
- package/carbon/CLAUDE.md +3 -0
- package/carbon/DEVELOPMENT.md +187 -0
- package/carbon/Dockerfile +98 -0
- package/carbon/LICENSE +21 -0
- package/carbon/PRODUCTION.md +364 -0
- package/carbon/README.md +193 -0
- package/carbon/TESTING.md +138 -0
- package/carbon/backup/Dockerfile +75 -0
- package/carbon/backup/backup.sh +95 -0
- package/carbon/backup/compose-backup.sh +140 -0
- package/carbon/biome.json +83 -0
- package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
- package/carbon/cloud-init/k3s/master-init.sh +215 -0
- package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
- package/carbon/cloud-init/k3s/worker-init.sh +147 -0
- package/carbon/components.json +24 -0
- package/carbon/content/blog/authentication-guide.mdx +34 -0
- package/carbon/content/blog/getting-started.mdx +41 -0
- package/carbon/content/changelog/v0-1-0.mdx +40 -0
- package/carbon/content/docs/analytics.mdx +90 -0
- package/carbon/content/docs/authentication.mdx +231 -0
- package/carbon/content/docs/background-jobs.mdx +116 -0
- package/carbon/content/docs/cli.mdx +630 -0
- package/carbon/content/docs/database.mdx +236 -0
- package/carbon/content/docs/deployment.mdx +227 -0
- package/carbon/content/docs/development.mdx +238 -0
- package/carbon/content/docs/environments.mdx +84 -0
- package/carbon/content/docs/getting-started.mdx +112 -0
- package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
- package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
- package/carbon/content/docs/optional-services.mdx +160 -0
- package/carbon/db/Dockerfile +23 -0
- package/carbon/docker-compose.dev-init.yml +5 -0
- package/carbon/docker-compose.metabase.override.yml +14 -0
- package/carbon/docker-compose.metabase.prod.yml +28 -0
- package/carbon/docker-compose.metabase.yml +84 -0
- package/carbon/docker-compose.n8n.override.yml +14 -0
- package/carbon/docker-compose.n8n.prod.yml +31 -0
- package/carbon/docker-compose.n8n.yml +97 -0
- package/carbon/docker-compose.observability.override.yml +18 -0
- package/carbon/docker-compose.observability.prod.yml +28 -0
- package/carbon/docker-compose.observability.yml +125 -0
- package/carbon/docker-compose.override.yml +28 -0
- package/carbon/docker-compose.prod.yml +294 -0
- package/carbon/docker-compose.yml +508 -0
- package/carbon/docker-entrypoint.sh +12 -0
- package/carbon/ha/activate-standby.sh +52 -0
- package/carbon/ha/primary-init.sql +36 -0
- package/carbon/ha/standby-init.sh +74 -0
- package/carbon/k8s/LICENSE +99 -0
- package/carbon/k8s/README.md +272 -0
- package/carbon/k8s/base/app/deployment.yaml +130 -0
- package/carbon/k8s/base/app/hpa.yaml +44 -0
- package/carbon/k8s/base/app/kustomization.yaml +10 -0
- package/carbon/k8s/base/app/network-policy.yaml +124 -0
- package/carbon/k8s/base/app/pdb.yaml +14 -0
- package/carbon/k8s/base/app/rbac.yaml +36 -0
- package/carbon/k8s/base/app/service.yaml +16 -0
- package/carbon/k8s/base/backup/cronjob.yaml +120 -0
- package/carbon/k8s/base/backup/kustomization.yaml +7 -0
- package/carbon/k8s/base/backup/network-policy.yaml +31 -0
- package/carbon/k8s/base/backup/rbac.yaml +7 -0
- package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
- package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
- package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
- package/carbon/k8s/base/config/configmap.yaml +48 -0
- package/carbon/k8s/base/config/kustomization.yaml +8 -0
- package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
- package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
- package/carbon/k8s/base/kustomization.yaml +43 -0
- package/carbon/k8s/base/namespace.yaml +7 -0
- package/carbon/k8s/base/network-policies.yaml +95 -0
- package/carbon/k8s/base/registry/kustomization.yaml +5 -0
- package/carbon/k8s/base/registry/local-registry.yaml +193 -0
- package/carbon/k8s/base/traefik/certificate.yaml +21 -0
- package/carbon/k8s/base/traefik/configmap.yaml +13 -0
- package/carbon/k8s/base/traefik/deployment.yaml +165 -0
- package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
- package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
- package/carbon/k8s/base/traefik/middleware.yaml +109 -0
- package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
- package/carbon/k8s/base/traefik/service.yaml +38 -0
- package/carbon/k8s/flux/README.md +49 -0
- package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
- package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
- package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
- package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
- package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
- package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
- package/carbon/k8s/infra/kustomization.yaml +21 -0
- package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
- package/carbon/k8s/overlays/local/configmap.yaml +23 -0
- package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
- package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
- package/carbon/k8s/overlays/local/namespace.yaml +8 -0
- package/carbon/k8s/overlays/local/secrets.yaml +32 -0
- package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
- package/carbon/k8s/test-local.sh +318 -0
- package/carbon/k8s/values/supabase.values.yaml +133 -0
- package/carbon/package.json +154 -0
- package/carbon/runtime.Dockerfile +17 -0
- package/carbon/scripts/_dev-jwt.mjs +45 -0
- package/carbon/scripts/check-rls.ts +53 -0
- package/carbon/scripts/dev-init.js +191 -0
- package/carbon/scripts/dev.js +191 -0
- package/carbon/scripts/docker-down.js +63 -0
- package/carbon/scripts/docker-logs.js +59 -0
- package/carbon/scripts/docker-up.js +222 -0
- package/carbon/scripts/generate-dev-configs.sh +131 -0
- package/carbon/scripts/generate-rss.ts +116 -0
- package/carbon/scripts/generate-sitemap.ts +102 -0
- package/carbon/scripts/k8s-apply.js +71 -0
- package/carbon/scripts/k8s-delete.js +75 -0
- package/carbon/scripts/lib/manifest.js +176 -0
- package/carbon/scripts/secret-scan.mjs +278 -0
- package/carbon/scripts/validate-dev-configs.sh +101 -0
- package/carbon/src/client/App.tsx +202 -0
- package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
- package/carbon/src/client/assets/hyperformant-light.svg +29 -0
- package/carbon/src/client/assets/logos/aider.svg +1 -0
- package/carbon/src/client/assets/logos/antigravity.svg +1 -0
- package/carbon/src/client/assets/logos/bolt.svg +1 -0
- package/carbon/src/client/assets/logos/claude-code.svg +1 -0
- package/carbon/src/client/assets/logos/copilot.svg +1 -0
- package/carbon/src/client/assets/logos/cursor.svg +1 -0
- package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
- package/carbon/src/client/assets/logos/lovable.svg +1 -0
- package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
- package/carbon/src/client/assets/logos/v0.svg +1 -0
- package/carbon/src/client/assets/logos/windsurf.svg +1 -0
- package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
- package/carbon/src/client/components/AppSidebar.tsx +760 -0
- package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
- package/carbon/src/client/components/CTAFooter.tsx +59 -0
- package/carbon/src/client/components/ComparisonSection.tsx +132 -0
- package/carbon/src/client/components/ContentPanel.tsx +66 -0
- package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
- package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
- package/carbon/src/client/components/FAQSection.tsx +76 -0
- package/carbon/src/client/components/FileUpload.tsx +210 -0
- package/carbon/src/client/components/HeaderActions.tsx +17 -0
- package/carbon/src/client/components/Hero.tsx +608 -0
- package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
- package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
- package/carbon/src/client/components/Logo.tsx +87 -0
- package/carbon/src/client/components/LogoStrip.tsx +76 -0
- package/carbon/src/client/components/MetricsStrip.tsx +48 -0
- package/carbon/src/client/components/Nav.tsx +195 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
- package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
- package/carbon/src/client/components/PageHeader.tsx +24 -0
- package/carbon/src/client/components/PlanGate.tsx +36 -0
- package/carbon/src/client/components/PricingSection.tsx +371 -0
- package/carbon/src/client/components/SEO.tsx +34 -0
- package/carbon/src/client/components/SmoothScroll.tsx +45 -0
- package/carbon/src/client/components/TechStackSection.tsx +165 -0
- package/carbon/src/client/components/WorkflowSection.tsx +604 -0
- package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
- package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
- package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
- package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
- package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
- package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
- package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
- package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
- package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
- package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
- package/carbon/src/client/components/effects/index.ts +2 -0
- package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
- package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
- package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
- package/carbon/src/client/components/scroll/index.ts +2 -0
- package/carbon/src/client/components/ui/accordion.tsx +71 -0
- package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
- package/carbon/src/client/components/ui/alert.tsx +73 -0
- package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
- package/carbon/src/client/components/ui/avatar.tsx +91 -0
- package/carbon/src/client/components/ui/badge.tsx +50 -0
- package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
- package/carbon/src/client/components/ui/button-group.tsx +78 -0
- package/carbon/src/client/components/ui/button.tsx +120 -0
- package/carbon/src/client/components/ui/calendar.tsx +182 -0
- package/carbon/src/client/components/ui/card.tsx +85 -0
- package/carbon/src/client/components/ui/carousel.tsx +227 -0
- package/carbon/src/client/components/ui/chart.tsx +357 -0
- package/carbon/src/client/components/ui/checkbox.tsx +27 -0
- package/carbon/src/client/components/ui/collapsible.tsx +15 -0
- package/carbon/src/client/components/ui/command.tsx +178 -0
- package/carbon/src/client/components/ui/context-menu.tsx +233 -0
- package/carbon/src/client/components/ui/dialog.tsx +132 -0
- package/carbon/src/client/components/ui/drawer.tsx +118 -0
- package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
- package/carbon/src/client/components/ui/empty.tsx +94 -0
- package/carbon/src/client/components/ui/field.tsx +226 -0
- package/carbon/src/client/components/ui/hover-card.tsx +44 -0
- package/carbon/src/client/components/ui/input-group.tsx +146 -0
- package/carbon/src/client/components/ui/input-otp.tsx +83 -0
- package/carbon/src/client/components/ui/input.tsx +20 -0
- package/carbon/src/client/components/ui/item.tsx +188 -0
- package/carbon/src/client/components/ui/kbd.tsx +26 -0
- package/carbon/src/client/components/ui/label.tsx +21 -0
- package/carbon/src/client/components/ui/menubar.tsx +250 -0
- package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
- package/carbon/src/client/components/ui/pagination.tsx +102 -0
- package/carbon/src/client/components/ui/popover.tsx +75 -0
- package/carbon/src/client/components/ui/progress.tsx +66 -0
- package/carbon/src/client/components/ui/radio-group.tsx +36 -0
- package/carbon/src/client/components/ui/resizable.tsx +46 -0
- package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
- package/carbon/src/client/components/ui/select.tsx +186 -0
- package/carbon/src/client/components/ui/separator.tsx +21 -0
- package/carbon/src/client/components/ui/sheet.tsx +124 -0
- package/carbon/src/client/components/ui/sidebar.tsx +702 -0
- package/carbon/src/client/components/ui/skeleton.tsx +13 -0
- package/carbon/src/client/components/ui/slider.tsx +57 -0
- package/carbon/src/client/components/ui/sonner.tsx +45 -0
- package/carbon/src/client/components/ui/spinner.tsx +15 -0
- package/carbon/src/client/components/ui/switch.tsx +30 -0
- package/carbon/src/client/components/ui/table.tsx +89 -0
- package/carbon/src/client/components/ui/tabs.tsx +73 -0
- package/carbon/src/client/components/ui/textarea.tsx +18 -0
- package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
- package/carbon/src/client/components/ui/toggle.tsx +44 -0
- package/carbon/src/client/components/ui/tooltip.tsx +56 -0
- package/carbon/src/client/hooks/api/index.ts +14 -0
- package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
- package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
- package/carbon/src/client/hooks/use-mobile.ts +21 -0
- package/carbon/src/client/hooks/useMousePosition.ts +91 -0
- package/carbon/src/client/hooks/useNotifications.tsx +124 -0
- package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
- package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
- package/carbon/src/client/hooks/usePackageManager.ts +69 -0
- package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
- package/carbon/src/client/hooks/useRunningServices.ts +114 -0
- package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
- package/carbon/src/client/index.css +467 -0
- package/carbon/src/client/index.html +56 -0
- package/carbon/src/client/lib/admin-services.ts +151 -0
- package/carbon/src/client/lib/api.ts +32 -0
- package/carbon/src/client/lib/blog.ts +35 -0
- package/carbon/src/client/lib/changelog.ts +33 -0
- package/carbon/src/client/lib/docs-search.ts +101 -0
- package/carbon/src/client/lib/docs.ts +37 -0
- package/carbon/src/client/lib/i18n.ts +32 -0
- package/carbon/src/client/lib/supabase.ts +72 -0
- package/carbon/src/client/lib/tailwind-colors.ts +357 -0
- package/carbon/src/client/lib/theme.ts +117 -0
- package/carbon/src/client/lib/utils.ts +22 -0
- package/carbon/src/client/locales/de.json +529 -0
- package/carbon/src/client/locales/en.json +461 -0
- package/carbon/src/client/locales/es.json +529 -0
- package/carbon/src/client/locales/fr.json +529 -0
- package/carbon/src/client/locales/pt.json +529 -0
- package/carbon/src/client/main.tsx +56 -0
- package/carbon/src/client/mdx.d.ts +13 -0
- package/carbon/src/client/pages/ApiDocs.tsx +76 -0
- package/carbon/src/client/pages/AuthCallback.tsx +34 -0
- package/carbon/src/client/pages/Blog.tsx +167 -0
- package/carbon/src/client/pages/Changelog.tsx +171 -0
- package/carbon/src/client/pages/Charts.tsx +388 -0
- package/carbon/src/client/pages/Checkout.tsx +227 -0
- package/carbon/src/client/pages/Contact.tsx +174 -0
- package/carbon/src/client/pages/Dashboard.tsx +368 -0
- package/carbon/src/client/pages/Docs.tsx +372 -0
- package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
- package/carbon/src/client/pages/Home.tsx +187 -0
- package/carbon/src/client/pages/Legal.tsx +100 -0
- package/carbon/src/client/pages/Login.tsx +408 -0
- package/carbon/src/client/pages/MFAVerify.tsx +156 -0
- package/carbon/src/client/pages/NotFound.tsx +21 -0
- package/carbon/src/client/pages/Onboarding.tsx +246 -0
- package/carbon/src/client/pages/ResetPassword.tsx +200 -0
- package/carbon/src/client/pages/UIComponents.tsx +390 -0
- package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
- package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
- package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
- package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
- package/carbon/src/client/pages/admin/Logs.tsx +18 -0
- package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
- package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
- package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
- package/carbon/src/client/pages/admin/Settings.tsx +314 -0
- package/carbon/src/client/pages/admin/Theme.tsx +465 -0
- package/carbon/src/client/pages/admin/Users.tsx +365 -0
- package/carbon/src/client/pages/api-docs.css +156 -0
- package/carbon/src/client/pages/organizations/Details.tsx +200 -0
- package/carbon/src/client/pages/organizations/Members.tsx +402 -0
- package/carbon/src/client/pages/settings/Billing.tsx +473 -0
- package/carbon/src/client/pages/settings/Profile.tsx +160 -0
- package/carbon/src/client/pages/settings/Security.tsx +341 -0
- package/carbon/src/client/public/favicon.svg +19 -0
- package/carbon/src/client/public/robots.txt +8 -0
- package/carbon/src/server/billing/index.ts +104 -0
- package/carbon/src/server/billing/provider.ts +81 -0
- package/carbon/src/server/billing/providers/paddle.ts +314 -0
- package/carbon/src/server/billing/providers/polar.ts +325 -0
- package/carbon/src/server/billing/providers/stripe.ts +233 -0
- package/carbon/src/server/emails/templates.ts +116 -0
- package/carbon/src/server/index.ts +554 -0
- package/carbon/src/server/lib/auth.ts +6 -0
- package/carbon/src/server/lib/email.ts +64 -0
- package/carbon/src/server/lib/env.ts +112 -0
- package/carbon/src/server/lib/errors.ts +21 -0
- package/carbon/src/server/lib/logger.ts +17 -0
- package/carbon/src/server/lib/rate-limiter.ts +288 -0
- package/carbon/src/server/lib/request.ts +34 -0
- package/carbon/src/server/lib/stripe.ts +42 -0
- package/carbon/src/server/lib/supabase.ts +65 -0
- package/carbon/src/server/middleware/requirePlan.ts +80 -0
- package/carbon/src/server/routes/_internal/services-status.ts +958 -0
- package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
- package/carbon/src/server/routes/health.ts +48 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
- package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
- package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
- package/carbon/src/server/routes/v1/auth.ts +390 -0
- package/carbon/src/server/routes/v1/billing.ts +718 -0
- package/carbon/src/server/routes/v1/contact.ts +93 -0
- package/carbon/src/server/routes/v1/index.ts +1333 -0
- package/carbon/src/server/routes/v1/newsletter.ts +181 -0
- package/carbon/src/server/routes/v1/performance.ts +157 -0
- package/carbon/src/server/routes/v1/stats.ts +170 -0
- package/carbon/src/server/routes/v1/theme.ts +106 -0
- package/carbon/src/server/routes/webhooks/billing.ts +376 -0
- package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
- package/carbon/src/server/types.ts +11 -0
- package/carbon/src/shared/pricing.ts +155 -0
- package/carbon/src/shared/types.ts +338 -0
- package/carbon/supabase/migrations/00001_init.sql +717 -0
- package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
- package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
- package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
- package/carbon/supabase/seed.sql +16 -0
- package/carbon/tests/_helpers/app.ts +45 -0
- package/carbon/tests/_helpers/env.ts +37 -0
- package/carbon/tests/_helpers/factories.ts +69 -0
- package/carbon/tests/_helpers/jwt.ts +23 -0
- package/carbon/tests/_helpers/setup-integration.ts +20 -0
- package/carbon/tests/_helpers/setup-rtl.ts +12 -0
- package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
- package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
- package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
- package/carbon/tests/integration/server/routes/health.test.ts +61 -0
- package/carbon/tests/structural/i18n-parity.test.ts +42 -0
- package/carbon/tests/unit/client/utils.test.ts +49 -0
- package/carbon/tests/unit/shared/pricing.test.ts +93 -0
- package/carbon/tsconfig.json +27 -0
- package/carbon/tsconfig.server.json +27 -0
- package/carbon/tsconfig.test.json +9 -0
- package/carbon/vite.config.ts +110 -0
- package/carbon/vitest.config.ts +74 -0
- package/carbon/volumes/db/jwt.sql +57 -0
- package/carbon/volumes/db/metabase-init.sh +29 -0
- package/carbon/volumes/db/n8n-init.sh +25 -0
- package/carbon/volumes/db/realtime.sql +33 -0
- package/carbon/volumes/db/roles.sql +93 -0
- package/carbon/volumes/db/set-passwords.sh +12 -0
- package/carbon/volumes/db/super-admin.dev.sql +113 -0
- package/carbon/volumes/db/super-admin.generated.sql +113 -0
- package/carbon/volumes/db/super-admin.sql +114 -0
- package/carbon/volumes/grafana/dashboards/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards/overview.json +523 -0
- package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
- package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
- package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
- package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
- package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
- package/carbon/volumes/kong/kong.yml +208 -0
- package/carbon/volumes/loki/loki-config.yml +58 -0
- package/carbon/volumes/n8n/hooks.js +131 -0
- package/carbon/volumes/n8n/scripts/setup.sh +66 -0
- package/carbon/volumes/prometheus/prometheus.yml +43 -0
- package/carbon/volumes/promtail/promtail-config.yml +64 -0
- package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
- package/carbon/volumes/traefik/middlewares.yml +95 -0
- package/carbon/volumes/traefik/vite-dev.yml +20 -0
- package/package.json +95 -0
- package/src/access.js +354 -0
- package/src/activate.js +187 -0
- package/src/add.js +718 -0
- package/src/backup.js +786 -0
- package/src/cli.js +350 -0
- package/src/configure.js +967 -0
- package/src/console.js +155 -0
- package/src/create.js +1499 -0
- package/src/deploy.js +311 -0
- package/src/destroy.js +2033 -0
- package/src/diagnose.js +735 -0
- package/src/down.js +80 -0
- package/src/failover.js +1032 -0
- package/src/lib/backup-s3.js +179 -0
- package/src/lib/build.js +33 -0
- package/src/lib/checksum.js +28 -0
- package/src/lib/ci-setup.js +666 -0
- package/src/lib/cli/help.js +129 -0
- package/src/lib/cli/parse-flags.js +160 -0
- package/src/lib/cli/select-action.js +65 -0
- package/src/lib/cli/select-environment.js +108 -0
- package/src/lib/cli/tty-guard.js +75 -0
- package/src/lib/cloudflare.js +447 -0
- package/src/lib/colors.js +52 -0
- package/src/lib/command.js +361 -0
- package/src/lib/config.js +359 -0
- package/src/lib/cost.js +103 -0
- package/src/lib/deploy/bundle.js +231 -0
- package/src/lib/deploy/compose/acme-verify.js +121 -0
- package/src/lib/deploy/compose/build-args.js +78 -0
- package/src/lib/deploy/compose/ha.js +1874 -0
- package/src/lib/deploy/compose/index.js +1294 -0
- package/src/lib/deploy/compose/reconcile.js +74 -0
- package/src/lib/deploy/github.js +382 -0
- package/src/lib/deploy/image.js +191 -0
- package/src/lib/deploy/index.js +119 -0
- package/src/lib/deploy/k8s/LICENSE +99 -0
- package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
- package/src/lib/deploy/k8s/ha/index.js +1000 -0
- package/src/lib/deploy/k8s/index.js +62 -0
- package/src/lib/deploy/k8s/k3s.js +2515 -0
- package/src/lib/deploy/orchestrator.js +1401 -0
- package/src/lib/deploy/prompts.js +545 -0
- package/src/lib/deploy/remote-build.js +130 -0
- package/src/lib/deploy/state.js +120 -0
- package/src/lib/deploy/utils.js +328 -0
- package/src/lib/deploy-logger.js +93 -0
- package/src/lib/dns-propagation.js +48 -0
- package/src/lib/environment.js +58 -0
- package/src/lib/fetch-retry.js +103 -0
- package/src/lib/github-environments.js +335 -0
- package/src/lib/hetzner-dns.js +377 -0
- package/src/lib/hetzner-guided-setup.js +275 -0
- package/src/lib/host-keys.js +37 -0
- package/src/lib/iac/cloud-init.js +52 -0
- package/src/lib/iac/index.js +325 -0
- package/src/lib/iac/programs/hetzner-compose.js +130 -0
- package/src/lib/iac/programs/hetzner-k8s.js +320 -0
- package/src/lib/kubectl.js +81 -0
- package/src/lib/licensing/index.js +259 -0
- package/src/lib/licensing/tiers.js +181 -0
- package/src/lib/licensing/validator.js +171 -0
- package/src/lib/merge-package-json.js +90 -0
- package/src/lib/operator-ip.js +381 -0
- package/src/lib/package-manager.js +111 -0
- package/src/lib/perf.js +71 -0
- package/src/lib/project-guard.js +39 -0
- package/src/lib/project.js +334 -0
- package/src/lib/providers/base.js +276 -0
- package/src/lib/providers/hetzner-s3.js +656 -0
- package/src/lib/providers/hetzner.js +755 -0
- package/src/lib/providers/index.js +164 -0
- package/src/lib/s3.js +510 -0
- package/src/lib/secret-scan.js +583 -0
- package/src/lib/secrets.js +63 -0
- package/src/lib/server-types.js +195 -0
- package/src/lib/shell.js +91 -0
- package/src/lib/ssh.js +241 -0
- package/src/lib/tracker.js +242 -0
- package/src/lib/upgrade-policy.js +170 -0
- package/src/lib/validators.js +105 -0
- package/src/lib/version.js +5 -0
- package/src/remove.js +292 -0
- package/src/reset.js +97 -0
- package/src/restore.js +871 -0
- package/src/scale.js +1734 -0
- package/src/shell.js +222 -0
- package/src/status.js +981 -0
- package/src/up.js +264 -0
- 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
|
+
);
|