@nextsparkjs/core 0.1.0-beta.82 → 0.1.0-beta.84
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 +3 -3
- 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,358 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
-
import { queryWithRLS, queryOneWithRLS, mutateWithRLS } from '@nextsparkjs/core/lib/db'
|
|
3
|
-
import {
|
|
4
|
-
createApiResponse,
|
|
5
|
-
createApiError,
|
|
6
|
-
createPaginationMeta,
|
|
7
|
-
withApiLogging,
|
|
8
|
-
handleCorsPreflightRequest,
|
|
9
|
-
addCorsHeaders,
|
|
10
|
-
} from '@nextsparkjs/core/lib/api/helpers'
|
|
11
|
-
import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
12
|
-
import { isSuperAdmin } from '@nextsparkjs/core/lib/api/auth/permissions'
|
|
13
|
-
import { checkRateLimit } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
14
|
-
import { RATE_LIMITS } from '@nextsparkjs/core/lib/api/keys'
|
|
15
|
-
import { inviteMemberSchema, memberListQuerySchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
16
|
-
import { TeamMemberService, MembershipService } from '@nextsparkjs/core/lib/services'
|
|
17
|
-
import type { TeamMember, TeamInvitation, TeamRole, Team } from '@nextsparkjs/core/lib/teams/types'
|
|
18
|
-
import { EmailFactory } from '@nextsparkjs/core/lib/email/factory'
|
|
19
|
-
import { createTeamInvitationEmail } from '@nextsparkjs/core/lib/email/templates'
|
|
20
|
-
|
|
21
|
-
// Role hierarchy for invite validation (higher number = more power)
|
|
22
|
-
const ROLE_HIERARCHY: Record<TeamRole, number> = {
|
|
23
|
-
owner: 4,
|
|
24
|
-
admin: 3,
|
|
25
|
-
member: 2,
|
|
26
|
-
viewer: 1,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Check if a user can invite to a specific role (same level or below)
|
|
30
|
-
function canInviteToRole(actorRole: TeamRole, targetRole: TeamRole): boolean {
|
|
31
|
-
return ROLE_HIERARCHY[actorRole] >= ROLE_HIERARCHY[targetRole]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Handle CORS preflight
|
|
35
|
-
export async function OPTIONS() {
|
|
36
|
-
return handleCorsPreflightRequest()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// GET /api/v1/teams/:teamId/members - List team members
|
|
40
|
-
export const GET = withApiLogging(
|
|
41
|
-
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
42
|
-
try {
|
|
43
|
-
// Authenticate using dual auth
|
|
44
|
-
const authResult = await authenticateRequest(req)
|
|
45
|
-
|
|
46
|
-
if (!authResult.success) {
|
|
47
|
-
return NextResponse.json(
|
|
48
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
49
|
-
{ status: 401 }
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (authResult.rateLimitResponse) {
|
|
54
|
-
return authResult.rateLimitResponse as NextResponse
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const { teamId } = await params
|
|
58
|
-
|
|
59
|
-
// Validate that teamId is not empty
|
|
60
|
-
if (!teamId || teamId.trim() === '') {
|
|
61
|
-
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
62
|
-
return addCorsHeaders(response)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Superadmins can view members of any team
|
|
66
|
-
const userIsSuperAdmin = isSuperAdmin(authResult)
|
|
67
|
-
|
|
68
|
-
// Check if user is a member of the team (unless superadmin)
|
|
69
|
-
if (!userIsSuperAdmin) {
|
|
70
|
-
const isMember = await TeamMemberService.isMember(teamId, authResult.user!.id)
|
|
71
|
-
|
|
72
|
-
if (!isMember) {
|
|
73
|
-
const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
|
|
74
|
-
return addCorsHeaders(response)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Parse query parameters (filter out null values so Zod defaults work)
|
|
79
|
-
const { searchParams } = new URL(req.url)
|
|
80
|
-
const queryParams = Object.fromEntries(
|
|
81
|
-
Object.entries({
|
|
82
|
-
page: searchParams.get('page'),
|
|
83
|
-
limit: searchParams.get('limit'),
|
|
84
|
-
role: searchParams.get('role'),
|
|
85
|
-
search: searchParams.get('search'),
|
|
86
|
-
}).filter(([, value]) => value !== null && value !== '')
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
const validatedQuery = memberListQuerySchema.parse(queryParams)
|
|
90
|
-
const { page, limit, role, search } = validatedQuery
|
|
91
|
-
const offset = (page - 1) * limit
|
|
92
|
-
|
|
93
|
-
// Build WHERE clause based on filters
|
|
94
|
-
let whereClause = 'WHERE tm."teamId" = $1'
|
|
95
|
-
const queryValues: unknown[] = [teamId]
|
|
96
|
-
let paramCount = 2
|
|
97
|
-
|
|
98
|
-
if (role) {
|
|
99
|
-
whereClause += ` AND tm.role = $${paramCount}`
|
|
100
|
-
queryValues.push(role)
|
|
101
|
-
paramCount++
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (search) {
|
|
105
|
-
whereClause += ` AND (u.name ILIKE $${paramCount} OR u.email ILIKE $${paramCount})`
|
|
106
|
-
queryValues.push(`%${search}%`)
|
|
107
|
-
paramCount++
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Add pagination params
|
|
111
|
-
queryValues.push(limit, offset)
|
|
112
|
-
|
|
113
|
-
const members = await queryWithRLS<
|
|
114
|
-
TeamMember & {
|
|
115
|
-
userName: string | null
|
|
116
|
-
userEmail: string
|
|
117
|
-
userImage: string | null
|
|
118
|
-
}
|
|
119
|
-
>(
|
|
120
|
-
`SELECT
|
|
121
|
-
tm.*,
|
|
122
|
-
u.name as "userName",
|
|
123
|
-
u.email as "userEmail",
|
|
124
|
-
u.image as "userImage"
|
|
125
|
-
FROM "team_members" tm
|
|
126
|
-
INNER JOIN "users" u ON tm."userId" = u.id
|
|
127
|
-
${whereClause}
|
|
128
|
-
ORDER BY
|
|
129
|
-
CASE tm.role
|
|
130
|
-
WHEN 'owner' THEN 1
|
|
131
|
-
WHEN 'admin' THEN 2
|
|
132
|
-
WHEN 'member' THEN 3
|
|
133
|
-
WHEN 'viewer' THEN 4
|
|
134
|
-
END,
|
|
135
|
-
tm."joinedAt" ASC
|
|
136
|
-
LIMIT $${paramCount++} OFFSET $${paramCount++}`,
|
|
137
|
-
queryValues,
|
|
138
|
-
authResult.user!.id
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
// Get total count for pagination
|
|
142
|
-
const totalResult = await queryWithRLS<{ count: string }>(
|
|
143
|
-
`SELECT COUNT(*) as count
|
|
144
|
-
FROM "team_members" tm
|
|
145
|
-
INNER JOIN "users" u ON tm."userId" = u.id
|
|
146
|
-
${whereClause}`,
|
|
147
|
-
queryValues.slice(0, -2), // Remove limit and offset
|
|
148
|
-
authResult.user!.id
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
const total = parseInt(totalResult[0]?.count || '0', 10)
|
|
152
|
-
const paginationMeta = createPaginationMeta(page, limit, total)
|
|
153
|
-
|
|
154
|
-
const response = createApiResponse(members, paginationMeta)
|
|
155
|
-
return addCorsHeaders(response)
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error('Error fetching team members:', error)
|
|
158
|
-
const response = createApiError('Internal server error', 500)
|
|
159
|
-
return addCorsHeaders(response)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
// POST /api/v1/teams/:teamId/members - Invite new member (creates invitation)
|
|
165
|
-
export const POST = withApiLogging(
|
|
166
|
-
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
167
|
-
try {
|
|
168
|
-
// Authenticate using dual auth
|
|
169
|
-
const authResult = await authenticateRequest(req)
|
|
170
|
-
|
|
171
|
-
if (!authResult.success) {
|
|
172
|
-
return NextResponse.json(
|
|
173
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
174
|
-
{ status: 401 }
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (authResult.rateLimitResponse) {
|
|
179
|
-
return authResult.rateLimitResponse as NextResponse
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Apply specific rate limit for creating invitations (20 req/min)
|
|
183
|
-
const rateLimitConfig = RATE_LIMITS['teams:invite']
|
|
184
|
-
const rateLimitKey = `invite:create:${authResult.user!.id}`
|
|
185
|
-
const rateLimit = checkRateLimit(rateLimitKey, rateLimitConfig.requests, rateLimitConfig.windowMs)
|
|
186
|
-
|
|
187
|
-
if (!rateLimit.allowed) {
|
|
188
|
-
const response = createApiError(
|
|
189
|
-
'Rate limit exceeded. Please try again later.',
|
|
190
|
-
429,
|
|
191
|
-
{ retryAfter: Math.ceil((rateLimit.resetTime - Date.now()) / 1000) },
|
|
192
|
-
'RATE_LIMIT_EXCEEDED'
|
|
193
|
-
)
|
|
194
|
-
return addCorsHeaders(response)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const { teamId } = await params
|
|
198
|
-
|
|
199
|
-
// Validate that teamId is not empty
|
|
200
|
-
if (!teamId || teamId.trim() === '') {
|
|
201
|
-
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
202
|
-
return addCorsHeaders(response)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Check if user has permission to invite members using MembershipService
|
|
206
|
-
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
207
|
-
const actionResult = membership.canPerformAction('members.invite')
|
|
208
|
-
|
|
209
|
-
if (!actionResult.allowed) {
|
|
210
|
-
const response = NextResponse.json(
|
|
211
|
-
{
|
|
212
|
-
success: false,
|
|
213
|
-
error: actionResult.message,
|
|
214
|
-
reason: actionResult.reason,
|
|
215
|
-
meta: actionResult.meta,
|
|
216
|
-
},
|
|
217
|
-
{ status: 403 }
|
|
218
|
-
)
|
|
219
|
-
return addCorsHeaders(response)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Get user's role for role hierarchy validation
|
|
223
|
-
const userRole = membership.role as TeamRole
|
|
224
|
-
|
|
225
|
-
const body = await req.json()
|
|
226
|
-
const validatedData = inviteMemberSchema.parse(body)
|
|
227
|
-
|
|
228
|
-
// Check role hierarchy - users can only invite to same role or below
|
|
229
|
-
if (!canInviteToRole(userRole, validatedData.role)) {
|
|
230
|
-
const response = createApiError(
|
|
231
|
-
`You cannot invite members to a role higher than your own. Your role: ${userRole}, requested role: ${validatedData.role}`,
|
|
232
|
-
403,
|
|
233
|
-
null,
|
|
234
|
-
'ROLE_HIERARCHY_VIOLATION'
|
|
235
|
-
)
|
|
236
|
-
return addCorsHeaders(response)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Check if user already exists and is already a member
|
|
240
|
-
const existingUser = await queryOneWithRLS<{ id: string }>(
|
|
241
|
-
'SELECT id FROM "users" WHERE email = $1',
|
|
242
|
-
[validatedData.email],
|
|
243
|
-
authResult.user!.id
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
if (existingUser) {
|
|
247
|
-
// Check if already a member
|
|
248
|
-
const existingMember = await queryOneWithRLS<{ id: string }>(
|
|
249
|
-
'SELECT id FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
|
|
250
|
-
[teamId, existingUser.id],
|
|
251
|
-
authResult.user!.id
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
if (existingMember) {
|
|
255
|
-
const response = createApiError('User is already a member of this team', 409, null, 'ALREADY_MEMBER')
|
|
256
|
-
return addCorsHeaders(response)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Check if there's already a pending invitation for this email
|
|
261
|
-
const existingInvitation = await queryOneWithRLS<TeamInvitation>(
|
|
262
|
-
`SELECT * FROM "team_invitations"
|
|
263
|
-
WHERE "teamId" = $1 AND email = $2 AND status = 'pending'`,
|
|
264
|
-
[teamId, validatedData.email],
|
|
265
|
-
authResult.user!.id
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
if (existingInvitation) {
|
|
269
|
-
const response = createApiError(
|
|
270
|
-
'A pending invitation already exists for this email',
|
|
271
|
-
409,
|
|
272
|
-
null,
|
|
273
|
-
'INVITATION_EXISTS'
|
|
274
|
-
)
|
|
275
|
-
return addCorsHeaders(response)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Get team info for email
|
|
279
|
-
const team = await queryOneWithRLS<Team>(
|
|
280
|
-
'SELECT * FROM "teams" WHERE id = $1',
|
|
281
|
-
[teamId],
|
|
282
|
-
authResult.user!.id
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
if (!team) {
|
|
286
|
-
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
287
|
-
return addCorsHeaders(response)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Create invitation
|
|
291
|
-
const token = globalThis.crypto.randomUUID()
|
|
292
|
-
const expiresAt = new Date()
|
|
293
|
-
expiresAt.setDate(expiresAt.getDate() + 7) // Expires in 7 days
|
|
294
|
-
|
|
295
|
-
const result = await mutateWithRLS(
|
|
296
|
-
`INSERT INTO "team_invitations" ("teamId", email, role, status, token, "invitedBy", "expiresAt")
|
|
297
|
-
VALUES ($1, $2, $3, 'pending', $4, $5, $6)
|
|
298
|
-
RETURNING *`,
|
|
299
|
-
[teamId, validatedData.email, validatedData.role, token, authResult.user!.id, expiresAt.toISOString()],
|
|
300
|
-
authResult.user!.id
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
const invitation = result.rows[0]
|
|
304
|
-
|
|
305
|
-
// Build accept URL
|
|
306
|
-
const baseUrl = (process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:5173').replace(/\/$/, '')
|
|
307
|
-
const acceptUrl = `${baseUrl}/accept-invite/${token}`
|
|
308
|
-
|
|
309
|
-
// Send invitation email
|
|
310
|
-
try {
|
|
311
|
-
const emailProvider = EmailFactory.getInstance()
|
|
312
|
-
const inviterName = authResult.user!.email // Use email as inviter name
|
|
313
|
-
const emailContent = createTeamInvitationEmail(
|
|
314
|
-
validatedData.email,
|
|
315
|
-
inviterName,
|
|
316
|
-
team.name,
|
|
317
|
-
validatedData.role,
|
|
318
|
-
acceptUrl,
|
|
319
|
-
'7 days'
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
await emailProvider.send({
|
|
323
|
-
to: validatedData.email,
|
|
324
|
-
subject: emailContent.subject,
|
|
325
|
-
html: emailContent.html
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
// Log for testing purposes (like email verification)
|
|
329
|
-
console.log('\n' + '🎫'.repeat(30))
|
|
330
|
-
console.log('📨 TEAM INVITATION CREATED')
|
|
331
|
-
console.log('🎫'.repeat(30))
|
|
332
|
-
console.log(`📧 To: ${validatedData.email}`)
|
|
333
|
-
console.log(`👤 Invited by: ${inviterName}`)
|
|
334
|
-
console.log(`🏢 Team: ${team.name}`)
|
|
335
|
-
console.log(`👑 Role: ${validatedData.role}`)
|
|
336
|
-
console.log(`🔗 Accept URL: ${acceptUrl}`)
|
|
337
|
-
console.log('🎫'.repeat(30) + '\n')
|
|
338
|
-
} catch (emailError) {
|
|
339
|
-
// Log error but don't fail the invitation creation
|
|
340
|
-
console.error('Failed to send invitation email:', emailError)
|
|
341
|
-
console.log(`⚠️ Email failed but invitation created. Accept URL: ${acceptUrl}`)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const response = createApiResponse(invitation, { created: true }, 201)
|
|
345
|
-
return addCorsHeaders(response)
|
|
346
|
-
} catch (error) {
|
|
347
|
-
if (error instanceof Error && error.name === 'ZodError') {
|
|
348
|
-
const zodError = error as { issues?: unknown[] }
|
|
349
|
-
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
350
|
-
return addCorsHeaders(response)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
console.error('Error creating invitation:', error)
|
|
354
|
-
const response = createApiError('Internal server error', 500)
|
|
355
|
-
return addCorsHeaders(response)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
)
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
-
import { queryOneWithRLS, 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 } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
11
|
-
import { ownerUpdateTeamSchema, adminUpdateTeamSchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
12
|
-
import { TeamService, MembershipService } from '@nextsparkjs/core/lib/services'
|
|
13
|
-
import type { Team, TeamRole } from '@nextsparkjs/core/lib/teams/types'
|
|
14
|
-
|
|
15
|
-
// Handle CORS preflight
|
|
16
|
-
export async function OPTIONS() {
|
|
17
|
-
return handleCorsPreflightRequest()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// GET /api/v1/teams/:teamId - Get team details
|
|
21
|
-
export const GET = withApiLogging(
|
|
22
|
-
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
23
|
-
try {
|
|
24
|
-
// Authenticate using dual auth
|
|
25
|
-
const authResult = await authenticateRequest(req)
|
|
26
|
-
|
|
27
|
-
if (!authResult.success) {
|
|
28
|
-
return NextResponse.json(
|
|
29
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
30
|
-
{ status: 401 }
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (authResult.rateLimitResponse) {
|
|
35
|
-
return authResult.rateLimitResponse as NextResponse
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const { teamId } = await params
|
|
39
|
-
|
|
40
|
-
// Validate that teamId is not empty
|
|
41
|
-
if (!teamId || teamId.trim() === '') {
|
|
42
|
-
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
43
|
-
return addCorsHeaders(response)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Check if user is a member of the team
|
|
47
|
-
const userRole = await queryOneWithRLS<{ role: TeamRole }>(
|
|
48
|
-
'SELECT role FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
|
|
49
|
-
[teamId, authResult.user!.id],
|
|
50
|
-
authResult.user!.id
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
if (!userRole) {
|
|
54
|
-
const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
|
|
55
|
-
return addCorsHeaders(response)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Fetch team with member count
|
|
59
|
-
const team = await queryOneWithRLS<Team & { memberCount: string; userRole: TeamRole }>(
|
|
60
|
-
`SELECT
|
|
61
|
-
t.*,
|
|
62
|
-
COUNT(DISTINCT tm.id) as "memberCount",
|
|
63
|
-
$2::text as "userRole"
|
|
64
|
-
FROM "teams" t
|
|
65
|
-
LEFT JOIN "team_members" tm ON t.id = tm."teamId"
|
|
66
|
-
WHERE t.id = $1
|
|
67
|
-
GROUP BY t.id`,
|
|
68
|
-
[teamId, userRole.role],
|
|
69
|
-
authResult.user!.id
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
if (!team) {
|
|
73
|
-
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
74
|
-
return addCorsHeaders(response)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const responseData = {
|
|
78
|
-
...team,
|
|
79
|
-
memberCount: parseInt(team.memberCount, 10),
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const response = createApiResponse(responseData)
|
|
83
|
-
return addCorsHeaders(response)
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error('Error fetching team:', error)
|
|
86
|
-
const response = createApiError('Internal server error', 500)
|
|
87
|
-
return addCorsHeaders(response)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
// PATCH /api/v1/teams/:teamId - Update team (owners/admins only)
|
|
93
|
-
export const PATCH = withApiLogging(
|
|
94
|
-
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
95
|
-
try {
|
|
96
|
-
// Authenticate using dual auth
|
|
97
|
-
const authResult = await authenticateRequest(req)
|
|
98
|
-
|
|
99
|
-
if (!authResult.success) {
|
|
100
|
-
return NextResponse.json(
|
|
101
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
102
|
-
{ status: 401 }
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (authResult.rateLimitResponse) {
|
|
107
|
-
return authResult.rateLimitResponse as NextResponse
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const { teamId } = await params
|
|
111
|
-
|
|
112
|
-
// Validate that teamId is not empty
|
|
113
|
-
if (!teamId || teamId.trim() === '') {
|
|
114
|
-
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
115
|
-
return addCorsHeaders(response)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const body = await req.json()
|
|
119
|
-
|
|
120
|
-
// FIX #2: Check owner-only requirement FIRST for name/description fields
|
|
121
|
-
// This provides clearer error messages to users
|
|
122
|
-
// Issue: https://github.com/NextSpark-js/nextspark/pull/1 (Issue #2)
|
|
123
|
-
// FIX #1: Use 'in' operator to check property existence (not value truthiness)
|
|
124
|
-
// This prevents type coercion bypass with falsy values (empty string, null, 0, false)
|
|
125
|
-
// Issue: https://github.com/NextSpark-js/nextspark/pull/1 (Issue #1)
|
|
126
|
-
const isOwnerOnlyUpdate = 'name' in body || 'description' in body
|
|
127
|
-
|
|
128
|
-
if (isOwnerOnlyUpdate) {
|
|
129
|
-
// Fetch team to verify ownership BEFORE other checks
|
|
130
|
-
const team = await TeamService.getById(teamId, authResult.user!.id)
|
|
131
|
-
|
|
132
|
-
if (!team || team.ownerId !== authResult.user!.id) {
|
|
133
|
-
const response = createApiError(
|
|
134
|
-
'Only team owners can edit team name and description',
|
|
135
|
-
403,
|
|
136
|
-
null,
|
|
137
|
-
'OWNER_ONLY'
|
|
138
|
-
)
|
|
139
|
-
return addCorsHeaders(response)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Proceed with owner update (skip general permission check)
|
|
143
|
-
} else {
|
|
144
|
-
// Check general teams.update permission for non-owner-only fields
|
|
145
|
-
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
146
|
-
const actionResult = membership.canPerformAction('teams.update')
|
|
147
|
-
|
|
148
|
-
if (!actionResult.allowed) {
|
|
149
|
-
const response = NextResponse.json(
|
|
150
|
-
{
|
|
151
|
-
success: false,
|
|
152
|
-
error: actionResult.message,
|
|
153
|
-
reason: actionResult.reason,
|
|
154
|
-
meta: actionResult.meta,
|
|
155
|
-
},
|
|
156
|
-
{ status: 403 }
|
|
157
|
-
)
|
|
158
|
-
return addCorsHeaders(response)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Validate with appropriate schema based on update type
|
|
163
|
-
const schema = isOwnerOnlyUpdate ? ownerUpdateTeamSchema : adminUpdateTeamSchema
|
|
164
|
-
const validatedData = schema.parse(body) as Record<string, unknown>
|
|
165
|
-
|
|
166
|
-
// Check if slug is being changed and if it's available
|
|
167
|
-
if ('slug' in validatedData && typeof validatedData.slug === 'string') {
|
|
168
|
-
const slugAvailable = await TeamService.isSlugAvailable(validatedData.slug, teamId)
|
|
169
|
-
if (!slugAvailable) {
|
|
170
|
-
const response = createApiError('Team slug already exists', 409, null, 'SLUG_EXISTS')
|
|
171
|
-
return addCorsHeaders(response)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Build dynamic update query
|
|
176
|
-
const updates = []
|
|
177
|
-
const values = []
|
|
178
|
-
let paramCount = 1
|
|
179
|
-
|
|
180
|
-
if ('name' in validatedData && validatedData.name !== undefined) {
|
|
181
|
-
updates.push(`name = $${paramCount++}`)
|
|
182
|
-
values.push(validatedData.name)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if ('slug' in validatedData && validatedData.slug !== undefined) {
|
|
186
|
-
updates.push(`slug = $${paramCount++}`)
|
|
187
|
-
values.push(validatedData.slug)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if ('description' in validatedData && validatedData.description !== undefined) {
|
|
191
|
-
updates.push(`description = $${paramCount++}`)
|
|
192
|
-
values.push(validatedData.description)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if ('avatarUrl' in validatedData && validatedData.avatarUrl !== undefined) {
|
|
196
|
-
updates.push(`"avatarUrl" = $${paramCount++}`)
|
|
197
|
-
values.push(validatedData.avatarUrl)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if ('settings' in validatedData && validatedData.settings !== undefined) {
|
|
201
|
-
updates.push(`settings = $${paramCount++}`)
|
|
202
|
-
values.push(JSON.stringify(validatedData.settings))
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (updates.length === 0) {
|
|
206
|
-
const response = createApiError('No fields to update', 400, null, 'NO_FIELDS')
|
|
207
|
-
return addCorsHeaders(response)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
updates.push(`"updatedAt" = CURRENT_TIMESTAMP`)
|
|
211
|
-
values.push(teamId)
|
|
212
|
-
|
|
213
|
-
const query = `
|
|
214
|
-
UPDATE "teams"
|
|
215
|
-
SET ${updates.join(', ')}
|
|
216
|
-
WHERE id = $${paramCount}
|
|
217
|
-
RETURNING *
|
|
218
|
-
`
|
|
219
|
-
|
|
220
|
-
const result = await mutateWithRLS(query, values, authResult.user!.id)
|
|
221
|
-
|
|
222
|
-
if (result.rows.length === 0) {
|
|
223
|
-
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
224
|
-
return addCorsHeaders(response)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Fetch team with member count
|
|
228
|
-
const teamWithCount = await queryOneWithRLS<Team & { memberCount: string }>(
|
|
229
|
-
`SELECT
|
|
230
|
-
t.*,
|
|
231
|
-
COUNT(DISTINCT tm.id) as "memberCount"
|
|
232
|
-
FROM "teams" t
|
|
233
|
-
LEFT JOIN "team_members" tm ON t.id = tm."teamId"
|
|
234
|
-
WHERE t.id = $1
|
|
235
|
-
GROUP BY t.id`,
|
|
236
|
-
[teamId],
|
|
237
|
-
authResult.user!.id
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
const responseData = {
|
|
241
|
-
...teamWithCount,
|
|
242
|
-
memberCount: parseInt(teamWithCount?.memberCount || '0', 10),
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const response = createApiResponse(responseData)
|
|
246
|
-
return addCorsHeaders(response)
|
|
247
|
-
} catch (error) {
|
|
248
|
-
if (error instanceof Error && error.name === 'ZodError') {
|
|
249
|
-
const zodError = error as { issues?: unknown[] }
|
|
250
|
-
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
251
|
-
return addCorsHeaders(response)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
console.error('Error updating team:', error)
|
|
255
|
-
const response = createApiError('Internal server error', 500)
|
|
256
|
-
return addCorsHeaders(response)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
// DELETE /api/v1/teams/:teamId - Delete team (owners only, NOT personal teams)
|
|
262
|
-
export const DELETE = withApiLogging(
|
|
263
|
-
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
264
|
-
try {
|
|
265
|
-
// Authenticate using dual auth
|
|
266
|
-
const authResult = await authenticateRequest(req)
|
|
267
|
-
|
|
268
|
-
if (!authResult.success) {
|
|
269
|
-
return NextResponse.json(
|
|
270
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
271
|
-
{ status: 401 }
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (authResult.rateLimitResponse) {
|
|
276
|
-
return authResult.rateLimitResponse as NextResponse
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const { teamId } = await params
|
|
280
|
-
|
|
281
|
-
// Validate that teamId is not empty
|
|
282
|
-
if (!teamId || teamId.trim() === '') {
|
|
283
|
-
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
284
|
-
return addCorsHeaders(response)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Use the TeamService.delete (handles owner check and personal team protection)
|
|
288
|
-
try {
|
|
289
|
-
await TeamService.delete(teamId, authResult.user!.id)
|
|
290
|
-
|
|
291
|
-
const response = createApiResponse({ deleted: true, id: teamId })
|
|
292
|
-
return addCorsHeaders(response)
|
|
293
|
-
} catch (error) {
|
|
294
|
-
if (error instanceof Error && error.message === 'Only team owner can delete the team') {
|
|
295
|
-
const response = createApiError(
|
|
296
|
-
'Insufficient permissions. Only team owner can delete the team.',
|
|
297
|
-
403,
|
|
298
|
-
null,
|
|
299
|
-
'INSUFFICIENT_PERMISSIONS'
|
|
300
|
-
)
|
|
301
|
-
return addCorsHeaders(response)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (error instanceof Error && error.message === 'Team not found') {
|
|
305
|
-
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
306
|
-
return addCorsHeaders(response)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (error instanceof Error && error.message === 'Personal teams cannot be deleted') {
|
|
310
|
-
const response = createApiError('Personal teams cannot be deleted', 403, null, 'PERSONAL_TEAM_DELETE_FORBIDDEN')
|
|
311
|
-
return addCorsHeaders(response)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
throw error
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
console.error('Error deleting team:', error)
|
|
318
|
-
const response = createApiError('Internal server error', 500)
|
|
319
|
-
return addCorsHeaders(response)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
)
|