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,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Analytics"
|
|
3
|
+
description: "Privacy-friendly product analytics with Plausible."
|
|
4
|
+
order: 9
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Your app includes built-in support for **Plausible Analytics** — a lightweight, privacy-friendly alternative to Google Analytics. Plausible is:
|
|
10
|
+
|
|
11
|
+
- **Cookie-free** — no consent banners needed
|
|
12
|
+
- **GDPR/CCPA compliant** out of the box
|
|
13
|
+
- **Lightweight** — under 1KB script, no impact on page speed
|
|
14
|
+
- **Open source** — self-host or use their cloud service
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
### Option 1: Plausible Cloud
|
|
19
|
+
|
|
20
|
+
1. Sign up at [plausible.io](https://plausible.io)
|
|
21
|
+
2. Add your domain in the Plausible dashboard
|
|
22
|
+
3. Set the environment variables:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
VITE_PLAUSIBLE_DOMAIN="yourdomain.com"
|
|
26
|
+
VITE_PLAUSIBLE_SCRIPT_URL="https://plausible.io/js/script.js"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Option 2: Self-Hosted Plausible
|
|
30
|
+
|
|
31
|
+
1. Follow the [Plausible self-hosting guide](https://plausible.io/docs/self-hosting)
|
|
32
|
+
2. Deploy Plausible alongside your app (or on a separate server)
|
|
33
|
+
3. Set the environment variables:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
VITE_PLAUSIBLE_DOMAIN="yourdomain.com"
|
|
37
|
+
VITE_PLAUSIBLE_SCRIPT_URL="https://analytics.yourdomain.com/js/script.js"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## How It Works
|
|
41
|
+
|
|
42
|
+
The analytics script is conditionally injected into the HTML `<head>` at build time. When `VITE_PLAUSIBLE_DOMAIN` is empty (the default), no script is loaded and no data is collected.
|
|
43
|
+
|
|
44
|
+
The script tag uses Plausible's `defer` attribute and `data-domain` configuration — no additional client-side code is needed.
|
|
45
|
+
|
|
46
|
+
## What Gets Tracked
|
|
47
|
+
|
|
48
|
+
By default, Plausible tracks:
|
|
49
|
+
|
|
50
|
+
- **Page views** — which pages are visited
|
|
51
|
+
- **Referral sources** — where visitors come from
|
|
52
|
+
- **Browser & OS** — what devices are used
|
|
53
|
+
- **Country** — geographic distribution (city-level detail is not collected)
|
|
54
|
+
|
|
55
|
+
Plausible does **not** track:
|
|
56
|
+
|
|
57
|
+
- Individual users or sessions
|
|
58
|
+
- Personal data or IP addresses
|
|
59
|
+
- Cross-site browsing behavior
|
|
60
|
+
|
|
61
|
+
## Custom Events (Optional)
|
|
62
|
+
|
|
63
|
+
To track custom events (button clicks, signups, etc.), use the Plausible JavaScript API:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Track a custom event
|
|
67
|
+
window.plausible?.('Signup', { props: { plan: 'pro' } });
|
|
68
|
+
|
|
69
|
+
// Track a goal conversion
|
|
70
|
+
window.plausible?.('Purchase', { revenue: { currency: 'USD', amount: '19.00' } });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Add the TypeScript declaration to avoid type errors:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// src/client/types/plausible.d.ts
|
|
77
|
+
declare global {
|
|
78
|
+
interface Window {
|
|
79
|
+
plausible?: (event: string, options?: { props?: Record<string, string>; revenue?: { currency: string; amount: string } }) => void;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export {};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Dashboard
|
|
86
|
+
|
|
87
|
+
Access your analytics dashboard at:
|
|
88
|
+
|
|
89
|
+
- **Cloud**: `https://plausible.io/yourdomain.com`
|
|
90
|
+
- **Self-hosted**: `https://analytics.yourdomain.com/yourdomain.com`
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authentication"
|
|
3
|
+
description: "Configure sign-in methods, OAuth providers, and multi-factor authentication."
|
|
4
|
+
order: 4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Authentication is handled by self-hosted Supabase Auth. It supports:
|
|
10
|
+
|
|
11
|
+
- **Email/password** sign-up and sign-in
|
|
12
|
+
- **OAuth** providers (Google, Microsoft, GitHub, Apple, Discord)
|
|
13
|
+
- **Magic links** — passwordless email authentication
|
|
14
|
+
- **Multi-factor authentication** (TOTP)
|
|
15
|
+
|
|
16
|
+
All auth state is managed through the `useAuth()` hook on the client and JWT middleware on the server.
|
|
17
|
+
|
|
18
|
+
## useAuth() Hook
|
|
19
|
+
|
|
20
|
+
The `useAuth()` hook (from `AuthProvider`) exposes everything you need for auth in React components:
|
|
21
|
+
|
|
22
|
+
| Property | Type | Description |
|
|
23
|
+
|----------|------|-------------|
|
|
24
|
+
| `user` | `User \| null` | Current Supabase user object |
|
|
25
|
+
| `session` | `Session \| null` | Current session with tokens |
|
|
26
|
+
| `isLoading` | `boolean` | `true` while session is being validated |
|
|
27
|
+
| `isSuperAdmin` | `boolean` | User has `super_admin` role in `app_metadata` |
|
|
28
|
+
| `mfaRequired` | `boolean` | MFA is enforced app-wide (set by admin) |
|
|
29
|
+
| `hasMfaEnabled` | `boolean` | Current user has enrolled a TOTP factor |
|
|
30
|
+
| `signIn` | `object` | Sign-in methods (see below) |
|
|
31
|
+
| `signUp` | `function` | Create a new account |
|
|
32
|
+
| `signOut` | `function` | Sign out and clear session |
|
|
33
|
+
| `mfa` | `object` | MFA operations (enroll, challenge, verify) |
|
|
34
|
+
| `impersonateUser` | `function` | Impersonate a user (super admin only) |
|
|
35
|
+
| `stopImpersonating` | `function` | Return to admin session |
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { useAuth } from '@/components/auth/AuthProvider';
|
|
39
|
+
|
|
40
|
+
function MyComponent() {
|
|
41
|
+
const { user, isLoading, isSuperAdmin } = useAuth();
|
|
42
|
+
|
|
43
|
+
if (isLoading) return <p>Loading...</p>;
|
|
44
|
+
if (!user) return <p>Not signed in</p>;
|
|
45
|
+
|
|
46
|
+
return <p>Hello, {user.email}</p>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Sign-In Methods
|
|
51
|
+
|
|
52
|
+
### Email & Password
|
|
53
|
+
|
|
54
|
+
The default sign-in method. Calls the server-side login endpoint which handles account lockout checks before authenticating.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const { signIn } = useAuth();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const { requiresMfa } = await signIn.email('user@example.com', 'password');
|
|
61
|
+
if (requiresMfa) {
|
|
62
|
+
// Redirect to MFA verification page
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Handle error (invalid credentials, account locked, etc.)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The `signIn.email()` method returns `{ requiresMfa: boolean }`. When `true`, the user has MFA enrolled and must verify a TOTP code before accessing the app.
|
|
70
|
+
|
|
71
|
+
### Magic Links
|
|
72
|
+
|
|
73
|
+
Users receive a sign-in link via email — no password required. Requires SMTP to be configured.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const { signIn } = useAuth();
|
|
77
|
+
|
|
78
|
+
await signIn.magicLink('user@example.com');
|
|
79
|
+
// User receives an email with a sign-in link
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### OAuth Providers
|
|
83
|
+
|
|
84
|
+
Enable social login by configuring providers. Each provider has a dedicated method:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const { signIn } = useAuth();
|
|
88
|
+
|
|
89
|
+
await signIn.google();
|
|
90
|
+
await signIn.github();
|
|
91
|
+
await signIn.microsoft();
|
|
92
|
+
await signIn.apple();
|
|
93
|
+
await signIn.discord();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To enable an OAuth provider:
|
|
97
|
+
|
|
98
|
+
1. Set the environment variable in `.env.local` (e.g., `GOOGLE_ENABLED=true`)
|
|
99
|
+
2. Open **Supabase Studio** → **Authentication** → **Providers**
|
|
100
|
+
3. Configure the **client ID** and **secret** from the provider's developer console
|
|
101
|
+
4. The provider button automatically appears on the sign-in page
|
|
102
|
+
|
|
103
|
+
## Sign-Up
|
|
104
|
+
|
|
105
|
+
Create a new account with email, password, and an optional display name:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const { signUp } = useAuth();
|
|
109
|
+
|
|
110
|
+
await signUp('user@example.com', 'password', 'Jane Smith');
|
|
111
|
+
// User is signed in and redirected to onboarding
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `name` parameter is stored in `user_metadata.full_name`.
|
|
115
|
+
|
|
116
|
+
## Route Protection
|
|
117
|
+
|
|
118
|
+
Two route guard components are defined in `App.tsx`:
|
|
119
|
+
|
|
120
|
+
**`ProtectedRoute`** — Requires any authenticated user. Redirects to `/sign-in` if not logged in. Also redirects to `/mfa-verify` if MFA is required but not yet verified.
|
|
121
|
+
|
|
122
|
+
**`SuperAdminProtectedRoute`** — Requires `super_admin` role. Redirects to the dashboard if the user is not a super admin.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// In App.tsx routes
|
|
126
|
+
<Route
|
|
127
|
+
path="/dashboard"
|
|
128
|
+
element={
|
|
129
|
+
<ProtectedRoute>
|
|
130
|
+
<Dashboard />
|
|
131
|
+
</ProtectedRoute>
|
|
132
|
+
}
|
|
133
|
+
/>
|
|
134
|
+
|
|
135
|
+
<Route
|
|
136
|
+
path="/admin/*"
|
|
137
|
+
element={
|
|
138
|
+
<SuperAdminProtectedRoute>
|
|
139
|
+
<AdminPanel />
|
|
140
|
+
</SuperAdminProtectedRoute>
|
|
141
|
+
}
|
|
142
|
+
/>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Public pages (like the landing page, blog, docs) don't need a wrapper.
|
|
146
|
+
|
|
147
|
+
## Multi-Factor Authentication
|
|
148
|
+
|
|
149
|
+
### Enrollment
|
|
150
|
+
|
|
151
|
+
Users enroll in MFA from **Settings → Security**. The flow:
|
|
152
|
+
|
|
153
|
+
1. Call `mfa.enroll()` to generate a TOTP secret and QR code
|
|
154
|
+
2. User scans the QR code with an authenticator app
|
|
155
|
+
3. User enters a TOTP code to verify enrollment
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const { mfa } = useAuth();
|
|
159
|
+
|
|
160
|
+
// Step 1: Enroll
|
|
161
|
+
const data = await mfa.enroll('My Authenticator');
|
|
162
|
+
// data.totp.qr_code — data URI for the QR code
|
|
163
|
+
// data.totp.uri — otpauth:// URI for manual entry
|
|
164
|
+
|
|
165
|
+
// Step 2: Verify enrollment with a code from the authenticator app
|
|
166
|
+
await mfa.challengeAndVerify(data.id, '123456');
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Sign-In with MFA
|
|
170
|
+
|
|
171
|
+
When a user with MFA signs in via email/password:
|
|
172
|
+
|
|
173
|
+
1. `signIn.email()` returns `{ requiresMfa: true }`
|
|
174
|
+
2. The app redirects to `/mfa-verify`
|
|
175
|
+
3. User enters their TOTP code
|
|
176
|
+
4. The code is verified against the pending challenge
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const { signIn, pendingMfaChallenge, mfa } = useAuth();
|
|
180
|
+
|
|
181
|
+
// During sign-in
|
|
182
|
+
const { requiresMfa } = await signIn.email(email, password);
|
|
183
|
+
if (requiresMfa) {
|
|
184
|
+
// Navigate to MFA verification page
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// On the MFA verification page
|
|
188
|
+
if (pendingMfaChallenge) {
|
|
189
|
+
await mfa.verify(
|
|
190
|
+
pendingMfaChallenge.factorId,
|
|
191
|
+
pendingMfaChallenge.challengeId,
|
|
192
|
+
totpCode
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Admin Enforcement
|
|
198
|
+
|
|
199
|
+
Super admins can require MFA for all users from the admin dashboard. This sets the `mfa_enabled` key in the `app_settings` table. When enforced, `ProtectedRoute` redirects users without MFA to the enrollment page.
|
|
200
|
+
|
|
201
|
+
## Account Lockout
|
|
202
|
+
|
|
203
|
+
Brute force protection is built in. After **5 failed login attempts** from the same email and IP address within a **15-minute window**, the account is locked.
|
|
204
|
+
|
|
205
|
+
- Locked accounts receive an HTTP `429 Too Many Requests` response with `remaining_minutes`
|
|
206
|
+
- Failed attempts are tracked in the `failed_login_attempts` table
|
|
207
|
+
- Successful login clears all failed attempts for that email/IP pair
|
|
208
|
+
- Super admins can unlock accounts via the `admin_unlock_account(email)` database function
|
|
209
|
+
|
|
210
|
+
## Server-Side Auth
|
|
211
|
+
|
|
212
|
+
API requests include the user's JWT in the `Authorization` header. The server middleware validates it and makes the user available in route handlers:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const user = c.get('user');
|
|
216
|
+
if (!user) return c.json({ error: 'Unauthorized' }, 401);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The middleware chain runs on every request:
|
|
220
|
+
|
|
221
|
+
1. **Extract token** from `Authorization: Bearer <token>` header
|
|
222
|
+
2. **Validate** with `supabase.auth.getUser()` (server-side verification)
|
|
223
|
+
3. **Set context** — `c.set('user', user)` and `c.set('supabase', client)` where `supabase` is a user-scoped client that respects RLS
|
|
224
|
+
|
|
225
|
+
For operations that need to bypass RLS (admin tasks, webhooks), use the service role client:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { supabaseAdmin } from '@/server/lib/supabase';
|
|
229
|
+
|
|
230
|
+
const { data } = await supabaseAdmin.from('organizations').select('*');
|
|
231
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Background Jobs"
|
|
3
|
+
description: "Schedule and manage recurring tasks with pg_cron."
|
|
4
|
+
order: 7
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Background jobs are powered by **pg_cron**, a PostgreSQL extension that runs scheduled tasks directly in the database. This means zero additional infrastructure — no Redis, no separate worker process, no external job queue.
|
|
10
|
+
|
|
11
|
+
## Built-in Jobs
|
|
12
|
+
|
|
13
|
+
Your app comes with three pre-configured jobs:
|
|
14
|
+
|
|
15
|
+
| Job | Schedule | Description |
|
|
16
|
+
|-----|----------|-------------|
|
|
17
|
+
| `cleanup-login-attempts` | Every 6 hours | Removes failed login attempts older than 24 hours |
|
|
18
|
+
| `cleanup-expired-notifications` | Daily at 3 AM | Deletes notifications past their expiry date |
|
|
19
|
+
| `cleanup-job-history` | Weekly (Sunday 4 AM) | Prunes job execution history older than 30 days |
|
|
20
|
+
|
|
21
|
+
## Admin Panel
|
|
22
|
+
|
|
23
|
+
Super admins can view and manage jobs from **Admin > Jobs**:
|
|
24
|
+
|
|
25
|
+
- View all scheduled jobs and their last execution status
|
|
26
|
+
- See execution history with timestamps and results
|
|
27
|
+
- Manually trigger any job with the "Run Now" button
|
|
28
|
+
|
|
29
|
+
## Adding Custom Jobs
|
|
30
|
+
|
|
31
|
+
To add a new scheduled job, create a new SQL migration:
|
|
32
|
+
|
|
33
|
+
```sql
|
|
34
|
+
-- supabase/migrations/XXXXXX_my_custom_job.sql
|
|
35
|
+
|
|
36
|
+
SELECT cron.schedule(
|
|
37
|
+
'my-job-name', -- unique job name
|
|
38
|
+
'*/30 * * * *', -- cron expression (every 30 minutes)
|
|
39
|
+
$$
|
|
40
|
+
DO $$
|
|
41
|
+
DECLARE
|
|
42
|
+
affected INTEGER;
|
|
43
|
+
BEGIN
|
|
44
|
+
-- Your SQL logic here
|
|
45
|
+
DELETE FROM some_table WHERE expired_at < now();
|
|
46
|
+
GET DIAGNOSTICS affected = ROW_COUNT;
|
|
47
|
+
|
|
48
|
+
-- Log the result
|
|
49
|
+
PERFORM public.log_cron_job('my-job-name', 'succeeded',
|
|
50
|
+
'Processed ' || affected || ' rows');
|
|
51
|
+
EXCEPTION WHEN OTHERS THEN
|
|
52
|
+
PERFORM public.log_cron_job('my-job-name', 'failed', NULL, SQLERRM);
|
|
53
|
+
END $$;
|
|
54
|
+
$$
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run `pnpm db:migrate` to apply the migration.
|
|
59
|
+
|
|
60
|
+
## Cron Expression Reference
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
┌───────────── minute (0-59)
|
|
64
|
+
│ ┌───────────── hour (0-23)
|
|
65
|
+
│ │ ┌───────────── day of month (1-31)
|
|
66
|
+
│ │ │ ┌───────────── month (1-12)
|
|
67
|
+
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
|
|
68
|
+
│ │ │ │ │
|
|
69
|
+
* * * * *
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Common patterns:
|
|
73
|
+
|
|
74
|
+
| Expression | Meaning |
|
|
75
|
+
|-----------|---------|
|
|
76
|
+
| `*/5 * * * *` | Every 5 minutes |
|
|
77
|
+
| `0 */6 * * *` | Every 6 hours |
|
|
78
|
+
| `0 3 * * *` | Daily at 3:00 AM |
|
|
79
|
+
| `0 0 * * 0` | Weekly on Sunday at midnight |
|
|
80
|
+
| `0 0 1 * *` | Monthly on the 1st at midnight |
|
|
81
|
+
|
|
82
|
+
## Managing Jobs
|
|
83
|
+
|
|
84
|
+
### Remove a Job
|
|
85
|
+
|
|
86
|
+
```sql
|
|
87
|
+
SELECT cron.unschedule('job-name');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### List All Jobs
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
SELECT * FROM cron.job;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### View Execution Log
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
SELECT * FROM cron.job_run_details
|
|
100
|
+
ORDER BY start_time DESC
|
|
101
|
+
LIMIT 20;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Job History
|
|
105
|
+
|
|
106
|
+
All job executions are logged in the `cron_job_history` table. Use `public.log_cron_job()` in your custom jobs to record results:
|
|
107
|
+
|
|
108
|
+
```sql
|
|
109
|
+
-- Log success
|
|
110
|
+
PERFORM public.log_cron_job('my-job', 'succeeded', 'Processed 42 rows');
|
|
111
|
+
|
|
112
|
+
-- Log failure
|
|
113
|
+
PERFORM public.log_cron_job('my-job', 'failed', NULL, 'Connection timeout');
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
History is automatically cleaned up after 30 days by the `cleanup-job-history` job.
|