@nextsparkjs/core 0.1.0-beta.84 → 0.1.0-beta.85
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/styles/classes.json +1 -1
- package/dist/templates/app/(auth)/forgot-password/page.tsx +216 -0
- package/dist/templates/app/(auth)/layout.tsx +51 -0
- package/dist/templates/app/(auth)/login/page.tsx +21 -0
- package/dist/templates/app/(auth)/reset-password/page.tsx +212 -0
- package/dist/templates/app/(auth)/signup/page.tsx +21 -0
- package/dist/templates/app/(auth)/verify-email/page.tsx +190 -0
- package/dist/templates/app/(public)/[...slug]/page.tsx +378 -0
- package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +90 -0
- package/dist/templates/app/(public)/docs/layout.tsx +25 -0
- package/dist/templates/app/(public)/docs/page.tsx +81 -0
- package/dist/templates/app/(public)/layout.tsx +41 -0
- package/dist/templates/app/(public)/page.tsx +19 -0
- package/dist/templates/app/403/page.tsx +89 -0
- package/dist/templates/app/api/auth/[...all]/route.ts +78 -0
- package/dist/templates/app/api/cron/billing/lifecycle/route.ts +98 -0
- package/dist/templates/app/api/csp-report/route.ts +175 -0
- package/dist/templates/app/api/devtools/config/entities/route.ts +108 -0
- package/dist/templates/app/api/devtools/config/theme/route.ts +66 -0
- package/dist/templates/app/api/devtools/tests/[...path]/route.ts +130 -0
- package/dist/templates/app/api/devtools/tests/route.ts +134 -0
- package/dist/templates/app/api/health/route.ts +29 -0
- package/dist/templates/app/api/internal/user-metadata/route.ts +36 -0
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +310 -0
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +286 -0
- package/dist/templates/app/api/superadmin/teams/route.ts +188 -0
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +540 -0
- package/dist/templates/app/api/superadmin/users/route.ts +323 -0
- package/dist/templates/app/api/user/delete-account/route.ts +55 -0
- package/dist/templates/app/api/user/plan-flags/route.ts +283 -0
- package/dist/templates/app/api/user/profile/route.ts +133 -0
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +210 -0
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +331 -0
- package/dist/templates/app/api/v1/[entity]/[id]/route.ts +35 -0
- package/dist/templates/app/api/v1/[entity]/docs.md +369 -0
- package/dist/templates/app/api/v1/[entity]/presets.ts +194 -0
- package/dist/templates/app/api/v1/[entity]/route.ts +31 -0
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +303 -0
- package/dist/templates/app/api/v1/api-keys/docs.md +101 -0
- package/dist/templates/app/api/v1/api-keys/presets.ts +31 -0
- package/dist/templates/app/api/v1/api-keys/route.ts +250 -0
- package/dist/templates/app/api/v1/auth/docs.md +184 -0
- package/dist/templates/app/api/v1/auth/presets.ts +44 -0
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +227 -0
- package/dist/templates/app/api/v1/billing/cancel/route.ts +206 -0
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +97 -0
- package/dist/templates/app/api/v1/billing/check-action/route.ts +81 -0
- package/dist/templates/app/api/v1/billing/checkout/route.ts +124 -0
- package/dist/templates/app/api/v1/billing/docs.md +209 -0
- package/dist/templates/app/api/v1/billing/plans/route.ts +85 -0
- package/dist/templates/app/api/v1/billing/portal/route.ts +90 -0
- package/dist/templates/app/api/v1/billing/presets.ts +121 -0
- package/dist/templates/app/api/v1/billing/webhooks/stripe/route.ts +428 -0
- package/dist/templates/app/api/v1/blocks/[slug]/route.ts +29 -0
- package/dist/templates/app/api/v1/blocks/docs.md +173 -0
- package/dist/templates/app/api/v1/blocks/presets.ts +121 -0
- package/dist/templates/app/api/v1/blocks/route.ts +45 -0
- package/dist/templates/app/api/v1/blocks/validate/route.ts +45 -0
- package/dist/templates/app/api/v1/cron/docs.md +116 -0
- package/dist/templates/app/api/v1/cron/presets.ts +26 -0
- package/dist/templates/app/api/v1/cron/process/route.ts +108 -0
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +82 -0
- package/dist/templates/app/api/v1/devtools/docs/route.ts +150 -0
- package/dist/templates/app/api/v1/devtools/docs.md +204 -0
- package/dist/templates/app/api/v1/devtools/features/route.ts +61 -0
- package/dist/templates/app/api/v1/devtools/flows/route.ts +61 -0
- package/dist/templates/app/api/v1/devtools/presets.ts +113 -0
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +120 -0
- package/dist/templates/app/api/v1/devtools/testing/route.ts +82 -0
- package/dist/templates/app/api/v1/media/docs.md +117 -0
- package/dist/templates/app/api/v1/media/presets.ts +24 -0
- package/dist/templates/app/api/v1/media/upload/route.ts +150 -0
- package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +116 -0
- package/dist/templates/app/api/v1/plugin/[...path]/route.ts +373 -0
- package/dist/templates/app/api/v1/plugin/docs.md +79 -0
- package/dist/templates/app/api/v1/plugin/presets.ts +21 -0
- package/dist/templates/app/api/v1/plugin/route.ts +96 -0
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +255 -0
- package/dist/templates/app/api/v1/post-categories/docs.md +134 -0
- package/dist/templates/app/api/v1/post-categories/presets.ts +78 -0
- package/dist/templates/app/api/v1/post-categories/route.ts +119 -0
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +179 -0
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +120 -0
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +89 -0
- package/dist/templates/app/api/v1/team-invitations/docs.md +88 -0
- package/dist/templates/app/api/v1/team-invitations/presets.ts +43 -0
- package/dist/templates/app/api/v1/team-invitations/route.ts +114 -0
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +171 -0
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +105 -0
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +125 -0
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +263 -0
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +358 -0
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +322 -0
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +50 -0
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +91 -0
- package/dist/templates/app/api/v1/teams/docs.md +320 -0
- package/dist/templates/app/api/v1/teams/presets.ts +178 -0
- package/dist/templates/app/api/v1/teams/route.ts +293 -0
- package/dist/templates/app/api/v1/teams/switch/route.ts +88 -0
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +361 -0
- package/dist/templates/app/api/v1/theme/docs.md +74 -0
- package/dist/templates/app/api/v1/theme/presets.ts +21 -0
- package/dist/templates/app/api/v1/theme/route.ts +96 -0
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +363 -0
- package/dist/templates/app/api/v1/users/[id]/route.ts +302 -0
- package/dist/templates/app/api/v1/users/docs.md +93 -0
- package/dist/templates/app/api/v1/users/presets.ts +59 -0
- package/dist/templates/app/api/v1/users/route.ts +197 -0
- package/dist/templates/app/dashboard/(main)/[entity]/[id]/edit/page.tsx +117 -0
- package/dist/templates/app/dashboard/(main)/[entity]/[id]/page.tsx +103 -0
- package/dist/templates/app/dashboard/(main)/[entity]/create/page.tsx +95 -0
- package/dist/templates/app/dashboard/(main)/[entity]/error.tsx +51 -0
- package/dist/templates/app/dashboard/(main)/[entity]/layout.tsx +113 -0
- package/dist/templates/app/dashboard/(main)/[entity]/loading.tsx +61 -0
- package/dist/templates/app/dashboard/(main)/[entity]/page.tsx +90 -0
- package/dist/templates/app/dashboard/(main)/layout.tsx +98 -0
- package/dist/templates/app/dashboard/(main)/loading.tsx +5 -0
- package/dist/templates/app/dashboard/(main)/page.tsx +201 -0
- package/dist/templates/app/dashboard/(main)/patterns/[id]/edit/page.tsx +114 -0
- package/dist/templates/app/dashboard/(main)/patterns/[id]/page.tsx +20 -0
- package/dist/templates/app/dashboard/(main)/patterns/[id]/reports/page.tsx +171 -0
- package/dist/templates/app/dashboard/(main)/patterns/create/page.tsx +86 -0
- package/dist/templates/app/dashboard/(main)/patterns/page.tsx +444 -0
- package/dist/templates/app/dashboard/features/analytics/page.tsx +35 -0
- package/dist/templates/app/dashboard/features/automation/page.tsx +35 -0
- package/dist/templates/app/dashboard/features/layout.tsx +13 -0
- package/dist/templates/app/dashboard/features/loading.tsx +5 -0
- package/dist/templates/app/dashboard/features/webhooks/page.tsx +35 -0
- package/dist/templates/app/dashboard/layout.tsx +86 -0
- package/dist/templates/app/dashboard/permission-denied/page.tsx +29 -0
- package/dist/templates/app/dashboard/settings/api-keys/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/api-keys/page.tsx +513 -0
- package/dist/templates/app/dashboard/settings/billing/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/billing/page.tsx +284 -0
- package/dist/templates/app/dashboard/settings/invoices/[invoiceNumber]/page.tsx +222 -0
- package/dist/templates/app/dashboard/settings/invoices/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/invoices/page.tsx +82 -0
- package/dist/templates/app/dashboard/settings/layout.tsx +151 -0
- package/dist/templates/app/dashboard/settings/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/notifications/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/notifications/page.tsx +462 -0
- package/dist/templates/app/dashboard/settings/page.tsx +92 -0
- package/dist/templates/app/dashboard/settings/password/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/password/page.tsx +306 -0
- package/dist/templates/app/dashboard/settings/plans/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/plans/page.tsx +40 -0
- package/dist/templates/app/dashboard/settings/profile/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/profile/page.tsx +686 -0
- package/dist/templates/app/dashboard/settings/security/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/security/page.tsx +505 -0
- package/dist/templates/app/dashboard/settings/teams/loading.tsx +5 -0
- package/dist/templates/app/dashboard/settings/teams/page.tsx +272 -0
- package/dist/templates/app/dashboard/settings/teams/permissions/page.tsx +92 -0
- package/dist/templates/app/devtools/blocks/[slug]/page.tsx +39 -0
- package/dist/templates/app/devtools/blocks/page.tsx +31 -0
- package/dist/templates/app/devtools/config/page.tsx +31 -0
- package/dist/templates/app/devtools/features/page.tsx +31 -0
- package/dist/templates/app/devtools/flows/page.tsx +31 -0
- package/dist/templates/app/devtools/layout.tsx +58 -0
- package/dist/templates/app/devtools/page.tsx +121 -0
- package/dist/templates/app/devtools/scheduled-actions/page.tsx +157 -0
- package/dist/templates/app/devtools/style/page.tsx +330 -0
- package/dist/templates/app/devtools/tags/page.tsx +31 -0
- package/dist/templates/app/devtools/tests/[[...path]]/page.tsx +47 -0
- package/dist/templates/app/favicon.ico +0 -0
- package/dist/templates/app/globals.css +12 -0
- package/dist/templates/app/layout.tsx +96 -0
- package/dist/templates/app/public/page.tsx +30 -0
- package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +92 -0
- package/dist/templates/app/superadmin/docs/page.tsx +75 -0
- package/dist/templates/app/superadmin/layout.tsx +67 -0
- package/dist/templates/app/superadmin/page.tsx +149 -0
- package/dist/templates/app/superadmin/subscriptions/page.tsx +655 -0
- package/dist/templates/app/superadmin/team-roles/page.tsx +493 -0
- package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +687 -0
- package/dist/templates/app/superadmin/teams/page.tsx +302 -0
- package/dist/templates/app/superadmin/users/[userId]/page.tsx +548 -0
- package/dist/templates/app/superadmin/users/page.tsx +528 -0
- package/package.json +2 -2
- package/templates/app/(auth)/forgot-password/page.tsx +216 -0
- package/templates/app/(auth)/layout.tsx +51 -0
- package/templates/app/(auth)/login/page.tsx +21 -0
- package/templates/app/(auth)/reset-password/page.tsx +212 -0
- package/templates/app/(auth)/signup/page.tsx +21 -0
- package/templates/app/(auth)/verify-email/page.tsx +190 -0
- package/templates/app/(public)/[...slug]/page.tsx +378 -0
- package/templates/app/(public)/docs/[section]/[page]/page.tsx +90 -0
- package/templates/app/(public)/docs/layout.tsx +25 -0
- package/templates/app/(public)/docs/page.tsx +81 -0
- package/templates/app/(public)/layout.tsx +41 -0
- package/templates/app/(public)/page.tsx +19 -0
- package/templates/app/403/page.tsx +89 -0
- package/templates/app/api/auth/[...all]/route.ts +78 -0
- package/templates/app/api/cron/billing/lifecycle/route.ts +98 -0
- package/templates/app/api/csp-report/route.ts +175 -0
- package/templates/app/api/devtools/config/entities/route.ts +108 -0
- package/templates/app/api/devtools/config/theme/route.ts +66 -0
- package/templates/app/api/devtools/tests/[...path]/route.ts +130 -0
- package/templates/app/api/devtools/tests/route.ts +134 -0
- package/templates/app/api/health/route.ts +29 -0
- package/templates/app/api/internal/user-metadata/route.ts +36 -0
- package/templates/app/api/superadmin/subscriptions/route.ts +310 -0
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +286 -0
- package/templates/app/api/superadmin/teams/route.ts +188 -0
- package/templates/app/api/superadmin/users/[userId]/route.ts +540 -0
- package/templates/app/api/superadmin/users/route.ts +323 -0
- package/templates/app/api/user/delete-account/route.ts +55 -0
- package/templates/app/api/user/plan-flags/route.ts +283 -0
- package/templates/app/api/user/profile/route.ts +133 -0
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +210 -0
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +331 -0
- package/templates/app/api/v1/[entity]/[id]/route.ts +35 -0
- package/templates/app/api/v1/[entity]/docs.md +369 -0
- package/templates/app/api/v1/[entity]/presets.ts +194 -0
- package/templates/app/api/v1/[entity]/route.ts +31 -0
- package/templates/app/api/v1/api-keys/[id]/route.ts +303 -0
- package/templates/app/api/v1/api-keys/docs.md +101 -0
- package/templates/app/api/v1/api-keys/presets.ts +31 -0
- package/templates/app/api/v1/api-keys/route.ts +250 -0
- package/templates/app/api/v1/auth/docs.md +184 -0
- package/templates/app/api/v1/auth/presets.ts +44 -0
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +227 -0
- package/templates/app/api/v1/billing/cancel/route.ts +206 -0
- package/templates/app/api/v1/billing/change-plan/route.ts +97 -0
- package/templates/app/api/v1/billing/check-action/route.ts +81 -0
- package/templates/app/api/v1/billing/checkout/route.ts +124 -0
- package/templates/app/api/v1/billing/docs.md +209 -0
- package/templates/app/api/v1/billing/plans/route.ts +85 -0
- package/templates/app/api/v1/billing/portal/route.ts +90 -0
- package/templates/app/api/v1/billing/presets.ts +121 -0
- package/templates/app/api/v1/billing/webhooks/stripe/route.ts +428 -0
- package/templates/app/api/v1/blocks/[slug]/route.ts +29 -0
- package/templates/app/api/v1/blocks/docs.md +173 -0
- package/templates/app/api/v1/blocks/presets.ts +121 -0
- package/templates/app/api/v1/blocks/route.ts +45 -0
- package/templates/app/api/v1/blocks/validate/route.ts +45 -0
- package/templates/app/api/v1/cron/docs.md +116 -0
- package/templates/app/api/v1/cron/presets.ts +26 -0
- package/templates/app/api/v1/cron/process/route.ts +108 -0
- package/templates/app/api/v1/devtools/blocks/route.ts +82 -0
- package/templates/app/api/v1/devtools/docs/route.ts +150 -0
- package/templates/app/api/v1/devtools/docs.md +204 -0
- package/templates/app/api/v1/devtools/features/route.ts +61 -0
- package/templates/app/api/v1/devtools/flows/route.ts +61 -0
- package/templates/app/api/v1/devtools/presets.ts +113 -0
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +120 -0
- package/templates/app/api/v1/devtools/testing/route.ts +82 -0
- package/templates/app/api/v1/media/docs.md +117 -0
- package/templates/app/api/v1/media/presets.ts +24 -0
- package/templates/app/api/v1/media/upload/route.ts +150 -0
- package/templates/app/api/v1/patterns/[id]/usages/route.ts +116 -0
- package/templates/app/api/v1/plugin/[...path]/route.ts +373 -0
- package/templates/app/api/v1/plugin/docs.md +79 -0
- package/templates/app/api/v1/plugin/presets.ts +21 -0
- package/templates/app/api/v1/plugin/route.ts +96 -0
- package/templates/app/api/v1/post-categories/[id]/route.ts +255 -0
- package/templates/app/api/v1/post-categories/docs.md +134 -0
- package/templates/app/api/v1/post-categories/presets.ts +78 -0
- package/templates/app/api/v1/post-categories/route.ts +119 -0
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +179 -0
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +120 -0
- package/templates/app/api/v1/team-invitations/[token]/route.ts +89 -0
- package/templates/app/api/v1/team-invitations/docs.md +88 -0
- package/templates/app/api/v1/team-invitations/presets.ts +43 -0
- package/templates/app/api/v1/team-invitations/route.ts +114 -0
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +171 -0
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +105 -0
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +125 -0
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +263 -0
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +358 -0
- package/templates/app/api/v1/teams/[teamId]/route.ts +322 -0
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +50 -0
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +91 -0
- package/templates/app/api/v1/teams/docs.md +320 -0
- package/templates/app/api/v1/teams/presets.ts +178 -0
- package/templates/app/api/v1/teams/route.ts +293 -0
- package/templates/app/api/v1/teams/switch/route.ts +88 -0
- package/templates/app/api/v1/theme/[...path]/route.ts +361 -0
- package/templates/app/api/v1/theme/docs.md +74 -0
- package/templates/app/api/v1/theme/presets.ts +21 -0
- package/templates/app/api/v1/theme/route.ts +96 -0
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +363 -0
- package/templates/app/api/v1/users/[id]/route.ts +302 -0
- package/templates/app/api/v1/users/docs.md +93 -0
- package/templates/app/api/v1/users/presets.ts +59 -0
- package/templates/app/api/v1/users/route.ts +197 -0
- package/templates/app/dashboard/(main)/[entity]/[id]/edit/page.tsx +117 -0
- package/templates/app/dashboard/(main)/[entity]/[id]/page.tsx +103 -0
- package/templates/app/dashboard/(main)/[entity]/create/page.tsx +95 -0
- package/templates/app/dashboard/(main)/[entity]/error.tsx +51 -0
- package/templates/app/dashboard/(main)/[entity]/layout.tsx +113 -0
- package/templates/app/dashboard/(main)/[entity]/loading.tsx +61 -0
- package/templates/app/dashboard/(main)/[entity]/page.tsx +90 -0
- package/templates/app/dashboard/(main)/layout.tsx +98 -0
- package/templates/app/dashboard/(main)/loading.tsx +5 -0
- package/templates/app/dashboard/(main)/page.tsx +201 -0
- package/templates/app/dashboard/(main)/patterns/[id]/edit/page.tsx +114 -0
- package/templates/app/dashboard/(main)/patterns/[id]/page.tsx +20 -0
- package/templates/app/dashboard/(main)/patterns/[id]/reports/page.tsx +171 -0
- package/templates/app/dashboard/(main)/patterns/create/page.tsx +86 -0
- package/templates/app/dashboard/(main)/patterns/page.tsx +444 -0
- package/templates/app/dashboard/features/analytics/page.tsx +35 -0
- package/templates/app/dashboard/features/automation/page.tsx +35 -0
- package/templates/app/dashboard/features/layout.tsx +13 -0
- package/templates/app/dashboard/features/loading.tsx +5 -0
- package/templates/app/dashboard/features/webhooks/page.tsx +35 -0
- package/templates/app/dashboard/layout.tsx +86 -0
- package/templates/app/dashboard/permission-denied/page.tsx +29 -0
- package/templates/app/dashboard/settings/api-keys/loading.tsx +5 -0
- package/templates/app/dashboard/settings/api-keys/page.tsx +513 -0
- package/templates/app/dashboard/settings/billing/loading.tsx +5 -0
- package/templates/app/dashboard/settings/billing/page.tsx +284 -0
- package/templates/app/dashboard/settings/invoices/[invoiceNumber]/page.tsx +222 -0
- package/templates/app/dashboard/settings/invoices/loading.tsx +5 -0
- package/templates/app/dashboard/settings/invoices/page.tsx +82 -0
- package/templates/app/dashboard/settings/layout.tsx +151 -0
- package/templates/app/dashboard/settings/loading.tsx +5 -0
- package/templates/app/dashboard/settings/notifications/loading.tsx +5 -0
- package/templates/app/dashboard/settings/notifications/page.tsx +462 -0
- package/templates/app/dashboard/settings/page.tsx +92 -0
- package/templates/app/dashboard/settings/password/loading.tsx +5 -0
- package/templates/app/dashboard/settings/password/page.tsx +306 -0
- package/templates/app/dashboard/settings/plans/loading.tsx +5 -0
- package/templates/app/dashboard/settings/plans/page.tsx +40 -0
- package/templates/app/dashboard/settings/profile/loading.tsx +5 -0
- package/templates/app/dashboard/settings/profile/page.tsx +686 -0
- package/templates/app/dashboard/settings/security/loading.tsx +5 -0
- package/templates/app/dashboard/settings/security/page.tsx +505 -0
- package/templates/app/dashboard/settings/teams/loading.tsx +5 -0
- package/templates/app/dashboard/settings/teams/page.tsx +272 -0
- package/templates/app/dashboard/settings/teams/permissions/page.tsx +92 -0
- package/templates/app/devtools/blocks/[slug]/page.tsx +39 -0
- package/templates/app/devtools/blocks/page.tsx +31 -0
- package/templates/app/devtools/config/page.tsx +31 -0
- package/templates/app/devtools/features/page.tsx +31 -0
- package/templates/app/devtools/flows/page.tsx +31 -0
- package/templates/app/devtools/layout.tsx +58 -0
- package/templates/app/devtools/page.tsx +121 -0
- package/templates/app/devtools/scheduled-actions/page.tsx +157 -0
- package/templates/app/devtools/style/page.tsx +330 -0
- package/templates/app/devtools/tags/page.tsx +31 -0
- package/templates/app/devtools/tests/[[...path]]/page.tsx +47 -0
- package/templates/app/favicon.ico +0 -0
- package/templates/app/globals.css +12 -0
- package/templates/app/layout.tsx +96 -0
- package/templates/app/public/page.tsx +30 -0
- package/templates/app/superadmin/docs/[section]/[page]/page.tsx +92 -0
- package/templates/app/superadmin/docs/page.tsx +75 -0
- package/templates/app/superadmin/layout.tsx +67 -0
- package/templates/app/superadmin/page.tsx +149 -0
- package/templates/app/superadmin/subscriptions/page.tsx +655 -0
- package/templates/app/superadmin/team-roles/page.tsx +493 -0
- package/templates/app/superadmin/teams/[teamId]/page.tsx +687 -0
- package/templates/app/superadmin/teams/page.tsx +302 -0
- package/templates/app/superadmin/users/[userId]/page.tsx +548 -0
- package/templates/app/superadmin/users/page.tsx +528 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { queryWithRLS, 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 { TeamMemberService, MembershipService } from '@nextsparkjs/core/lib/services'
|
|
13
|
+
import type { TeamInvitation } 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/invitations - List pending invitations for a team
|
|
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 isMember = await TeamMemberService.isMember(teamId, authResult.user!.id)
|
|
48
|
+
|
|
49
|
+
if (!isMember) {
|
|
50
|
+
const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
|
|
51
|
+
return addCorsHeaders(response)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Parse query parameters
|
|
55
|
+
const { searchParams } = new URL(req.url)
|
|
56
|
+
const page = parseInt(searchParams.get('page') || '1', 10)
|
|
57
|
+
const limit = parseInt(searchParams.get('limit') || '50', 10)
|
|
58
|
+
const status = searchParams.get('status') || 'pending'
|
|
59
|
+
const offset = (page - 1) * limit
|
|
60
|
+
|
|
61
|
+
// Fetch team invitations
|
|
62
|
+
const invitations = await queryWithRLS<
|
|
63
|
+
TeamInvitation & {
|
|
64
|
+
inviterName: string | null
|
|
65
|
+
inviterEmail: string
|
|
66
|
+
}
|
|
67
|
+
>(
|
|
68
|
+
`SELECT
|
|
69
|
+
ti.*,
|
|
70
|
+
u.name as "inviterName",
|
|
71
|
+
u.email as "inviterEmail"
|
|
72
|
+
FROM "team_invitations" ti
|
|
73
|
+
INNER JOIN "users" u ON ti."invitedBy" = u.id
|
|
74
|
+
WHERE ti."teamId" = $1 AND ti.status = $2
|
|
75
|
+
ORDER BY ti."createdAt" DESC
|
|
76
|
+
LIMIT $3 OFFSET $4`,
|
|
77
|
+
[teamId, status, limit, offset],
|
|
78
|
+
authResult.user!.id
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// Get total count for pagination
|
|
82
|
+
const totalResult = await queryWithRLS<{ count: string }>(
|
|
83
|
+
`SELECT COUNT(*) as count
|
|
84
|
+
FROM "team_invitations" ti
|
|
85
|
+
WHERE ti."teamId" = $1 AND ti.status = $2`,
|
|
86
|
+
[teamId, status],
|
|
87
|
+
authResult.user!.id
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const total = parseInt(totalResult[0]?.count || '0', 10)
|
|
91
|
+
const paginationMeta = createPaginationMeta(page, limit, total)
|
|
92
|
+
|
|
93
|
+
const response = createApiResponse(invitations, paginationMeta)
|
|
94
|
+
return addCorsHeaders(response)
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error fetching team invitations:', error)
|
|
97
|
+
const response = createApiError('Internal server error', 500)
|
|
98
|
+
return addCorsHeaders(response)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// DELETE /api/v1/teams/:teamId/invitations/:invitationId - Cancel/revoke an invitation
|
|
104
|
+
export const DELETE = withApiLogging(
|
|
105
|
+
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
106
|
+
try {
|
|
107
|
+
// Authenticate using dual auth
|
|
108
|
+
const authResult = await authenticateRequest(req)
|
|
109
|
+
|
|
110
|
+
if (!authResult.success) {
|
|
111
|
+
return NextResponse.json(
|
|
112
|
+
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
113
|
+
{ status: 401 }
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (authResult.rateLimitResponse) {
|
|
118
|
+
return authResult.rateLimitResponse as NextResponse
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const { teamId } = await params
|
|
122
|
+
|
|
123
|
+
// Get invitation ID from URL
|
|
124
|
+
const url = new URL(req.url)
|
|
125
|
+
const invitationId = url.searchParams.get('id')
|
|
126
|
+
|
|
127
|
+
if (!invitationId) {
|
|
128
|
+
const response = createApiError('Invitation ID is required', 400, null, 'MISSING_INVITATION_ID')
|
|
129
|
+
return addCorsHeaders(response)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check if user has permission to manage invitations using MembershipService
|
|
133
|
+
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
134
|
+
const actionResult = membership.canPerformAction('members.invite')
|
|
135
|
+
|
|
136
|
+
if (!actionResult.allowed) {
|
|
137
|
+
const response = NextResponse.json(
|
|
138
|
+
{
|
|
139
|
+
success: false,
|
|
140
|
+
error: actionResult.message,
|
|
141
|
+
reason: actionResult.reason,
|
|
142
|
+
meta: actionResult.meta,
|
|
143
|
+
},
|
|
144
|
+
{ status: 403 }
|
|
145
|
+
)
|
|
146
|
+
return addCorsHeaders(response)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Update invitation status to 'cancelled' (or delete it)
|
|
150
|
+
const result = await mutateWithRLS(
|
|
151
|
+
`DELETE FROM "team_invitations"
|
|
152
|
+
WHERE id = $1 AND "teamId" = $2 AND status = 'pending'
|
|
153
|
+
RETURNING *`,
|
|
154
|
+
[invitationId, teamId],
|
|
155
|
+
authResult.user!.id
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if (result.rowCount === 0) {
|
|
159
|
+
const response = createApiError('Invitation not found or already processed', 404, null, 'INVITATION_NOT_FOUND')
|
|
160
|
+
return addCorsHeaders(response)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const response = createApiResponse({ deleted: true, id: invitationId })
|
|
164
|
+
return addCorsHeaders(response)
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('Error cancelling invitation:', error)
|
|
167
|
+
const response = createApiError('Internal server error', 500)
|
|
168
|
+
return addCorsHeaders(response)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { queryWithRLS } 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 { MembershipService } from '@nextsparkjs/core/lib/services'
|
|
12
|
+
import type { InvoiceResponse } from '@nextsparkjs/core/lib/validation/invoices'
|
|
13
|
+
|
|
14
|
+
// Handle CORS preflight
|
|
15
|
+
export async function OPTIONS() {
|
|
16
|
+
return handleCorsPreflightRequest()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// GET /api/v1/teams/:teamId/invoices/:invoiceNumber - Get single invoice (owner only)
|
|
20
|
+
export const GET = withApiLogging(
|
|
21
|
+
async (
|
|
22
|
+
req: NextRequest,
|
|
23
|
+
{ params }: { params: Promise<{ teamId: string; invoiceNumber: string }> }
|
|
24
|
+
): Promise<NextResponse> => {
|
|
25
|
+
try {
|
|
26
|
+
// Authenticate using dual auth (API key OR session)
|
|
27
|
+
const authResult = await authenticateRequest(req)
|
|
28
|
+
|
|
29
|
+
if (!authResult.success) {
|
|
30
|
+
return NextResponse.json(
|
|
31
|
+
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
32
|
+
{ status: 401 }
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (authResult.rateLimitResponse) {
|
|
37
|
+
return authResult.rateLimitResponse as NextResponse
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { teamId, invoiceNumber } = await params
|
|
41
|
+
|
|
42
|
+
// Validate that teamId is not empty
|
|
43
|
+
if (!teamId || teamId.trim() === '') {
|
|
44
|
+
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
45
|
+
return addCorsHeaders(response)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate that invoiceNumber is not empty
|
|
49
|
+
if (!invoiceNumber || invoiceNumber.trim() === '') {
|
|
50
|
+
const response = createApiError('Invoice number is required', 400, null, 'MISSING_INVOICE_NUMBER')
|
|
51
|
+
return addCorsHeaders(response)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if user has permission to view invoices using MembershipService
|
|
55
|
+
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
56
|
+
const actionResult = membership.canPerformAction('billing.invoices')
|
|
57
|
+
|
|
58
|
+
if (!actionResult.allowed) {
|
|
59
|
+
const response = NextResponse.json(
|
|
60
|
+
{
|
|
61
|
+
success: false,
|
|
62
|
+
error: actionResult.message,
|
|
63
|
+
reason: actionResult.reason,
|
|
64
|
+
meta: actionResult.meta,
|
|
65
|
+
},
|
|
66
|
+
{ status: 403 }
|
|
67
|
+
)
|
|
68
|
+
return addCorsHeaders(response)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Query single invoice by invoiceNumber
|
|
72
|
+
const invoices = await queryWithRLS<InvoiceResponse>(
|
|
73
|
+
`SELECT
|
|
74
|
+
id,
|
|
75
|
+
"teamId",
|
|
76
|
+
"invoiceNumber",
|
|
77
|
+
to_char(date, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as date,
|
|
78
|
+
amount::NUMERIC(10,2)::FLOAT as amount,
|
|
79
|
+
currency,
|
|
80
|
+
status::TEXT as status,
|
|
81
|
+
"pdfUrl",
|
|
82
|
+
description,
|
|
83
|
+
to_char("createdAt", 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as "createdAt",
|
|
84
|
+
to_char("updatedAt", 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as "updatedAt"
|
|
85
|
+
FROM "invoices"
|
|
86
|
+
WHERE "teamId" = $1 AND "invoiceNumber" = $2
|
|
87
|
+
LIMIT 1`,
|
|
88
|
+
[teamId, decodeURIComponent(invoiceNumber)],
|
|
89
|
+
authResult.user!.id
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (invoices.length === 0) {
|
|
93
|
+
const response = createApiError('Invoice not found', 404, null, 'INVOICE_NOT_FOUND')
|
|
94
|
+
return addCorsHeaders(response)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const response = createApiResponse(invoices[0])
|
|
98
|
+
return addCorsHeaders(response)
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error fetching invoice:', error)
|
|
101
|
+
const response = createApiError('Internal server error', 500)
|
|
102
|
+
return addCorsHeaders(response)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { queryWithRLS } 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 { MembershipService } from '@nextsparkjs/core/lib/services'
|
|
13
|
+
import { invoiceQuerySchema } from '@nextsparkjs/core/lib/validation/invoices'
|
|
14
|
+
import type { InvoiceResponse } from '@nextsparkjs/core/lib/validation/invoices'
|
|
15
|
+
|
|
16
|
+
// Handle CORS preflight
|
|
17
|
+
export async function OPTIONS() {
|
|
18
|
+
return handleCorsPreflightRequest()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// GET /api/v1/teams/:teamId/invoices - List team invoices (owner only)
|
|
22
|
+
export const GET = withApiLogging(
|
|
23
|
+
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
24
|
+
try {
|
|
25
|
+
// Authenticate using dual auth (API key OR session)
|
|
26
|
+
const authResult = await authenticateRequest(req)
|
|
27
|
+
|
|
28
|
+
if (!authResult.success) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
31
|
+
{ status: 401 }
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (authResult.rateLimitResponse) {
|
|
36
|
+
return authResult.rateLimitResponse as NextResponse
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { teamId } = await params
|
|
40
|
+
|
|
41
|
+
// Validate that teamId is not empty
|
|
42
|
+
if (!teamId || teamId.trim() === '') {
|
|
43
|
+
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
44
|
+
return addCorsHeaders(response)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check if user has permission to view invoices using MembershipService
|
|
48
|
+
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
49
|
+
const actionResult = membership.canPerformAction('billing.invoices')
|
|
50
|
+
|
|
51
|
+
if (!actionResult.allowed) {
|
|
52
|
+
const response = NextResponse.json(
|
|
53
|
+
{
|
|
54
|
+
success: false,
|
|
55
|
+
error: actionResult.message,
|
|
56
|
+
reason: actionResult.reason,
|
|
57
|
+
meta: actionResult.meta,
|
|
58
|
+
},
|
|
59
|
+
{ status: 403 }
|
|
60
|
+
)
|
|
61
|
+
return addCorsHeaders(response)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Parse query parameters for pagination
|
|
65
|
+
const { searchParams } = new URL(req.url)
|
|
66
|
+
const queryParams = Object.fromEntries(
|
|
67
|
+
Object.entries({
|
|
68
|
+
limit: searchParams.get('limit'),
|
|
69
|
+
offset: searchParams.get('offset'),
|
|
70
|
+
}).filter(([, value]) => value !== null && value !== '')
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const validatedQuery = invoiceQuerySchema.parse(queryParams)
|
|
74
|
+
const { limit, offset } = validatedQuery
|
|
75
|
+
|
|
76
|
+
// Query invoices with pagination (ordered by date DESC)
|
|
77
|
+
const invoices = await queryWithRLS<InvoiceResponse>(
|
|
78
|
+
`SELECT
|
|
79
|
+
id,
|
|
80
|
+
"teamId",
|
|
81
|
+
"invoiceNumber",
|
|
82
|
+
to_char(date, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as date,
|
|
83
|
+
amount::NUMERIC(10,2)::FLOAT as amount,
|
|
84
|
+
currency,
|
|
85
|
+
status::TEXT as status,
|
|
86
|
+
"pdfUrl",
|
|
87
|
+
description,
|
|
88
|
+
to_char("createdAt", 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as "createdAt",
|
|
89
|
+
to_char("updatedAt", 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as "updatedAt"
|
|
90
|
+
FROM "invoices"
|
|
91
|
+
WHERE "teamId" = $1
|
|
92
|
+
ORDER BY date DESC
|
|
93
|
+
LIMIT $2 OFFSET $3`,
|
|
94
|
+
[teamId, limit, offset],
|
|
95
|
+
authResult.user!.id
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// Get total count for pagination
|
|
99
|
+
const totalResult = await queryWithRLS<{ count: string }>(
|
|
100
|
+
`SELECT COUNT(*) as count FROM "invoices" WHERE "teamId" = $1`,
|
|
101
|
+
[teamId],
|
|
102
|
+
authResult.user!.id
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const total = parseInt(totalResult[0]?.count || '0', 10)
|
|
106
|
+
|
|
107
|
+
// Calculate pagination metadata
|
|
108
|
+
const page = Math.floor(offset / limit) + 1
|
|
109
|
+
const paginationMeta = createPaginationMeta(page, limit, total)
|
|
110
|
+
|
|
111
|
+
const response = createApiResponse(invoices, paginationMeta)
|
|
112
|
+
return addCorsHeaders(response)
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error instanceof Error && error.name === 'ZodError') {
|
|
115
|
+
const zodError = error as { issues?: unknown[] }
|
|
116
|
+
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
117
|
+
return addCorsHeaders(response)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.error('Error fetching invoices:', error)
|
|
121
|
+
const response = createApiError('Internal server error', 500)
|
|
122
|
+
return addCorsHeaders(response)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
)
|
|
@@ -0,0 +1,263 @@
|
|
|
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 { updateMemberRoleSchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
12
|
+
import { MembershipService } from '@nextsparkjs/core/lib/services'
|
|
13
|
+
import { validateRoleTransition, canManageRole } from '@nextsparkjs/core/lib/teams/permissions'
|
|
14
|
+
import type { TeamMember, TeamRole } from '@nextsparkjs/core/lib/teams/types'
|
|
15
|
+
|
|
16
|
+
// Handle CORS preflight
|
|
17
|
+
export async function OPTIONS() {
|
|
18
|
+
return handleCorsPreflightRequest()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// PATCH /api/v1/teams/:teamId/members/:memberId - Update member role
|
|
22
|
+
export const PATCH = withApiLogging(
|
|
23
|
+
async (
|
|
24
|
+
req: NextRequest,
|
|
25
|
+
{ params }: { params: Promise<{ teamId: string; memberId: string }> }
|
|
26
|
+
): Promise<NextResponse> => {
|
|
27
|
+
try {
|
|
28
|
+
// Authenticate using dual auth
|
|
29
|
+
const authResult = await authenticateRequest(req)
|
|
30
|
+
|
|
31
|
+
if (!authResult.success) {
|
|
32
|
+
return NextResponse.json(
|
|
33
|
+
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
34
|
+
{ status: 401 }
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (authResult.rateLimitResponse) {
|
|
39
|
+
return authResult.rateLimitResponse as NextResponse
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { teamId, memberId } = await params
|
|
43
|
+
|
|
44
|
+
// Validate parameters
|
|
45
|
+
if (!teamId || teamId.trim() === '') {
|
|
46
|
+
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
47
|
+
return addCorsHeaders(response)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!memberId || memberId.trim() === '') {
|
|
51
|
+
const response = createApiError('Member ID is required', 400, null, 'MISSING_MEMBER_ID')
|
|
52
|
+
return addCorsHeaders(response)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if user has permission to update member roles using MembershipService
|
|
56
|
+
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
57
|
+
const actionResult = membership.canPerformAction('members.update_role')
|
|
58
|
+
|
|
59
|
+
if (!actionResult.allowed) {
|
|
60
|
+
const response = NextResponse.json(
|
|
61
|
+
{
|
|
62
|
+
success: false,
|
|
63
|
+
error: actionResult.message,
|
|
64
|
+
reason: actionResult.reason,
|
|
65
|
+
meta: actionResult.meta,
|
|
66
|
+
},
|
|
67
|
+
{ status: 403 }
|
|
68
|
+
)
|
|
69
|
+
return addCorsHeaders(response)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get user's role for role hierarchy validation
|
|
73
|
+
const userRole = membership.role as TeamRole
|
|
74
|
+
|
|
75
|
+
// Get target member
|
|
76
|
+
const targetMember = await queryOneWithRLS<TeamMember>(
|
|
77
|
+
'SELECT * FROM "team_members" WHERE id = $1 AND "teamId" = $2',
|
|
78
|
+
[memberId, teamId],
|
|
79
|
+
authResult.user!.id
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if (!targetMember) {
|
|
83
|
+
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
84
|
+
return addCorsHeaders(response)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const body = await req.json()
|
|
88
|
+
const validatedData = updateMemberRoleSchema.parse(body)
|
|
89
|
+
|
|
90
|
+
// Validate role transition
|
|
91
|
+
const transitionValidation = validateRoleTransition(targetMember.role, validatedData.role, userRole)
|
|
92
|
+
|
|
93
|
+
if (!transitionValidation.allowed) {
|
|
94
|
+
const response = createApiError(transitionValidation.reason || 'Invalid role transition', 403, null, 'INVALID_ROLE_TRANSITION')
|
|
95
|
+
return addCorsHeaders(response)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check if actor can manage the target role
|
|
99
|
+
if (!canManageRole(userRole, targetMember.role)) {
|
|
100
|
+
const response = createApiError(
|
|
101
|
+
'You do not have permission to change this user\'s role.',
|
|
102
|
+
403,
|
|
103
|
+
null,
|
|
104
|
+
'INSUFFICIENT_PERMISSIONS'
|
|
105
|
+
)
|
|
106
|
+
return addCorsHeaders(response)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Update member role
|
|
110
|
+
const result = await mutateWithRLS(
|
|
111
|
+
`UPDATE "team_members"
|
|
112
|
+
SET role = $1, "updatedAt" = CURRENT_TIMESTAMP
|
|
113
|
+
WHERE id = $2 AND "teamId" = $3
|
|
114
|
+
RETURNING *`,
|
|
115
|
+
[validatedData.role, memberId, teamId],
|
|
116
|
+
authResult.user!.id
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if (result.rows.length === 0) {
|
|
120
|
+
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
121
|
+
return addCorsHeaders(response)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fetch member with user details
|
|
125
|
+
const memberWithUser = await queryOneWithRLS<
|
|
126
|
+
TeamMember & {
|
|
127
|
+
userName: string | null
|
|
128
|
+
userEmail: string
|
|
129
|
+
userImage: string | null
|
|
130
|
+
}
|
|
131
|
+
>(
|
|
132
|
+
`SELECT
|
|
133
|
+
tm.*,
|
|
134
|
+
u.name as "userName",
|
|
135
|
+
u.email as "userEmail",
|
|
136
|
+
u.image as "userImage"
|
|
137
|
+
FROM "team_members" tm
|
|
138
|
+
INNER JOIN "users" u ON tm."userId" = u.id
|
|
139
|
+
WHERE tm.id = $1`,
|
|
140
|
+
[memberId],
|
|
141
|
+
authResult.user!.id
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const response = createApiResponse(memberWithUser)
|
|
145
|
+
return addCorsHeaders(response)
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof Error && error.name === 'ZodError') {
|
|
148
|
+
const zodError = error as { issues?: unknown[] }
|
|
149
|
+
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
150
|
+
return addCorsHeaders(response)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.error('Error updating member role:', error)
|
|
154
|
+
const response = createApiError('Internal server error', 500)
|
|
155
|
+
return addCorsHeaders(response)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
// DELETE /api/v1/teams/:teamId/members/:memberId - Remove member from team
|
|
161
|
+
export const DELETE = withApiLogging(
|
|
162
|
+
async (
|
|
163
|
+
req: NextRequest,
|
|
164
|
+
{ params }: { params: Promise<{ teamId: string; memberId: string }> }
|
|
165
|
+
): Promise<NextResponse> => {
|
|
166
|
+
try {
|
|
167
|
+
// Authenticate using dual auth
|
|
168
|
+
const authResult = await authenticateRequest(req)
|
|
169
|
+
|
|
170
|
+
if (!authResult.success) {
|
|
171
|
+
return NextResponse.json(
|
|
172
|
+
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
173
|
+
{ status: 401 }
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (authResult.rateLimitResponse) {
|
|
178
|
+
return authResult.rateLimitResponse as NextResponse
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { teamId, memberId } = await params
|
|
182
|
+
|
|
183
|
+
// Validate parameters
|
|
184
|
+
if (!teamId || teamId.trim() === '') {
|
|
185
|
+
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
186
|
+
return addCorsHeaders(response)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!memberId || memberId.trim() === '') {
|
|
190
|
+
const response = createApiError('Member ID is required', 400, null, 'MISSING_MEMBER_ID')
|
|
191
|
+
return addCorsHeaders(response)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check if user has permission to remove members using MembershipService
|
|
195
|
+
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
196
|
+
const actionResult = membership.canPerformAction('members.remove')
|
|
197
|
+
|
|
198
|
+
if (!actionResult.allowed) {
|
|
199
|
+
const response = NextResponse.json(
|
|
200
|
+
{
|
|
201
|
+
success: false,
|
|
202
|
+
error: actionResult.message,
|
|
203
|
+
reason: actionResult.reason,
|
|
204
|
+
meta: actionResult.meta,
|
|
205
|
+
},
|
|
206
|
+
{ status: 403 }
|
|
207
|
+
)
|
|
208
|
+
return addCorsHeaders(response)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Get user's role for role hierarchy validation
|
|
212
|
+
const userRole = membership.role as TeamRole
|
|
213
|
+
|
|
214
|
+
// Get target member
|
|
215
|
+
const targetMember = await queryOneWithRLS<TeamMember>(
|
|
216
|
+
'SELECT * FROM "team_members" WHERE id = $1 AND "teamId" = $2',
|
|
217
|
+
[memberId, teamId],
|
|
218
|
+
authResult.user!.id
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if (!targetMember) {
|
|
222
|
+
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
223
|
+
return addCorsHeaders(response)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Cannot remove the owner
|
|
227
|
+
if (targetMember.role === 'owner') {
|
|
228
|
+
const response = createApiError('Cannot remove the team owner', 403, null, 'CANNOT_REMOVE_OWNER')
|
|
229
|
+
return addCorsHeaders(response)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check if actor can manage the target role
|
|
233
|
+
if (!canManageRole(userRole, targetMember.role)) {
|
|
234
|
+
const response = createApiError(
|
|
235
|
+
'You do not have permission to remove this user.',
|
|
236
|
+
403,
|
|
237
|
+
null,
|
|
238
|
+
'INSUFFICIENT_PERMISSIONS'
|
|
239
|
+
)
|
|
240
|
+
return addCorsHeaders(response)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Remove member
|
|
244
|
+
const result = await mutateWithRLS(
|
|
245
|
+
'DELETE FROM "team_members" WHERE id = $1 AND "teamId" = $2 RETURNING id',
|
|
246
|
+
[memberId, teamId],
|
|
247
|
+
authResult.user!.id
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if (result.rows.length === 0) {
|
|
251
|
+
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
252
|
+
return addCorsHeaders(response)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const response = createApiResponse({ deleted: true, id: memberId })
|
|
256
|
+
return addCorsHeaders(response)
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error('Error removing member:', error)
|
|
259
|
+
const response = createApiError('Internal server error', 500)
|
|
260
|
+
return addCorsHeaders(response)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
)
|