@nextsparkjs/core 0.1.0-beta.185 → 0.1.0-beta.187
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/DeveloperGuard.js +8 -4
- package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
- package/dist/components/app/guards/SuperAdminGuard.js +8 -4
- 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/devtools/api-explorer/ApiExplorer.js +4 -4
- 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/patterns/pages/PatternEditPage.d.ts +5 -1
- package/dist/components/patterns/pages/PatternEditPage.d.ts.map +1 -1
- package/dist/components/patterns/pages/PatternEditPage.js +16 -5
- package/dist/components/patterns/pages/PatternsCreatePage.d.ts +2 -1
- package/dist/components/patterns/pages/PatternsCreatePage.d.ts.map +1 -1
- package/dist/components/patterns/pages/PatternsCreatePage.js +17 -5
- 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/entities.js +2 -1
- 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 +333 -138
- package/dist/lib/api/entity-handler.js +2 -2
- 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/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 +13 -1
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +45 -11
- 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/email/templates.d.ts +3 -3
- package/dist/lib/email/templates.d.ts.map +1 -1
- package/dist/lib/entities/api-generator.js +5 -5
- 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 +20 -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/api-docs.service.d.ts +5 -1
- package/dist/lib/services/api-docs.service.d.ts.map +1 -1
- package/dist/lib/services/api-presets.service.d.ts +6 -1
- package/dist/lib/services/api-presets.service.d.ts.map +1 -1
- package/dist/lib/services/feature.service.d.ts +58 -4
- package/dist/lib/services/feature.service.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/permission.service.d.ts +10 -4
- package/dist/lib/services/permission.service.d.ts.map +1 -1
- package/dist/lib/services/plugin.service.d.ts +1 -1
- package/dist/lib/services/plugin.service.d.ts.map +1 -1
- 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/template-resolver.d.ts +3 -3
- 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/lib/utils/cors.d.ts +13 -0
- package/dist/lib/utils/cors.d.ts.map +1 -1
- package/dist/lib/utils/cors.js +20 -0
- package/dist/messages/de/auth.json +25 -2
- package/dist/messages/de/index.d.ts +83 -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/de/navigation.json +3 -1
- package/dist/messages/en/auth.json +25 -2
- package/dist/messages/en/index.d.ts +83 -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/en/navigation.json +3 -1
- package/dist/messages/es/auth.json +25 -2
- package/dist/messages/es/index.d.ts +83 -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/es/navigation.json +3 -1
- package/dist/messages/fr/auth.json +25 -2
- package/dist/messages/fr/index.d.ts +83 -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/fr/navigation.json +3 -1
- package/dist/messages/it/auth.json +25 -2
- package/dist/messages/it/index.d.ts +83 -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/it/navigation.json +3 -1
- package/dist/messages/pt/auth.json +25 -2
- package/dist/messages/pt/index.d.ts +83 -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/messages/pt/navigation.json +3 -1
- 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/nextspark-registries.d.ts +580 -236
- 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 +15 -15
- package/dist/templates/app/layout.tsx +46 -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 +15 -15
- package/templates/app/layout.tsx +46 -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/components/teams/TeamSwitchModal.d.js +0 -0
- package/dist/components/ui/accordion.d.js +0 -0
- package/dist/components/ui/avatar.d.js +0 -0
- package/dist/components/ui/badge.d.js +0 -0
- package/dist/components/ui/button.d.js +0 -0
- package/dist/components/ui/card.d.js +0 -0
- package/dist/components/ui/dialog.d.js +0 -0
- package/dist/components/ui/dropdown-menu.d.js +0 -0
- package/dist/components/ui/input.d.js +0 -0
- package/dist/components/ui/progress.d.js +0 -0
- package/dist/components/ui/scroll-area.d.js +0 -0
- package/dist/components/ui/select.d.js +0 -0
- package/dist/components/ui/separator.d.js +0 -0
- package/dist/components/ui/table.d.js +0 -0
- package/dist/components/ui/textarea.d.js +0 -0
- package/dist/contexts/TeamContext.d.js +0 -0
- package/dist/hooks/useAuth.d.js +0 -0
- package/dist/hooks/useLastAuthMethod.d.js +0 -0
- package/dist/hooks/useOrigin.d.js +0 -0
- package/dist/lib/api/auth/dual-auth.d.js +0 -0
- package/dist/lib/api/auth.d.js +0 -0
- package/dist/lib/api/cache.d.js +0 -0
- package/dist/lib/api/keys.d.js +0 -0
- package/dist/lib/auth-client.d.js +0 -0
- package/dist/lib/auth-context.d.js +0 -0
- package/dist/lib/auth.d.js +0 -0
- package/dist/lib/billing/config-types.d.js +0 -0
- package/dist/lib/billing/enforcement.d.js +0 -0
- package/dist/lib/billing/helpers.d.js +0 -0
- package/dist/lib/billing/jobs.d.js +0 -0
- package/dist/lib/billing/types.d.js +0 -0
- package/dist/lib/blocks/loader.d.js +0 -0
- package/dist/lib/config/app.config.d.js +0 -0
- package/dist/lib/config/config-loader.d.js +0 -0
- package/dist/lib/config/config-sync.d.js +0 -0
- package/dist/lib/config/config-types.d.js +0 -0
- package/dist/lib/config/config-utils.d.js +0 -0
- package/dist/lib/config/dashboard-types.d.js +0 -0
- package/dist/lib/config/dashboard.config.d.js +0 -0
- package/dist/lib/config/icon-map.d.js +0 -0
- package/dist/lib/config/index.d.js +0 -7
- package/dist/lib/config/nextspark-types.d.js +0 -0
- package/dist/lib/config/roles-merge.d.js +0 -0
- package/dist/lib/config/types.d.js +0 -0
- package/dist/lib/db.d.js +0 -0
- package/dist/lib/email/factory.d.js +0 -0
- package/dist/lib/email/index.d.js +0 -3
- package/dist/lib/email/providers/console.d.js +0 -0
- package/dist/lib/email/providers/resend.d.js +0 -0
- package/dist/lib/email/templates.d.js +0 -0
- package/dist/lib/email/types.d.js +0 -0
- package/dist/lib/entities/types.d.js +0 -0
- package/dist/lib/permissions/system.d.js +0 -0
- package/dist/lib/permissions/types.d.js +0 -0
- package/dist/lib/plugins/hook-system.d.js +0 -0
- package/dist/lib/scheduled-actions/cleanup.d.js +0 -0
- package/dist/lib/scheduled-actions/index.d.js +0 -19
- package/dist/lib/scheduled-actions/initializer.d.js +0 -1
- package/dist/lib/scheduled-actions/processor.d.js +0 -0
- package/dist/lib/scheduled-actions/registry.d.js +0 -0
- package/dist/lib/scheduled-actions/scheduler.d.js +0 -0
- package/dist/lib/scheduled-actions/types.d.js +0 -0
- package/dist/lib/services/api-routes.service.d.js +0 -0
- package/dist/lib/services/entity-type.service.d.js +0 -0
- package/dist/lib/services/index.d.js +0 -40
- package/dist/lib/services/invoice.service.d.js +0 -0
- package/dist/lib/services/membership.service.d.js +0 -0
- package/dist/lib/services/meta.service.d.js +0 -0
- package/dist/lib/services/middleware.service.d.js +0 -0
- package/dist/lib/services/namespace.service.d.js +0 -0
- package/dist/lib/services/permission.service.d.js +0 -0
- package/dist/lib/services/plan.service.d.js +0 -0
- package/dist/lib/services/plugin.service.d.js +0 -0
- package/dist/lib/services/route-handler.service.d.js +0 -0
- package/dist/lib/services/scope.service.d.js +0 -0
- package/dist/lib/services/subscription.service.d.js +0 -0
- package/dist/lib/services/team-member.service.d.js +0 -0
- package/dist/lib/services/team.service.d.js +0 -0
- package/dist/lib/services/theme.service.d.js +0 -0
- package/dist/lib/services/usage.service.d.js +0 -0
- package/dist/lib/services/user-flags.service.d.js +0 -0
- package/dist/lib/services/user.service.d.js +0 -0
- package/dist/lib/teams/helpers.d.js +0 -0
- package/dist/lib/teams/permissions.d.js +0 -0
- package/dist/lib/teams/types.d.js +0 -0
- package/dist/lib/utils/config-merge.d.js +0 -0
- package/dist/lib/utils/file-logger.d.js +0 -0
- package/dist/lib/utils.d.js +0 -0
- package/dist/styles/classes.json +0 -1093
- package/dist/styles/ui.css +0 -2
- package/dist/types/auth.d.js +0 -0
- package/dist/types/blocks.d.js +0 -0
- package/dist/types/meta.types.d.js +0 -0
- package/dist/types/plugin.d.js +0 -0
- package/dist/types/testing.types.d.js +0 -0
- package/dist/types/theme.d.js +0 -0
- package/dist/types/user.types.d.js +0 -0
- package/scripts/build/build-ui-css.mjs +0 -351
- /package/dist/{components/app/layouts/PublicFooter.d.js → lib/auth/security-notifications/types.js} +0 -0
- /package/dist/{components/app/layouts/PublicNavbar.d.js → nextspark-registries.d.js} +0 -0
- /package/dist/{components/app/misc/ThemeToggle.d.js → types/use-intl-react-shim.d.js} +0 -0
|
@@ -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,16 +553,16 @@ 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)`);
|
|
407
564
|
}
|
|
565
|
+
const forcePublishedOnly = isPublicEntity && userId === null && entityConfig.fields.some((f) => f.name === "status");
|
|
408
566
|
const tableName = getTableName(entityConfig);
|
|
409
567
|
const taxonomyFilter = parseTaxonomyFilterParams(url, entityConfig);
|
|
410
568
|
let ownershipFilter = { applies: false };
|
|
@@ -429,7 +587,10 @@ async function handleGenericList(request) {
|
|
|
429
587
|
query += ` AND t."teamId" = $${paramIndex++}`;
|
|
430
588
|
queryParams.push(teamId);
|
|
431
589
|
}
|
|
432
|
-
if (
|
|
590
|
+
if (forcePublishedOnly) {
|
|
591
|
+
query += ` AND t."status" = 'published'`;
|
|
592
|
+
}
|
|
593
|
+
if (userId && !((_g = entityConfig.access) == null ? void 0 : _g.shared) && !skipUserFilter && !isBypass) {
|
|
433
594
|
query += ` AND t."userId" = $${paramIndex++}`;
|
|
434
595
|
queryParams.push(userId);
|
|
435
596
|
}
|
|
@@ -437,7 +598,7 @@ async function handleGenericList(request) {
|
|
|
437
598
|
query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
|
|
438
599
|
queryParams.push(ownershipFilter.value);
|
|
439
600
|
}
|
|
440
|
-
if (((
|
|
601
|
+
if (((_h = entityConfig.table) == null ? void 0 : _h.softDelete) && !isBypass) {
|
|
441
602
|
query += ` AND t."deletedAt" IS NULL`;
|
|
442
603
|
}
|
|
443
604
|
} else if (fieldsParam && distinctParam) {
|
|
@@ -453,7 +614,7 @@ async function handleGenericList(request) {
|
|
|
453
614
|
query += ` AND t."parentId" = $${paramIndex++}`;
|
|
454
615
|
queryParams.push(parentId);
|
|
455
616
|
}
|
|
456
|
-
if (userId && !((
|
|
617
|
+
if (userId && !((_i = entityConfig.access) == null ? void 0 : _i.shared) && !skipUserFilter && !isBypass) {
|
|
457
618
|
query += ` AND t."userId" = $${paramIndex++}`;
|
|
458
619
|
queryParams.push(userId);
|
|
459
620
|
}
|
|
@@ -461,9 +622,12 @@ async function handleGenericList(request) {
|
|
|
461
622
|
query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
|
|
462
623
|
queryParams.push(ownershipFilter.value);
|
|
463
624
|
}
|
|
464
|
-
if (((
|
|
625
|
+
if (((_j = entityConfig.table) == null ? void 0 : _j.softDelete) && !isBypass) {
|
|
465
626
|
query += ` AND t."deletedAt" IS NULL`;
|
|
466
627
|
}
|
|
628
|
+
if (forcePublishedOnly) {
|
|
629
|
+
query += ` AND t."status" = 'published'`;
|
|
630
|
+
}
|
|
467
631
|
query += ` ORDER BY "${fieldName}" ASC LIMIT $${paramIndex++}`;
|
|
468
632
|
queryParams.push(pagination.limit);
|
|
469
633
|
} else {
|
|
@@ -474,7 +638,7 @@ async function handleGenericList(request) {
|
|
|
474
638
|
whereConditions.push(`t."teamId" = $${paramIndex++}`);
|
|
475
639
|
queryParams.push(teamId);
|
|
476
640
|
}
|
|
477
|
-
if (userId && !((
|
|
641
|
+
if (userId && !((_k = entityConfig.access) == null ? void 0 : _k.shared) && !skipUserFilter && !isBypass) {
|
|
478
642
|
whereConditions.push(`t."userId" = $${paramIndex++}`);
|
|
479
643
|
queryParams.push(userId);
|
|
480
644
|
}
|
|
@@ -482,33 +646,18 @@ async function handleGenericList(request) {
|
|
|
482
646
|
whereConditions.push(`t."${ownershipFilter.field}" = $${paramIndex++}`);
|
|
483
647
|
queryParams.push(ownershipFilter.value);
|
|
484
648
|
}
|
|
485
|
-
if (((
|
|
649
|
+
if (((_l = entityConfig.table) == null ? void 0 : _l.softDelete) && !isBypass) {
|
|
486
650
|
whereConditions.push(`t."deletedAt" IS NULL`);
|
|
487
651
|
}
|
|
488
|
-
if (
|
|
652
|
+
if (forcePublishedOnly) {
|
|
653
|
+
whereConditions.push(`t."status" = 'published'`);
|
|
654
|
+
}
|
|
655
|
+
if (searchParam && searchParam.trim() !== "" && searchableFields.length > 0) {
|
|
489
656
|
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
|
-
}
|
|
657
|
+
const searchConditions = searchableFields.map((name) => `t.${name} ILIKE $${paramIndex}`);
|
|
658
|
+
whereConditions.push(`(${searchConditions.join(" OR ")})`);
|
|
659
|
+
queryParams.push(`%${searchTerm}%`);
|
|
660
|
+
paramIndex++;
|
|
512
661
|
}
|
|
513
662
|
if (dateFieldName && (fromDate || toDate)) {
|
|
514
663
|
const field = entityConfig.fields.find((f) => f.name === dateFieldName);
|
|
@@ -536,6 +685,18 @@ async function handleGenericList(request) {
|
|
|
536
685
|
queryParams.push(JSON.stringify([value]));
|
|
537
686
|
});
|
|
538
687
|
whereConditions.push(`(${orConditions.join(" OR ")})`);
|
|
688
|
+
} else if (field.type === "date" || field.type === "datetime") {
|
|
689
|
+
const orConditions = values.map((value) => {
|
|
690
|
+
if (BARE_DATE_RE.test(value)) {
|
|
691
|
+
const lower = paramIndex++;
|
|
692
|
+
const upper = paramIndex++;
|
|
693
|
+
queryParams.push(value, value);
|
|
694
|
+
return `(t.${columnName} >= $${lower}::date AND t.${columnName} < $${upper}::date + 1)`;
|
|
695
|
+
}
|
|
696
|
+
queryParams.push(value);
|
|
697
|
+
return `t.${columnName} = $${paramIndex++}`;
|
|
698
|
+
});
|
|
699
|
+
whereConditions.push(`(${orConditions.join(" OR ")})`);
|
|
539
700
|
} else {
|
|
540
701
|
const orConditions = values.map(() => {
|
|
541
702
|
const condition = `t.${columnName} = $${paramIndex++}`;
|
|
@@ -568,16 +729,9 @@ async function handleGenericList(request) {
|
|
|
568
729
|
) pu ON t.id = pu."patternId"
|
|
569
730
|
`;
|
|
570
731
|
}
|
|
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
|
-
}
|
|
732
|
+
const sortOrderRaw = url.searchParams.get("sortOrder") || url.searchParams.get("order");
|
|
733
|
+
const sortOrderParam = (sortOrderRaw == null ? void 0 : sortOrderRaw.toUpperCase()) === "ASC" ? "ASC" : "DESC";
|
|
734
|
+
const orderByClause = sortByParam ? `t."${sortByParam}" ${sortOrderParam}` : 't."createdAt" DESC';
|
|
581
735
|
query = `
|
|
582
736
|
SELECT ${fields}, COUNT(*) OVER() as total_count
|
|
583
737
|
FROM "${tableName}" t
|
|
@@ -624,7 +778,8 @@ async function handleGenericList(request) {
|
|
|
624
778
|
);
|
|
625
779
|
}
|
|
626
780
|
const finalData = filterPublicFields(dataWithTaxonomies, entityConfig, userId);
|
|
627
|
-
const
|
|
781
|
+
const shapedData = await afterEntityRead(entityConfig.slug, finalData, { ...customFilters }, userId ?? void 0);
|
|
782
|
+
const response = createApiResponse(shapedData, paginationMeta);
|
|
628
783
|
return addCorsHeaders(response, request);
|
|
629
784
|
} catch (error) {
|
|
630
785
|
console.error("Error in generic list handler:", error);
|
|
@@ -633,6 +788,9 @@ async function handleGenericList(request) {
|
|
|
633
788
|
}
|
|
634
789
|
}
|
|
635
790
|
async function handleGenericCreate(request) {
|
|
791
|
+
return runWithAuditLog(request, (audit) => handleGenericCreateImpl(request, audit));
|
|
792
|
+
}
|
|
793
|
+
async function handleGenericCreateImpl(request, audit) {
|
|
636
794
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
637
795
|
try {
|
|
638
796
|
const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
|
|
@@ -644,20 +802,14 @@ async function handleGenericCreate(request) {
|
|
|
644
802
|
const response2 = createApiError("Create operation not supported for this entity", 405);
|
|
645
803
|
return addCorsHeaders(response2, request);
|
|
646
804
|
}
|
|
647
|
-
const authResult = await authenticateRequest(request);
|
|
805
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
|
|
806
|
+
audit.auth = authResult;
|
|
648
807
|
if (!authResult.success) {
|
|
649
|
-
return
|
|
650
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
651
|
-
{ status: 401 }
|
|
652
|
-
);
|
|
808
|
+
return createAuthFailureResponse(authResult);
|
|
653
809
|
}
|
|
654
810
|
if (authResult.rateLimitResponse) {
|
|
655
811
|
return authResult.rateLimitResponse;
|
|
656
812
|
}
|
|
657
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
|
|
658
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
659
|
-
return addCorsHeaders(response2, request);
|
|
660
|
-
}
|
|
661
813
|
let body;
|
|
662
814
|
try {
|
|
663
815
|
body = await request.json();
|
|
@@ -670,16 +822,21 @@ async function handleGenericCreate(request) {
|
|
|
670
822
|
const entityConfig = resolution.entityConfig;
|
|
671
823
|
const tableName = getTableName(entityConfig);
|
|
672
824
|
const schemas = generateEntitySchemas(entityConfig);
|
|
673
|
-
const validation = schemas.create.safeParse(entityData);
|
|
825
|
+
const validation = schemas.create.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
|
|
674
826
|
if (!validation.success) {
|
|
675
827
|
console.error(`[${entityConfig.slug}] Validation failed:`, {
|
|
676
828
|
entityData,
|
|
677
829
|
errors: validation.error.issues
|
|
678
830
|
});
|
|
679
|
-
const response2 = createApiError(
|
|
831
|
+
const response2 = createApiError(
|
|
832
|
+
validationErrorMessage(validation.error.issues),
|
|
833
|
+
400,
|
|
834
|
+
validation.error.issues,
|
|
835
|
+
"VALIDATION_ERROR"
|
|
836
|
+
);
|
|
680
837
|
return addCorsHeaders(response2, request);
|
|
681
838
|
}
|
|
682
|
-
|
|
839
|
+
let validatedData = validation.data;
|
|
683
840
|
const xssField = checkNameFieldXss(entityConfig, validatedData);
|
|
684
841
|
if (xssField) {
|
|
685
842
|
const xssResponse = createApiError(
|
|
@@ -715,6 +872,22 @@ async function handleGenericCreate(request) {
|
|
|
715
872
|
}
|
|
716
873
|
const permDenied = await checkAuthPermission(authResult, entityConfig.slug, "create", teamId, request);
|
|
717
874
|
if (permDenied) return permDenied;
|
|
875
|
+
try {
|
|
876
|
+
validatedData = await beforeEntityCreate(
|
|
877
|
+
entityConfig.slug,
|
|
878
|
+
{ ...validatedData, userId: userIdToUse, teamId },
|
|
879
|
+
authResult.user.id
|
|
880
|
+
);
|
|
881
|
+
} catch (hookError) {
|
|
882
|
+
console.error(`[generic-handler] beforeEntityCreate rejected ${entityConfig.slug} create:`, hookError);
|
|
883
|
+
const response2 = createApiError(
|
|
884
|
+
hookError instanceof Error && hookError.message ? hookError.message : `Create rejected by ${entityConfig.slug} before_create hook`,
|
|
885
|
+
hookRejectionStatus(hookError),
|
|
886
|
+
void 0,
|
|
887
|
+
"BEFORE_CREATE_REJECTED"
|
|
888
|
+
);
|
|
889
|
+
return addCorsHeaders(response2, request);
|
|
890
|
+
}
|
|
718
891
|
if (idStrategy === "serial") {
|
|
719
892
|
insertFields = ['"userId"', '"teamId"'];
|
|
720
893
|
placeholders = ["$1", "$2"];
|
|
@@ -800,6 +973,7 @@ async function handleGenericCreate(request) {
|
|
|
800
973
|
if (!actionResult.allowed) {
|
|
801
974
|
const statusCode = actionResult.reason === "quota_exceeded" ? 429 : 403;
|
|
802
975
|
const response2 = createApiError(
|
|
976
|
+
// @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
|
|
803
977
|
actionResult.message || `Quota exceeded for ${entityConfig.slug}`,
|
|
804
978
|
statusCode,
|
|
805
979
|
void 0,
|
|
@@ -882,23 +1056,19 @@ async function handleGenericCreate(request) {
|
|
|
882
1056
|
return addCorsHeaders(response, request);
|
|
883
1057
|
} catch (error) {
|
|
884
1058
|
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);
|
|
1059
|
+
const constraintResponse = mapConstraintViolation(error, "create");
|
|
1060
|
+
if (constraintResponse) {
|
|
1061
|
+
return addCorsHeaders(constraintResponse, request);
|
|
895
1062
|
}
|
|
896
1063
|
const response = createApiError("Internal server error", 500);
|
|
897
1064
|
return addCorsHeaders(response, request);
|
|
898
1065
|
}
|
|
899
1066
|
}
|
|
900
1067
|
async function handleGenericRead(request, { params }) {
|
|
901
|
-
|
|
1068
|
+
return runWithAuditLog(request, (audit) => handleGenericReadImpl(request, audit, { params }));
|
|
1069
|
+
}
|
|
1070
|
+
async function handleGenericReadImpl(request, audit, { params }) {
|
|
1071
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
902
1072
|
try {
|
|
903
1073
|
const { id } = await params;
|
|
904
1074
|
const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
|
|
@@ -910,25 +1080,22 @@ async function handleGenericRead(request, { params }) {
|
|
|
910
1080
|
const response2 = createApiError("Read operation not supported for this entity", 405);
|
|
911
1081
|
return addCorsHeaders(response2, request);
|
|
912
1082
|
}
|
|
913
|
-
const authResult = await authenticateRequest(request);
|
|
1083
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
|
|
1084
|
+
audit.auth = authResult;
|
|
914
1085
|
let userId = null;
|
|
915
1086
|
let teamId = null;
|
|
916
1087
|
let isBypass = false;
|
|
1088
|
+
if (!authResult.success && authResult.type === "api-key") {
|
|
1089
|
+
return addCorsHeaders(createAuthFailureResponse(authResult), request);
|
|
1090
|
+
}
|
|
917
1091
|
if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
|
|
918
1092
|
console.log(`[GenericHandler] Public access allowed for ${resolution.entityConfig.slug} read`);
|
|
919
1093
|
} else if (!authResult.success) {
|
|
920
|
-
return
|
|
921
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
922
|
-
{ status: 401 }
|
|
923
|
-
);
|
|
1094
|
+
return createAuthFailureResponse(authResult);
|
|
924
1095
|
} else {
|
|
925
1096
|
if (authResult.rateLimitResponse) {
|
|
926
1097
|
return authResult.rateLimitResponse;
|
|
927
1098
|
}
|
|
928
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
|
|
929
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
930
|
-
return addCorsHeaders(response2, request);
|
|
931
|
-
}
|
|
932
1099
|
userId = authResult.user.id;
|
|
933
1100
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
|
|
934
1101
|
if (!teamValidation.valid) {
|
|
@@ -959,6 +1126,7 @@ async function handleGenericRead(request, { params }) {
|
|
|
959
1126
|
return `t.${columnName}`;
|
|
960
1127
|
});
|
|
961
1128
|
const fields = [...systemFieldsFormatted, ...configFields].join(", ");
|
|
1129
|
+
const forcePublishedOnly = ((_d = entityConfig.access) == null ? void 0 : _d.public) === true && userId === null && entityConfig.fields.some((f) => f.name === "status");
|
|
962
1130
|
let query;
|
|
963
1131
|
let queryParams;
|
|
964
1132
|
let paramIndex = 1;
|
|
@@ -972,7 +1140,7 @@ async function handleGenericRead(request, { params }) {
|
|
|
972
1140
|
query += ` AND t."teamId" = $${paramIndex++}`;
|
|
973
1141
|
queryParams.push(teamId);
|
|
974
1142
|
}
|
|
975
|
-
if (userId && !((
|
|
1143
|
+
if (userId && !((_e = entityConfig.access) == null ? void 0 : _e.shared) && !isBypass) {
|
|
976
1144
|
query += ` AND t."userId" = $${paramIndex++}`;
|
|
977
1145
|
queryParams.push(userId);
|
|
978
1146
|
}
|
|
@@ -992,9 +1160,12 @@ async function handleGenericRead(request, { params }) {
|
|
|
992
1160
|
queryParams.push(readOwnershipFilter.value);
|
|
993
1161
|
}
|
|
994
1162
|
}
|
|
995
|
-
if (((
|
|
1163
|
+
if (((_f = entityConfig.table) == null ? void 0 : _f.softDelete) && !isBypass) {
|
|
996
1164
|
query += ` AND t."deletedAt" IS NULL`;
|
|
997
1165
|
}
|
|
1166
|
+
if (forcePublishedOnly) {
|
|
1167
|
+
query += ` AND t."status" = 'published'`;
|
|
1168
|
+
}
|
|
998
1169
|
const items = await queryWithRLS(query, queryParams, userId);
|
|
999
1170
|
if (!items[0]) {
|
|
1000
1171
|
const response2 = createApiError("Item not found", 404);
|
|
@@ -1025,7 +1196,7 @@ async function handleGenericRead(request, { params }) {
|
|
|
1025
1196
|
itemWithChild = itemsWithChild[0] || itemWithMeta;
|
|
1026
1197
|
}
|
|
1027
1198
|
let itemWithTaxonomies = itemWithChild;
|
|
1028
|
-
if ((
|
|
1199
|
+
if ((_g = entityConfig.taxonomies) == null ? void 0 : _g.enabled) {
|
|
1029
1200
|
const itemsWithTaxonomies = await includeTaxonomiesInData(
|
|
1030
1201
|
entityConfig,
|
|
1031
1202
|
[itemWithChild],
|
|
@@ -1034,7 +1205,8 @@ async function handleGenericRead(request, { params }) {
|
|
|
1034
1205
|
itemWithTaxonomies = itemsWithTaxonomies[0] || itemWithChild;
|
|
1035
1206
|
}
|
|
1036
1207
|
const finalItem = filterPublicFields(itemWithTaxonomies, entityConfig, userId);
|
|
1037
|
-
const
|
|
1208
|
+
const [shapedItem] = await afterEntityRead(entityConfig.slug, [finalItem], { id }, userId ?? void 0);
|
|
1209
|
+
const response = createApiResponse(shapedItem ?? finalItem);
|
|
1038
1210
|
return addCorsHeaders(response, request);
|
|
1039
1211
|
} catch (error) {
|
|
1040
1212
|
console.error("Error in generic read handler:", error);
|
|
@@ -1043,6 +1215,9 @@ async function handleGenericRead(request, { params }) {
|
|
|
1043
1215
|
}
|
|
1044
1216
|
}
|
|
1045
1217
|
async function handleGenericUpdate(request, { params }) {
|
|
1218
|
+
return runWithAuditLog(request, (audit) => handleGenericUpdateImpl(request, audit, { params }));
|
|
1219
|
+
}
|
|
1220
|
+
async function handleGenericUpdateImpl(request, audit, { params }) {
|
|
1046
1221
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1047
1222
|
try {
|
|
1048
1223
|
const { id } = await params;
|
|
@@ -1055,20 +1230,14 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1055
1230
|
const response2 = createApiError("Update operation not supported for this entity", 405);
|
|
1056
1231
|
return addCorsHeaders(response2, request);
|
|
1057
1232
|
}
|
|
1058
|
-
const authResult = await authenticateRequest(request);
|
|
1233
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
|
|
1234
|
+
audit.auth = authResult;
|
|
1059
1235
|
if (!authResult.success) {
|
|
1060
|
-
return
|
|
1061
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
1062
|
-
{ status: 401 }
|
|
1063
|
-
);
|
|
1236
|
+
return createAuthFailureResponse(authResult);
|
|
1064
1237
|
}
|
|
1065
1238
|
if (authResult.rateLimitResponse) {
|
|
1066
1239
|
return authResult.rateLimitResponse;
|
|
1067
1240
|
}
|
|
1068
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
|
|
1069
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
1070
|
-
return addCorsHeaders(response2, request);
|
|
1071
|
-
}
|
|
1072
1241
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
|
|
1073
1242
|
if (!teamValidation.valid) {
|
|
1074
1243
|
return teamValidation.error;
|
|
@@ -1115,16 +1284,21 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1115
1284
|
}
|
|
1116
1285
|
const tableName = getTableName(entityConfig);
|
|
1117
1286
|
const schemas = generateEntitySchemas(entityConfig);
|
|
1118
|
-
const validation = schemas.update.safeParse(entityData);
|
|
1287
|
+
const validation = schemas.update.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
|
|
1119
1288
|
if (!validation.success) {
|
|
1120
1289
|
console.log(`[${entityConfig.slug}] Validation failed:`, {
|
|
1121
1290
|
entityData,
|
|
1122
1291
|
errors: validation.error.issues
|
|
1123
1292
|
});
|
|
1124
|
-
const response2 = createApiError(
|
|
1293
|
+
const response2 = createApiError(
|
|
1294
|
+
validationErrorMessage(validation.error.issues),
|
|
1295
|
+
400,
|
|
1296
|
+
validation.error.issues,
|
|
1297
|
+
"VALIDATION_ERROR"
|
|
1298
|
+
);
|
|
1125
1299
|
return addCorsHeaders(response2, request);
|
|
1126
1300
|
}
|
|
1127
|
-
|
|
1301
|
+
let validatedData = validation.data;
|
|
1128
1302
|
const updateXssField = checkNameFieldXss(entityConfig, validatedData);
|
|
1129
1303
|
if (updateXssField) {
|
|
1130
1304
|
const updateXssResponse = createApiError(
|
|
@@ -1135,6 +1309,23 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1135
1309
|
);
|
|
1136
1310
|
return addCorsHeaders(updateXssResponse, request);
|
|
1137
1311
|
}
|
|
1312
|
+
try {
|
|
1313
|
+
validatedData = await beforeEntityUpdate(
|
|
1314
|
+
entityConfig.slug,
|
|
1315
|
+
id,
|
|
1316
|
+
validatedData,
|
|
1317
|
+
authResult.user.id
|
|
1318
|
+
);
|
|
1319
|
+
} catch (hookError) {
|
|
1320
|
+
console.error(`[generic-handler] beforeEntityUpdate rejected ${entityConfig.slug} update:`, hookError);
|
|
1321
|
+
const response2 = createApiError(
|
|
1322
|
+
hookError instanceof Error && hookError.message ? hookError.message : `Update rejected by ${entityConfig.slug} before_update hook`,
|
|
1323
|
+
hookRejectionStatus(hookError),
|
|
1324
|
+
void 0,
|
|
1325
|
+
"BEFORE_UPDATE_REJECTED"
|
|
1326
|
+
);
|
|
1327
|
+
return addCorsHeaders(response2, request);
|
|
1328
|
+
}
|
|
1138
1329
|
const updates = [];
|
|
1139
1330
|
const values = [];
|
|
1140
1331
|
let paramCount = 1;
|
|
@@ -1294,22 +1485,18 @@ async function handleGenericUpdate(request, { params }) {
|
|
|
1294
1485
|
return addCorsHeaders(response, request);
|
|
1295
1486
|
} catch (error) {
|
|
1296
1487
|
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);
|
|
1488
|
+
const constraintResponse = mapConstraintViolation(error, "update");
|
|
1489
|
+
if (constraintResponse) {
|
|
1490
|
+
return addCorsHeaders(constraintResponse, request);
|
|
1307
1491
|
}
|
|
1308
1492
|
const response = createApiError("Internal server error", 500);
|
|
1309
1493
|
return addCorsHeaders(response, request);
|
|
1310
1494
|
}
|
|
1311
1495
|
}
|
|
1312
1496
|
async function handleGenericDelete(request, { params }) {
|
|
1497
|
+
return runWithAuditLog(request, (audit) => handleGenericDeleteImpl(request, audit, { params }));
|
|
1498
|
+
}
|
|
1499
|
+
async function handleGenericDeleteImpl(request, audit, { params }) {
|
|
1313
1500
|
var _a, _b, _c, _d, _e;
|
|
1314
1501
|
try {
|
|
1315
1502
|
const { id } = await params;
|
|
@@ -1322,20 +1509,14 @@ async function handleGenericDelete(request, { params }) {
|
|
|
1322
1509
|
const response2 = createApiError("Delete operation not supported for this entity", 405);
|
|
1323
1510
|
return addCorsHeaders(response2, request);
|
|
1324
1511
|
}
|
|
1325
|
-
const authResult = await authenticateRequest(request);
|
|
1512
|
+
const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:delete` });
|
|
1513
|
+
audit.auth = authResult;
|
|
1326
1514
|
if (!authResult.success) {
|
|
1327
|
-
return
|
|
1328
|
-
{ success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
|
|
1329
|
-
{ status: 401 }
|
|
1330
|
-
);
|
|
1515
|
+
return createAuthFailureResponse(authResult);
|
|
1331
1516
|
}
|
|
1332
1517
|
if (authResult.rateLimitResponse) {
|
|
1333
1518
|
return authResult.rateLimitResponse;
|
|
1334
1519
|
}
|
|
1335
|
-
if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:delete`)) {
|
|
1336
|
-
const response2 = createApiError("Insufficient permissions", 403);
|
|
1337
|
-
return addCorsHeaders(response2, request);
|
|
1338
|
-
}
|
|
1339
1520
|
const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
|
|
1340
1521
|
if (!teamValidation.valid) {
|
|
1341
1522
|
return teamValidation.error;
|
|
@@ -1349,6 +1530,27 @@ async function handleGenericDelete(request, { params }) {
|
|
|
1349
1530
|
if (permDenied) return permDenied;
|
|
1350
1531
|
const entityConfig = resolution.entityConfig;
|
|
1351
1532
|
const tableName = getTableName(entityConfig);
|
|
1533
|
+
try {
|
|
1534
|
+
const allowDelete = await beforeEntityDelete(entityConfig.slug, id, authResult.user.id);
|
|
1535
|
+
if (!allowDelete) {
|
|
1536
|
+
const response2 = createApiError(
|
|
1537
|
+
`Delete rejected by ${entityConfig.slug} before_delete hook`,
|
|
1538
|
+
403,
|
|
1539
|
+
void 0,
|
|
1540
|
+
"BEFORE_DELETE_REJECTED"
|
|
1541
|
+
);
|
|
1542
|
+
return addCorsHeaders(response2, request);
|
|
1543
|
+
}
|
|
1544
|
+
} catch (hookError) {
|
|
1545
|
+
console.error(`[generic-handler] beforeEntityDelete rejected ${entityConfig.slug} delete:`, hookError);
|
|
1546
|
+
const response2 = createApiError(
|
|
1547
|
+
hookError instanceof Error && hookError.message ? hookError.message : `Delete rejected by ${entityConfig.slug} before_delete hook`,
|
|
1548
|
+
hookRejectionStatus(hookError),
|
|
1549
|
+
void 0,
|
|
1550
|
+
"BEFORE_DELETE_REJECTED"
|
|
1551
|
+
);
|
|
1552
|
+
return addCorsHeaders(response2, request);
|
|
1553
|
+
}
|
|
1352
1554
|
const whereConditions = ["id = $1"];
|
|
1353
1555
|
const deleteParams = [id];
|
|
1354
1556
|
let paramIndex = 2;
|
|
@@ -1405,16 +1607,9 @@ async function handleGenericDelete(request, { params }) {
|
|
|
1405
1607
|
return addCorsHeaders(response, request);
|
|
1406
1608
|
} catch (error) {
|
|
1407
1609
|
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);
|
|
1610
|
+
const constraintResponse = mapConstraintViolation(error, "delete");
|
|
1611
|
+
if (constraintResponse) {
|
|
1612
|
+
return addCorsHeaders(constraintResponse, request);
|
|
1418
1613
|
}
|
|
1419
1614
|
const response = createApiError("Internal server error", 500);
|
|
1420
1615
|
return addCorsHeaders(response, request);
|