@nextsparkjs/core 0.1.0-beta.82 → 0.1.0-beta.83
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/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityDetailWrapper.js +11 -39
- package/dist/hooks/useEntityQuery.d.ts.map +1 -1
- package/dist/hooks/useEntityQuery.js +21 -3
- package/dist/lib/theme/get-default-theme-mode.d.ts +11 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
- package/dist/lib/theme/get-default-theme-mode.js +42 -25
- package/dist/styles/classes.json +1 -1
- package/dist/types/theme.d.ts +2 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/package.json +16 -16
- package/scripts/build/docs-registry.mjs +0 -0
- package/scripts/create-theme.mjs +0 -0
- package/scripts/deploy/release-version.mjs +0 -0
- package/scripts/deploy/vercel-deploy.mjs +0 -0
- package/scripts/dev/watch-plugins.mjs +0 -0
- package/scripts/maintenance/update-core.mjs +0 -0
- package/scripts/setup/npm-postinstall.mjs +0 -0
- package/scripts/setup/setup-claude.mjs +0 -0
- package/scripts/validation/check-imports.sh +0 -0
- package/dist/templates/app/(auth)/forgot-password/page.tsx +0 -216
- package/dist/templates/app/(auth)/layout.tsx +0 -51
- package/dist/templates/app/(auth)/login/page.tsx +0 -21
- package/dist/templates/app/(auth)/reset-password/page.tsx +0 -212
- package/dist/templates/app/(auth)/signup/page.tsx +0 -21
- package/dist/templates/app/(auth)/verify-email/page.tsx +0 -190
- package/dist/templates/app/(public)/[...slug]/page.tsx +0 -378
- package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +0 -90
- package/dist/templates/app/(public)/docs/layout.tsx +0 -25
- package/dist/templates/app/(public)/docs/page.tsx +0 -81
- package/dist/templates/app/(public)/layout.tsx +0 -41
- package/dist/templates/app/(public)/page.tsx +0 -19
- package/dist/templates/app/403/page.tsx +0 -89
- package/dist/templates/app/api/auth/[...all]/route.ts +0 -78
- package/dist/templates/app/api/cron/billing/lifecycle/route.ts +0 -98
- package/dist/templates/app/api/csp-report/route.ts +0 -175
- package/dist/templates/app/api/devtools/config/entities/route.ts +0 -108
- package/dist/templates/app/api/devtools/config/theme/route.ts +0 -66
- package/dist/templates/app/api/devtools/tests/[...path]/route.ts +0 -130
- package/dist/templates/app/api/devtools/tests/route.ts +0 -134
- package/dist/templates/app/api/health/route.ts +0 -29
- package/dist/templates/app/api/internal/user-metadata/route.ts +0 -36
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +0 -310
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +0 -286
- package/dist/templates/app/api/superadmin/teams/route.ts +0 -188
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +0 -540
- package/dist/templates/app/api/superadmin/users/route.ts +0 -323
- package/dist/templates/app/api/user/delete-account/route.ts +0 -55
- package/dist/templates/app/api/user/plan-flags/route.ts +0 -283
- package/dist/templates/app/api/user/profile/route.ts +0 -133
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +0 -210
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +0 -331
- package/dist/templates/app/api/v1/[entity]/[id]/route.ts +0 -35
- package/dist/templates/app/api/v1/[entity]/docs.md +0 -369
- package/dist/templates/app/api/v1/[entity]/presets.ts +0 -194
- package/dist/templates/app/api/v1/[entity]/route.ts +0 -31
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +0 -303
- package/dist/templates/app/api/v1/api-keys/docs.md +0 -101
- package/dist/templates/app/api/v1/api-keys/presets.ts +0 -31
- package/dist/templates/app/api/v1/api-keys/route.ts +0 -250
- package/dist/templates/app/api/v1/auth/docs.md +0 -184
- package/dist/templates/app/api/v1/auth/presets.ts +0 -44
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +0 -227
- package/dist/templates/app/api/v1/billing/cancel/route.ts +0 -206
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +0 -97
- package/dist/templates/app/api/v1/billing/check-action/route.ts +0 -81
- package/dist/templates/app/api/v1/billing/checkout/route.ts +0 -124
- package/dist/templates/app/api/v1/billing/docs.md +0 -209
- package/dist/templates/app/api/v1/billing/plans/route.ts +0 -85
- package/dist/templates/app/api/v1/billing/portal/route.ts +0 -90
- package/dist/templates/app/api/v1/billing/presets.ts +0 -121
- package/dist/templates/app/api/v1/billing/webhooks/stripe/route.ts +0 -428
- package/dist/templates/app/api/v1/blocks/[slug]/route.ts +0 -29
- package/dist/templates/app/api/v1/blocks/docs.md +0 -173
- package/dist/templates/app/api/v1/blocks/presets.ts +0 -121
- package/dist/templates/app/api/v1/blocks/route.ts +0 -45
- package/dist/templates/app/api/v1/blocks/validate/route.ts +0 -45
- package/dist/templates/app/api/v1/cron/docs.md +0 -116
- package/dist/templates/app/api/v1/cron/presets.ts +0 -26
- package/dist/templates/app/api/v1/cron/process/route.ts +0 -108
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +0 -82
- package/dist/templates/app/api/v1/devtools/docs/route.ts +0 -150
- package/dist/templates/app/api/v1/devtools/docs.md +0 -204
- package/dist/templates/app/api/v1/devtools/features/route.ts +0 -61
- package/dist/templates/app/api/v1/devtools/flows/route.ts +0 -61
- package/dist/templates/app/api/v1/devtools/presets.ts +0 -113
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +0 -120
- package/dist/templates/app/api/v1/devtools/testing/route.ts +0 -82
- package/dist/templates/app/api/v1/media/docs.md +0 -117
- package/dist/templates/app/api/v1/media/presets.ts +0 -24
- package/dist/templates/app/api/v1/media/upload/route.ts +0 -150
- package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +0 -116
- package/dist/templates/app/api/v1/plugin/[...path]/route.ts +0 -373
- package/dist/templates/app/api/v1/plugin/docs.md +0 -79
- package/dist/templates/app/api/v1/plugin/presets.ts +0 -21
- package/dist/templates/app/api/v1/plugin/route.ts +0 -96
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +0 -255
- package/dist/templates/app/api/v1/post-categories/docs.md +0 -134
- package/dist/templates/app/api/v1/post-categories/presets.ts +0 -78
- package/dist/templates/app/api/v1/post-categories/route.ts +0 -119
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +0 -179
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +0 -120
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +0 -89
- package/dist/templates/app/api/v1/team-invitations/docs.md +0 -88
- package/dist/templates/app/api/v1/team-invitations/presets.ts +0 -43
- package/dist/templates/app/api/v1/team-invitations/route.ts +0 -114
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +0 -171
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +0 -105
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +0 -125
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +0 -263
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +0 -358
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +0 -322
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +0 -50
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +0 -91
- package/dist/templates/app/api/v1/teams/docs.md +0 -320
- package/dist/templates/app/api/v1/teams/presets.ts +0 -178
- package/dist/templates/app/api/v1/teams/route.ts +0 -293
- package/dist/templates/app/api/v1/teams/switch/route.ts +0 -88
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +0 -361
- package/dist/templates/app/api/v1/theme/docs.md +0 -74
- package/dist/templates/app/api/v1/theme/presets.ts +0 -21
- package/dist/templates/app/api/v1/theme/route.ts +0 -96
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +0 -363
- package/dist/templates/app/api/v1/users/[id]/route.ts +0 -302
- package/dist/templates/app/api/v1/users/docs.md +0 -93
- package/dist/templates/app/api/v1/users/presets.ts +0 -59
- package/dist/templates/app/api/v1/users/route.ts +0 -197
- package/dist/templates/app/dashboard/(main)/[entity]/[id]/edit/page.tsx +0 -117
- package/dist/templates/app/dashboard/(main)/[entity]/[id]/page.tsx +0 -103
- package/dist/templates/app/dashboard/(main)/[entity]/create/page.tsx +0 -95
- package/dist/templates/app/dashboard/(main)/[entity]/error.tsx +0 -51
- package/dist/templates/app/dashboard/(main)/[entity]/layout.tsx +0 -113
- package/dist/templates/app/dashboard/(main)/[entity]/loading.tsx +0 -61
- package/dist/templates/app/dashboard/(main)/[entity]/page.tsx +0 -90
- package/dist/templates/app/dashboard/(main)/layout.tsx +0 -98
- package/dist/templates/app/dashboard/(main)/loading.tsx +0 -5
- package/dist/templates/app/dashboard/(main)/page.tsx +0 -201
- package/dist/templates/app/dashboard/(main)/patterns/[id]/edit/page.tsx +0 -114
- package/dist/templates/app/dashboard/(main)/patterns/[id]/page.tsx +0 -20
- package/dist/templates/app/dashboard/(main)/patterns/[id]/reports/page.tsx +0 -171
- package/dist/templates/app/dashboard/(main)/patterns/create/page.tsx +0 -86
- package/dist/templates/app/dashboard/(main)/patterns/page.tsx +0 -444
- package/dist/templates/app/dashboard/features/analytics/page.tsx +0 -35
- package/dist/templates/app/dashboard/features/automation/page.tsx +0 -35
- package/dist/templates/app/dashboard/features/layout.tsx +0 -13
- package/dist/templates/app/dashboard/features/loading.tsx +0 -5
- package/dist/templates/app/dashboard/features/webhooks/page.tsx +0 -35
- package/dist/templates/app/dashboard/layout.tsx +0 -86
- package/dist/templates/app/dashboard/permission-denied/page.tsx +0 -29
- package/dist/templates/app/dashboard/settings/api-keys/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/api-keys/page.tsx +0 -513
- package/dist/templates/app/dashboard/settings/billing/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/billing/page.tsx +0 -284
- package/dist/templates/app/dashboard/settings/invoices/[invoiceNumber]/page.tsx +0 -222
- package/dist/templates/app/dashboard/settings/invoices/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/invoices/page.tsx +0 -82
- package/dist/templates/app/dashboard/settings/layout.tsx +0 -151
- package/dist/templates/app/dashboard/settings/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/notifications/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/notifications/page.tsx +0 -462
- package/dist/templates/app/dashboard/settings/page.tsx +0 -92
- package/dist/templates/app/dashboard/settings/password/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/password/page.tsx +0 -306
- package/dist/templates/app/dashboard/settings/plans/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/plans/page.tsx +0 -40
- package/dist/templates/app/dashboard/settings/profile/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/profile/page.tsx +0 -686
- package/dist/templates/app/dashboard/settings/security/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/security/page.tsx +0 -505
- package/dist/templates/app/dashboard/settings/teams/loading.tsx +0 -5
- package/dist/templates/app/dashboard/settings/teams/page.tsx +0 -272
- package/dist/templates/app/dashboard/settings/teams/permissions/page.tsx +0 -92
- package/dist/templates/app/devtools/blocks/[slug]/page.tsx +0 -39
- package/dist/templates/app/devtools/blocks/page.tsx +0 -31
- package/dist/templates/app/devtools/config/page.tsx +0 -31
- package/dist/templates/app/devtools/features/page.tsx +0 -31
- package/dist/templates/app/devtools/flows/page.tsx +0 -31
- package/dist/templates/app/devtools/layout.tsx +0 -58
- package/dist/templates/app/devtools/page.tsx +0 -121
- package/dist/templates/app/devtools/scheduled-actions/page.tsx +0 -157
- package/dist/templates/app/devtools/style/page.tsx +0 -330
- package/dist/templates/app/devtools/tags/page.tsx +0 -31
- package/dist/templates/app/devtools/tests/[[...path]]/page.tsx +0 -47
- package/dist/templates/app/favicon.ico +0 -0
- package/dist/templates/app/globals.css +0 -12
- package/dist/templates/app/layout.tsx +0 -96
- package/dist/templates/app/public/page.tsx +0 -30
- package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +0 -92
- package/dist/templates/app/superadmin/docs/page.tsx +0 -75
- package/dist/templates/app/superadmin/layout.tsx +0 -67
- package/dist/templates/app/superadmin/page.tsx +0 -149
- package/dist/templates/app/superadmin/subscriptions/page.tsx +0 -655
- package/dist/templates/app/superadmin/team-roles/page.tsx +0 -493
- package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +0 -687
- package/dist/templates/app/superadmin/teams/page.tsx +0 -302
- package/dist/templates/app/superadmin/users/[userId]/page.tsx +0 -548
- package/dist/templates/app/superadmin/users/page.tsx +0 -528
- package/templates/app/(auth)/forgot-password/page.tsx +0 -216
- package/templates/app/(auth)/layout.tsx +0 -51
- package/templates/app/(auth)/login/page.tsx +0 -21
- package/templates/app/(auth)/reset-password/page.tsx +0 -212
- package/templates/app/(auth)/signup/page.tsx +0 -21
- package/templates/app/(auth)/verify-email/page.tsx +0 -190
- package/templates/app/(public)/[...slug]/page.tsx +0 -378
- package/templates/app/(public)/docs/[section]/[page]/page.tsx +0 -90
- package/templates/app/(public)/docs/layout.tsx +0 -25
- package/templates/app/(public)/docs/page.tsx +0 -81
- package/templates/app/(public)/layout.tsx +0 -41
- package/templates/app/(public)/page.tsx +0 -19
- package/templates/app/403/page.tsx +0 -89
- package/templates/app/api/auth/[...all]/route.ts +0 -78
- package/templates/app/api/cron/billing/lifecycle/route.ts +0 -98
- package/templates/app/api/csp-report/route.ts +0 -175
- package/templates/app/api/devtools/config/entities/route.ts +0 -108
- package/templates/app/api/devtools/config/theme/route.ts +0 -66
- package/templates/app/api/devtools/tests/[...path]/route.ts +0 -130
- package/templates/app/api/devtools/tests/route.ts +0 -134
- package/templates/app/api/health/route.ts +0 -29
- package/templates/app/api/internal/user-metadata/route.ts +0 -36
- package/templates/app/api/superadmin/subscriptions/route.ts +0 -310
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +0 -286
- package/templates/app/api/superadmin/teams/route.ts +0 -188
- package/templates/app/api/superadmin/users/[userId]/route.ts +0 -540
- package/templates/app/api/superadmin/users/route.ts +0 -323
- package/templates/app/api/user/delete-account/route.ts +0 -55
- package/templates/app/api/user/plan-flags/route.ts +0 -283
- package/templates/app/api/user/profile/route.ts +0 -133
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +0 -210
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +0 -331
- package/templates/app/api/v1/[entity]/[id]/route.ts +0 -35
- package/templates/app/api/v1/[entity]/docs.md +0 -369
- package/templates/app/api/v1/[entity]/presets.ts +0 -194
- package/templates/app/api/v1/[entity]/route.ts +0 -31
- package/templates/app/api/v1/api-keys/[id]/route.ts +0 -303
- package/templates/app/api/v1/api-keys/docs.md +0 -101
- package/templates/app/api/v1/api-keys/presets.ts +0 -31
- package/templates/app/api/v1/api-keys/route.ts +0 -250
- package/templates/app/api/v1/auth/docs.md +0 -184
- package/templates/app/api/v1/auth/presets.ts +0 -44
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +0 -227
- package/templates/app/api/v1/billing/cancel/route.ts +0 -206
- package/templates/app/api/v1/billing/change-plan/route.ts +0 -97
- package/templates/app/api/v1/billing/check-action/route.ts +0 -81
- package/templates/app/api/v1/billing/checkout/route.ts +0 -124
- package/templates/app/api/v1/billing/docs.md +0 -209
- package/templates/app/api/v1/billing/plans/route.ts +0 -85
- package/templates/app/api/v1/billing/portal/route.ts +0 -90
- package/templates/app/api/v1/billing/presets.ts +0 -121
- package/templates/app/api/v1/billing/webhooks/stripe/route.ts +0 -428
- package/templates/app/api/v1/blocks/[slug]/route.ts +0 -29
- package/templates/app/api/v1/blocks/docs.md +0 -173
- package/templates/app/api/v1/blocks/presets.ts +0 -121
- package/templates/app/api/v1/blocks/route.ts +0 -45
- package/templates/app/api/v1/blocks/validate/route.ts +0 -45
- package/templates/app/api/v1/cron/docs.md +0 -116
- package/templates/app/api/v1/cron/presets.ts +0 -26
- package/templates/app/api/v1/cron/process/route.ts +0 -108
- package/templates/app/api/v1/devtools/blocks/route.ts +0 -82
- package/templates/app/api/v1/devtools/docs/route.ts +0 -150
- package/templates/app/api/v1/devtools/docs.md +0 -204
- package/templates/app/api/v1/devtools/features/route.ts +0 -61
- package/templates/app/api/v1/devtools/flows/route.ts +0 -61
- package/templates/app/api/v1/devtools/presets.ts +0 -113
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +0 -120
- package/templates/app/api/v1/devtools/testing/route.ts +0 -82
- package/templates/app/api/v1/media/docs.md +0 -117
- package/templates/app/api/v1/media/presets.ts +0 -24
- package/templates/app/api/v1/media/upload/route.ts +0 -150
- package/templates/app/api/v1/patterns/[id]/usages/route.ts +0 -116
- package/templates/app/api/v1/plugin/[...path]/route.ts +0 -373
- package/templates/app/api/v1/plugin/docs.md +0 -79
- package/templates/app/api/v1/plugin/presets.ts +0 -21
- package/templates/app/api/v1/plugin/route.ts +0 -96
- package/templates/app/api/v1/post-categories/[id]/route.ts +0 -255
- package/templates/app/api/v1/post-categories/docs.md +0 -134
- package/templates/app/api/v1/post-categories/presets.ts +0 -78
- package/templates/app/api/v1/post-categories/route.ts +0 -119
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +0 -179
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +0 -120
- package/templates/app/api/v1/team-invitations/[token]/route.ts +0 -89
- package/templates/app/api/v1/team-invitations/docs.md +0 -88
- package/templates/app/api/v1/team-invitations/presets.ts +0 -43
- package/templates/app/api/v1/team-invitations/route.ts +0 -114
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +0 -171
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +0 -105
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +0 -125
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +0 -263
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +0 -358
- package/templates/app/api/v1/teams/[teamId]/route.ts +0 -322
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +0 -50
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +0 -91
- package/templates/app/api/v1/teams/docs.md +0 -320
- package/templates/app/api/v1/teams/presets.ts +0 -178
- package/templates/app/api/v1/teams/route.ts +0 -293
- package/templates/app/api/v1/teams/switch/route.ts +0 -88
- package/templates/app/api/v1/theme/[...path]/route.ts +0 -361
- package/templates/app/api/v1/theme/docs.md +0 -74
- package/templates/app/api/v1/theme/presets.ts +0 -21
- package/templates/app/api/v1/theme/route.ts +0 -96
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +0 -363
- package/templates/app/api/v1/users/[id]/route.ts +0 -302
- package/templates/app/api/v1/users/docs.md +0 -93
- package/templates/app/api/v1/users/presets.ts +0 -59
- package/templates/app/api/v1/users/route.ts +0 -197
- package/templates/app/dashboard/(main)/[entity]/[id]/edit/page.tsx +0 -117
- package/templates/app/dashboard/(main)/[entity]/[id]/page.tsx +0 -103
- package/templates/app/dashboard/(main)/[entity]/create/page.tsx +0 -95
- package/templates/app/dashboard/(main)/[entity]/error.tsx +0 -51
- package/templates/app/dashboard/(main)/[entity]/layout.tsx +0 -113
- package/templates/app/dashboard/(main)/[entity]/loading.tsx +0 -61
- package/templates/app/dashboard/(main)/[entity]/page.tsx +0 -90
- package/templates/app/dashboard/(main)/layout.tsx +0 -98
- package/templates/app/dashboard/(main)/loading.tsx +0 -5
- package/templates/app/dashboard/(main)/page.tsx +0 -201
- package/templates/app/dashboard/(main)/patterns/[id]/edit/page.tsx +0 -114
- package/templates/app/dashboard/(main)/patterns/[id]/page.tsx +0 -20
- package/templates/app/dashboard/(main)/patterns/[id]/reports/page.tsx +0 -171
- package/templates/app/dashboard/(main)/patterns/create/page.tsx +0 -86
- package/templates/app/dashboard/(main)/patterns/page.tsx +0 -444
- package/templates/app/dashboard/features/analytics/page.tsx +0 -35
- package/templates/app/dashboard/features/automation/page.tsx +0 -35
- package/templates/app/dashboard/features/layout.tsx +0 -13
- package/templates/app/dashboard/features/loading.tsx +0 -5
- package/templates/app/dashboard/features/webhooks/page.tsx +0 -35
- package/templates/app/dashboard/layout.tsx +0 -86
- package/templates/app/dashboard/permission-denied/page.tsx +0 -29
- package/templates/app/dashboard/settings/api-keys/loading.tsx +0 -5
- package/templates/app/dashboard/settings/api-keys/page.tsx +0 -513
- package/templates/app/dashboard/settings/billing/loading.tsx +0 -5
- package/templates/app/dashboard/settings/billing/page.tsx +0 -284
- package/templates/app/dashboard/settings/invoices/[invoiceNumber]/page.tsx +0 -222
- package/templates/app/dashboard/settings/invoices/loading.tsx +0 -5
- package/templates/app/dashboard/settings/invoices/page.tsx +0 -82
- package/templates/app/dashboard/settings/layout.tsx +0 -151
- package/templates/app/dashboard/settings/loading.tsx +0 -5
- package/templates/app/dashboard/settings/notifications/loading.tsx +0 -5
- package/templates/app/dashboard/settings/notifications/page.tsx +0 -462
- package/templates/app/dashboard/settings/page.tsx +0 -92
- package/templates/app/dashboard/settings/password/loading.tsx +0 -5
- package/templates/app/dashboard/settings/password/page.tsx +0 -306
- package/templates/app/dashboard/settings/plans/loading.tsx +0 -5
- package/templates/app/dashboard/settings/plans/page.tsx +0 -40
- package/templates/app/dashboard/settings/profile/loading.tsx +0 -5
- package/templates/app/dashboard/settings/profile/page.tsx +0 -686
- package/templates/app/dashboard/settings/security/loading.tsx +0 -5
- package/templates/app/dashboard/settings/security/page.tsx +0 -505
- package/templates/app/dashboard/settings/teams/loading.tsx +0 -5
- package/templates/app/dashboard/settings/teams/page.tsx +0 -272
- package/templates/app/dashboard/settings/teams/permissions/page.tsx +0 -92
- package/templates/app/devtools/blocks/[slug]/page.tsx +0 -39
- package/templates/app/devtools/blocks/page.tsx +0 -31
- package/templates/app/devtools/config/page.tsx +0 -31
- package/templates/app/devtools/features/page.tsx +0 -31
- package/templates/app/devtools/flows/page.tsx +0 -31
- package/templates/app/devtools/layout.tsx +0 -58
- package/templates/app/devtools/page.tsx +0 -121
- package/templates/app/devtools/scheduled-actions/page.tsx +0 -157
- package/templates/app/devtools/style/page.tsx +0 -330
- package/templates/app/devtools/tags/page.tsx +0 -31
- package/templates/app/devtools/tests/[[...path]]/page.tsx +0 -47
- package/templates/app/favicon.ico +0 -0
- package/templates/app/globals.css +0 -12
- package/templates/app/layout.tsx +0 -96
- package/templates/app/public/page.tsx +0 -30
- package/templates/app/superadmin/docs/[section]/[page]/page.tsx +0 -92
- package/templates/app/superadmin/docs/page.tsx +0 -75
- package/templates/app/superadmin/layout.tsx +0 -67
- package/templates/app/superadmin/page.tsx +0 -149
- package/templates/app/superadmin/subscriptions/page.tsx +0 -655
- package/templates/app/superadmin/team-roles/page.tsx +0 -493
- package/templates/app/superadmin/teams/[teamId]/page.tsx +0 -687
- package/templates/app/superadmin/teams/page.tsx +0 -302
- package/templates/app/superadmin/users/[userId]/page.tsx +0 -548
- package/templates/app/superadmin/users/page.tsx +0 -528
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Key Detail Routes
|
|
3
|
-
*
|
|
4
|
-
* Core system endpoints for individual API key management.
|
|
5
|
-
* Supports dual authentication (API Keys + Sessions).
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Get detailed API key information with usage stats
|
|
9
|
-
* - Update API key name and status
|
|
10
|
-
* - Revoke (soft delete) API keys
|
|
11
|
-
* - Admin-only access control
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
15
|
-
import { queryOneWithRLS, mutateWithRLS } from '@nextsparkjs/core/lib/db';
|
|
16
|
-
import {
|
|
17
|
-
createApiResponse,
|
|
18
|
-
createApiError,
|
|
19
|
-
withApiLogging,
|
|
20
|
-
handleCorsPreflightRequest,
|
|
21
|
-
addCorsHeaders
|
|
22
|
-
} from '@nextsparkjs/core/lib/api/helpers';
|
|
23
|
-
import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth';
|
|
24
|
-
|
|
25
|
-
// Handle CORS preflight
|
|
26
|
-
export async function OPTIONS() {
|
|
27
|
-
return handleCorsPreflightRequest();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// GET /api/v1/api-keys/:id - Get specific API key details
|
|
31
|
-
export const GET = withApiLogging(async (
|
|
32
|
-
req: NextRequest,
|
|
33
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
34
|
-
): Promise<NextResponse> => {
|
|
35
|
-
try {
|
|
36
|
-
// Authenticate using dual auth
|
|
37
|
-
const authResult = await authenticateRequest(req);
|
|
38
|
-
|
|
39
|
-
if (!authResult.success) {
|
|
40
|
-
return NextResponse.json(
|
|
41
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
42
|
-
{ status: 401 }
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (authResult.rateLimitResponse) {
|
|
47
|
-
return authResult.rateLimitResponse as NextResponse;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check required permissions - session users have admin access, API key users need specific scope
|
|
51
|
-
const hasPermission = authResult.type === 'session' ||
|
|
52
|
-
(authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
|
|
53
|
-
|
|
54
|
-
if (!hasPermission) {
|
|
55
|
-
const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
|
|
56
|
-
return addCorsHeaders(response);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const { id } = await params;
|
|
60
|
-
|
|
61
|
-
if (!id || id.trim() === '') {
|
|
62
|
-
const response = createApiError('API key ID is required', 400, null, 'MISSING_API_KEY_ID');
|
|
63
|
-
return addCorsHeaders(response);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const apiKey = await queryOneWithRLS(
|
|
67
|
-
`SELECT id, "keyPrefix", name, scopes, status, "lastUsedAt", "expiresAt", "createdAt", "updatedAt"
|
|
68
|
-
FROM "api_key"
|
|
69
|
-
WHERE id = $1 AND "userId" = $2`,
|
|
70
|
-
[id, authResult.user!.id],
|
|
71
|
-
authResult.user!.id
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
if (!apiKey) {
|
|
75
|
-
const response = createApiError('API key not found', 404, null, 'API_KEY_NOT_FOUND');
|
|
76
|
-
return addCorsHeaders(response);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Get detailed usage statistics
|
|
80
|
-
let usageStats: {
|
|
81
|
-
total_requests: number;
|
|
82
|
-
last_24h: number;
|
|
83
|
-
last_7d: number;
|
|
84
|
-
last_30d: number;
|
|
85
|
-
avg_response_time: number;
|
|
86
|
-
success_rate: number;
|
|
87
|
-
} | null = null;
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
usageStats = await queryOneWithRLS<{
|
|
91
|
-
total_requests: number;
|
|
92
|
-
last_24h: number;
|
|
93
|
-
last_7d: number;
|
|
94
|
-
last_30d: number;
|
|
95
|
-
avg_response_time: number;
|
|
96
|
-
success_rate: number;
|
|
97
|
-
}>(
|
|
98
|
-
`SELECT
|
|
99
|
-
COUNT(*) as total_requests,
|
|
100
|
-
COUNT(CASE WHEN "createdAt" > NOW() - INTERVAL '24 hours' THEN 1 END) as last_24h,
|
|
101
|
-
COUNT(CASE WHEN "createdAt" > NOW() - INTERVAL '7 days' THEN 1 END) as last_7d,
|
|
102
|
-
COUNT(CASE WHEN "createdAt" > NOW() - INTERVAL '30 days' THEN 1 END) as last_30d,
|
|
103
|
-
AVG("responseTime") as avg_response_time,
|
|
104
|
-
(COUNT(CASE WHEN "statusCode" < 400 THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0)) as success_rate
|
|
105
|
-
FROM "api_audit_log"
|
|
106
|
-
WHERE "apiKeyId" = $1`,
|
|
107
|
-
[id],
|
|
108
|
-
authResult.user!.id
|
|
109
|
-
);
|
|
110
|
-
} catch (error: unknown) {
|
|
111
|
-
// If api_audit_log table doesn't exist, use default stats
|
|
112
|
-
if (error && typeof error === 'object' && 'code' in error && error.code === '42P01') {
|
|
113
|
-
console.warn('api_audit_log table does not exist, returning default stats');
|
|
114
|
-
usageStats = null;
|
|
115
|
-
} else {
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const response = createApiResponse({
|
|
121
|
-
...apiKey,
|
|
122
|
-
usage_stats: usageStats || {
|
|
123
|
-
total_requests: 0,
|
|
124
|
-
last_24h: 0,
|
|
125
|
-
last_7d: 0,
|
|
126
|
-
last_30d: 0,
|
|
127
|
-
avg_response_time: null,
|
|
128
|
-
success_rate: null
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
return addCorsHeaders(response);
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error('Error fetching API key:', error);
|
|
134
|
-
const response = createApiError('Internal server error', 500);
|
|
135
|
-
return addCorsHeaders(response);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// PATCH /api/v1/api-keys/:id - Update API key (only name and active status)
|
|
140
|
-
export const PATCH = withApiLogging(async (
|
|
141
|
-
req: NextRequest,
|
|
142
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
143
|
-
): Promise<NextResponse> => {
|
|
144
|
-
try {
|
|
145
|
-
// Authenticate using dual auth
|
|
146
|
-
const authResult = await authenticateRequest(req);
|
|
147
|
-
|
|
148
|
-
if (!authResult.success) {
|
|
149
|
-
return NextResponse.json(
|
|
150
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
151
|
-
{ status: 401 }
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (authResult.rateLimitResponse) {
|
|
156
|
-
return authResult.rateLimitResponse as NextResponse;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Check required permissions - session users have admin access, API key users need specific scope
|
|
160
|
-
const hasPermission = authResult.type === 'session' ||
|
|
161
|
-
(authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
|
|
162
|
-
|
|
163
|
-
if (!hasPermission) {
|
|
164
|
-
const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
|
|
165
|
-
return addCorsHeaders(response);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const { id } = await params;
|
|
169
|
-
|
|
170
|
-
if (!id || id.trim() === '') {
|
|
171
|
-
const response = createApiError('API key ID is required', 400, null, 'MISSING_API_KEY_ID');
|
|
172
|
-
return addCorsHeaders(response);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const body = await req.json();
|
|
176
|
-
|
|
177
|
-
// Only allow updating name and status
|
|
178
|
-
const allowedUpdates = ['name', 'status'];
|
|
179
|
-
const updates = [];
|
|
180
|
-
const values = [];
|
|
181
|
-
let paramCount = 1;
|
|
182
|
-
|
|
183
|
-
for (const field of allowedUpdates) {
|
|
184
|
-
if (body[field] !== undefined) {
|
|
185
|
-
if (field === 'name' && typeof body[field] !== 'string') {
|
|
186
|
-
const response = createApiError('Name must be a string', 400, null, 'INVALID_NAME_TYPE');
|
|
187
|
-
return addCorsHeaders(response);
|
|
188
|
-
}
|
|
189
|
-
if (field === 'status' && !['active', 'inactive', 'expired'].includes(body[field])) {
|
|
190
|
-
const response = createApiError('status must be active, inactive, or expired', 400, null, 'INVALID_STATUS_TYPE');
|
|
191
|
-
return addCorsHeaders(response);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
updates.push(`"${field}" = $${paramCount++}`);
|
|
195
|
-
values.push(body[field]);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (updates.length === 0) {
|
|
200
|
-
const response = createApiError('No valid fields to update', 400, null, 'NO_FIELDS');
|
|
201
|
-
return addCorsHeaders(response);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
updates.push(`"updatedAt" = CURRENT_TIMESTAMP`);
|
|
205
|
-
values.push(id, authResult.user!.id);
|
|
206
|
-
|
|
207
|
-
const query = `
|
|
208
|
-
UPDATE "api_key"
|
|
209
|
-
SET ${updates.join(", ")}
|
|
210
|
-
WHERE id = $${paramCount} AND "userId" = $${paramCount + 1}
|
|
211
|
-
RETURNING id, "keyPrefix", name, scopes, status, "lastUsedAt", "expiresAt", "createdAt", "updatedAt"
|
|
212
|
-
`;
|
|
213
|
-
|
|
214
|
-
const result = await mutateWithRLS(query, values, authResult.user!.id);
|
|
215
|
-
|
|
216
|
-
if (result.rows.length === 0) {
|
|
217
|
-
const response = createApiError('API key not found', 404, null, 'API_KEY_NOT_FOUND');
|
|
218
|
-
return addCorsHeaders(response);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const response = createApiResponse(result.rows[0]);
|
|
222
|
-
return addCorsHeaders(response);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
console.error('Error updating API key:', error);
|
|
225
|
-
const response = createApiError('Internal server error', 500);
|
|
226
|
-
return addCorsHeaders(response);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// DELETE /api/v1/api-keys/:id - Revoke API key
|
|
231
|
-
export const DELETE = withApiLogging(async (
|
|
232
|
-
req: NextRequest,
|
|
233
|
-
{ params }: { params: Promise<{ id: string }> }
|
|
234
|
-
): Promise<NextResponse> => {
|
|
235
|
-
try {
|
|
236
|
-
// Authenticate using dual auth
|
|
237
|
-
const authResult = await authenticateRequest(req);
|
|
238
|
-
|
|
239
|
-
if (!authResult.success) {
|
|
240
|
-
return NextResponse.json(
|
|
241
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
242
|
-
{ status: 401 }
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (authResult.rateLimitResponse) {
|
|
247
|
-
return authResult.rateLimitResponse as NextResponse;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Check required permissions - session users have admin access, API key users need specific scope
|
|
251
|
-
const hasPermission = authResult.type === 'session' ||
|
|
252
|
-
(authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
|
|
253
|
-
|
|
254
|
-
if (!hasPermission) {
|
|
255
|
-
const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
|
|
256
|
-
return addCorsHeaders(response);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const { id } = await params;
|
|
260
|
-
|
|
261
|
-
if (!id || id.trim() === '') {
|
|
262
|
-
const response = createApiError('API key ID is required', 400, null, 'MISSING_API_KEY_ID');
|
|
263
|
-
return addCorsHeaders(response);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// For API key auth, prevent deletion of the current API key being used
|
|
267
|
-
if (authResult.type === 'api-key' && 'keyId' in authResult && id === authResult.keyId) {
|
|
268
|
-
const response = createApiError(
|
|
269
|
-
'Cannot revoke the API key currently being used',
|
|
270
|
-
403,
|
|
271
|
-
null,
|
|
272
|
-
'SELF_REVOKE_FORBIDDEN'
|
|
273
|
-
);
|
|
274
|
-
return addCorsHeaders(response);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Soft delete - just mark as inactive
|
|
278
|
-
const result = await mutateWithRLS(
|
|
279
|
-
`UPDATE "api_key"
|
|
280
|
-
SET status = 'inactive', "updatedAt" = CURRENT_TIMESTAMP
|
|
281
|
-
WHERE id = $1 AND "userId" = $2
|
|
282
|
-
RETURNING id, name`,
|
|
283
|
-
[id, authResult.user!.id],
|
|
284
|
-
authResult.user!.id
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
if (result.rows.length === 0) {
|
|
288
|
-
const response = createApiError('API key not found', 404, null, 'API_KEY_NOT_FOUND');
|
|
289
|
-
return addCorsHeaders(response);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const response = createApiResponse({
|
|
293
|
-
revoked: true,
|
|
294
|
-
id,
|
|
295
|
-
name: (result.rows[0] as { name: string }).name
|
|
296
|
-
});
|
|
297
|
-
return addCorsHeaders(response);
|
|
298
|
-
} catch (error) {
|
|
299
|
-
console.error('Error revoking API key:', error);
|
|
300
|
-
const response = createApiError('Internal server error', 500);
|
|
301
|
-
return addCorsHeaders(response);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# API Keys API
|
|
2
|
-
|
|
3
|
-
Manage API keys for programmatic access to your team's resources.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The API Keys API allows you to create, list, and revoke API keys. API keys provide server-to-server authentication and are scoped to a specific team.
|
|
8
|
-
|
|
9
|
-
## Authentication
|
|
10
|
-
|
|
11
|
-
All endpoints require authentication via:
|
|
12
|
-
- **Session cookie** (for browser-based requests)
|
|
13
|
-
|
|
14
|
-
Note: API key management requires session authentication for security.
|
|
15
|
-
|
|
16
|
-
## Endpoints
|
|
17
|
-
|
|
18
|
-
### List API Keys
|
|
19
|
-
`GET /api/v1/api-keys`
|
|
20
|
-
|
|
21
|
-
Returns all API keys for the current team.
|
|
22
|
-
|
|
23
|
-
**Example Response:**
|
|
24
|
-
```json
|
|
25
|
-
{
|
|
26
|
-
"data": [
|
|
27
|
-
{
|
|
28
|
-
"id": "key_123",
|
|
29
|
-
"name": "Production API Key",
|
|
30
|
-
"prefix": "nsk_prod_",
|
|
31
|
-
"lastUsedAt": "2024-01-20T15:30:00Z",
|
|
32
|
-
"createdAt": "2024-01-15T10:30:00Z"
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Create API Key
|
|
39
|
-
`POST /api/v1/api-keys`
|
|
40
|
-
|
|
41
|
-
Create a new API key. The full key is only returned once on creation.
|
|
42
|
-
|
|
43
|
-
**Request Body:**
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"name": "My API Key"
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Response:**
|
|
51
|
-
```json
|
|
52
|
-
{
|
|
53
|
-
"id": "key_456",
|
|
54
|
-
"name": "My API Key",
|
|
55
|
-
"key": "nsk_prod_abc123xyz...",
|
|
56
|
-
"prefix": "nsk_prod_",
|
|
57
|
-
"createdAt": "2024-01-21T10:30:00Z"
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Important:** Save the full `key` value immediately - it cannot be retrieved again.
|
|
62
|
-
|
|
63
|
-
### Delete API Key
|
|
64
|
-
`DELETE /api/v1/api-keys/[id]`
|
|
65
|
-
|
|
66
|
-
Revoke an API key. This action is immediate and irreversible.
|
|
67
|
-
|
|
68
|
-
**Path Parameters:**
|
|
69
|
-
- `id` (string, required): API Key ID
|
|
70
|
-
|
|
71
|
-
## Using API Keys
|
|
72
|
-
|
|
73
|
-
Include the API key in the `x-api-key` header:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
curl -H "x-api-key: nsk_prod_abc123xyz..." \
|
|
77
|
-
https://api.example.com/api/v1/customers
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Security Best Practices
|
|
81
|
-
|
|
82
|
-
- Store API keys securely (environment variables, secrets manager)
|
|
83
|
-
- Rotate keys periodically
|
|
84
|
-
- Use different keys for different environments
|
|
85
|
-
- Revoke keys immediately if compromised
|
|
86
|
-
|
|
87
|
-
## Error Responses
|
|
88
|
-
|
|
89
|
-
| Status | Description |
|
|
90
|
-
|--------|-------------|
|
|
91
|
-
| 400 | Bad Request - Invalid parameters |
|
|
92
|
-
| 401 | Unauthorized - Session required |
|
|
93
|
-
| 403 | Forbidden - Insufficient permissions |
|
|
94
|
-
| 404 | Not Found - API key doesn't exist |
|
|
95
|
-
|
|
96
|
-
## Related APIs
|
|
97
|
-
|
|
98
|
-
- **[Auth](/api/v1/auth)** - Session-based authentication for browser requests
|
|
99
|
-
- **[Teams](/api/v1/teams)** - API keys are scoped to teams
|
|
100
|
-
- **[Dynamic Entities](/api/v1/{entity})** - Use API keys to access entity CRUD endpoints
|
|
101
|
-
- **[Media](/api/v1/media)** - API keys support `media:read` and `media:write` scopes
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Presets for API Keys
|
|
3
|
-
*
|
|
4
|
-
* These presets appear in the DevTools API Explorer's "Presets" tab.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
8
|
-
|
|
9
|
-
export default defineApiEndpoint({
|
|
10
|
-
endpoint: '/api/v1/api-keys',
|
|
11
|
-
summary: 'Manage API keys for programmatic access',
|
|
12
|
-
presets: [
|
|
13
|
-
{
|
|
14
|
-
id: 'list-all',
|
|
15
|
-
title: 'List API Keys',
|
|
16
|
-
description: 'Fetch all API keys for the current team',
|
|
17
|
-
method: 'GET',
|
|
18
|
-
tags: ['read', 'list']
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: 'create-key',
|
|
22
|
-
title: 'Create API Key',
|
|
23
|
-
description: 'Create a new API key for the team',
|
|
24
|
-
method: 'POST',
|
|
25
|
-
payload: {
|
|
26
|
-
name: 'My New API Key'
|
|
27
|
-
},
|
|
28
|
-
tags: ['write', 'create']
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
})
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { queryWithRLS, mutateWithRLS } from '@nextsparkjs/core/lib/db';
|
|
3
|
-
import {
|
|
4
|
-
createApiResponse,
|
|
5
|
-
createApiError,
|
|
6
|
-
withApiLogging,
|
|
7
|
-
handleCorsPreflightRequest,
|
|
8
|
-
addCorsHeaders
|
|
9
|
-
} from '@nextsparkjs/core/lib/api/helpers';
|
|
10
|
-
import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth';
|
|
11
|
-
import { ApiKeyManager, API_SCOPES, API_KEY_LIMITS } from '@nextsparkjs/core/lib/api/keys';
|
|
12
|
-
import { validateScopesForUser } from '@nextsparkjs/core/lib/api/auth';
|
|
13
|
-
import { z } from 'zod';
|
|
14
|
-
|
|
15
|
-
const createApiKeySchema = z.object({
|
|
16
|
-
name: z.string().min(1, 'Name is required').max(API_KEY_LIMITS.maxKeyNameLength, 'Name too long'),
|
|
17
|
-
scopes: z.array(z.string()).min(1, 'At least one scope is required'),
|
|
18
|
-
expiresAt: z.string().datetime().optional()
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// Handle CORS preflight
|
|
22
|
-
export async function OPTIONS() {
|
|
23
|
-
return handleCorsPreflightRequest();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// GET /api/v1/api-keys - List user's API keys with dual auth
|
|
27
|
-
export const GET = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
|
|
28
|
-
try {
|
|
29
|
-
// Authenticate using dual auth
|
|
30
|
-
const authResult = await authenticateRequest(req);
|
|
31
|
-
|
|
32
|
-
if (!authResult.success) {
|
|
33
|
-
return NextResponse.json(
|
|
34
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
35
|
-
{ status: 401 }
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (authResult.rateLimitResponse) {
|
|
40
|
-
return authResult.rateLimitResponse as NextResponse;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Check required permissions - session users have admin access, API key users need specific scope
|
|
44
|
-
const hasPermission = authResult.type === 'session' ||
|
|
45
|
-
(authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
|
|
46
|
-
|
|
47
|
-
if (!hasPermission) {
|
|
48
|
-
const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
|
|
49
|
-
return addCorsHeaders(response);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const apiKeys = await queryWithRLS<{
|
|
53
|
-
id: string;
|
|
54
|
-
keyPrefix: string;
|
|
55
|
-
name: string;
|
|
56
|
-
scopes: string[];
|
|
57
|
-
status: 'active' | 'inactive' | 'expired';
|
|
58
|
-
lastUsedAt: string | null;
|
|
59
|
-
expiresAt: string | null;
|
|
60
|
-
createdAt: string;
|
|
61
|
-
updatedAt: string;
|
|
62
|
-
}>(
|
|
63
|
-
`SELECT id, "keyPrefix", name, scopes, status, "lastUsedAt", "expiresAt", "createdAt", "updatedAt"
|
|
64
|
-
FROM "api_key"
|
|
65
|
-
WHERE "userId" = $1
|
|
66
|
-
ORDER BY "createdAt" DESC`,
|
|
67
|
-
[authResult.user!.id],
|
|
68
|
-
authResult.user!.id
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Optimización: Una sola consulta para todas las estadísticas (evita N+1)
|
|
72
|
-
const keyIds = apiKeys.map(key => key.id);
|
|
73
|
-
let allStats: {
|
|
74
|
-
apiKeyId: string;
|
|
75
|
-
total_requests: number;
|
|
76
|
-
last_24h: number;
|
|
77
|
-
avg_response_time: number;
|
|
78
|
-
}[] = [];
|
|
79
|
-
|
|
80
|
-
// Intentar obtener estadísticas si existe la tabla api_audit_log
|
|
81
|
-
if (keyIds.length > 0) {
|
|
82
|
-
try {
|
|
83
|
-
allStats = await queryWithRLS<{
|
|
84
|
-
apiKeyId: string;
|
|
85
|
-
total_requests: number;
|
|
86
|
-
last_24h: number;
|
|
87
|
-
avg_response_time: number;
|
|
88
|
-
}>(
|
|
89
|
-
`SELECT
|
|
90
|
-
"apiKeyId",
|
|
91
|
-
COUNT(*) as total_requests,
|
|
92
|
-
COUNT(CASE WHEN "createdAt" > NOW() - INTERVAL '24 hours' THEN 1 END) as last_24h,
|
|
93
|
-
AVG("responseTime") as avg_response_time
|
|
94
|
-
FROM "api_audit_log"
|
|
95
|
-
WHERE "apiKeyId" = ANY($1)
|
|
96
|
-
GROUP BY "apiKeyId"`,
|
|
97
|
-
[keyIds],
|
|
98
|
-
authResult.user!.id
|
|
99
|
-
);
|
|
100
|
-
} catch (error: unknown) {
|
|
101
|
-
// Si la tabla api_audit_log no existe, simplemente usar estadísticas vacías
|
|
102
|
-
if (error && typeof error === 'object' && 'code' in error && error.code === '42P01') {
|
|
103
|
-
console.warn('api_audit_log table does not exist, returning empty stats');
|
|
104
|
-
allStats = [];
|
|
105
|
-
} else {
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Crear mapa de estadísticas para lookup O(1)
|
|
112
|
-
const statsMap = new Map(
|
|
113
|
-
allStats.map(stat => [stat.apiKeyId, stat])
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// Combinar datos con estadísticas
|
|
117
|
-
const keysWithStats = apiKeys.map((key) => ({
|
|
118
|
-
...key,
|
|
119
|
-
usage_stats: statsMap.get(key.id) || {
|
|
120
|
-
total_requests: 0,
|
|
121
|
-
last_24h: 0,
|
|
122
|
-
avg_response_time: null
|
|
123
|
-
}
|
|
124
|
-
}));
|
|
125
|
-
|
|
126
|
-
const response = createApiResponse(keysWithStats);
|
|
127
|
-
return addCorsHeaders(response);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('Error fetching API keys:', error);
|
|
130
|
-
const response = createApiError('Internal server error', 500);
|
|
131
|
-
return addCorsHeaders(response);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// POST /api/v1/api-keys - Create new API key with dual auth
|
|
136
|
-
export const POST = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
|
|
137
|
-
try {
|
|
138
|
-
// Authenticate using dual auth
|
|
139
|
-
const authResult = await authenticateRequest(req);
|
|
140
|
-
|
|
141
|
-
if (!authResult.success) {
|
|
142
|
-
return NextResponse.json(
|
|
143
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
144
|
-
{ status: 401 }
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (authResult.rateLimitResponse) {
|
|
149
|
-
return authResult.rateLimitResponse as NextResponse;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Check required permissions - session users have admin access, API key users need specific scope
|
|
153
|
-
const hasPermission = authResult.type === 'session' ||
|
|
154
|
-
(authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
|
|
155
|
-
|
|
156
|
-
if (!hasPermission) {
|
|
157
|
-
const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
|
|
158
|
-
return addCorsHeaders(response);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const body = await req.json();
|
|
162
|
-
const validatedData = createApiKeySchema.parse(body);
|
|
163
|
-
|
|
164
|
-
// Validar que los scopes sean válidos
|
|
165
|
-
const scopeValidation = ApiKeyManager.validateScopes(validatedData.scopes);
|
|
166
|
-
if (!scopeValidation.valid) {
|
|
167
|
-
const response = createApiError(
|
|
168
|
-
'Invalid scopes provided',
|
|
169
|
-
400,
|
|
170
|
-
{
|
|
171
|
-
invalidScopes: scopeValidation.invalidScopes,
|
|
172
|
-
validScopes: Object.keys(API_SCOPES)
|
|
173
|
-
},
|
|
174
|
-
'INVALID_SCOPES'
|
|
175
|
-
);
|
|
176
|
-
return addCorsHeaders(response);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Validar que el usuario pueda crear API keys con estos scopes
|
|
180
|
-
const userScopeValidation = await validateScopesForUser(authResult.user!.id, validatedData.scopes);
|
|
181
|
-
if (!userScopeValidation.valid) {
|
|
182
|
-
const response = createApiError(
|
|
183
|
-
'You do not have permission to create API keys with these scopes',
|
|
184
|
-
403,
|
|
185
|
-
{
|
|
186
|
-
deniedScopes: userScopeValidation.deniedScopes,
|
|
187
|
-
allowedScopes: userScopeValidation.allowedScopes
|
|
188
|
-
},
|
|
189
|
-
'INSUFFICIENT_SCOPE_PERMISSIONS'
|
|
190
|
-
);
|
|
191
|
-
return addCorsHeaders(response);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Verificar límite de API keys por usuario (máximo 10)
|
|
195
|
-
const existingKeysCount = await queryWithRLS<{ count: number }>(
|
|
196
|
-
'SELECT COUNT(*) as count FROM "api_key" WHERE "userId" = $1 AND status = $2',
|
|
197
|
-
[authResult.user!.id, 'active'],
|
|
198
|
-
authResult.user!.id
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
if ((existingKeysCount[0]?.count || 0) >= API_KEY_LIMITS.maxKeysPerUser) {
|
|
202
|
-
const response = createApiError(
|
|
203
|
-
`Maximum number of API keys reached (${API_KEY_LIMITS.maxKeysPerUser})`,
|
|
204
|
-
429,
|
|
205
|
-
null,
|
|
206
|
-
'API_KEY_LIMIT_REACHED'
|
|
207
|
-
);
|
|
208
|
-
return addCorsHeaders(response);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Generar API key
|
|
212
|
-
const { key, hash, prefix } = await ApiKeyManager.generateApiKey();
|
|
213
|
-
|
|
214
|
-
// Guardar en DB
|
|
215
|
-
const result = await mutateWithRLS(
|
|
216
|
-
`INSERT INTO "api_key" (id, "keyHash", "keyPrefix", name, "userId", scopes, "expiresAt")
|
|
217
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
218
|
-
RETURNING id, "keyPrefix", name, scopes, status, "expiresAt", "createdAt"`,
|
|
219
|
-
[
|
|
220
|
-
globalThis.crypto.randomUUID(),
|
|
221
|
-
hash,
|
|
222
|
-
prefix,
|
|
223
|
-
validatedData.name,
|
|
224
|
-
authResult.user!.id,
|
|
225
|
-
validatedData.scopes,
|
|
226
|
-
validatedData.expiresAt || null
|
|
227
|
-
],
|
|
228
|
-
authResult.user!.id
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
// Retornar la key completa SOLO una vez
|
|
232
|
-
const responseData = {
|
|
233
|
-
...(result.rows[0] as Record<string, unknown>),
|
|
234
|
-
key, // Esta es la única vez que se muestra la key completa
|
|
235
|
-
warning: 'Save this API key now. You will not be able to see it again.'
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
const response = createApiResponse(responseData, { created: true }, 201);
|
|
239
|
-
return addCorsHeaders(response);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
if (error instanceof z.ZodError) {
|
|
242
|
-
const response = createApiError('Validation error', 400, error.issues, 'VALIDATION_ERROR');
|
|
243
|
-
return addCorsHeaders(response);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
console.error('Error creating API key:', error);
|
|
247
|
-
const response = createApiError('Internal server error', 500);
|
|
248
|
-
return addCorsHeaders(response);
|
|
249
|
-
}
|
|
250
|
-
});
|