@nextsparkjs/core 0.1.0-beta.92 → 0.1.0-beta.94
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/dashboard/block-editor/array-field.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/array-field.js +55 -3
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/dynamic-form.js +82 -2
- package/dist/components/dashboard/navigation/DynamicNavigation.d.ts.map +1 -1
- package/dist/components/dashboard/navigation/DynamicNavigation.js +7 -1
- package/dist/components/devtools/scheduled-actions/actions-table.d.ts +1 -0
- package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
- package/dist/components/devtools/scheduled-actions/actions-table.js +182 -46
- package/dist/components/devtools/scheduled-actions/types.d.ts +1 -0
- package/dist/components/devtools/scheduled-actions/types.d.ts.map +1 -1
- package/dist/components/media/MediaCard.d.ts +23 -0
- package/dist/components/media/MediaCard.d.ts.map +1 -0
- package/dist/components/media/MediaCard.js +154 -0
- package/dist/components/media/MediaDetailPanel.d.ts +17 -0
- package/dist/components/media/MediaDetailPanel.d.ts.map +1 -0
- package/dist/components/media/MediaDetailPanel.js +331 -0
- package/dist/components/media/MediaGrid.d.ts +26 -0
- package/dist/components/media/MediaGrid.d.ts.map +1 -0
- package/dist/components/media/MediaGrid.js +77 -0
- package/dist/components/media/MediaLibrary.d.ts +20 -0
- package/dist/components/media/MediaLibrary.d.ts.map +1 -0
- package/dist/components/media/MediaLibrary.js +229 -0
- package/dist/components/media/MediaList.d.ts +24 -0
- package/dist/components/media/MediaList.d.ts.map +1 -0
- package/dist/components/media/MediaList.js +181 -0
- package/dist/components/media/MediaSelector.d.ts +19 -0
- package/dist/components/media/MediaSelector.d.ts.map +1 -0
- package/dist/components/media/MediaSelector.js +145 -0
- package/dist/components/media/MediaTagFilter.d.ts +16 -0
- package/dist/components/media/MediaTagFilter.d.ts.map +1 -0
- package/dist/components/media/MediaTagFilter.js +122 -0
- package/dist/components/media/MediaToolbar.d.ts +25 -0
- package/dist/components/media/MediaToolbar.d.ts.map +1 -0
- package/dist/components/media/MediaToolbar.js +136 -0
- package/dist/components/media/MediaUploadZone.d.ts +19 -0
- package/dist/components/media/MediaUploadZone.d.ts.map +1 -0
- package/dist/components/media/MediaUploadZone.js +248 -0
- package/dist/components/media/index.d.ts +15 -0
- package/dist/components/media/index.d.ts.map +1 -0
- package/dist/components/media/index.js +20 -0
- package/dist/contexts/TeamContext.js +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts +4 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +85 -60
- package/dist/hooks/useEntityMutations.d.ts.map +1 -1
- package/dist/hooks/useEntityMutations.js +5 -9
- package/dist/hooks/useMedia.d.ts +56 -0
- package/dist/hooks/useMedia.d.ts.map +1 -0
- package/dist/hooks/useMedia.js +181 -0
- package/dist/hooks/useMediaUpload.d.ts +27 -0
- package/dist/hooks/useMediaUpload.d.ts.map +1 -0
- package/dist/hooks/useMediaUpload.js +36 -0
- package/dist/hooks/useUserSettings.d.ts +5 -4
- package/dist/hooks/useUserSettings.d.ts.map +1 -1
- package/dist/hooks/useUserSettings.js +42 -40
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/lib/api/auth/dual-auth.d.ts +6 -2
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +5 -9
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +3 -3
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +37 -0
- package/dist/lib/config/config-sync.d.ts +1 -0
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +2 -0
- package/dist/lib/config/types.d.ts +29 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/media/schemas.d.ts +39 -0
- package/dist/lib/media/schemas.d.ts.map +1 -0
- package/dist/lib/media/schemas.js +32 -0
- package/dist/lib/media/types.d.ts +69 -0
- package/dist/lib/media/types.d.ts.map +1 -0
- package/dist/lib/media/types.js +0 -0
- package/dist/lib/media/utils.d.ts +26 -0
- package/dist/lib/media/utils.d.ts.map +1 -0
- package/dist/lib/media/utils.js +33 -0
- package/dist/lib/rate-limit-redis.d.ts.map +1 -1
- package/dist/lib/rate-limit-redis.js +13 -4
- package/dist/lib/scheduled-actions/initializer.d.ts +6 -3
- package/dist/lib/scheduled-actions/initializer.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/initializer.js +11 -6
- package/dist/lib/scheduled-actions/processor.d.ts +20 -4
- package/dist/lib/scheduled-actions/processor.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/processor.js +128 -34
- package/dist/lib/scheduled-actions/registry.d.ts +3 -0
- package/dist/lib/scheduled-actions/registry.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/registry.js +2 -1
- package/dist/lib/scheduled-actions/scheduler.d.ts +1 -1
- package/dist/lib/scheduled-actions/scheduler.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/scheduler.js +76 -38
- package/dist/lib/scheduled-actions/types.d.ts +73 -0
- package/dist/lib/scheduled-actions/types.d.ts.map +1 -1
- package/dist/lib/selectors/core-selectors.d.ts +102 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/core-selectors.js +3 -1
- package/dist/lib/selectors/domains/block-editor.selectors.d.ts +8 -0
- package/dist/lib/selectors/domains/block-editor.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/block-editor.selectors.js +9 -0
- package/dist/lib/selectors/domains/devtools.selectors.d.ts +6 -0
- package/dist/lib/selectors/domains/devtools.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/devtools.selectors.js +6 -0
- package/dist/lib/selectors/domains/index.d.ts +1 -0
- package/dist/lib/selectors/domains/index.d.ts.map +1 -1
- package/dist/lib/selectors/domains/index.js +2 -0
- package/dist/lib/selectors/domains/media.selectors.d.ts +96 -0
- package/dist/lib/selectors/domains/media.selectors.d.ts.map +1 -0
- package/dist/lib/selectors/domains/media.selectors.js +103 -0
- package/dist/lib/selectors/selectors.d.ts +204 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/index.d.ts +2 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +2 -0
- package/dist/lib/services/media.service.d.ts +158 -0
- package/dist/lib/services/media.service.d.ts.map +1 -0
- package/dist/lib/services/media.service.js +410 -0
- package/dist/messages/de/devtools.json +16 -0
- package/dist/messages/de/index.d.ts +16 -0
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/en/admin.json +4 -1
- package/dist/messages/en/devtools.json +16 -0
- package/dist/messages/en/index.d.ts +167 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/en/media.json +147 -0
- package/dist/messages/en/navigation.json +1 -0
- package/dist/messages/es/admin.json +4 -1
- package/dist/messages/es/devtools.json +16 -0
- package/dist/messages/es/index.d.ts +167 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/es/media.json +147 -0
- package/dist/messages/es/navigation.json +1 -0
- package/dist/messages/fr/devtools.json +16 -0
- package/dist/messages/fr/index.d.ts +16 -0
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/it/devtools.json +16 -0
- package/dist/messages/it/index.d.ts +16 -0
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/pt/devtools.json +16 -0
- package/dist/messages/pt/index.d.ts +16 -0
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/migrations/017_scheduled_actions_table.sql +21 -0
- package/dist/migrations/021_media.sql +154 -0
- package/dist/migrations/090_sample_data.sql +53 -0
- package/dist/styles/classes.json +36 -3
- package/dist/styles/ui.css +1 -1
- package/dist/templates/app/api/devtools/config/entities/route.ts +18 -11
- package/dist/templates/app/api/devtools/config/theme/route.ts +5 -4
- package/dist/templates/app/api/devtools/tests/[...path]/route.ts +6 -5
- package/dist/templates/app/api/devtools/tests/route.ts +5 -4
- package/dist/templates/app/api/health/route.ts +6 -4
- package/dist/templates/app/api/internal/user-metadata/route.ts +3 -2
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +5 -6
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -7
- package/dist/templates/app/api/superadmin/teams/route.ts +5 -6
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +11 -16
- package/dist/templates/app/api/superadmin/users/route.ts +9 -10
- package/dist/templates/app/api/user/delete-account/route.ts +3 -2
- package/dist/templates/app/api/user/plan-flags/route.ts +11 -24
- package/dist/templates/app/api/user/profile/route.ts +7 -6
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +16 -18
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +17 -19
- package/dist/templates/app/api/v1/[entity]/[id]/route.ts +10 -12
- package/dist/templates/app/api/v1/[entity]/route.ts +9 -11
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +9 -8
- package/dist/templates/app/api/v1/api-keys/route.ts +7 -6
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +3 -2
- package/dist/templates/app/api/v1/billing/cancel/route.ts +15 -14
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +10 -9
- package/dist/templates/app/api/v1/billing/check-action/route.ts +8 -7
- package/dist/templates/app/api/v1/billing/checkout/route.ts +10 -9
- package/dist/templates/app/api/v1/billing/plans/route.ts +5 -4
- package/dist/templates/app/api/v1/billing/portal/route.ts +9 -8
- package/dist/templates/app/api/v1/blocks/[slug]/route.ts +4 -3
- package/dist/templates/app/api/v1/blocks/route.ts +3 -2
- package/dist/templates/app/api/v1/blocks/validate/route.ts +5 -3
- package/dist/templates/app/api/v1/cron/process/route.ts +4 -6
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/docs/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/features/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/flows/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +125 -3
- package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +110 -0
- package/dist/templates/app/api/v1/devtools/testing/route.ts +3 -2
- package/dist/templates/app/api/v1/media/[id]/route.ts +144 -0
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +154 -0
- package/dist/templates/app/api/v1/media/check-duplicates/route.ts +56 -0
- package/dist/templates/app/api/v1/media/route.ts +56 -0
- package/dist/templates/app/api/v1/media/upload/route.ts +157 -33
- package/dist/templates/app/api/v1/media-tags/route.ts +65 -0
- package/dist/templates/app/api/v1/plugin/[...path]/route.ts +16 -15
- package/dist/templates/app/api/v1/plugin/route.ts +3 -2
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +10 -9
- package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +3 -3
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +3 -3
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +3 -2
- package/dist/templates/app/api/v1/team-invitations/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +5 -4
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +5 -4
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +5 -5
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +31 -58
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +5 -4
- package/dist/templates/app/api/v1/teams/route.ts +18 -17
- package/dist/templates/app/api/v1/teams/switch/route.ts +3 -2
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +16 -15
- package/dist/templates/app/api/v1/theme/route.ts +3 -2
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -6
- package/dist/templates/app/api/v1/users/[id]/route.ts +9 -8
- package/dist/templates/app/api/v1/users/route.ts +7 -6
- package/dist/templates/app/dashboard/(main)/media/page.tsx +607 -0
- package/dist/templates/contents/themes/starter/messages/de/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/de/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/en/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/en/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/es/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/es/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/fr/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/fr/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/it/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/it/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/pt/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/pt/index.ts +2 -0
- package/dist/templates/contents/themes/starter/styles/globals.css +14 -0
- package/dist/templates/instrumentation.ts +33 -0
- package/dist/types/blocks.d.ts +1 -1
- package/dist/types/blocks.d.ts.map +1 -1
- package/migrations/017_scheduled_actions_table.sql +21 -0
- package/migrations/021_media.sql +154 -0
- package/migrations/090_sample_data.sql +53 -0
- package/package.json +3 -2
- package/scripts/build/registry/config.mjs +41 -0
- package/scripts/build/registry/discovery/templates.mjs +0 -1
- package/scripts/build/registry/generators/entity-registry.mjs +16 -6
- package/scripts/build/registry/generators/route-handlers.mjs +8 -2
- package/scripts/build/registry/generators/template-registry.mjs +16 -4
- package/scripts/build/registry/post-build/route-cleanup.mjs +0 -1
- package/scripts/build/registry/validate-env.test.mjs +92 -0
- package/scripts/build/registry.mjs +18 -1
- package/scripts/deploy/vercel-deploy.mjs +1 -1
- package/templates/app/api/devtools/config/entities/route.ts +18 -11
- package/templates/app/api/devtools/config/theme/route.ts +5 -4
- package/templates/app/api/devtools/tests/[...path]/route.ts +6 -5
- package/templates/app/api/devtools/tests/route.ts +5 -4
- package/templates/app/api/health/route.ts +6 -4
- package/templates/app/api/internal/user-metadata/route.ts +3 -2
- package/templates/app/api/superadmin/subscriptions/route.ts +5 -6
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -7
- package/templates/app/api/superadmin/teams/route.ts +5 -6
- package/templates/app/api/superadmin/users/[userId]/route.ts +11 -16
- package/templates/app/api/superadmin/users/route.ts +9 -10
- package/templates/app/api/user/delete-account/route.ts +3 -2
- package/templates/app/api/user/plan-flags/route.ts +11 -24
- package/templates/app/api/user/profile/route.ts +7 -6
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +16 -18
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +17 -19
- package/templates/app/api/v1/[entity]/[id]/route.ts +10 -12
- package/templates/app/api/v1/[entity]/route.ts +9 -11
- package/templates/app/api/v1/api-keys/[id]/route.ts +9 -8
- package/templates/app/api/v1/api-keys/route.ts +7 -6
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +3 -2
- package/templates/app/api/v1/billing/cancel/route.ts +15 -14
- package/templates/app/api/v1/billing/change-plan/route.ts +10 -9
- package/templates/app/api/v1/billing/check-action/route.ts +8 -7
- package/templates/app/api/v1/billing/checkout/route.ts +10 -9
- package/templates/app/api/v1/billing/plans/route.ts +5 -4
- package/templates/app/api/v1/billing/portal/route.ts +9 -8
- package/templates/app/api/v1/blocks/[slug]/route.ts +4 -3
- package/templates/app/api/v1/blocks/route.ts +3 -2
- package/templates/app/api/v1/blocks/validate/route.ts +5 -3
- package/templates/app/api/v1/cron/process/route.ts +4 -6
- package/templates/app/api/v1/devtools/blocks/route.ts +3 -2
- package/templates/app/api/v1/devtools/docs/route.ts +3 -2
- package/templates/app/api/v1/devtools/features/route.ts +3 -2
- package/templates/app/api/v1/devtools/flows/route.ts +3 -2
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +125 -3
- package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +110 -0
- package/templates/app/api/v1/devtools/testing/route.ts +3 -2
- package/templates/app/api/v1/media/[id]/route.ts +144 -0
- package/templates/app/api/v1/media/[id]/tags/route.ts +154 -0
- package/templates/app/api/v1/media/check-duplicates/route.ts +56 -0
- package/templates/app/api/v1/media/route.ts +56 -0
- package/templates/app/api/v1/media/upload/route.ts +157 -33
- package/templates/app/api/v1/media-tags/route.ts +65 -0
- package/templates/app/api/v1/plugin/[...path]/route.ts +16 -15
- package/templates/app/api/v1/plugin/route.ts +3 -2
- package/templates/app/api/v1/post-categories/[id]/route.ts +10 -9
- package/templates/app/api/v1/post-categories/route.ts +5 -4
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +3 -3
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +3 -3
- package/templates/app/api/v1/team-invitations/[token]/route.ts +3 -2
- package/templates/app/api/v1/team-invitations/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +5 -4
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +5 -4
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +5 -5
- package/templates/app/api/v1/teams/[teamId]/route.ts +31 -58
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +5 -4
- package/templates/app/api/v1/teams/route.ts +18 -17
- package/templates/app/api/v1/teams/switch/route.ts +3 -2
- package/templates/app/api/v1/theme/[...path]/route.ts +16 -15
- package/templates/app/api/v1/theme/route.ts +3 -2
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -6
- package/templates/app/api/v1/users/[id]/route.ts +9 -8
- package/templates/app/api/v1/users/route.ts +7 -6
- package/templates/app/dashboard/(main)/media/page.tsx +607 -0
- package/templates/contents/themes/starter/messages/de/dev.json +106 -0
- package/templates/contents/themes/starter/messages/de/index.ts +2 -0
- package/templates/contents/themes/starter/messages/en/dev.json +106 -0
- package/templates/contents/themes/starter/messages/en/index.ts +2 -0
- package/templates/contents/themes/starter/messages/es/dev.json +106 -0
- package/templates/contents/themes/starter/messages/es/index.ts +2 -0
- package/templates/contents/themes/starter/messages/fr/dev.json +106 -0
- package/templates/contents/themes/starter/messages/fr/index.ts +2 -0
- package/templates/contents/themes/starter/messages/it/dev.json +106 -0
- package/templates/contents/themes/starter/messages/it/index.ts +2 -0
- package/templates/contents/themes/starter/messages/pt/dev.json +106 -0
- package/templates/contents/themes/starter/messages/pt/index.ts +2 -0
- package/templates/contents/themes/starter/styles/globals.css +14 -0
- package/templates/instrumentation.ts +33 -0
- package/dist/presets/plugin/.env.example.template +0 -19
- package/dist/presets/plugin/entities/.gitkeep +0 -18
- package/dist/presets/theme/blocks/.gitkeep +0 -17
- package/dist/presets/theme/public/brand/.gitkeep +0 -8
- package/dist/presets/theme/tests/cypress/.gitkeep +0 -10
- package/dist/templates/contents/plugins/starter/plugin/.env.example.template +0 -19
- package/templates/contents/plugins/starter/plugin/.env.example.template +0 -19
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools Scheduled Actions Run API
|
|
3
|
+
*
|
|
4
|
+
* POST /api/v1/devtools/scheduled-actions/run
|
|
5
|
+
*
|
|
6
|
+
* Executes a pending action immediately.
|
|
7
|
+
* Requires superadmin or developer user role.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
11
|
+
import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
12
|
+
import {
|
|
13
|
+
canAccessDevtoolsApi,
|
|
14
|
+
createDevtoolsAccessDeniedResponse,
|
|
15
|
+
createDevtoolsUnauthorizedResponse,
|
|
16
|
+
} from '@nextsparkjs/core/lib/api/auth/devtools-auth'
|
|
17
|
+
import { queryWithRLS } from '@nextsparkjs/core/lib/db'
|
|
18
|
+
import type { ScheduledAction } from '@nextsparkjs/core/lib/scheduled-actions/types'
|
|
19
|
+
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
20
|
+
import { executeAction } from '@nextsparkjs/core/lib/scheduled-actions/processor'
|
|
21
|
+
|
|
22
|
+
export const POST = withRateLimitTier(async (request: NextRequest) => {
|
|
23
|
+
// Authenticate request
|
|
24
|
+
const authResult = await authenticateRequest(request)
|
|
25
|
+
|
|
26
|
+
if (!authResult.success) {
|
|
27
|
+
return createDevtoolsUnauthorizedResponse()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check DevTools access permission
|
|
31
|
+
if (!canAccessDevtoolsApi(authResult)) {
|
|
32
|
+
return createDevtoolsAccessDeniedResponse()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Parse request body
|
|
36
|
+
const body = await request.json()
|
|
37
|
+
const { actionId } = body
|
|
38
|
+
|
|
39
|
+
if (!actionId) {
|
|
40
|
+
return NextResponse.json({
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'actionId is required'
|
|
43
|
+
}, { status: 400 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fetch the pending action
|
|
47
|
+
const actions = await queryWithRLS<ScheduledAction>(
|
|
48
|
+
`SELECT
|
|
49
|
+
id,
|
|
50
|
+
"actionType",
|
|
51
|
+
status,
|
|
52
|
+
payload,
|
|
53
|
+
"teamId",
|
|
54
|
+
"scheduledAt",
|
|
55
|
+
attempts,
|
|
56
|
+
"maxRetries",
|
|
57
|
+
"recurringInterval",
|
|
58
|
+
"recurrenceType",
|
|
59
|
+
"lockGroup"
|
|
60
|
+
FROM "scheduled_actions"
|
|
61
|
+
WHERE id = $1`,
|
|
62
|
+
[actionId],
|
|
63
|
+
null
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if (actions.length === 0) {
|
|
67
|
+
return NextResponse.json({
|
|
68
|
+
success: false,
|
|
69
|
+
error: 'Action not found'
|
|
70
|
+
}, { status: 404 })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const action = actions[0]
|
|
74
|
+
|
|
75
|
+
// Only allow running pending actions
|
|
76
|
+
if (action.status !== 'pending') {
|
|
77
|
+
return NextResponse.json({
|
|
78
|
+
success: false,
|
|
79
|
+
error: 'Only pending actions can be executed'
|
|
80
|
+
}, { status: 400 })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Execute the action immediately
|
|
85
|
+
await executeAction(action)
|
|
86
|
+
|
|
87
|
+
return NextResponse.json({
|
|
88
|
+
success: true,
|
|
89
|
+
data: {
|
|
90
|
+
executed: true
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('[DevTools] Failed to execute action:', error)
|
|
95
|
+
return NextResponse.json({
|
|
96
|
+
success: false,
|
|
97
|
+
error: error instanceof Error ? error.message : 'Failed to execute action'
|
|
98
|
+
}, { status: 500 })
|
|
99
|
+
}
|
|
100
|
+
}, 'write');
|
|
101
|
+
|
|
102
|
+
export async function OPTIONS() {
|
|
103
|
+
return new NextResponse(null, {
|
|
104
|
+
status: 204,
|
|
105
|
+
headers: {
|
|
106
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
107
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key',
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
}
|
|
@@ -20,8 +20,9 @@ import {
|
|
|
20
20
|
FEATURE_REGISTRY,
|
|
21
21
|
FLOW_REGISTRY,
|
|
22
22
|
} from '@nextsparkjs/registries/testing-registry'
|
|
23
|
+
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
23
24
|
|
|
24
|
-
export
|
|
25
|
+
export const GET = withRateLimitTier(async (request: NextRequest) => {
|
|
25
26
|
// Authenticate request
|
|
26
27
|
const authResult = await authenticateRequest(request)
|
|
27
28
|
|
|
@@ -69,7 +70,7 @@ export async function GET(request: NextRequest) {
|
|
|
69
70
|
},
|
|
70
71
|
},
|
|
71
72
|
})
|
|
72
|
-
}
|
|
73
|
+
}, 'read');
|
|
73
74
|
|
|
74
75
|
export async function OPTIONS() {
|
|
75
76
|
return new NextResponse(null, {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
3
|
+
import { createApiResponse, createApiError } from '@nextsparkjs/core/lib/api/helpers'
|
|
4
|
+
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
5
|
+
import { MediaService } from '@nextsparkjs/core/lib/services/media.service'
|
|
6
|
+
import { updateMediaSchema } from '@nextsparkjs/core/lib/media/schemas'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/v1/media/:id
|
|
10
|
+
*
|
|
11
|
+
* Get a single media item by ID.
|
|
12
|
+
*
|
|
13
|
+
* Authentication: Requires valid session or API key with media:read scope
|
|
14
|
+
* RLS: Returns only media from teams the user is a member of
|
|
15
|
+
*/
|
|
16
|
+
export const GET = withRateLimitTier(async (
|
|
17
|
+
request: NextRequest,
|
|
18
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
19
|
+
) => {
|
|
20
|
+
try {
|
|
21
|
+
// 1. Authenticate
|
|
22
|
+
const authResult = await authenticateRequest(request)
|
|
23
|
+
if (!authResult.success) {
|
|
24
|
+
return createApiError('Unauthorized', 401)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Check permissions
|
|
28
|
+
if (!hasRequiredScope(authResult, 'media:read')) {
|
|
29
|
+
return createApiError('Insufficient permissions', 403)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 3. Get media ID from params
|
|
33
|
+
const { id } = await params
|
|
34
|
+
|
|
35
|
+
// 4. Fetch media with RLS
|
|
36
|
+
const media = await MediaService.getById(id, authResult.user!.id)
|
|
37
|
+
|
|
38
|
+
if (!media) {
|
|
39
|
+
return createApiError('Media not found', 404)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return createApiResponse(media)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('[Media API] Error fetching media:', error)
|
|
45
|
+
return createApiError('Failed to fetch media', 500)
|
|
46
|
+
}
|
|
47
|
+
}, 'read')
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* PATCH /api/v1/media/:id
|
|
51
|
+
*
|
|
52
|
+
* Update media metadata (alt text and caption).
|
|
53
|
+
* File properties (url, filename, size, dimensions) are immutable.
|
|
54
|
+
*
|
|
55
|
+
* Request Body:
|
|
56
|
+
* - alt: Alt text for accessibility (max 500 characters, optional)
|
|
57
|
+
* - caption: Caption or description (max 1000 characters, optional)
|
|
58
|
+
*
|
|
59
|
+
* Authentication: Requires valid session or API key with media:write scope
|
|
60
|
+
* RLS: Can only update media from teams the user is a member of
|
|
61
|
+
*/
|
|
62
|
+
export const PATCH = withRateLimitTier(async (
|
|
63
|
+
request: NextRequest,
|
|
64
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
65
|
+
) => {
|
|
66
|
+
try {
|
|
67
|
+
// 1. Authenticate
|
|
68
|
+
const authResult = await authenticateRequest(request)
|
|
69
|
+
if (!authResult.success) {
|
|
70
|
+
return createApiError('Unauthorized', 401)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Check permissions
|
|
74
|
+
if (!hasRequiredScope(authResult, 'media:write')) {
|
|
75
|
+
return createApiError('Insufficient permissions', 403)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Get media ID from params
|
|
79
|
+
const { id } = await params
|
|
80
|
+
|
|
81
|
+
// 4. Parse and validate request body
|
|
82
|
+
const body = await request.json()
|
|
83
|
+
const parsed = updateMediaSchema.safeParse(body)
|
|
84
|
+
|
|
85
|
+
if (!parsed.success) {
|
|
86
|
+
return createApiError('Validation failed', 400, {
|
|
87
|
+
errors: parsed.error.issues,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 5. Update media with RLS
|
|
92
|
+
const media = await MediaService.update(id, authResult.user!.id, parsed.data)
|
|
93
|
+
|
|
94
|
+
return createApiResponse(media)
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('[Media API] Error updating media:', error)
|
|
97
|
+
return createApiError(
|
|
98
|
+
error instanceof Error ? error.message : 'Failed to update media',
|
|
99
|
+
500
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}, 'write')
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* DELETE /api/v1/media/:id
|
|
106
|
+
*
|
|
107
|
+
* Soft delete a media item (sets status to 'deleted').
|
|
108
|
+
* The file remains in storage but is hidden from queries.
|
|
109
|
+
*
|
|
110
|
+
* Authentication: Requires valid session or API key with media:delete scope
|
|
111
|
+
* RLS: Can only delete media from teams the user is a member of
|
|
112
|
+
*/
|
|
113
|
+
export const DELETE = withRateLimitTier(async (
|
|
114
|
+
request: NextRequest,
|
|
115
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
116
|
+
) => {
|
|
117
|
+
try {
|
|
118
|
+
// 1. Authenticate
|
|
119
|
+
const authResult = await authenticateRequest(request)
|
|
120
|
+
if (!authResult.success) {
|
|
121
|
+
return createApiError('Unauthorized', 401)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 2. Check permissions
|
|
125
|
+
if (!hasRequiredScope(authResult, 'media:delete')) {
|
|
126
|
+
return createApiError('Insufficient permissions', 403)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 3. Get media ID from params
|
|
130
|
+
const { id } = await params
|
|
131
|
+
|
|
132
|
+
// 4. Soft delete media with RLS
|
|
133
|
+
const deleted = await MediaService.softDelete(id, authResult.user!.id)
|
|
134
|
+
|
|
135
|
+
if (!deleted) {
|
|
136
|
+
return createApiError('Media not found', 404)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return createApiResponse({ message: 'Media deleted successfully' })
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('[Media API] Error deleting media:', error)
|
|
142
|
+
return createApiError('Failed to delete media', 500)
|
|
143
|
+
}
|
|
144
|
+
}, 'write')
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
3
|
+
import { createApiResponse, createApiError } from '@nextsparkjs/core/lib/api/helpers'
|
|
4
|
+
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
5
|
+
import { MediaService } from '@nextsparkjs/core/lib/services/media.service'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
const addTagSchema = z.object({
|
|
9
|
+
tagId: z.string().min(1),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const setTagsSchema = z.object({
|
|
13
|
+
tagIds: z.array(z.string().min(1)),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GET /api/v1/media/{id}/tags
|
|
18
|
+
*
|
|
19
|
+
* Get all tags assigned to a media item.
|
|
20
|
+
*/
|
|
21
|
+
export const GET = withRateLimitTier(async (
|
|
22
|
+
request: NextRequest,
|
|
23
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
24
|
+
) => {
|
|
25
|
+
try {
|
|
26
|
+
const authResult = await authenticateRequest(request)
|
|
27
|
+
if (!authResult.success) {
|
|
28
|
+
return createApiError('Unauthorized', 401)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!hasRequiredScope(authResult, 'media:read')) {
|
|
32
|
+
return createApiError('Insufficient permissions', 403)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { id } = await params
|
|
36
|
+
const tags = await MediaService.getMediaTags(id, authResult.user!.id)
|
|
37
|
+
return createApiResponse(tags)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('[Media Tags API] Error getting tags:', error)
|
|
40
|
+
return createApiError('Failed to get media tags', 500)
|
|
41
|
+
}
|
|
42
|
+
}, 'read')
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* POST /api/v1/media/{id}/tags
|
|
46
|
+
*
|
|
47
|
+
* Add a tag to a media item.
|
|
48
|
+
* Body: { tagId: string }
|
|
49
|
+
*/
|
|
50
|
+
export const POST = withRateLimitTier(async (
|
|
51
|
+
request: NextRequest,
|
|
52
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
53
|
+
) => {
|
|
54
|
+
try {
|
|
55
|
+
const authResult = await authenticateRequest(request)
|
|
56
|
+
if (!authResult.success) {
|
|
57
|
+
return createApiError('Unauthorized', 401)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!hasRequiredScope(authResult, 'media:write')) {
|
|
61
|
+
return createApiError('Insufficient permissions', 403)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { id } = await params
|
|
65
|
+
const body = await request.json()
|
|
66
|
+
const parsed = addTagSchema.safeParse(body)
|
|
67
|
+
|
|
68
|
+
if (!parsed.success) {
|
|
69
|
+
return createApiError('Invalid request body', 400, { errors: parsed.error.issues })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await MediaService.addTag(id, parsed.data.tagId, authResult.user!.id)
|
|
73
|
+
const tags = await MediaService.getMediaTags(id, authResult.user!.id)
|
|
74
|
+
|
|
75
|
+
return createApiResponse(tags, undefined, 201)
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('[Media Tags API] Error adding tag:', error)
|
|
78
|
+
return createApiError('Failed to add tag', 500)
|
|
79
|
+
}
|
|
80
|
+
}, 'write')
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* PUT /api/v1/media/{id}/tags
|
|
84
|
+
*
|
|
85
|
+
* Replace all tags for a media item.
|
|
86
|
+
* Body: { tagIds: string[] }
|
|
87
|
+
*/
|
|
88
|
+
export const PUT = withRateLimitTier(async (
|
|
89
|
+
request: NextRequest,
|
|
90
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
91
|
+
) => {
|
|
92
|
+
try {
|
|
93
|
+
const authResult = await authenticateRequest(request)
|
|
94
|
+
if (!authResult.success) {
|
|
95
|
+
return createApiError('Unauthorized', 401)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!hasRequiredScope(authResult, 'media:write')) {
|
|
99
|
+
return createApiError('Insufficient permissions', 403)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { id } = await params
|
|
103
|
+
const body = await request.json()
|
|
104
|
+
const parsed = setTagsSchema.safeParse(body)
|
|
105
|
+
|
|
106
|
+
if (!parsed.success) {
|
|
107
|
+
return createApiError('Invalid request body', 400, { errors: parsed.error.issues })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await MediaService.setTags(id, parsed.data.tagIds, authResult.user!.id)
|
|
111
|
+
const tags = await MediaService.getMediaTags(id, authResult.user!.id)
|
|
112
|
+
|
|
113
|
+
return createApiResponse(tags)
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('[Media Tags API] Error setting tags:', error)
|
|
116
|
+
return createApiError('Failed to set tags', 500)
|
|
117
|
+
}
|
|
118
|
+
}, 'write')
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* DELETE /api/v1/media/{id}/tags
|
|
122
|
+
*
|
|
123
|
+
* Remove a tag from a media item.
|
|
124
|
+
* Query parameter: tagId
|
|
125
|
+
*/
|
|
126
|
+
export const DELETE = withRateLimitTier(async (
|
|
127
|
+
request: NextRequest,
|
|
128
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
129
|
+
) => {
|
|
130
|
+
try {
|
|
131
|
+
const authResult = await authenticateRequest(request)
|
|
132
|
+
if (!authResult.success) {
|
|
133
|
+
return createApiError('Unauthorized', 401)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!hasRequiredScope(authResult, 'media:delete')) {
|
|
137
|
+
return createApiError('Insufficient permissions', 403)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { id } = await params
|
|
141
|
+
const { searchParams } = new URL(request.url)
|
|
142
|
+
const tagId = searchParams.get('tagId')
|
|
143
|
+
|
|
144
|
+
if (!tagId) {
|
|
145
|
+
return createApiError('tagId query parameter is required', 400)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await MediaService.removeTag(id, tagId, authResult.user!.id)
|
|
149
|
+
return createApiResponse({ success: true })
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('[Media Tags API] Error removing tag:', error)
|
|
152
|
+
return createApiError('Failed to remove tag', 500)
|
|
153
|
+
}
|
|
154
|
+
}, 'write')
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
3
|
+
import { createApiResponse, createApiError } from '@nextsparkjs/core/lib/api/helpers'
|
|
4
|
+
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
5
|
+
import { MediaService } from '@nextsparkjs/core/lib/services/media.service'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* POST /api/v1/media/check-duplicates
|
|
9
|
+
*
|
|
10
|
+
* Check if files with the same name+size already exist in the media library.
|
|
11
|
+
* Used by the upload zone to warn users before uploading duplicates.
|
|
12
|
+
*
|
|
13
|
+
* Body: { files: [{ filename: string, fileSize: number }] }
|
|
14
|
+
* Returns: { duplicates: [{ filename, fileSize, existing: Media[] }] }
|
|
15
|
+
*/
|
|
16
|
+
export const POST = withRateLimitTier(async (request: NextRequest) => {
|
|
17
|
+
try {
|
|
18
|
+
const authResult = await authenticateRequest(request)
|
|
19
|
+
if (!authResult.success) {
|
|
20
|
+
return createApiError('Unauthorized', 401)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!hasRequiredScope(authResult, 'media:read')) {
|
|
24
|
+
return createApiError('Insufficient permissions - media:read scope required', 403)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const body = await request.json()
|
|
28
|
+
const files = body.files as { filename: string; fileSize: number }[]
|
|
29
|
+
|
|
30
|
+
if (!files || !Array.isArray(files) || files.length === 0) {
|
|
31
|
+
return createApiError('files array is required', 400)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const duplicates: { filename: string; fileSize: number; existing: { id: string; url: string; createdAt: string }[] }[] = []
|
|
35
|
+
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const existing = await MediaService.findDuplicates(
|
|
38
|
+
authResult.user!.id,
|
|
39
|
+
file.filename,
|
|
40
|
+
file.fileSize
|
|
41
|
+
)
|
|
42
|
+
if (existing.length > 0) {
|
|
43
|
+
duplicates.push({
|
|
44
|
+
filename: file.filename,
|
|
45
|
+
fileSize: file.fileSize,
|
|
46
|
+
existing: existing.map(m => ({ id: m.id, url: m.url, createdAt: m.createdAt })),
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return createApiResponse({ duplicates })
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error checking duplicates:', error)
|
|
54
|
+
return createApiError('Failed to check duplicates', 500)
|
|
55
|
+
}
|
|
56
|
+
}, 'read')
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
3
|
+
import { createApiResponse, createApiError } from '@nextsparkjs/core/lib/api/helpers'
|
|
4
|
+
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
5
|
+
import { MediaService } from '@nextsparkjs/core/lib/services/media.service'
|
|
6
|
+
import { mediaListQuerySchema } from '@nextsparkjs/core/lib/media/schemas'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GET /api/v1/media
|
|
10
|
+
*
|
|
11
|
+
* List media files with pagination, filtering, and search.
|
|
12
|
+
* Supports filtering by type (image/video), searching by filename, and sorting.
|
|
13
|
+
*
|
|
14
|
+
* Query Parameters:
|
|
15
|
+
* - limit: Number of items per page (default: 20, max: 100)
|
|
16
|
+
* - offset: Number of items to skip (default: 0)
|
|
17
|
+
* - orderBy: Sort field (createdAt|filename|fileSize, default: createdAt)
|
|
18
|
+
* - orderDir: Sort direction (asc|desc, default: desc)
|
|
19
|
+
* - type: Filter by type (image|video|all, default: all)
|
|
20
|
+
* - search: Search by filename (case-insensitive)
|
|
21
|
+
*
|
|
22
|
+
* Authentication: Requires valid session or API key with media:read scope
|
|
23
|
+
* RLS: Returns only media from teams the user is a member of
|
|
24
|
+
*/
|
|
25
|
+
export const GET = withRateLimitTier(async (request: NextRequest) => {
|
|
26
|
+
try {
|
|
27
|
+
// 1. Authenticate
|
|
28
|
+
const authResult = await authenticateRequest(request)
|
|
29
|
+
if (!authResult.success) {
|
|
30
|
+
return createApiError('Unauthorized', 401)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 2. Check permissions
|
|
34
|
+
if (!hasRequiredScope(authResult, 'media:read')) {
|
|
35
|
+
return createApiError('Insufficient permissions - media:read scope required', 403)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Parse and validate query parameters
|
|
39
|
+
const { searchParams } = new URL(request.url)
|
|
40
|
+
const parsed = mediaListQuerySchema.safeParse(Object.fromEntries(searchParams))
|
|
41
|
+
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
return createApiError('Invalid query parameters', 400, {
|
|
44
|
+
errors: parsed.error.issues,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 4. Query media list with RLS
|
|
49
|
+
const result = await MediaService.list(authResult.user!.id, parsed.data)
|
|
50
|
+
|
|
51
|
+
return createApiResponse(result)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('[Media API] Error listing media:', error)
|
|
54
|
+
return createApiError('Failed to list media', 500)
|
|
55
|
+
}
|
|
56
|
+
}, 'read')
|