@nextsparkjs/core 0.1.0-beta.185 → 0.1.0-beta.186
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/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
- package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.js +2 -2
- package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicNavbar.js +2 -2
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +4 -1
- package/dist/components/auth/DevKeyring.d.ts.map +1 -1
- package/dist/components/auth/DevKeyring.js +10 -2
- package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
- package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
- package/dist/components/auth/SessionCookieRefresher.js +11 -0
- package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
- package/dist/components/auth/forms/LoginForm.js +300 -6
- package/dist/components/auth/index.d.ts +1 -0
- package/dist/components/auth/index.d.ts.map +1 -1
- package/dist/components/auth/index.js +2 -0
- package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
- package/dist/components/billing/InvoicesTable.js +4 -3
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
- package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
- package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/Sidebar.js +2 -3
- package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
- package/dist/components/dashboard/layouts/index.d.ts +1 -0
- package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/index.js +2 -0
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +3 -0
- package/dist/components/entities/EntityTable.d.ts.map +1 -1
- package/dist/components/entities/EntityTable.js +2 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
- package/dist/components/ui/combobox.d.ts +8 -1
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox.js +37 -44
- package/dist/components/ui/country-select.d.ts +3 -1
- package/dist/components/ui/country-select.d.ts.map +1 -1
- package/dist/components/ui/country-select.js +3 -1
- package/dist/components/ui/currency-select.d.ts +3 -1
- package/dist/components/ui/currency-select.d.ts.map +1 -1
- package/dist/components/ui/currency-select.js +3 -1
- package/dist/components/ui/sheet.d.ts +5 -0
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +46 -26
- package/dist/components/ui/timezone-select.d.ts +3 -1
- package/dist/components/ui/timezone-select.d.ts.map +1 -1
- package/dist/components/ui/timezone-select.js +3 -1
- package/dist/contexts/TeamContext.d.ts +6 -0
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +8 -1
- package/dist/emails/otp-verification.d.ts.map +1 -1
- package/dist/emails/reset-password.d.ts.map +1 -1
- package/dist/emails/team-invitation.d.ts.map +1 -1
- package/dist/emails/team-invitation.js +3 -2
- package/dist/emails/verify-email.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useAuth.d.ts +29 -0
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +24 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +18 -5
- package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
- package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
- package/dist/hooks/useSessionCookieRefresh.js +49 -0
- package/dist/lib/api/api-error.d.ts +3 -1
- package/dist/lib/api/api-error.d.ts.map +1 -1
- package/dist/lib/api/api-error.js +4 -2
- package/dist/lib/api/auth/dual-auth.d.ts +38 -3
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +42 -7
- package/dist/lib/api/auth/permissions.d.ts +7 -2
- package/dist/lib/api/auth/permissions.d.ts.map +1 -1
- package/dist/lib/api/auth/permissions.js +2 -2
- package/dist/lib/api/auth/scope-policy.d.ts +71 -0
- package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
- package/dist/lib/api/auth/scope-policy.js +53 -0
- package/dist/lib/api/auth.d.ts +15 -0
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +17 -4
- package/dist/lib/api/entity/audit-log.d.ts +35 -0
- package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
- package/dist/lib/api/entity/audit-log.js +35 -0
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +315 -134
- package/dist/lib/api/helpers.d.ts +29 -4
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +30 -6
- package/dist/lib/api/keys.d.ts +16 -0
- package/dist/lib/api/keys.d.ts.map +1 -1
- package/dist/lib/api/keys.js +19 -1
- package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
- package/dist/lib/api/routes/patterns-usages.js +3 -11
- package/dist/lib/auth/auth-methods.d.ts +52 -0
- package/dist/lib/auth/auth-methods.d.ts.map +1 -0
- package/dist/lib/auth/auth-methods.js +63 -0
- package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.d.ts +9 -0
- package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.js +8 -2
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
- package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
- package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
- package/dist/lib/auth/security-notifications/index.d.ts +20 -0
- package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/index.js +22 -0
- package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
- package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/registry.js +26 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
- package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
- package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
- package/dist/lib/auth/security-notifications/types.d.ts +74 -0
- package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/types.js +0 -0
- package/dist/lib/auth/session-config.d.ts +37 -0
- package/dist/lib/auth/session-config.d.ts.map +1 -0
- package/dist/lib/auth/session-config.js +59 -0
- package/dist/lib/auth/session-refresh.d.ts +46 -0
- package/dist/lib/auth/session-refresh.d.ts.map +1 -0
- package/dist/lib/auth/session-refresh.js +11 -0
- package/dist/lib/auth-client.d.ts +1639 -0
- package/dist/lib/auth-client.d.ts.map +1 -0
- package/dist/lib/auth-client.js +4 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +21 -8
- package/dist/lib/billing/gateways/factory.d.ts +8 -3
- package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
- package/dist/lib/billing/gateways/factory.js +37 -23
- package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
- package/dist/lib/billing/stripe-webhook.js +2 -1
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +71 -0
- package/dist/lib/config/config-sync.d.ts +1 -4
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +6 -2
- package/dist/lib/config/types.d.ts +130 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.d.ts +9 -1
- package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.js +77 -4
- package/dist/lib/entities/schema-generator.d.ts.map +1 -1
- package/dist/lib/entities/schema-generator.js +20 -4
- package/dist/lib/entities/serialization.d.ts +2 -2
- package/dist/lib/entities/serialization.d.ts.map +1 -1
- package/dist/lib/entities/serialization.js +3 -2
- package/dist/lib/entities/types.d.ts +8 -0
- package/dist/lib/entities/types.d.ts.map +1 -1
- package/dist/lib/locale.d.ts.map +1 -1
- package/dist/lib/locale.js +4 -1
- package/dist/lib/mcp/audit.d.ts +8 -4
- package/dist/lib/mcp/audit.d.ts.map +1 -1
- package/dist/lib/plugins/hook-system.d.ts +13 -0
- package/dist/lib/plugins/hook-system.d.ts.map +1 -1
- package/dist/lib/plugins/hook-system.js +32 -2
- package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
- package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/handlers/index.js +3 -1
- package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
- package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
- package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
- package/dist/lib/selectors/core-selectors.d.ts +12 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
- package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.js +14 -1
- package/dist/lib/selectors/selectors.d.ts +24 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +16 -32
- package/dist/lib/services/membership.service.d.ts +10 -2
- package/dist/lib/services/membership.service.d.ts.map +1 -1
- package/dist/lib/services/membership.service.js +37 -16
- package/dist/lib/services/subscription.service.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.js +2 -1
- package/dist/lib/services/team-member.service.d.ts +15 -3
- package/dist/lib/services/team-member.service.d.ts.map +1 -1
- package/dist/lib/services/team-member.service.js +27 -6
- package/dist/lib/services/team.service.d.ts.map +1 -1
- package/dist/lib/teams/permissions.d.ts +3 -1
- package/dist/lib/teams/permissions.d.ts.map +1 -1
- package/dist/lib/teams/permissions.js +6 -0
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +3 -0
- package/dist/lib/theme/forced-theme.d.ts +34 -0
- package/dist/lib/theme/forced-theme.d.ts.map +1 -0
- package/dist/lib/theme/forced-theme.js +27 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
- package/dist/lib/theme/get-default-theme-mode.js +5 -3
- package/dist/messages/de/auth.json +25 -2
- package/dist/messages/de/index.d.ts +81 -0
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/index.js +2 -0
- package/dist/messages/de/index.ts +2 -0
- package/dist/messages/en/auth.json +25 -2
- package/dist/messages/en/index.d.ts +81 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/es/auth.json +25 -2
- package/dist/messages/es/index.d.ts +81 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/fr/auth.json +25 -2
- package/dist/messages/fr/index.d.ts +81 -0
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/index.js +2 -0
- package/dist/messages/fr/index.ts +2 -0
- package/dist/messages/it/auth.json +25 -2
- package/dist/messages/it/index.d.ts +81 -0
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/index.js +2 -0
- package/dist/messages/it/index.ts +2 -0
- package/dist/messages/pt/auth.json +25 -2
- package/dist/messages/pt/index.d.ts +81 -0
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/index.js +2 -0
- package/dist/messages/pt/index.ts +2 -0
- package/dist/migrations/005_api_audit_log.sql +2 -1
- package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
- package/dist/migrations/026_login_events.sql +91 -0
- package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
- package/dist/providers/DashboardProviders.d.ts +12 -3
- package/dist/providers/DashboardProviders.d.ts.map +1 -1
- package/dist/providers/DashboardProviders.js +6 -2
- package/dist/providers/theme-provider.d.ts +19 -2
- package/dist/providers/theme-provider.d.ts.map +1 -1
- package/dist/providers/theme-provider.js +6 -2
- package/dist/templates/app/(auth)/signup/page.tsx +8 -0
- package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
- package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
- package/dist/templates/app/api/health/route.ts +4 -2
- package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
- package/dist/templates/app/api/mcp/route.ts +4 -1
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
- package/dist/templates/app/api/v1/api-keys/route.ts +21 -43
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
- package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
- package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
- package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
- package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
- package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
- package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
- package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
- package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
- package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
- package/dist/templates/app/api/v1/media/route.ts +5 -9
- package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
- package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
- package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
- package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
- package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
- package/dist/templates/app/api/v1/teams/route.ts +33 -27
- package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
- package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
- package/dist/templates/app/api/v1/users/route.ts +21 -25
- package/dist/templates/app/dashboard/layout.tsx +2 -6
- package/dist/templates/app/layout.ppr.tsx +9 -14
- package/dist/templates/app/layout.tsx +37 -20
- package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
- package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
- package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
- package/dist/templates/proxy.ts +46 -10
- package/dist/templates/tsconfig.json +4 -0
- package/dist/types/theme.d.ts +9 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/migrations/005_api_audit_log.sql +2 -1
- package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
- package/migrations/026_login_events.sql +91 -0
- package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
- package/package.json +14 -11
- package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
- package/scripts/build/registry/generators/scope-registry.mjs +9 -2
- package/templates/app/(auth)/signup/page.tsx +8 -0
- package/templates/app/(public)/[...slug]/page.tsx +9 -14
- package/templates/app/api/auth/[...all]/route.ts +23 -1
- package/templates/app/api/health/route.ts +4 -2
- package/templates/app/api/internal/user-metadata/route.ts +5 -3
- package/templates/app/api/mcp/route.ts +4 -1
- package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
- package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
- package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
- package/templates/app/api/v1/api-keys/route.ts +21 -43
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
- package/templates/app/api/v1/billing/cancel/route.ts +7 -6
- package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
- package/templates/app/api/v1/billing/check-action/route.ts +6 -4
- package/templates/app/api/v1/billing/checkout/route.ts +7 -5
- package/templates/app/api/v1/billing/plans/route.ts +8 -12
- package/templates/app/api/v1/billing/portal/route.ts +7 -5
- package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
- package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
- package/templates/app/api/v1/devtools/features/route.ts +5 -4
- package/templates/app/api/v1/devtools/flows/route.ts +5 -4
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
- package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
- package/templates/app/api/v1/devtools/testing/route.ts +5 -4
- package/templates/app/api/v1/media/[id]/route.ts +13 -25
- package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
- package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
- package/templates/app/api/v1/media/route.ts +5 -9
- package/templates/app/api/v1/media/upload/route.ts +9 -21
- package/templates/app/api/v1/media-tags/route.ts +9 -13
- package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
- package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
- package/templates/app/api/v1/post-categories/route.ts +5 -4
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
- package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
- package/templates/app/api/v1/team-invitations/route.ts +9 -11
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
- package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
- package/templates/app/api/v1/teams/route.ts +33 -27
- package/templates/app/api/v1/teams/switch/route.ts +11 -13
- package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
- package/templates/app/api/v1/users/[id]/route.ts +35 -68
- package/templates/app/api/v1/users/route.ts +21 -25
- package/templates/app/dashboard/layout.tsx +2 -6
- package/templates/app/layout.ppr.tsx +9 -14
- package/templates/app/layout.tsx +37 -20
- package/templates/contents/themes/starter/config/app.config.ts +19 -0
- package/templates/contents/themes/starter/config/theme.config.ts +6 -0
- package/templates/contents/themes/starter/styles/globals.css +6 -2
- package/templates/proxy.ts +46 -10
- package/templates/tsconfig.json +4 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
- package/tests/jest/__mocks__/next-server.js +24 -0
- package/dist/styles/classes.json +0 -1093
- package/dist/styles/ui.css +0 -2
- package/scripts/build/build-ui-css.mjs +0 -351
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { authenticateRequest,
|
|
2
|
+
import { authenticateRequest, createAuthFailureResponse, canBypassTeamContext } from "../auth/dual-auth.js";
|
|
3
3
|
import { resolveEntityFromUrl, validateEntityOperation } from "./resolver.js";
|
|
4
4
|
import { generateEntitySchemas } from "../../entities/schema-generator.js";
|
|
5
5
|
import { queryWithRLS, mutateWithRLS, queryOneWithRLS } from "../../db.js";
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
addCorsHeaders,
|
|
18
18
|
handleCorsPreflightRequest
|
|
19
19
|
} from "../helpers.js";
|
|
20
|
-
import { afterEntityCreate, afterEntityUpdate, afterEntityDelete } from "../../entities/entity-hooks.js";
|
|
20
|
+
import { beforeEntityCreate, afterEntityCreate, beforeEntityUpdate, afterEntityUpdate, beforeEntityDelete, afterEntityDelete, beforeEntityRead, afterEntityRead } from "../../entities/entity-hooks.js";
|
|
21
|
+
import { logGenericHandlerUsage } from "./audit-log.js";
|
|
21
22
|
import { checkPermission } from "../../permissions/check.js";
|
|
22
23
|
import { extractPatternIds } from "../../blocks/pattern-resolver.js";
|
|
23
24
|
import { isPatternReference } from "../../../types/pattern-reference.js";
|
|
@@ -169,6 +170,102 @@ function parseTaxonomyFilterParams(url, entityConfig) {
|
|
|
169
170
|
function getTableName(entityConfig) {
|
|
170
171
|
return entityConfig.tableName || entityConfig.slug;
|
|
171
172
|
}
|
|
173
|
+
function isEntityField(entityConfig, fieldName) {
|
|
174
|
+
return entityConfig.fields.some((field) => field.name === fieldName);
|
|
175
|
+
}
|
|
176
|
+
const LIST_RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
177
|
+
"page",
|
|
178
|
+
"limit",
|
|
179
|
+
"fields",
|
|
180
|
+
"distinct",
|
|
181
|
+
"ids",
|
|
182
|
+
"parentId",
|
|
183
|
+
"search",
|
|
184
|
+
"sortBy",
|
|
185
|
+
"sortOrder",
|
|
186
|
+
"sort",
|
|
187
|
+
"order",
|
|
188
|
+
"child",
|
|
189
|
+
"meta",
|
|
190
|
+
"metas",
|
|
191
|
+
"includeMeta",
|
|
192
|
+
"userId",
|
|
193
|
+
"from",
|
|
194
|
+
"to",
|
|
195
|
+
"dateField",
|
|
196
|
+
"userFiltered"
|
|
197
|
+
]);
|
|
198
|
+
const TAXONOMY_FILTER_PARAMS = ["categoryId", "taxonomyId", "taxonomyType"];
|
|
199
|
+
const SEARCHABLE_FIELDS = ["name", "title", "slug", "content"];
|
|
200
|
+
const SORTABLE_BASE_FIELDS = ["id", "createdAt", "updatedAt", "teamId"];
|
|
201
|
+
const BARE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
202
|
+
function omitHandlerManagedKeys(entityConfig, data) {
|
|
203
|
+
var _a;
|
|
204
|
+
const managed = /* @__PURE__ */ new Set(["userId", "teamId"]);
|
|
205
|
+
if ((_a = entityConfig.taxonomies) == null ? void 0 : _a.enabled) {
|
|
206
|
+
for (const taxonomyType of entityConfig.taxonomies.types ?? []) {
|
|
207
|
+
managed.add(taxonomyType.field);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return Object.fromEntries(Object.entries(data).filter(([key]) => !managed.has(key)));
|
|
211
|
+
}
|
|
212
|
+
function validationErrorMessage(issues) {
|
|
213
|
+
const unknownKeys = issues.filter((issue) => issue.code === "unrecognized_keys").flatMap((issue) => issue.keys ?? []);
|
|
214
|
+
return unknownKeys.length > 0 ? `Unknown field(s): ${unknownKeys.join(", ")}` : "Validation error";
|
|
215
|
+
}
|
|
216
|
+
function asPgError(error) {
|
|
217
|
+
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
|
|
218
|
+
return error;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function mapConstraintViolation(error, operation) {
|
|
223
|
+
const pgError = asPgError(error);
|
|
224
|
+
if (!pgError) return null;
|
|
225
|
+
const { constraint, detail, table } = pgError;
|
|
226
|
+
switch (pgError.code) {
|
|
227
|
+
case "23505":
|
|
228
|
+
return createApiError(
|
|
229
|
+
"A record with this value already exists",
|
|
230
|
+
409,
|
|
231
|
+
{ constraint, detail },
|
|
232
|
+
"UNIQUE_CONSTRAINT_VIOLATION"
|
|
233
|
+
);
|
|
234
|
+
case "23514":
|
|
235
|
+
return createApiError(
|
|
236
|
+
constraint ? `Value rejected by check constraint "${constraint}"` : "Value rejected by a database check constraint",
|
|
237
|
+
422,
|
|
238
|
+
{ constraint, table },
|
|
239
|
+
"CHECK_CONSTRAINT_VIOLATION"
|
|
240
|
+
);
|
|
241
|
+
case "23503":
|
|
242
|
+
if (operation === "delete") {
|
|
243
|
+
return createApiError(
|
|
244
|
+
detail || "Cannot delete: this record is referenced by other records",
|
|
245
|
+
409,
|
|
246
|
+
{ constraint },
|
|
247
|
+
"FOREIGN_KEY_VIOLATION"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return createApiError(
|
|
251
|
+
detail ? `Referenced record does not exist: ${detail}` : "Referenced record does not exist",
|
|
252
|
+
422,
|
|
253
|
+
{ constraint, detail },
|
|
254
|
+
"FOREIGN_KEY_VIOLATION"
|
|
255
|
+
);
|
|
256
|
+
default:
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function hookRejectionStatus(error) {
|
|
261
|
+
if (error && typeof error === "object") {
|
|
262
|
+
const candidate = error.status ?? error.statusCode;
|
|
263
|
+
if (typeof candidate === "number" && candidate >= 400 && candidate < 500) {
|
|
264
|
+
return candidate;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return 400;
|
|
268
|
+
}
|
|
172
269
|
async function filterOrphanedPatternReferences(blocks, userId) {
|
|
173
270
|
if (!Array.isArray(blocks) || blocks.length === 0) return blocks;
|
|
174
271
|
const patternIds = extractPatternIds(blocks);
|
|
@@ -291,7 +388,24 @@ async function validateTeamContextWithBypass(request, authResult, userId) {
|
|
|
291
388
|
}
|
|
292
389
|
return { valid: true, teamId, isBypass: false };
|
|
293
390
|
}
|
|
391
|
+
async function runWithAuditLog(request, run) {
|
|
392
|
+
const startedAt = Date.now();
|
|
393
|
+
const audit = { auth: null };
|
|
394
|
+
let statusCode = 500;
|
|
395
|
+
try {
|
|
396
|
+
const response = await run(audit);
|
|
397
|
+
statusCode = response.status;
|
|
398
|
+
return response;
|
|
399
|
+
} finally {
|
|
400
|
+
logGenericHandlerUsage(audit.auth, request, statusCode, Date.now() - startedAt).catch((error) => {
|
|
401
|
+
console.error("[generic-handler:audit] unexpected audit failure:", error);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
294
405
|
async function handleGenericList(request) {
|
|
406
|
+
return runWithAuditLog(request, (audit) => handleGenericListImpl(request, audit));
|
|
407
|
+
}
|
|
408
|
+
async function handleGenericListImpl(request, audit) {
|
|
295
409
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
296
410
|
try {
|
|
297
411
|
const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
|
|
@@ -303,25 +417,22 @@ async function handleGenericList(request) {
|
|
|
303
417
|
const response2 = createApiError("List operation not supported for this entity", 405);
|
|
304
418
|
return addCorsHeaders(response2, request);
|
|
305
419
|
}
|
|
306
|
-
const authResult = await authenticateRequest(request);
|
|
420
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
|
|
421
|
+
audit.auth = authResult;
|
|
307
422
|
let userId = null;
|
|
308
423
|
let teamId = null;
|
|
309
424
|
let isBypass = false;
|
|
425
|
+
if (!authResult.success && authResult.type === "api-key") {
|
|
426
|
+
return addCorsHeaders(createAuthFailureResponse(authResult), request);
|
|
427
|
+
}
|
|
310
428
|
if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
|
|
311
429
|
teamId = request.headers.get("x-team-id") ?? null;
|
|
312
430
|
} else if (!authResult.success) {
|
|
313
|
-
return
|
|
314
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
315
|
-
{ status: 401 }
|
|
316
|
-
);
|
|
431
|
+
return createAuthFailureResponse(authResult);
|
|
317
432
|
} else {
|
|
318
433
|
if (authResult.rateLimitResponse) {
|
|
319
434
|
return authResult.rateLimitResponse;
|
|
320
435
|
}
|
|
321
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
|
|
322
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
323
|
-
return addCorsHeaders(response2, request);
|
|
324
|
-
}
|
|
325
436
|
userId = authResult.user.id;
|
|
326
437
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
|
|
327
438
|
if (!teamValidation.valid) {
|
|
@@ -341,10 +452,9 @@ async function handleGenericList(request) {
|
|
|
341
452
|
const distinctParam = url.searchParams.get("distinct") === "true";
|
|
342
453
|
const idsFromParams = url.searchParams.getAll("ids");
|
|
343
454
|
const specificIds = idsFromParams.length > 0 ? idsFromParams.flatMap((param) => param.split(",").map((id) => id.trim())).filter(Boolean) : null;
|
|
344
|
-
const knownParams = /* @__PURE__ */ new Set(["page", "limit", "fields", "distinct", "ids", "parentId", "search", "sortBy", "sortOrder", "child", "meta", "from", "to", "dateField"]);
|
|
345
455
|
const customFilters = {};
|
|
346
456
|
url.searchParams.forEach((value, key) => {
|
|
347
|
-
if (!
|
|
457
|
+
if (!LIST_RESERVED_PARAMS.has(key) && value) {
|
|
348
458
|
if (!customFilters[key]) {
|
|
349
459
|
customFilters[key] = [];
|
|
350
460
|
}
|
|
@@ -353,9 +463,59 @@ async function handleGenericList(request) {
|
|
|
353
463
|
}
|
|
354
464
|
});
|
|
355
465
|
const entityConfig = resolution.entityConfig;
|
|
466
|
+
const shapedQuery = await beforeEntityRead(entityConfig.slug, { ...customFilters }, userId ?? void 0);
|
|
467
|
+
for (const [key, value] of Object.entries(shapedQuery)) {
|
|
468
|
+
customFilters[key] = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
469
|
+
}
|
|
470
|
+
const taxonomyFilterParams = ((_b = entityConfig.taxonomies) == null ? void 0 : _b.enabled) ? TAXONOMY_FILTER_PARAMS : [];
|
|
471
|
+
const invalidFilterKeys = Object.keys(customFilters).filter(
|
|
472
|
+
(key) => !isEntityField(entityConfig, key) && !taxonomyFilterParams.includes(key)
|
|
473
|
+
);
|
|
474
|
+
if (invalidFilterKeys.length > 0) {
|
|
475
|
+
const response2 = createApiError(
|
|
476
|
+
`Unknown filter parameter(s) for ${entityConfig.slug}: ${invalidFilterKeys.join(", ")}`,
|
|
477
|
+
400,
|
|
478
|
+
{
|
|
479
|
+
invalidKeys: invalidFilterKeys,
|
|
480
|
+
allowedKeys: [...entityConfig.fields.map((f) => f.name), ...taxonomyFilterParams]
|
|
481
|
+
},
|
|
482
|
+
"INVALID_FILTER"
|
|
483
|
+
);
|
|
484
|
+
return addCorsHeaders(response2, request);
|
|
485
|
+
}
|
|
486
|
+
const searchParam = url.searchParams.get("search");
|
|
487
|
+
const searchableFields = SEARCHABLE_FIELDS.filter((name) => isEntityField(entityConfig, name));
|
|
488
|
+
if (searchParam && searchParam.trim() !== "" && searchableFields.length === 0) {
|
|
489
|
+
const response2 = createApiError(
|
|
490
|
+
`Entity ${entityConfig.slug} has no searchable field (${SEARCHABLE_FIELDS.join(", ")}); the search parameter is not supported`,
|
|
491
|
+
400,
|
|
492
|
+
{ searchableFields: SEARCHABLE_FIELDS },
|
|
493
|
+
"SEARCH_NOT_SUPPORTED"
|
|
494
|
+
);
|
|
495
|
+
return addCorsHeaders(response2, request);
|
|
496
|
+
}
|
|
497
|
+
const sortByParam = url.searchParams.get("sortBy") || url.searchParams.get("sort");
|
|
498
|
+
if (sortByParam && !isEntityField(entityConfig, sortByParam) && !SORTABLE_BASE_FIELDS.includes(sortByParam)) {
|
|
499
|
+
const response2 = createApiError(
|
|
500
|
+
`Invalid sortBy field for ${entityConfig.slug}: ${sortByParam}`,
|
|
501
|
+
400,
|
|
502
|
+
{ allowedFields: [...SORTABLE_BASE_FIELDS, ...entityConfig.fields.map((f) => f.name)] },
|
|
503
|
+
"INVALID_SORT_FIELD"
|
|
504
|
+
);
|
|
505
|
+
return addCorsHeaders(response2, request);
|
|
506
|
+
}
|
|
356
507
|
let fields;
|
|
357
508
|
if (fieldsParam && distinctParam) {
|
|
358
509
|
const fieldName = fieldsParam;
|
|
510
|
+
if (!isEntityField(entityConfig, fieldName)) {
|
|
511
|
+
const response2 = createApiError(
|
|
512
|
+
`Invalid field for distinct query: ${fieldName}`,
|
|
513
|
+
400,
|
|
514
|
+
void 0,
|
|
515
|
+
"INVALID_FIELD"
|
|
516
|
+
);
|
|
517
|
+
return addCorsHeaders(response2, request);
|
|
518
|
+
}
|
|
359
519
|
const isJsonbField = fieldName === "contentLanguages" || fieldName === "brandValues" || fieldName === "hashtags";
|
|
360
520
|
if (isJsonbField) {
|
|
361
521
|
return handleDistinctJsonbField(entityConfig, fieldName, url, userId);
|
|
@@ -364,19 +524,17 @@ async function handleGenericList(request) {
|
|
|
364
524
|
}
|
|
365
525
|
} else if (fieldsParam) {
|
|
366
526
|
const requestedFields = fieldsParam.split(",").map((f) => f.trim());
|
|
367
|
-
const validFields = requestedFields.filter(
|
|
368
|
-
(fieldName) => entityConfig.fields.some((field) => field.name === fieldName)
|
|
369
|
-
);
|
|
527
|
+
const validFields = requestedFields.filter((fieldName) => isEntityField(entityConfig, fieldName));
|
|
370
528
|
fields = ["id", ...validFields].map((fieldName) => {
|
|
371
529
|
const columnName = /[A-Z]/.test(fieldName) ? `"${fieldName}"` : fieldName;
|
|
372
530
|
return `t.${columnName}`;
|
|
373
531
|
}).join(", ");
|
|
374
532
|
} else {
|
|
375
533
|
const systemFields = ["id", "userId", "teamId", "createdAt", "updatedAt"];
|
|
376
|
-
if ((
|
|
534
|
+
if ((_c = entityConfig.builder) == null ? void 0 : _c.enabled) {
|
|
377
535
|
systemFields.push("blocks");
|
|
378
536
|
}
|
|
379
|
-
if ((
|
|
537
|
+
if ((_d = entityConfig.table) == null ? void 0 : _d.softDelete) {
|
|
380
538
|
systemFields.push("deletedAt", "deletedBy");
|
|
381
539
|
}
|
|
382
540
|
const configFields = entityConfig.fields.map((field) => {
|
|
@@ -395,12 +553,11 @@ async function handleGenericList(request) {
|
|
|
395
553
|
let query;
|
|
396
554
|
let queryParams;
|
|
397
555
|
let paramIndex = 1;
|
|
398
|
-
const searchParam = url.searchParams.get("search");
|
|
399
556
|
const fromDate = url.searchParams.get("from");
|
|
400
557
|
const toDate = url.searchParams.get("to");
|
|
401
558
|
const dateFieldName = url.searchParams.get("dateField");
|
|
402
|
-
const isPublicEntity = ((
|
|
403
|
-
const requestingPublishedOnly = (
|
|
559
|
+
const isPublicEntity = ((_e = entityConfig.access) == null ? void 0 : _e.public) === true;
|
|
560
|
+
const requestingPublishedOnly = (_f = customFilters["status"]) == null ? void 0 : _f.includes("published");
|
|
404
561
|
const skipUserFilter = isPublicEntity && requestingPublishedOnly;
|
|
405
562
|
if (skipUserFilter && process.env.NODE_ENV === "development") {
|
|
406
563
|
console.log(`[GenericHandler] Public access mode: skipping userId filter for ${entityConfig.slug} (status=published)`);
|
|
@@ -429,7 +586,7 @@ async function handleGenericList(request) {
|
|
|
429
586
|
query += ` AND t."teamId" = $${paramIndex++}`;
|
|
430
587
|
queryParams.push(teamId);
|
|
431
588
|
}
|
|
432
|
-
if (userId && !((
|
|
589
|
+
if (userId && !((_g = entityConfig.access) == null ? void 0 : _g.shared) && !skipUserFilter && !isBypass) {
|
|
433
590
|
query += ` AND t."userId" = $${paramIndex++}`;
|
|
434
591
|
queryParams.push(userId);
|
|
435
592
|
}
|
|
@@ -437,7 +594,7 @@ async function handleGenericList(request) {
|
|
|
437
594
|
query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
|
|
438
595
|
queryParams.push(ownershipFilter.value);
|
|
439
596
|
}
|
|
440
|
-
if (((
|
|
597
|
+
if (((_h = entityConfig.table) == null ? void 0 : _h.softDelete) && !isBypass) {
|
|
441
598
|
query += ` AND t."deletedAt" IS NULL`;
|
|
442
599
|
}
|
|
443
600
|
} else if (fieldsParam && distinctParam) {
|
|
@@ -453,7 +610,7 @@ async function handleGenericList(request) {
|
|
|
453
610
|
query += ` AND t."parentId" = $${paramIndex++}`;
|
|
454
611
|
queryParams.push(parentId);
|
|
455
612
|
}
|
|
456
|
-
if (userId && !((
|
|
613
|
+
if (userId && !((_i = entityConfig.access) == null ? void 0 : _i.shared) && !skipUserFilter && !isBypass) {
|
|
457
614
|
query += ` AND t."userId" = $${paramIndex++}`;
|
|
458
615
|
queryParams.push(userId);
|
|
459
616
|
}
|
|
@@ -461,7 +618,7 @@ async function handleGenericList(request) {
|
|
|
461
618
|
query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
|
|
462
619
|
queryParams.push(ownershipFilter.value);
|
|
463
620
|
}
|
|
464
|
-
if (((
|
|
621
|
+
if (((_j = entityConfig.table) == null ? void 0 : _j.softDelete) && !isBypass) {
|
|
465
622
|
query += ` AND t."deletedAt" IS NULL`;
|
|
466
623
|
}
|
|
467
624
|
query += ` ORDER BY "${fieldName}" ASC LIMIT $${paramIndex++}`;
|
|
@@ -474,7 +631,7 @@ async function handleGenericList(request) {
|
|
|
474
631
|
whereConditions.push(`t."teamId" = $${paramIndex++}`);
|
|
475
632
|
queryParams.push(teamId);
|
|
476
633
|
}
|
|
477
|
-
if (userId && !((
|
|
634
|
+
if (userId && !((_k = entityConfig.access) == null ? void 0 : _k.shared) && !skipUserFilter && !isBypass) {
|
|
478
635
|
whereConditions.push(`t."userId" = $${paramIndex++}`);
|
|
479
636
|
queryParams.push(userId);
|
|
480
637
|
}
|
|
@@ -482,33 +639,15 @@ async function handleGenericList(request) {
|
|
|
482
639
|
whereConditions.push(`t."${ownershipFilter.field}" = $${paramIndex++}`);
|
|
483
640
|
queryParams.push(ownershipFilter.value);
|
|
484
641
|
}
|
|
485
|
-
if (((
|
|
642
|
+
if (((_l = entityConfig.table) == null ? void 0 : _l.softDelete) && !isBypass) {
|
|
486
643
|
whereConditions.push(`t."deletedAt" IS NULL`);
|
|
487
644
|
}
|
|
488
|
-
if (searchParam && searchParam.trim() !== "") {
|
|
645
|
+
if (searchParam && searchParam.trim() !== "" && searchableFields.length > 0) {
|
|
489
646
|
const searchTerm = searchParam.trim();
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (hasName || hasTitle || hasSlug || hasContent) {
|
|
495
|
-
const searchConditions = [];
|
|
496
|
-
if (hasName) {
|
|
497
|
-
searchConditions.push(`t.name ILIKE $${paramIndex}`);
|
|
498
|
-
}
|
|
499
|
-
if (hasTitle) {
|
|
500
|
-
searchConditions.push(`t.title ILIKE $${paramIndex}`);
|
|
501
|
-
}
|
|
502
|
-
if (hasSlug) {
|
|
503
|
-
searchConditions.push(`t.slug ILIKE $${paramIndex}`);
|
|
504
|
-
}
|
|
505
|
-
if (hasContent) {
|
|
506
|
-
searchConditions.push(`t.content ILIKE $${paramIndex}`);
|
|
507
|
-
}
|
|
508
|
-
whereConditions.push(`(${searchConditions.join(" OR ")})`);
|
|
509
|
-
queryParams.push(`%${searchTerm}%`);
|
|
510
|
-
paramIndex++;
|
|
511
|
-
}
|
|
647
|
+
const searchConditions = searchableFields.map((name) => `t.${name} ILIKE $${paramIndex}`);
|
|
648
|
+
whereConditions.push(`(${searchConditions.join(" OR ")})`);
|
|
649
|
+
queryParams.push(`%${searchTerm}%`);
|
|
650
|
+
paramIndex++;
|
|
512
651
|
}
|
|
513
652
|
if (dateFieldName && (fromDate || toDate)) {
|
|
514
653
|
const field = entityConfig.fields.find((f) => f.name === dateFieldName);
|
|
@@ -536,6 +675,18 @@ async function handleGenericList(request) {
|
|
|
536
675
|
queryParams.push(JSON.stringify([value]));
|
|
537
676
|
});
|
|
538
677
|
whereConditions.push(`(${orConditions.join(" OR ")})`);
|
|
678
|
+
} else if (field.type === "date" || field.type === "datetime") {
|
|
679
|
+
const orConditions = values.map((value) => {
|
|
680
|
+
if (BARE_DATE_RE.test(value)) {
|
|
681
|
+
const lower = paramIndex++;
|
|
682
|
+
const upper = paramIndex++;
|
|
683
|
+
queryParams.push(value, value);
|
|
684
|
+
return `(t.${columnName} >= $${lower}::date AND t.${columnName} < $${upper}::date + 1)`;
|
|
685
|
+
}
|
|
686
|
+
queryParams.push(value);
|
|
687
|
+
return `t.${columnName} = $${paramIndex++}`;
|
|
688
|
+
});
|
|
689
|
+
whereConditions.push(`(${orConditions.join(" OR ")})`);
|
|
539
690
|
} else {
|
|
540
691
|
const orConditions = values.map(() => {
|
|
541
692
|
const condition = `t.${columnName} = $${paramIndex++}`;
|
|
@@ -568,16 +719,9 @@ async function handleGenericList(request) {
|
|
|
568
719
|
) pu ON t.id = pu."patternId"
|
|
569
720
|
`;
|
|
570
721
|
}
|
|
571
|
-
const
|
|
572
|
-
const sortOrderParam = (
|
|
573
|
-
|
|
574
|
-
if (sortByParam) {
|
|
575
|
-
const baseFields = ["id", "createdAt", "updatedAt", "teamId"];
|
|
576
|
-
const isValidField = entityConfig.fields.some((f) => f.name === sortByParam) || baseFields.includes(sortByParam);
|
|
577
|
-
if (isValidField) {
|
|
578
|
-
orderByClause = `t."${sortByParam}" ${sortOrderParam}`;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
722
|
+
const sortOrderRaw = url.searchParams.get("sortOrder") || url.searchParams.get("order");
|
|
723
|
+
const sortOrderParam = (sortOrderRaw == null ? void 0 : sortOrderRaw.toUpperCase()) === "ASC" ? "ASC" : "DESC";
|
|
724
|
+
const orderByClause = sortByParam ? `t."${sortByParam}" ${sortOrderParam}` : 't."createdAt" DESC';
|
|
581
725
|
query = `
|
|
582
726
|
SELECT ${fields}, COUNT(*) OVER() as total_count
|
|
583
727
|
FROM "${tableName}" t
|
|
@@ -624,7 +768,8 @@ async function handleGenericList(request) {
|
|
|
624
768
|
);
|
|
625
769
|
}
|
|
626
770
|
const finalData = filterPublicFields(dataWithTaxonomies, entityConfig, userId);
|
|
627
|
-
const
|
|
771
|
+
const shapedData = await afterEntityRead(entityConfig.slug, finalData, { ...customFilters }, userId ?? void 0);
|
|
772
|
+
const response = createApiResponse(shapedData, paginationMeta);
|
|
628
773
|
return addCorsHeaders(response, request);
|
|
629
774
|
} catch (error) {
|
|
630
775
|
console.error("Error in generic list handler:", error);
|
|
@@ -633,6 +778,9 @@ async function handleGenericList(request) {
|
|
|
633
778
|
}
|
|
634
779
|
}
|
|
635
780
|
async function handleGenericCreate(request) {
|
|
781
|
+
return runWithAuditLog(request, (audit) => handleGenericCreateImpl(request, audit));
|
|
782
|
+
}
|
|
783
|
+
async function handleGenericCreateImpl(request, audit) {
|
|
636
784
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
637
785
|
try {
|
|
638
786
|
const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
|
|
@@ -644,20 +792,14 @@ async function handleGenericCreate(request) {
|
|
|
644
792
|
const response2 = createApiError("Create operation not supported for this entity", 405);
|
|
645
793
|
return addCorsHeaders(response2, request);
|
|
646
794
|
}
|
|
647
|
-
const authResult = await authenticateRequest(request);
|
|
795
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
|
|
796
|
+
audit.auth = authResult;
|
|
648
797
|
if (!authResult.success) {
|
|
649
|
-
return
|
|
650
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
651
|
-
{ status: 401 }
|
|
652
|
-
);
|
|
798
|
+
return createAuthFailureResponse(authResult);
|
|
653
799
|
}
|
|
654
800
|
if (authResult.rateLimitResponse) {
|
|
655
801
|
return authResult.rateLimitResponse;
|
|
656
802
|
}
|
|
657
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
|
|
658
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
659
|
-
return addCorsHeaders(response2, request);
|
|
660
|
-
}
|
|
661
803
|
let body;
|
|
662
804
|
try {
|
|
663
805
|
body = await request.json();
|
|
@@ -670,16 +812,21 @@ async function handleGenericCreate(request) {
|
|
|
670
812
|
const entityConfig = resolution.entityConfig;
|
|
671
813
|
const tableName = getTableName(entityConfig);
|
|
672
814
|
const schemas = generateEntitySchemas(entityConfig);
|
|
673
|
-
const validation = schemas.create.safeParse(entityData);
|
|
815
|
+
const validation = schemas.create.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
|
|
674
816
|
if (!validation.success) {
|
|
675
817
|
console.error(`[${entityConfig.slug}] Validation failed:`, {
|
|
676
818
|
entityData,
|
|
677
819
|
errors: validation.error.issues
|
|
678
820
|
});
|
|
679
|
-
const response2 = createApiError(
|
|
821
|
+
const response2 = createApiError(
|
|
822
|
+
validationErrorMessage(validation.error.issues),
|
|
823
|
+
400,
|
|
824
|
+
validation.error.issues,
|
|
825
|
+
"VALIDATION_ERROR"
|
|
826
|
+
);
|
|
680
827
|
return addCorsHeaders(response2, request);
|
|
681
828
|
}
|
|
682
|
-
|
|
829
|
+
let validatedData = validation.data;
|
|
683
830
|
const xssField = checkNameFieldXss(entityConfig, validatedData);
|
|
684
831
|
if (xssField) {
|
|
685
832
|
const xssResponse = createApiError(
|
|
@@ -715,6 +862,22 @@ async function handleGenericCreate(request) {
|
|
|
715
862
|
}
|
|
716
863
|
const permDenied = await checkAuthPermission(authResult, entityConfig.slug, "create", teamId, request);
|
|
717
864
|
if (permDenied) return permDenied;
|
|
865
|
+
try {
|
|
866
|
+
validatedData = await beforeEntityCreate(
|
|
867
|
+
entityConfig.slug,
|
|
868
|
+
{ ...validatedData, userId: userIdToUse, teamId },
|
|
869
|
+
authResult.user.id
|
|
870
|
+
);
|
|
871
|
+
} catch (hookError) {
|
|
872
|
+
console.error(`[generic-handler] beforeEntityCreate rejected ${entityConfig.slug} create:`, hookError);
|
|
873
|
+
const response2 = createApiError(
|
|
874
|
+
hookError instanceof Error && hookError.message ? hookError.message : `Create rejected by ${entityConfig.slug} before_create hook`,
|
|
875
|
+
hookRejectionStatus(hookError),
|
|
876
|
+
void 0,
|
|
877
|
+
"BEFORE_CREATE_REJECTED"
|
|
878
|
+
);
|
|
879
|
+
return addCorsHeaders(response2, request);
|
|
880
|
+
}
|
|
718
881
|
if (idStrategy === "serial") {
|
|
719
882
|
insertFields = ['"userId"', '"teamId"'];
|
|
720
883
|
placeholders = ["$1", "$2"];
|
|
@@ -800,6 +963,7 @@ async function handleGenericCreate(request) {
|
|
|
800
963
|
if (!actionResult.allowed) {
|
|
801
964
|
const statusCode = actionResult.reason === "quota_exceeded" ? 429 : 403;
|
|
802
965
|
const response2 = createApiError(
|
|
966
|
+
// @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
|
|
803
967
|
actionResult.message || `Quota exceeded for ${entityConfig.slug}`,
|
|
804
968
|
statusCode,
|
|
805
969
|
void 0,
|
|
@@ -882,22 +1046,18 @@ async function handleGenericCreate(request) {
|
|
|
882
1046
|
return addCorsHeaders(response, request);
|
|
883
1047
|
} catch (error) {
|
|
884
1048
|
console.error("Error in generic create handler:", error);
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const response2 = createApiError(
|
|
889
|
-
"A record with this value already exists",
|
|
890
|
-
409,
|
|
891
|
-
{ constraint, detail },
|
|
892
|
-
"UNIQUE_CONSTRAINT_VIOLATION"
|
|
893
|
-
);
|
|
894
|
-
return addCorsHeaders(response2, request);
|
|
1049
|
+
const constraintResponse = mapConstraintViolation(error, "create");
|
|
1050
|
+
if (constraintResponse) {
|
|
1051
|
+
return addCorsHeaders(constraintResponse, request);
|
|
895
1052
|
}
|
|
896
1053
|
const response = createApiError("Internal server error", 500);
|
|
897
1054
|
return addCorsHeaders(response, request);
|
|
898
1055
|
}
|
|
899
1056
|
}
|
|
900
1057
|
async function handleGenericRead(request, { params }) {
|
|
1058
|
+
return runWithAuditLog(request, (audit) => handleGenericReadImpl(request, audit, { params }));
|
|
1059
|
+
}
|
|
1060
|
+
async function handleGenericReadImpl(request, audit, { params }) {
|
|
901
1061
|
var _a, _b, _c, _d, _e, _f;
|
|
902
1062
|
try {
|
|
903
1063
|
const { id } = await params;
|
|
@@ -910,25 +1070,22 @@ async function handleGenericRead(request, { params }) {
|
|
|
910
1070
|
const response2 = createApiError("Read operation not supported for this entity", 405);
|
|
911
1071
|
return addCorsHeaders(response2, request);
|
|
912
1072
|
}
|
|
913
|
-
const authResult = await authenticateRequest(request);
|
|
1073
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
|
|
1074
|
+
audit.auth = authResult;
|
|
914
1075
|
let userId = null;
|
|
915
1076
|
let teamId = null;
|
|
916
1077
|
let isBypass = false;
|
|
1078
|
+
if (!authResult.success && authResult.type === "api-key") {
|
|
1079
|
+
return addCorsHeaders(createAuthFailureResponse(authResult), request);
|
|
1080
|
+
}
|
|
917
1081
|
if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
|
|
918
1082
|
console.log(`[GenericHandler] Public access allowed for ${resolution.entityConfig.slug} read`);
|
|
919
1083
|
} else if (!authResult.success) {
|
|
920
|
-
return
|
|
921
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
922
|
-
{ status: 401 }
|
|
923
|
-
);
|
|
1084
|
+
return createAuthFailureResponse(authResult);
|
|
924
1085
|
} else {
|
|
925
1086
|
if (authResult.rateLimitResponse) {
|
|
926
1087
|
return authResult.rateLimitResponse;
|
|
927
1088
|
}
|
|
928
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
|
|
929
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
930
|
-
return addCorsHeaders(response2, request);
|
|
931
|
-
}
|
|
932
1089
|
userId = authResult.user.id;
|
|
933
1090
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
|
|
934
1091
|
if (!teamValidation.valid) {
|
|
@@ -1034,7 +1191,8 @@ async function handleGenericRead(request, { params }) {
|
|
|
1034
1191
|
itemWithTaxonomies = itemsWithTaxonomies[0] || itemWithChild;
|
|
1035
1192
|
}
|
|
1036
1193
|
const finalItem = filterPublicFields(itemWithTaxonomies, entityConfig, userId);
|
|
1037
|
-
const
|
|
1194
|
+
const [shapedItem] = await afterEntityRead(entityConfig.slug, [finalItem], { id }, userId ?? void 0);
|
|
1195
|
+
const response = createApiResponse(shapedItem ?? finalItem);
|
|
1038
1196
|
return addCorsHeaders(response, request);
|
|
1039
1197
|
} catch (error) {
|
|
1040
1198
|
console.error("Error in generic read handler:", error);
|
|
@@ -1043,6 +1201,9 @@ async function handleGenericRead(request, { params }) {
|
|
|
1043
1201
|
}
|
|
1044
1202
|
}
|
|
1045
1203
|
async function handleGenericUpdate(request, { params }) {
|
|
1204
|
+
return runWithAuditLog(request, (audit) => handleGenericUpdateImpl(request, audit, { params }));
|
|
1205
|
+
}
|
|
1206
|
+
async function handleGenericUpdateImpl(request, audit, { params }) {
|
|
1046
1207
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1047
1208
|
try {
|
|
1048
1209
|
const { id } = await params;
|
|
@@ -1055,20 +1216,14 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1055
1216
|
const response2 = createApiError("Update operation not supported for this entity", 405);
|
|
1056
1217
|
return addCorsHeaders(response2, request);
|
|
1057
1218
|
}
|
|
1058
|
-
const authResult = await authenticateRequest(request);
|
|
1219
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
|
|
1220
|
+
audit.auth = authResult;
|
|
1059
1221
|
if (!authResult.success) {
|
|
1060
|
-
return
|
|
1061
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
1062
|
-
{ status: 401 }
|
|
1063
|
-
);
|
|
1222
|
+
return createAuthFailureResponse(authResult);
|
|
1064
1223
|
}
|
|
1065
1224
|
if (authResult.rateLimitResponse) {
|
|
1066
1225
|
return authResult.rateLimitResponse;
|
|
1067
1226
|
}
|
|
1068
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
|
|
1069
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
1070
|
-
return addCorsHeaders(response2, request);
|
|
1071
|
-
}
|
|
1072
1227
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
|
|
1073
1228
|
if (!teamValidation.valid) {
|
|
1074
1229
|
return teamValidation.error;
|
|
@@ -1115,16 +1270,21 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1115
1270
|
}
|
|
1116
1271
|
const tableName = getTableName(entityConfig);
|
|
1117
1272
|
const schemas = generateEntitySchemas(entityConfig);
|
|
1118
|
-
const validation = schemas.update.safeParse(entityData);
|
|
1273
|
+
const validation = schemas.update.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
|
|
1119
1274
|
if (!validation.success) {
|
|
1120
1275
|
console.log(`[${entityConfig.slug}] Validation failed:`, {
|
|
1121
1276
|
entityData,
|
|
1122
1277
|
errors: validation.error.issues
|
|
1123
1278
|
});
|
|
1124
|
-
const response2 = createApiError(
|
|
1279
|
+
const response2 = createApiError(
|
|
1280
|
+
validationErrorMessage(validation.error.issues),
|
|
1281
|
+
400,
|
|
1282
|
+
validation.error.issues,
|
|
1283
|
+
"VALIDATION_ERROR"
|
|
1284
|
+
);
|
|
1125
1285
|
return addCorsHeaders(response2, request);
|
|
1126
1286
|
}
|
|
1127
|
-
|
|
1287
|
+
let validatedData = validation.data;
|
|
1128
1288
|
const updateXssField = checkNameFieldXss(entityConfig, validatedData);
|
|
1129
1289
|
if (updateXssField) {
|
|
1130
1290
|
const updateXssResponse = createApiError(
|
|
@@ -1135,6 +1295,23 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1135
1295
|
);
|
|
1136
1296
|
return addCorsHeaders(updateXssResponse, request);
|
|
1137
1297
|
}
|
|
1298
|
+
try {
|
|
1299
|
+
validatedData = await beforeEntityUpdate(
|
|
1300
|
+
entityConfig.slug,
|
|
1301
|
+
id,
|
|
1302
|
+
validatedData,
|
|
1303
|
+
authResult.user.id
|
|
1304
|
+
);
|
|
1305
|
+
} catch (hookError) {
|
|
1306
|
+
console.error(`[generic-handler] beforeEntityUpdate rejected ${entityConfig.slug} update:`, hookError);
|
|
1307
|
+
const response2 = createApiError(
|
|
1308
|
+
hookError instanceof Error && hookError.message ? hookError.message : `Update rejected by ${entityConfig.slug} before_update hook`,
|
|
1309
|
+
hookRejectionStatus(hookError),
|
|
1310
|
+
void 0,
|
|
1311
|
+
"BEFORE_UPDATE_REJECTED"
|
|
1312
|
+
);
|
|
1313
|
+
return addCorsHeaders(response2, request);
|
|
1314
|
+
}
|
|
1138
1315
|
const updates = [];
|
|
1139
1316
|
const values = [];
|
|
1140
1317
|
let paramCount = 1;
|
|
@@ -1294,22 +1471,18 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1294
1471
|
return addCorsHeaders(response, request);
|
|
1295
1472
|
} catch (error) {
|
|
1296
1473
|
console.error("Error in generic update handler:", error);
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const response2 = createApiError(
|
|
1301
|
-
"A record with this value already exists",
|
|
1302
|
-
409,
|
|
1303
|
-
{ constraint, detail },
|
|
1304
|
-
"UNIQUE_CONSTRAINT_VIOLATION"
|
|
1305
|
-
);
|
|
1306
|
-
return addCorsHeaders(response2, request);
|
|
1474
|
+
const constraintResponse = mapConstraintViolation(error, "update");
|
|
1475
|
+
if (constraintResponse) {
|
|
1476
|
+
return addCorsHeaders(constraintResponse, request);
|
|
1307
1477
|
}
|
|
1308
1478
|
const response = createApiError("Internal server error", 500);
|
|
1309
1479
|
return addCorsHeaders(response, request);
|
|
1310
1480
|
}
|
|
1311
1481
|
}
|
|
1312
1482
|
async function handleGenericDelete(request, { params }) {
|
|
1483
|
+
return runWithAuditLog(request, (audit) => handleGenericDeleteImpl(request, audit, { params }));
|
|
1484
|
+
}
|
|
1485
|
+
async function handleGenericDeleteImpl(request, audit, { params }) {
|
|
1313
1486
|
var _a, _b, _c, _d, _e;
|
|
1314
1487
|
try {
|
|
1315
1488
|
const { id } = await params;
|
|
@@ -1322,20 +1495,14 @@ async function handleGenericDelete(request, { params }) {
|
|
|
1322
1495
|
const response2 = createApiError("Delete operation not supported for this entity", 405);
|
|
1323
1496
|
return addCorsHeaders(response2, request);
|
|
1324
1497
|
}
|
|
1325
|
-
const authResult = await authenticateRequest(request);
|
|
1498
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:delete` });
|
|
1499
|
+
audit.auth = authResult;
|
|
1326
1500
|
if (!authResult.success) {
|
|
1327
|
-
return
|
|
1328
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
1329
|
-
{ status: 401 }
|
|
1330
|
-
);
|
|
1501
|
+
return createAuthFailureResponse(authResult);
|
|
1331
1502
|
}
|
|
1332
1503
|
if (authResult.rateLimitResponse) {
|
|
1333
1504
|
return authResult.rateLimitResponse;
|
|
1334
1505
|
}
|
|
1335
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:delete`)) {
|
|
1336
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
1337
|
-
return addCorsHeaders(response2, request);
|
|
1338
|
-
}
|
|
1339
1506
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
|
|
1340
1507
|
if (!teamValidation.valid) {
|
|
1341
1508
|
return teamValidation.error;
|
|
@@ -1349,6 +1516,27 @@ async function handleGenericDelete(request, { params }) {
|
|
|
1349
1516
|
if (permDenied) return permDenied;
|
|
1350
1517
|
const entityConfig = resolution.entityConfig;
|
|
1351
1518
|
const tableName = getTableName(entityConfig);
|
|
1519
|
+
try {
|
|
1520
|
+
const allowDelete = await beforeEntityDelete(entityConfig.slug, id, authResult.user.id);
|
|
1521
|
+
if (!allowDelete) {
|
|
1522
|
+
const response2 = createApiError(
|
|
1523
|
+
`Delete rejected by ${entityConfig.slug} before_delete hook`,
|
|
1524
|
+
403,
|
|
1525
|
+
void 0,
|
|
1526
|
+
"BEFORE_DELETE_REJECTED"
|
|
1527
|
+
);
|
|
1528
|
+
return addCorsHeaders(response2, request);
|
|
1529
|
+
}
|
|
1530
|
+
} catch (hookError) {
|
|
1531
|
+
console.error(`[generic-handler] beforeEntityDelete rejected ${entityConfig.slug} delete:`, hookError);
|
|
1532
|
+
const response2 = createApiError(
|
|
1533
|
+
hookError instanceof Error && hookError.message ? hookError.message : `Delete rejected by ${entityConfig.slug} before_delete hook`,
|
|
1534
|
+
hookRejectionStatus(hookError),
|
|
1535
|
+
void 0,
|
|
1536
|
+
"BEFORE_DELETE_REJECTED"
|
|
1537
|
+
);
|
|
1538
|
+
return addCorsHeaders(response2, request);
|
|
1539
|
+
}
|
|
1352
1540
|
const whereConditions = ["id = $1"];
|
|
1353
1541
|
const deleteParams = [id];
|
|
1354
1542
|
let paramIndex = 2;
|
|
@@ -1405,16 +1593,9 @@ async function handleGenericDelete(request, { params }) {
|
|
|
1405
1593
|
return addCorsHeaders(response, request);
|
|
1406
1594
|
} catch (error) {
|
|
1407
1595
|
console.error("Error in generic delete handler:", error);
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
const response2 = createApiError(
|
|
1412
|
-
detail || "Cannot delete: this record is referenced by other records",
|
|
1413
|
-
409,
|
|
1414
|
-
{ constraint },
|
|
1415
|
-
"FOREIGN_KEY_VIOLATION"
|
|
1416
|
-
);
|
|
1417
|
-
return addCorsHeaders(response2, request);
|
|
1596
|
+
const constraintResponse = mapConstraintViolation(error, "delete");
|
|
1597
|
+
if (constraintResponse) {
|
|
1598
|
+
return addCorsHeaders(constraintResponse, request);
|
|
1418
1599
|
}
|
|
1419
1600
|
const response = createApiError("Internal server error", 500);
|
|
1420
1601
|
return addCorsHeaders(response, request);
|