devpush 0.0.0-stage → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/agent-release.json +37 -0
- package/dist/package/LICENSE.md +21 -0
- package/dist/package/migrate.mjs +182 -0
- package/dist/package/plugins.mjs +125 -0
- package/dist/package/vite.mjs +439 -0
- package/package.json +168 -3
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon.svg +1 -0
- package/public/logo-email.png +0 -0
- package/public/robots.txt +3 -0
- package/public/social.png +0 -0
- package/src/cloudflare-runtime.d.ts +78 -0
- package/src/components/about-dialog.tsx +70 -0
- package/src/components/copy-button.tsx +74 -0
- package/src/components/logo-mark.tsx +20 -0
- package/src/components/pending-collection.tsx +103 -0
- package/src/components/resource-avatar.tsx +65 -0
- package/src/components/route-error-state.tsx +95 -0
- package/src/components/section-navigation.tsx +39 -0
- package/src/components/settings-layout.tsx +111 -0
- package/src/components/storage-capabilities.tsx +11 -0
- package/src/components/table-pager.tsx +101 -0
- package/src/components/ui/alert-dialog.tsx +180 -0
- package/src/components/ui/alert.tsx +78 -0
- package/src/components/ui/avatar.tsx +58 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button-group.tsx +38 -0
- package/src/components/ui/button.tsx +67 -0
- package/src/components/ui/card.tsx +82 -0
- package/src/components/ui/chart.tsx +307 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/combobox.tsx +256 -0
- package/src/components/ui/dialog.tsx +163 -0
- package/src/components/ui/dropdown-menu.tsx +110 -0
- package/src/components/ui/empty.tsx +65 -0
- package/src/components/ui/field.tsx +192 -0
- package/src/components/ui/input-group.tsx +118 -0
- package/src/components/ui/input-otp.tsx +70 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/pagination.tsx +34 -0
- package/src/components/ui/popover.tsx +86 -0
- package/src/components/ui/select.tsx +145 -0
- package/src/components/ui/skeleton.tsx +19 -0
- package/src/components/ui/sonner.tsx +47 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +81 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +55 -0
- package/src/features/admin/admin-settings.tsx +1048 -0
- package/src/features/auth/current-user-context-query.ts +11 -0
- package/src/features/auth/login-form.tsx +442 -0
- package/src/features/auth/sign-out-menu-item.tsx +42 -0
- package/src/features/deployments/deployment-actions.tsx +336 -0
- package/src/features/deployments/deployment-alias-badge.tsx +35 -0
- package/src/features/deployments/deployment-detail.tsx +596 -0
- package/src/features/deployments/deployment-filters.ts +66 -0
- package/src/features/deployments/deployment-list.tsx +465 -0
- package/src/features/deployments/deployment-visit-links.tsx +104 -0
- package/src/features/deployments/deployments-page.tsx +376 -0
- package/src/features/logs/log-scroll-area.tsx +69 -0
- package/src/features/notifications/email-preferences.tsx +232 -0
- package/src/features/onboarding/profile-onboarding.tsx +162 -0
- package/src/features/plugins/contributions.tsx +118 -0
- package/src/features/projects/backup-job-logs.tsx +143 -0
- package/src/features/projects/backup-policy-delete-dialog.tsx +82 -0
- package/src/features/projects/backup-restore-actions.tsx +573 -0
- package/src/features/projects/backup-status-icon.tsx +33 -0
- package/src/features/projects/backup-target-list.tsx +222 -0
- package/src/features/projects/backup-timestamp.tsx +32 -0
- package/src/features/projects/backup-volume-picker.tsx +158 -0
- package/src/features/projects/compose-service-input.tsx +64 -0
- package/src/features/projects/deploy-readiness.ts +28 -0
- package/src/features/projects/environment-dot.tsx +91 -0
- package/src/features/projects/log-keyword-filter.tsx +48 -0
- package/src/features/projects/manual-deploy-dialog.tsx +563 -0
- package/src/features/projects/pending-section.ts +26 -0
- package/src/features/projects/project-backup-details-pending.tsx +305 -0
- package/src/features/projects/project-backup-details.tsx +936 -0
- package/src/features/projects/project-backup-policy.tsx +644 -0
- package/src/features/projects/project-backup-utils.ts +190 -0
- package/src/features/projects/project-backups-pending.tsx +136 -0
- package/src/features/projects/project-backups.tsx +876 -0
- package/src/features/projects/project-environment-settings.tsx +1128 -0
- package/src/features/projects/project-heading.tsx +68 -0
- package/src/features/projects/project-layout.tsx +188 -0
- package/src/features/projects/project-logs.tsx +461 -0
- package/src/features/projects/project-overview.tsx +280 -0
- package/src/features/projects/project-route-pending.tsx +1064 -0
- package/src/features/projects/project-routing-settings.tsx +663 -0
- package/src/features/projects/project-settings-section.tsx +64 -0
- package/src/features/projects/project-settings-utils.ts +101 -0
- package/src/features/projects/project-settings.tsx +1472 -0
- package/src/features/projects/repository-picker-dialog.tsx +404 -0
- package/src/features/repositories/new-project-dialog-launcher.tsx +44 -0
- package/src/features/repositories/new-project-dialog.tsx +1204 -0
- package/src/features/repositories/new-project-pending.tsx +28 -0
- package/src/features/repositories/repository-list-pending.tsx +59 -0
- package/src/features/servers/server-availability-alert.tsx +44 -0
- package/src/features/servers/server-availability.ts +29 -0
- package/src/features/servers/server-avatar.tsx +52 -0
- package/src/features/servers/server-detail-pending.tsx +315 -0
- package/src/features/servers/server-detail.tsx +855 -0
- package/src/features/servers/server-metric-chart-pending.tsx +38 -0
- package/src/features/servers/server-metric-chart.tsx +182 -0
- package/src/features/servers/server-metric-format.ts +50 -0
- package/src/features/servers/server-metrics-pending.tsx +75 -0
- package/src/features/servers/server-metrics.tsx +136 -0
- package/src/features/servers/team-servers.tsx +432 -0
- package/src/features/settings/settings-route-pending.tsx +406 -0
- package/src/features/teams/backup-storage-settings.tsx +483 -0
- package/src/features/teams/navigation.ts +51 -0
- package/src/features/teams/new-team-dialog.tsx +132 -0
- package/src/features/teams/pending-section.ts +19 -0
- package/src/features/teams/team-header.tsx +1286 -0
- package/src/features/teams/team-heading.tsx +57 -0
- package/src/features/teams/team-home.tsx +391 -0
- package/src/features/teams/team-invite.tsx +155 -0
- package/src/features/teams/team-route-pending.tsx +505 -0
- package/src/features/teams/team-settings.tsx +1555 -0
- package/src/features/teams/use-workspace-updates.ts +126 -0
- package/src/features/users/api-keys-settings.tsx +544 -0
- package/src/features/users/api-scope-selection.tsx +269 -0
- package/src/features/users/connected-apps-settings.tsx +449 -0
- package/src/features/users/mcp-consent.tsx +220 -0
- package/src/features/users/profile-picture.tsx +171 -0
- package/src/features/users/user-settings.tsx +1064 -0
- package/src/lib/account-settings-schema.ts +78 -0
- package/src/lib/action-feedback.ts +81 -0
- package/src/lib/admin-search-schema.ts +8 -0
- package/src/lib/agent-release.d.mts +21 -0
- package/src/lib/agent-release.mjs +90 -0
- package/src/lib/allowlist-schema.ts +79 -0
- package/src/lib/api-key-schema.ts +176 -0
- package/src/lib/auth/client.ts +20 -0
- package/src/lib/avatar-schema.ts +11 -0
- package/src/lib/backup-cron.ts +118 -0
- package/src/lib/backup-database.ts +41 -0
- package/src/lib/backup-feature.ts +2 -0
- package/src/lib/backup-paths.ts +7 -0
- package/src/lib/backup-policy-schema.ts +85 -0
- package/src/lib/backup-resource-schema.ts +12 -0
- package/src/lib/backup-restore-schema.ts +15 -0
- package/src/lib/backup-storage-schema.ts +55 -0
- package/src/lib/deferred-module.ts +38 -0
- package/src/lib/deployment-logs-query.ts +63 -0
- package/src/lib/display-name.ts +7 -0
- package/src/lib/error-message.ts +119 -0
- package/src/lib/form-errors.ts +10 -0
- package/src/lib/internal-redirect.ts +25 -0
- package/src/lib/manual-deploy-options-query.ts +21 -0
- package/src/lib/notification-preferences.ts +43 -0
- package/src/lib/onboarding.ts +11 -0
- package/src/lib/operation-logs-query.ts +51 -0
- package/src/lib/page-head.ts +14 -0
- package/src/lib/project-context-query.ts +257 -0
- package/src/lib/project-creation-schema.ts +64 -0
- package/src/lib/project-env-schema.ts +23 -0
- package/src/lib/project-environments-schema.ts +56 -0
- package/src/lib/project-routing-schema.ts +94 -0
- package/src/lib/project-runtime-config.ts +131 -0
- package/src/lib/project-settings-schema.ts +75 -0
- package/src/lib/public-api-schema.ts +128 -0
- package/src/lib/public-error.ts +337 -0
- package/src/lib/query.ts +13 -0
- package/src/lib/repository-selection-query.ts +11 -0
- package/src/lib/resource-cache.ts +30 -0
- package/src/lib/runtime-id.ts +19 -0
- package/src/lib/search.ts +14 -0
- package/src/lib/server-context-query.ts +43 -0
- package/src/lib/server-name-schema.ts +11 -0
- package/src/lib/settings-conflict.ts +45 -0
- package/src/lib/settings-version.ts +7 -0
- package/src/lib/team-context-query.ts +126 -0
- package/src/lib/team-context-schema.ts +8 -0
- package/src/lib/team-forms-schema.ts +45 -0
- package/src/lib/team-role.ts +13 -0
- package/src/lib/team-settings-schema.ts +48 -0
- package/src/lib/urls.ts +3 -0
- package/src/lib/use-avatar-input.ts +61 -0
- package/src/lib/use-refresh-error.ts +18 -0
- package/src/lib/use-settings-version.ts +23 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +5 -0
- package/src/lib/workspace-updates.ts +53 -0
- package/src/package/config.ts +10 -0
- package/src/package/env.mjs +54 -0
- package/src/package/migrate-api.d.ts +5 -0
- package/src/package/migrate.ts +56 -0
- package/src/package/plugins.ts +141 -0
- package/src/package/sqlite.mjs +2 -0
- package/src/package/vite-api.d.ts +5 -0
- package/src/package/vite.ts +324 -0
- package/src/package/watch-guard.cjs +26 -0
- package/src/plugins/client.ts +57 -0
- package/src/plugins/contract.ts +188 -0
- package/src/plugins/default.server.ts +4 -0
- package/src/plugins/default.ui.ts +4 -0
- package/src/plugins/operations.ts +45 -0
- package/src/plugins/server.ts +197 -0
- package/src/plugins/version.ts +1 -0
- package/src/routeTree.gen.ts +1275 -0
- package/src/router.tsx +46 -0
- package/src/routes/$teamSlug.index.tsx +53 -0
- package/src/routes/$teamSlug.new-project.tsx +13 -0
- package/src/routes/$teamSlug.projects.$projectName.backups.$backupId.tsx +40 -0
- package/src/routes/$teamSlug.projects.$projectName.backups.tsx +82 -0
- package/src/routes/$teamSlug.projects.$projectName.deployments.$deploymentId.tsx +52 -0
- package/src/routes/$teamSlug.projects.$projectName.deployments.tsx +86 -0
- package/src/routes/$teamSlug.projects.$projectName.logs.tsx +55 -0
- package/src/routes/$teamSlug.projects.$projectName.settings.tsx +51 -0
- package/src/routes/$teamSlug.projects.$projectName.tsx +106 -0
- package/src/routes/$teamSlug.projects.tsx +40 -0
- package/src/routes/$teamSlug.servers.$serverId.tsx +31 -0
- package/src/routes/$teamSlug.servers.index.tsx +59 -0
- package/src/routes/$teamSlug.servers.tsx +7 -0
- package/src/routes/$teamSlug.settings.tsx +56 -0
- package/src/routes/$teamSlug.tsx +86 -0
- package/src/routes/[.]well-known.oauth-authorization-server.api.auth.ts +13 -0
- package/src/routes/[.]well-known.oauth-protected-resource.mcp.ts +6 -0
- package/src/routes/__root.tsx +155 -0
- package/src/routes/admin.tsx +55 -0
- package/src/routes/api/agents/certificates.ts +21 -0
- package/src/routes/api/agents/credentials/rotate.ts +22 -0
- package/src/routes/api/agents/heartbeat.ts +42 -0
- package/src/routes/api/agents/job-logs.ts +22 -0
- package/src/routes/api/agents/jobs/complete.ts +22 -0
- package/src/routes/api/agents/jobs/next.ts +29 -0
- package/src/routes/api/agents/jobs/progress.ts +32 -0
- package/src/routes/api/agents/jobs/status.ts +22 -0
- package/src/routes/api/agents/register.ts +45 -0
- package/src/routes/api/agents/runtime-logs.ts +22 -0
- package/src/routes/api/agents/switch.ts +25 -0
- package/src/routes/api/auth/$.ts +38 -0
- package/src/routes/api/cron/backups.ts +33 -0
- package/src/routes/api/cron/maintenance.ts +30 -0
- package/src/routes/api/github/install.callback.ts +51 -0
- package/src/routes/api/github/install.ts +41 -0
- package/src/routes/api/github/webhook.ts +11 -0
- package/src/routes/api/health.ts +19 -0
- package/src/routes/api/install/agent.$token.ts +50 -0
- package/src/routes/api/install/agent.binary.ts +83 -0
- package/src/routes/api/install/agent.checksum.ts +45 -0
- package/src/routes/api/install/agent.ts +26 -0
- package/src/routes/api/plugins/$.ts +11 -0
- package/src/routes/api/v1/$.ts +15 -0
- package/src/routes/api/workspaces/updates.ts +8 -0
- package/src/routes/auth.login.tsx +50 -0
- package/src/routes/deployments.$deploymentId.unavailable.tsx +21 -0
- package/src/routes/index.tsx +23 -0
- package/src/routes/invites.$token.tsx +35 -0
- package/src/routes/mcp.ts +13 -0
- package/src/routes/oauth.consent.tsx +54 -0
- package/src/routes/onboarding.tsx +37 -0
- package/src/routes/onboarding.workspace.tsx +44 -0
- package/src/routes/settings.tsx +30 -0
- package/src/routes/uploads/avatars/$fileName.ts +44 -0
- package/src/server/admin/access.ts +67 -0
- package/src/server/admin/functions.ts +122 -0
- package/src/server/admin/service.ts +260 -0
- package/src/server/agents/backup-state.ts +210 -0
- package/src/server/agents/certificates.ts +96 -0
- package/src/server/agents/claims.ts +116 -0
- package/src/server/agents/cleanup.ts +52 -0
- package/src/server/agents/compatibility.ts +116 -0
- package/src/server/agents/completion.ts +83 -0
- package/src/server/agents/container-cleanup-state.ts +51 -0
- package/src/server/agents/deployment-state.ts +111 -0
- package/src/server/agents/job-payloads.ts +159 -0
- package/src/server/agents/jobs.ts +1293 -0
- package/src/server/agents/protocol.ts +400 -0
- package/src/server/agents/reconciliation.ts +43 -0
- package/src/server/agents/runtime-logs.ts +337 -0
- package/src/server/agents/runtime-state.ts +125 -0
- package/src/server/api/admission.ts +12 -0
- package/src/server/api/authorization.ts +115 -0
- package/src/server/api/credentials.ts +569 -0
- package/src/server/api/cursors.ts +72 -0
- package/src/server/api/errors.ts +24 -0
- package/src/server/api/functions.ts +60 -0
- package/src/server/api/handler.ts +274 -0
- package/src/server/api/mutation-operations.ts +913 -0
- package/src/server/api/operation-context.ts +139 -0
- package/src/server/api/operation-contract.ts +1099 -0
- package/src/server/api/operations.ts +64 -0
- package/src/server/api/read-operations.ts +891 -0
- package/src/server/api/retention.ts +33 -0
- package/src/server/auth/access.ts +109 -0
- package/src/server/auth/auth-methods.ts +28 -0
- package/src/server/auth/better-auth.ts +518 -0
- package/src/server/auth/context.ts +28 -0
- package/src/server/auth/email-change.ts +47 -0
- package/src/server/auth/email-delivery.ts +51 -0
- package/src/server/auth/provider-identity.ts +19 -0
- package/src/server/auth/provider-tokens.ts +27 -0
- package/src/server/auth/session.server.ts +19 -0
- package/src/server/backups/database-commands.ts +40 -0
- package/src/server/backups/destination.ts +12 -0
- package/src/server/backups/feature.ts +6 -0
- package/src/server/backups/policy-validation.ts +51 -0
- package/src/server/backups/queue-state.ts +137 -0
- package/src/server/backups/service.ts +918 -0
- package/src/server/backups/storage-functions.ts +42 -0
- package/src/server/backups/storage-state.ts +67 -0
- package/src/server/backups/storage.ts +237 -0
- package/src/server/config.ts +274 -0
- package/src/server/db/atomic.ts +16 -0
- package/src/server/db/d1-connection.ts +83 -0
- package/src/server/db/errors.ts +17 -0
- package/src/server/db/index.cloudflare.ts +15 -0
- package/src/server/db/index.ts +35 -0
- package/src/server/db/libsql-connection.ts +72 -0
- package/src/server/db/log-schema.ts +102 -0
- package/src/server/db/migrate-libsql.ts +29 -0
- package/src/server/db/migrate-sqlite.ts +47 -0
- package/src/server/db/migration-history.ts +12 -0
- package/src/server/db/migrations-logs/0000_initial.sql +48 -0
- package/src/server/db/migrations-logs/0001_runtime_log_order.sql +2 -0
- package/src/server/db/migrations-logs/meta/0000_snapshot.json +327 -0
- package/src/server/db/migrations-logs/meta/0001_snapshot.json +329 -0
- package/src/server/db/migrations-logs/meta/_journal.json +20 -0
- package/src/server/db/migrations-sqlite/0000_initial.sql +894 -0
- package/src/server/db/migrations-sqlite/0001_agent_indexes.sql +5 -0
- package/src/server/db/migrations-sqlite/meta/0000_snapshot.json +5816 -0
- package/src/server/db/migrations-sqlite/meta/0001_snapshot.json +5841 -0
- package/src/server/db/migrations-sqlite/meta/_journal.json +20 -0
- package/src/server/db/oauth-schema.ts +216 -0
- package/src/server/db/remote-config.ts +47 -0
- package/src/server/db/schema.ts +1218 -0
- package/src/server/db/sqlite-connection.ts +94 -0
- package/src/server/db/sqlite-files.ts +27 -0
- package/src/server/db/sqlite-params.ts +19 -0
- package/src/server/db/worker-connection.ts +47 -0
- package/src/server/deleted-resources.ts +133 -0
- package/src/server/deletion-recovery.ts +272 -0
- package/src/server/email/layout.tsx +85 -0
- package/src/server/email/notification-template.tsx +62 -0
- package/src/server/email/otp-template.tsx +48 -0
- package/src/server/email/service.ts +251 -0
- package/src/server/email/team-invite-template.tsx +59 -0
- package/src/server/email/theme.ts +10 -0
- package/src/server/github/http.ts +14 -0
- package/src/server/github/install-state.ts +48 -0
- package/src/server/github/service.ts +831 -0
- package/src/server/github/webhook-handler.ts +198 -0
- package/src/server/github/webhooks.ts +205 -0
- package/src/server/http/auth.ts +11 -0
- package/src/server/http/error-diagnostics.ts +66 -0
- package/src/server/http/plugin-request.ts +15 -0
- package/src/server/http/proxy-request.ts +53 -0
- package/src/server/http/public-errors.ts +48 -0
- package/src/server/http/rate-limit.ts +59 -0
- package/src/server/http/request-body.ts +54 -0
- package/src/server/http/request-ip.ts +54 -0
- package/src/server/http/security-headers.ts +36 -0
- package/src/server/install/agent-binary.ts +230 -0
- package/src/server/install/agent-script.ts +565 -0
- package/src/server/install/release-binary.ts +65 -0
- package/src/server/logs/budget.ts +72 -0
- package/src/server/logs/cleanup.ts +64 -0
- package/src/server/logs/deployments.ts +98 -0
- package/src/server/logs/operations.ts +145 -0
- package/src/server/logs/retention.ts +39 -0
- package/src/server/maintenance.ts +61 -0
- package/src/server/mcp/connections.ts +358 -0
- package/src/server/mcp/functions.ts +63 -0
- package/src/server/mcp/handler.ts +295 -0
- package/src/server/mcp/oauth-discovery.cloudflare.ts +6 -0
- package/src/server/mcp/oauth-discovery.ts +8 -0
- package/src/server/mcp/oauth-provider.ts +220 -0
- package/src/server/mcp/urls.ts +20 -0
- package/src/server/notifications/functions.ts +51 -0
- package/src/server/notifications/observe.ts +303 -0
- package/src/server/notifications/outbox.ts +335 -0
- package/src/server/notifications/preferences.ts +131 -0
- package/src/server/notifications/query.ts +16 -0
- package/src/server/plugins/scope.ts +17 -0
- package/src/server/plugins/server-context.ts +157 -0
- package/src/server/plugins/services.server.ts +10 -0
- package/src/server/plugins/workspace-access.ts +74 -0
- package/src/server/projects/backup-policy-state.ts +68 -0
- package/src/server/projects/backups.ts +219 -0
- package/src/server/projects/compose-metadata.ts +174 -0
- package/src/server/projects/configuration-state.ts +90 -0
- package/src/server/projects/context.ts +909 -0
- package/src/server/projects/creation-state.ts +46 -0
- package/src/server/projects/deletion-state.ts +69 -0
- package/src/server/projects/deployment-config.ts +139 -0
- package/src/server/projects/deployment-logs.ts +97 -0
- package/src/server/projects/deployments.ts +1123 -0
- package/src/server/projects/domain-state.ts +71 -0
- package/src/server/projects/environment-branches.ts +75 -0
- package/src/server/projects/environment-state.ts +158 -0
- package/src/server/projects/environments.ts +424 -0
- package/src/server/projects/functions.ts +748 -0
- package/src/server/projects/generated-hostname.ts +77 -0
- package/src/server/projects/hostnames.ts +244 -0
- package/src/server/projects/mappers.ts +457 -0
- package/src/server/projects/names.ts +41 -0
- package/src/server/projects/project-env-vars.ts +28 -0
- package/src/server/projects/queue-state.ts +118 -0
- package/src/server/projects/repository.ts +368 -0
- package/src/server/projects/route-state.ts +37 -0
- package/src/server/projects/runtime-logs.ts +97 -0
- package/src/server/projects/settings-context.ts +58 -0
- package/src/server/projects/settings.ts +738 -0
- package/src/server/projects/types.ts +355 -0
- package/src/server/realtime/agent-events.ts +32 -0
- package/src/server/realtime/node-broadcaster.ts +71 -0
- package/src/server/realtime/node-handler.ts +55 -0
- package/src/server/realtime/service.ts +125 -0
- package/src/server/realtime/workspace-object.cloudflare.ts +62 -0
- package/src/server/realtime/workspace-object.ts +3 -0
- package/src/server/resources/color.ts +13 -0
- package/src/server/runtime/background.ts +21 -0
- package/src/server/runtime/node-asset-cache-plugin.ts +12 -0
- package/src/server/runtime/node-scheduler-plugin.ts +17 -0
- package/src/server/runtime/node-scheduler.ts +51 -0
- package/src/server/runtime/platform-env.cloudflare.ts +17 -0
- package/src/server/runtime/platform-env.ts +19 -0
- package/src/server/runtime/scheduled-task-error.ts +9 -0
- package/src/server/scheduled-tasks.ts +28 -0
- package/src/server/secrets/crypto.ts +56 -0
- package/src/server/servers/functions.ts +233 -0
- package/src/server/servers/lifecycle.ts +122 -0
- package/src/server/servers/metric-history-schema.ts +24 -0
- package/src/server/servers/metric-history.ts +55 -0
- package/src/server/servers/monitoring.ts +592 -0
- package/src/server/servers/service.ts +1211 -0
- package/src/server/servers/status.ts +32 -0
- package/src/server/servers/switch.ts +96 -0
- package/src/server/settings-version.ts +43 -0
- package/src/server/storage/capabilities.ts +6 -0
- package/src/server/storage/local-object-store.cloudflare.ts +7 -0
- package/src/server/storage/local-object-store.ts +50 -0
- package/src/server/storage/object-store.ts +160 -0
- package/src/server/teams/creation.ts +51 -0
- package/src/server/teams/delete-policy.ts +13 -0
- package/src/server/teams/deletion-state.ts +80 -0
- package/src/server/teams/functions.ts +324 -0
- package/src/server/teams/invitations.ts +38 -0
- package/src/server/teams/membership.ts +86 -0
- package/src/server/teams/queries.ts +261 -0
- package/src/server/teams/service.ts +1127 -0
- package/src/server/teams/types.ts +101 -0
- package/src/server/uploads/avatar.ts +133 -0
- package/src/server/users/deletion-state.ts +93 -0
- package/src/server/users/functions.ts +160 -0
- package/src/server/users/onboarding.ts +17 -0
- package/src/server/users/service.ts +644 -0
- package/src/server.ts +55 -0
- package/src/styles/shadcn.css +653 -0
- package/src/styles/theme.css +76 -0
- package/src/styles.css +177 -0
- package/wrangler.template.jsonc +37 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BetterAuthPlugin } from 'better-auth'
|
|
2
|
+
import {
|
|
3
|
+
APIError,
|
|
4
|
+
createAuthMiddleware,
|
|
5
|
+
getSessionFromCtx,
|
|
6
|
+
} from 'better-auth/api'
|
|
7
|
+
import { PublicError } from '#devpush/lib/public-error'
|
|
8
|
+
import { isEmailAllowlisted } from './access'
|
|
9
|
+
|
|
10
|
+
export async function assertAllowedEmailChange(email: string) {
|
|
11
|
+
if (!(await isEmailAllowlisted(email)))
|
|
12
|
+
throw new PublicError('ACCOUNT_EMAIL_NOT_ALLOWED')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function assertAllowedAuthEmailChange(email: string) {
|
|
16
|
+
try {
|
|
17
|
+
await assertAllowedEmailChange(email)
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error instanceof PublicError)
|
|
20
|
+
throw new APIError('FORBIDDEN', {
|
|
21
|
+
code: error.code,
|
|
22
|
+
message: error.message,
|
|
23
|
+
})
|
|
24
|
+
throw error
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function requireAllowedEmailChanges(): BetterAuthPlugin {
|
|
29
|
+
return {
|
|
30
|
+
id: 'devpush-allowed-email-changes',
|
|
31
|
+
hooks: {
|
|
32
|
+
before: [
|
|
33
|
+
{
|
|
34
|
+
matcher: (ctx) => ctx.path === '/change-email',
|
|
35
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
36
|
+
// Avoid exposing policy to anonymous callers and honor live revocation.
|
|
37
|
+
if (!(await getSessionFromCtx(ctx, { disableCookieCache: true })))
|
|
38
|
+
throw new APIError('UNAUTHORIZED')
|
|
39
|
+
const email: unknown = ctx.body?.newEmail
|
|
40
|
+
if (typeof email === 'string')
|
|
41
|
+
await assertAllowedAuthEmailChange(email)
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BetterAuthPlugin } from 'better-auth'
|
|
2
|
+
import { APIError, createAuthMiddleware } from 'better-auth/api'
|
|
3
|
+
|
|
4
|
+
export function requireAuthEmailDelivery(): BetterAuthPlugin {
|
|
5
|
+
return {
|
|
6
|
+
id: 'require-auth-email-delivery',
|
|
7
|
+
hooks: {
|
|
8
|
+
before: [
|
|
9
|
+
{
|
|
10
|
+
matcher: (ctx) =>
|
|
11
|
+
[
|
|
12
|
+
'/email-otp/send-verification-otp',
|
|
13
|
+
'/change-email',
|
|
14
|
+
'/send-verification-email',
|
|
15
|
+
'/verify-email',
|
|
16
|
+
].includes(ctx.path ?? ''),
|
|
17
|
+
handler: createAuthMiddleware(async (ctx) => ({
|
|
18
|
+
context: {
|
|
19
|
+
context: {
|
|
20
|
+
// Better Auth's default helper logs and swallows send failures.
|
|
21
|
+
// Override only this request: success must mean sending succeeded.
|
|
22
|
+
runInBackgroundOrAwait: async (
|
|
23
|
+
task: Promise<unknown> | void,
|
|
24
|
+
) => {
|
|
25
|
+
try {
|
|
26
|
+
await task
|
|
27
|
+
} catch (error) {
|
|
28
|
+
ctx.context.logger.error(
|
|
29
|
+
'Failed to send authentication email',
|
|
30
|
+
error,
|
|
31
|
+
)
|
|
32
|
+
throw new APIError('SERVICE_UNAVAILABLE', {
|
|
33
|
+
code:
|
|
34
|
+
ctx.path === '/email-otp/send-verification-otp'
|
|
35
|
+
? 'OTP_SEND_FAILED'
|
|
36
|
+
: 'EMAIL_SEND_FAILED',
|
|
37
|
+
message:
|
|
38
|
+
ctx.path === '/email-otp/send-verification-otp'
|
|
39
|
+
? 'Unable to send your sign-in code. Please try again in a moment.'
|
|
40
|
+
: 'Unable to send the verification email. Please try again in a moment.',
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
})),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm'
|
|
2
|
+
import type { AtomicStore } from '#devpush/server/db/atomic'
|
|
3
|
+
|
|
4
|
+
export async function replacePendingIdentity(
|
|
5
|
+
store: AtomicStore,
|
|
6
|
+
input: {
|
|
7
|
+
identifier: string
|
|
8
|
+
value: string
|
|
9
|
+
expiresAt: Date
|
|
10
|
+
},
|
|
11
|
+
) {
|
|
12
|
+
await store.batch([
|
|
13
|
+
store.run(
|
|
14
|
+
sql`delete from verification where identifier = ${input.identifier}`,
|
|
15
|
+
),
|
|
16
|
+
store.run(sql`insert into verification (id, identifier, value, expires_at)
|
|
17
|
+
values (${crypto.randomUUID()}, ${input.identifier}, ${input.value}, ${input.expiresAt.getTime()})`),
|
|
18
|
+
])
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { symmetricDecrypt, symmetricEncrypt } from 'better-auth/crypto'
|
|
2
|
+
import { config } from '#devpush/server/config'
|
|
3
|
+
|
|
4
|
+
// Better Auth's single-secret encryption format. Use the same key/codec as
|
|
5
|
+
// encryptOAuthTokens so refresh and reconnect never switch storage formats.
|
|
6
|
+
// Bare hex includes a 24-byte nonce and 16-byte authentication tag. Shorter
|
|
7
|
+
// hex strings cannot be encrypted envelopes.
|
|
8
|
+
function isEncrypted(value: string) {
|
|
9
|
+
return (
|
|
10
|
+
value.startsWith('$ba$') ||
|
|
11
|
+
(value.length >= 80 && value.length % 2 === 0 && /^[0-9a-f]+$/i.test(value))
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function readProviderToken(value: string) {
|
|
16
|
+
return isEncrypted(value)
|
|
17
|
+
? symmetricDecrypt({ key: config.BETTER_AUTH_SECRET, data: value })
|
|
18
|
+
: value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function storeProviderToken(value: string) {
|
|
22
|
+
if (isEncrypted(value)) {
|
|
23
|
+
await readProviderToken(value)
|
|
24
|
+
return value
|
|
25
|
+
}
|
|
26
|
+
return symmetricEncrypt({ key: config.BETTER_AUTH_SECRET, data: value })
|
|
27
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getRequest } from '@tanstack/react-start/server'
|
|
2
|
+
|
|
3
|
+
import { auth } from '#devpush/server/auth/better-auth'
|
|
4
|
+
|
|
5
|
+
export async function getAuthSession() {
|
|
6
|
+
const request = getRequest()
|
|
7
|
+
|
|
8
|
+
return auth.api.getSession({
|
|
9
|
+
headers: request.headers,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function requireAuthSession(message = 'Unauthorized') {
|
|
14
|
+
const session = await getAuthSession()
|
|
15
|
+
|
|
16
|
+
if (!session) throw new Error(message)
|
|
17
|
+
|
|
18
|
+
return session
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { backupDatabaseSchema } from '#devpush/lib/backup-database'
|
|
2
|
+
import type { BackupDatabase } from '#devpush/lib/backup-database'
|
|
3
|
+
|
|
4
|
+
function quote(value: string) {
|
|
5
|
+
return "'" + value.replaceAll("'", "'\\''") + "'"
|
|
6
|
+
}
|
|
7
|
+
export function buildDatabaseBackupCommand(value: BackupDatabase) {
|
|
8
|
+
const target = backupDatabaseSchema.parse(value)
|
|
9
|
+
const name = quote(target.database)
|
|
10
|
+
switch (target.engine) {
|
|
11
|
+
case 'postgres': {
|
|
12
|
+
const flags =
|
|
13
|
+
'pg_dump --clean --if-exists --no-owner --no-acl -U "${POSTGRES_USER:-postgres}"'
|
|
14
|
+
const dump = target.database
|
|
15
|
+
? `${flags} --dbname=${name}`
|
|
16
|
+
: `databases="$(psql -v ON_ERROR_STOP=1 -U "\${POSTGRES_USER:-postgres}" --dbname=postgres -Atc "SELECT datname FROM pg_database WHERE NOT datistemplate ORDER BY datname")" || exit $?; printf '%s\\n' "$databases" | while IFS= read -r database; do ${flags} --create --dbname="$database" || exit $?; done`
|
|
17
|
+
return `export PGPASSWORD="\${POSTGRES_PASSWORD:-}"; ${dump}`
|
|
18
|
+
}
|
|
19
|
+
case 'mysql':
|
|
20
|
+
return `export MYSQL_PWD="\${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}"; mysqldump -u root --single-transaction --quick --no-tablespaces ${target.database ? `--databases -- ${name}` : '--all-databases'}`
|
|
21
|
+
case 'mariadb':
|
|
22
|
+
return `export MYSQL_PWD="\${MARIADB_ROOT_PASSWORD:-\${MYSQL_ROOT_PASSWORD:?A root password is required}}"; mariadb-dump -u root --single-transaction --quick ${target.database ? `--databases -- ${name}` : '--all-databases'}`
|
|
23
|
+
case 'mongo':
|
|
24
|
+
return `mongodump --username="\${MONGO_INITDB_ROOT_USERNAME:?MongoDB username is required}" --password="\${MONGO_INITDB_ROOT_PASSWORD:?MongoDB password is required}" --authenticationDatabase=admin --archive${target.database ? ` --db=${name}` : ''}`
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function buildDatabaseRestoreCommand(value: BackupDatabase) {
|
|
28
|
+
const target = backupDatabaseSchema.parse(value)
|
|
29
|
+
const name = quote(target.database)
|
|
30
|
+
switch (target.engine) {
|
|
31
|
+
case 'postgres':
|
|
32
|
+
return `export PGPASSWORD="\${POSTGRES_PASSWORD:-}"; psql -v ON_ERROR_STOP=1 -U "\${POSTGRES_USER:-postgres}" --dbname=${target.database ? name : 'template1'}`
|
|
33
|
+
case 'mysql':
|
|
34
|
+
return 'export MYSQL_PWD="${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}"; mysql -u root'
|
|
35
|
+
case 'mariadb':
|
|
36
|
+
return 'export MYSQL_PWD="${MARIADB_ROOT_PASSWORD:-${MYSQL_ROOT_PASSWORD:?A root password is required}}"; mariadb -u root'
|
|
37
|
+
case 'mongo':
|
|
38
|
+
return 'mongorestore --username="${MONGO_INITDB_ROOT_USERNAME:?MongoDB username is required}" --password="${MONGO_INITDB_ROOT_PASSWORD:?MongoDB password is required}" --authenticationDatabase=admin --archive --drop'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BackupDestination } from './service'
|
|
2
|
+
|
|
3
|
+
// Runs and queued jobs keep location/ownership, never credential copies.
|
|
4
|
+
export function backupDestinationSnapshot(destination: BackupDestination) {
|
|
5
|
+
return {
|
|
6
|
+
storageId: destination.storageId,
|
|
7
|
+
bucket: destination.bucket,
|
|
8
|
+
region: destination.region,
|
|
9
|
+
endpoint: destination.endpoint,
|
|
10
|
+
prefix: destination.prefix,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import {
|
|
4
|
+
backupConfigSchema,
|
|
5
|
+
normalizeBackupVolumes,
|
|
6
|
+
} from '#devpush/lib/backup-policy-schema'
|
|
7
|
+
import { getDefaultBackupPrefix } from '#devpush/lib/backup-paths'
|
|
8
|
+
import { PublicError } from '#devpush/lib/public-error'
|
|
9
|
+
import { db } from '#devpush/server/db'
|
|
10
|
+
import { environments } from '#devpush/server/db/schema'
|
|
11
|
+
import { resolveBackupDestination } from './storage'
|
|
12
|
+
|
|
13
|
+
const policySchema = backupConfigSchema.extend({ id: z.string().trim().min(1) })
|
|
14
|
+
|
|
15
|
+
export async function prepareBackupPolicy({
|
|
16
|
+
teamId,
|
|
17
|
+
projectId,
|
|
18
|
+
policy,
|
|
19
|
+
}: {
|
|
20
|
+
teamId: string
|
|
21
|
+
projectId: string
|
|
22
|
+
policy: unknown
|
|
23
|
+
}) {
|
|
24
|
+
const parsed = policySchema.parse(policy)
|
|
25
|
+
const [environment] = await db
|
|
26
|
+
.select({ id: environments.id })
|
|
27
|
+
.from(environments)
|
|
28
|
+
.where(
|
|
29
|
+
and(
|
|
30
|
+
eq(environments.id, parsed.environmentId),
|
|
31
|
+
eq(environments.projectId, projectId),
|
|
32
|
+
eq(environments.status, 'active'),
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
.limit(1)
|
|
36
|
+
if (!environment) throw new PublicError('BACKUP_ENVIRONMENT_INVALID')
|
|
37
|
+
const normalized = {
|
|
38
|
+
...parsed,
|
|
39
|
+
volumes: normalizeBackupVolumes(parsed.volumes),
|
|
40
|
+
destination: {
|
|
41
|
+
...parsed.destination,
|
|
42
|
+
prefix:
|
|
43
|
+
parsed.destination.prefix ??
|
|
44
|
+
getDefaultBackupPrefix(projectId, parsed.environmentId, parsed.id),
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
if (normalized.destination.storageId || normalized.retention.remote.enabled) {
|
|
48
|
+
await resolveBackupDestination(teamId, normalized.destination)
|
|
49
|
+
}
|
|
50
|
+
return normalized
|
|
51
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { backupDestinationSnapshot } from './destination'
|
|
2
|
+
import { sql } from 'drizzle-orm'
|
|
3
|
+
import type { SQL } from 'drizzle-orm'
|
|
4
|
+
import type { AtomicStore } from '#devpush/server/db/atomic'
|
|
5
|
+
import {
|
|
6
|
+
agentJobs,
|
|
7
|
+
backupConfigs,
|
|
8
|
+
backupRuns,
|
|
9
|
+
environments,
|
|
10
|
+
projects,
|
|
11
|
+
servers,
|
|
12
|
+
} from '#devpush/server/db/schema'
|
|
13
|
+
import type { BackupDestination, BackupJobPayload } from './service'
|
|
14
|
+
import type { ApiMutationAdmission } from '#devpush/server/api/admission'
|
|
15
|
+
|
|
16
|
+
export const backupCleanupRetryDelayMs = 60 * 60 * 1000
|
|
17
|
+
|
|
18
|
+
// Release reservations before the associated jobs are canceled, in the same
|
|
19
|
+
// deletion batch. Match project/server ownership as well as payload run IDs.
|
|
20
|
+
export function releaseBackupCleanupRuns(input: {
|
|
21
|
+
jobIds: SQL
|
|
22
|
+
guard: SQL
|
|
23
|
+
message: string
|
|
24
|
+
now: Date
|
|
25
|
+
}) {
|
|
26
|
+
return sql`update backup_runs set cleanup_queued_at = null, cleanup_error_message = ${input.message}, updated_at = ${input.now.getTime()}
|
|
27
|
+
where ${input.guard} and exists(select 1 from agent_jobs as cleanup,
|
|
28
|
+
json_each(case when json_valid(cleanup.payload) then cleanup.payload else '{}' end, '$.backupRunIds') as target
|
|
29
|
+
where cleanup.id in (${input.jobIds}) and cleanup.type = 'backup_cleanup' and cleanup.status in ('queued','running')
|
|
30
|
+
and cleanup.server_id = backup_runs.server_id and cleanup.project_id = backup_runs.project_id and target.value = backup_runs.id)`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function enqueueBackupCleanup(
|
|
34
|
+
store: AtomicStore,
|
|
35
|
+
input: {
|
|
36
|
+
jobId: string
|
|
37
|
+
projectId: string
|
|
38
|
+
serverId: string
|
|
39
|
+
backupRunIds: string[]
|
|
40
|
+
now: Date
|
|
41
|
+
destination: BackupDestination
|
|
42
|
+
artifacts: Array<{
|
|
43
|
+
backupRunId: string
|
|
44
|
+
storage: 'local' | 'remote'
|
|
45
|
+
objectKey: string
|
|
46
|
+
}>
|
|
47
|
+
},
|
|
48
|
+
) {
|
|
49
|
+
const now = input.now.getTime()
|
|
50
|
+
// An ID collision must fail even when eligibility would otherwise skip the
|
|
51
|
+
// insert; the following statement must never find an older job with this ID.
|
|
52
|
+
const eligible = sql`select id from ${backupRuns}
|
|
53
|
+
where id in (select value from json_each(${JSON.stringify(input.backupRunIds)}))
|
|
54
|
+
and server_id = ${input.serverId} and project_id = ${input.projectId}
|
|
55
|
+
and exists(select 1 from servers where id = ${input.serverId} and status <> 'deleted')
|
|
56
|
+
and exists(select 1 from projects where id = ${input.projectId} and status = 'active')
|
|
57
|
+
and (cleanup_error_message is null or updated_at <= ${now - backupCleanupRetryDelayMs})
|
|
58
|
+
and (status = 'succeeded' or (status = 'failed' and json_extract(manifest, '$.localRecoveryAvailable') = 1)) and cleanup_queued_at is null`
|
|
59
|
+
const keys = (
|
|
60
|
+
storage: 'local' | 'remote',
|
|
61
|
+
) => sql`select distinct json_extract(artifact.value, '$.objectKey') as object_key
|
|
62
|
+
from json_each(${JSON.stringify(input.artifacts)}) as artifact
|
|
63
|
+
where json_extract(artifact.value, '$.backupRunId') in (select id from eligible)
|
|
64
|
+
and json_extract(artifact.value, '$.storage') = ${storage} order by object_key`
|
|
65
|
+
const [, marked] = await store.batch([
|
|
66
|
+
store.run(sql`with eligible as materialized (${eligible}), local_keys as (${keys('local')}), remote_keys as (${keys('remote')})
|
|
67
|
+
insert into ${agentJobs} (id, type, status, server_id, project_id, payload, max_attempts, available_at, created_at, updated_at)
|
|
68
|
+
select ${input.jobId}, 'backup_cleanup', 'queued', ${input.serverId}, ${input.projectId},
|
|
69
|
+
json_patch(json_object('schemaVersion', 1, 'backupRunIds', json((select json_group_array(id) from eligible)),
|
|
70
|
+
'localObjectKeys', json((select json_group_array(object_key) from local_keys)),
|
|
71
|
+
'remoteObjectKeys', json((select json_group_array(object_key) from remote_keys)), 'reason', 'Backup retention expired.'),
|
|
72
|
+
case when exists(select 1 from remote_keys) then json_object('destination', json(${JSON.stringify(backupDestinationSnapshot(input.destination))})) else '{}' end),
|
|
73
|
+
5, ${now}, ${now}, ${now} where exists(select 1 from eligible)
|
|
74
|
+
or exists(select 1 from ${agentJobs} where id = ${input.jobId})`),
|
|
75
|
+
store.all(sql`update ${backupRuns} set cleanup_queued_at = ${now}, cleanup_error_message = null, updated_at = ${now}
|
|
76
|
+
where id in (select runtime.value from ${agentJobs} as job, json_each(job.payload, '$.backupRunIds') as runtime where job.id = ${input.jobId})
|
|
77
|
+
returning id`),
|
|
78
|
+
])
|
|
79
|
+
return marked.map((row) => {
|
|
80
|
+
const id = Array.isArray(row)
|
|
81
|
+
? row[0]
|
|
82
|
+
: typeof row === 'object' && row !== null && 'id' in row
|
|
83
|
+
? row.id
|
|
84
|
+
: undefined
|
|
85
|
+
if (typeof id !== 'string')
|
|
86
|
+
throw new Error('Invalid backup cleanup queue result.')
|
|
87
|
+
return id
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function enqueueBackup(
|
|
92
|
+
store: AtomicStore,
|
|
93
|
+
input: {
|
|
94
|
+
backupRunId: string
|
|
95
|
+
agentJobId: string
|
|
96
|
+
configId: string
|
|
97
|
+
projectId: string
|
|
98
|
+
environmentId: string
|
|
99
|
+
deploymentId: string | null
|
|
100
|
+
serverId: string
|
|
101
|
+
runtimeId: string
|
|
102
|
+
scheduleKey: string | null
|
|
103
|
+
manifest: unknown
|
|
104
|
+
payload: BackupJobPayload
|
|
105
|
+
now: Date
|
|
106
|
+
admission?: ApiMutationAdmission
|
|
107
|
+
},
|
|
108
|
+
) {
|
|
109
|
+
const now = input.now.getTime()
|
|
110
|
+
// Force a primary-key error for a reused ID instead of letting a skipped
|
|
111
|
+
// insert attach the new job to an older backup run.
|
|
112
|
+
const [created] = await store.batch([
|
|
113
|
+
store.all(sql`insert into ${backupRuns}
|
|
114
|
+
(id, project_id, environment_id, deployment_id, server_id, runtime_id, config_id, schedule_key, status, manifest, destination, created_at, updated_at)
|
|
115
|
+
select ${input.backupRunId}, ${input.projectId}, ${input.environmentId}, ${input.deploymentId},
|
|
116
|
+
${input.serverId}, ${input.runtimeId}, ${input.configId}, ${input.scheduleKey}, 'queued',
|
|
117
|
+
${input.manifest == null ? null : JSON.stringify(input.manifest)}, ${input.payload.retention.remote.enabled ? JSON.stringify(backupDestinationSnapshot(input.payload.destination)) : null}, ${now}, ${now}
|
|
118
|
+
where exists(select 1 from ${backupRuns} where id = ${input.backupRunId}) or (
|
|
119
|
+
not exists(select 1 from ${backupRuns} where environment_id = ${input.environmentId} and status in ('queued', 'running'))
|
|
120
|
+
${input.scheduleKey === null ? sql`` : sql`and not exists(select 1 from ${backupRuns} where environment_id = ${input.environmentId} and schedule_key = ${input.scheduleKey})`}
|
|
121
|
+
and exists(select 1 from ${backupConfigs} as config
|
|
122
|
+
join ${environments} as env on env.id = config.environment_id
|
|
123
|
+
join ${projects} as project on project.id = env.project_id
|
|
124
|
+
join ${servers} as server on server.id = coalesce(env.server_id, project.default_server_id)
|
|
125
|
+
where config.id = ${input.configId} and config.enabled = 1 and env.id = ${input.environmentId}
|
|
126
|
+
and project.id = ${input.projectId} and env.status = 'active' and project.status = 'active'
|
|
127
|
+
and server.id = ${input.serverId} and server.status in ('connected', 'disconnected'))
|
|
128
|
+
and ${input.admission?.guard ?? sql`1`}) returning id`),
|
|
129
|
+
store.run(sql`insert into ${agentJobs}
|
|
130
|
+
(id, type, status, server_id, project_id, payload, max_attempts, available_at, created_at, updated_at)
|
|
131
|
+
select ${input.agentJobId}, 'backup', 'queued', server_id, project_id,
|
|
132
|
+
${JSON.stringify({ ...input.payload, destination: backupDestinationSnapshot(input.payload.destination) })}, 5, ${now}, ${now}, ${now}
|
|
133
|
+
from ${backupRuns} where id = ${input.backupRunId}`),
|
|
134
|
+
...(input.admission?.statements ?? []),
|
|
135
|
+
])
|
|
136
|
+
return created.length === 1
|
|
137
|
+
}
|