create-nextblock 0.14.1 → 0.14.2
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/docker-template/.dockerignore +23 -23
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/libs/db/tsconfig.lib.json +3 -3
- package/libs/editor/tsconfig.lib.json +3 -3
- package/libs/ui/tsconfig.lib.json +3 -3
- package/libs/utils/tsconfig.json +3 -3
- package/package.json +1 -1
- package/project.json +19 -19
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.dockerignore +23 -23
- package/templates/nextblock-template/README.md +34 -34
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
- package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/postActions.ts +146 -146
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5749 -5749
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
- package/templates/nextblock-template/app/api/search/route.ts +43 -43
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
- package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
- package/templates/nextblock-template/app/cart/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +4 -2
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
- package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/layout.tsx +73 -73
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
- package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
- package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
- package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +688 -688
- package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
- package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
- package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +23 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
- package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/components/SecurityPanel.tsx +9 -7
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
- package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
- package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
- package/templates/nextblock-template/app/profile/account-links.ts +22 -22
- package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
- package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
- package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
- package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
- package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/sitemap.ts +130 -130
- package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
- package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
- package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
- package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
- package/templates/nextblock-template/components/blocks/types.ts +7 -7
- package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
- package/templates/nextblock-template/components/form-message.tsx +32 -32
- package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
- package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
- package/templates/nextblock-template/components/submit-button.tsx +23 -23
- package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/AuthContext.tsx +23 -23
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
- package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
- package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
- package/templates/nextblock-template/docs/README.md +39 -39
- package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
- package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
- package/templates/nextblock-template/index.d.ts +7 -7
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
- package/templates/nextblock-template/lib/media/youtube.ts +99 -99
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
- package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/search/types.ts +27 -27
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +87 -87
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/site-url.ts +48 -48
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/postcss.config.js +6 -6
- package/templates/nextblock-template/scripts/backup.js +115 -115
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/restore.js +385 -385
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tailwind.config.js +25 -25
- package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
- package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
- package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
- package/tsconfig.base.json +3 -3
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
import 'server-only';
|
|
2
|
-
|
|
3
|
-
import { getServiceRoleSupabaseClient } from '@nextblock-cms/db/server';
|
|
4
|
-
|
|
5
|
-
import { getStorageBucket } from './provider';
|
|
6
|
-
|
|
7
|
-
// Native Supabase Storage operations, authenticated with the service-role/secret key.
|
|
8
|
-
// Used only when getStorageBackend() === 'supabase' (the zero-S3-key Vercel path). These
|
|
9
|
-
// mirror what the S3 routes do via @aws-sdk/client-s3, so the upload → process → display
|
|
10
|
-
// → delete pipeline works identically without any S3 access keys.
|
|
11
|
-
|
|
12
|
-
// The public bucket is created once per process. Cache the in-flight promise (not a bare
|
|
13
|
-
// boolean) so concurrent uploads share a SINGLE createBucket call instead of racing — and
|
|
14
|
-
// clear it on failure so a later request can retry rather than inheriting a cached rejection.
|
|
15
|
-
let bucketEnsurePromise: Promise<void> | null = null;
|
|
16
|
-
|
|
17
|
-
/** Idempotently ensure a PUBLIC bucket exists for media. Safe to call concurrently. */
|
|
18
|
-
export function ensureStorageBucket(): Promise<void> {
|
|
19
|
-
if (bucketEnsurePromise) return bucketEnsurePromise;
|
|
20
|
-
|
|
21
|
-
bucketEnsurePromise = (async () => {
|
|
22
|
-
const bucket = getStorageBucket();
|
|
23
|
-
if (!bucket) throw new Error('No storage bucket name is configured.');
|
|
24
|
-
|
|
25
|
-
const admin = getServiceRoleSupabaseClient();
|
|
26
|
-
// public: true is REQUIRED — media is served from /storage/v1/object/public/<bucket>,
|
|
27
|
-
// which returns 401 for a private bucket. Do not drop this flag.
|
|
28
|
-
const { error } = await admin.storage.createBucket(bucket, { public: true });
|
|
29
|
-
if (error && !/already exists|duplicate|resource already exists/i.test(error.message)) {
|
|
30
|
-
// Non-"exists" error — confirm via getBucket before treating it as fatal.
|
|
31
|
-
const { data } = await admin.storage.getBucket(bucket);
|
|
32
|
-
if (!data) {
|
|
33
|
-
throw new Error(`Could not ensure storage bucket "${bucket}": ${error.message}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
})();
|
|
37
|
-
|
|
38
|
-
// On failure, drop the cached promise so the next caller retries (don't cache a rejection).
|
|
39
|
-
bucketEnsurePromise.catch(() => {
|
|
40
|
-
bucketEnsurePromise = null;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return bucketEnsurePromise;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Upload (or overwrite) an object. */
|
|
47
|
-
export async function supabaseUploadObject(
|
|
48
|
-
key: string,
|
|
49
|
-
body: Buffer,
|
|
50
|
-
contentType: string,
|
|
51
|
-
): Promise<void> {
|
|
52
|
-
await ensureStorageBucket();
|
|
53
|
-
const admin = getServiceRoleSupabaseClient();
|
|
54
|
-
const { error } = await admin.storage
|
|
55
|
-
.from(getStorageBucket())
|
|
56
|
-
.upload(key, body, { contentType, upsert: true });
|
|
57
|
-
if (error) {
|
|
58
|
-
throw new Error(`Supabase Storage upload failed for "${key}": ${error.message}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Download an object as a Buffer (used by the image processor to fetch the original). */
|
|
63
|
-
export async function supabaseDownloadObject(key: string): Promise<Buffer> {
|
|
64
|
-
const admin = getServiceRoleSupabaseClient();
|
|
65
|
-
const { data, error } = await admin.storage.from(getStorageBucket()).download(key);
|
|
66
|
-
if (error || !data) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`Supabase Storage download failed for "${key}": ${error?.message ?? 'no data returned'}`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
return Buffer.from(await data.arrayBuffer());
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Create a signed upload URL for a NEW object key. The browser PUTs the file bytes to the
|
|
76
|
-
* returned URL directly (the same shape as an S3 presigned PUT), so large uploads never
|
|
77
|
-
* pass through the serverless function. The token is embedded in the URL.
|
|
78
|
-
*/
|
|
79
|
-
export async function supabaseCreateSignedUpload(
|
|
80
|
-
key: string,
|
|
81
|
-
): Promise<{ signedUrl: string; token: string }> {
|
|
82
|
-
await ensureStorageBucket();
|
|
83
|
-
const admin = getServiceRoleSupabaseClient();
|
|
84
|
-
const { data, error } = await admin.storage
|
|
85
|
-
.from(getStorageBucket())
|
|
86
|
-
.createSignedUploadUrl(key);
|
|
87
|
-
if (error || !data?.signedUrl) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
`Supabase Storage signed upload URL failed for "${key}": ${error?.message ?? 'no URL returned'}`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
return { signedUrl: data.signedUrl, token: data.token };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Remove one or more objects. No-op for an empty list. */
|
|
96
|
-
export async function supabaseRemoveObjects(keys: string[]): Promise<void> {
|
|
97
|
-
if (keys.length === 0) return;
|
|
98
|
-
const admin = getServiceRoleSupabaseClient();
|
|
99
|
-
const { error } = await admin.storage.from(getStorageBucket()).remove(keys);
|
|
100
|
-
if (error) {
|
|
101
|
-
throw new Error(`Supabase Storage remove failed: ${error.message}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
1
|
+
import 'server-only';
|
|
2
|
+
|
|
3
|
+
import { getServiceRoleSupabaseClient } from '@nextblock-cms/db/server';
|
|
4
|
+
|
|
5
|
+
import { getStorageBucket } from './provider';
|
|
6
|
+
|
|
7
|
+
// Native Supabase Storage operations, authenticated with the service-role/secret key.
|
|
8
|
+
// Used only when getStorageBackend() === 'supabase' (the zero-S3-key Vercel path). These
|
|
9
|
+
// mirror what the S3 routes do via @aws-sdk/client-s3, so the upload → process → display
|
|
10
|
+
// → delete pipeline works identically without any S3 access keys.
|
|
11
|
+
|
|
12
|
+
// The public bucket is created once per process. Cache the in-flight promise (not a bare
|
|
13
|
+
// boolean) so concurrent uploads share a SINGLE createBucket call instead of racing — and
|
|
14
|
+
// clear it on failure so a later request can retry rather than inheriting a cached rejection.
|
|
15
|
+
let bucketEnsurePromise: Promise<void> | null = null;
|
|
16
|
+
|
|
17
|
+
/** Idempotently ensure a PUBLIC bucket exists for media. Safe to call concurrently. */
|
|
18
|
+
export function ensureStorageBucket(): Promise<void> {
|
|
19
|
+
if (bucketEnsurePromise) return bucketEnsurePromise;
|
|
20
|
+
|
|
21
|
+
bucketEnsurePromise = (async () => {
|
|
22
|
+
const bucket = getStorageBucket();
|
|
23
|
+
if (!bucket) throw new Error('No storage bucket name is configured.');
|
|
24
|
+
|
|
25
|
+
const admin = getServiceRoleSupabaseClient();
|
|
26
|
+
// public: true is REQUIRED — media is served from /storage/v1/object/public/<bucket>,
|
|
27
|
+
// which returns 401 for a private bucket. Do not drop this flag.
|
|
28
|
+
const { error } = await admin.storage.createBucket(bucket, { public: true });
|
|
29
|
+
if (error && !/already exists|duplicate|resource already exists/i.test(error.message)) {
|
|
30
|
+
// Non-"exists" error — confirm via getBucket before treating it as fatal.
|
|
31
|
+
const { data } = await admin.storage.getBucket(bucket);
|
|
32
|
+
if (!data) {
|
|
33
|
+
throw new Error(`Could not ensure storage bucket "${bucket}": ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
// On failure, drop the cached promise so the next caller retries (don't cache a rejection).
|
|
39
|
+
bucketEnsurePromise.catch(() => {
|
|
40
|
+
bucketEnsurePromise = null;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return bucketEnsurePromise;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Upload (or overwrite) an object. */
|
|
47
|
+
export async function supabaseUploadObject(
|
|
48
|
+
key: string,
|
|
49
|
+
body: Buffer,
|
|
50
|
+
contentType: string,
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
await ensureStorageBucket();
|
|
53
|
+
const admin = getServiceRoleSupabaseClient();
|
|
54
|
+
const { error } = await admin.storage
|
|
55
|
+
.from(getStorageBucket())
|
|
56
|
+
.upload(key, body, { contentType, upsert: true });
|
|
57
|
+
if (error) {
|
|
58
|
+
throw new Error(`Supabase Storage upload failed for "${key}": ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Download an object as a Buffer (used by the image processor to fetch the original). */
|
|
63
|
+
export async function supabaseDownloadObject(key: string): Promise<Buffer> {
|
|
64
|
+
const admin = getServiceRoleSupabaseClient();
|
|
65
|
+
const { data, error } = await admin.storage.from(getStorageBucket()).download(key);
|
|
66
|
+
if (error || !data) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Supabase Storage download failed for "${key}": ${error?.message ?? 'no data returned'}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return Buffer.from(await data.arrayBuffer());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a signed upload URL for a NEW object key. The browser PUTs the file bytes to the
|
|
76
|
+
* returned URL directly (the same shape as an S3 presigned PUT), so large uploads never
|
|
77
|
+
* pass through the serverless function. The token is embedded in the URL.
|
|
78
|
+
*/
|
|
79
|
+
export async function supabaseCreateSignedUpload(
|
|
80
|
+
key: string,
|
|
81
|
+
): Promise<{ signedUrl: string; token: string }> {
|
|
82
|
+
await ensureStorageBucket();
|
|
83
|
+
const admin = getServiceRoleSupabaseClient();
|
|
84
|
+
const { data, error } = await admin.storage
|
|
85
|
+
.from(getStorageBucket())
|
|
86
|
+
.createSignedUploadUrl(key);
|
|
87
|
+
if (error || !data?.signedUrl) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Supabase Storage signed upload URL failed for "${key}": ${error?.message ?? 'no URL returned'}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return { signedUrl: data.signedUrl, token: data.token };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Remove one or more objects. No-op for an empty list. */
|
|
96
|
+
export async function supabaseRemoveObjects(keys: string[]): Promise<void> {
|
|
97
|
+
if (keys.length === 0) return;
|
|
98
|
+
const admin = getServiceRoleSupabaseClient();
|
|
99
|
+
const { error } = await admin.storage.from(getStorageBucket()).remove(keys);
|
|
100
|
+
if (error) {
|
|
101
|
+
throw new Error(`Supabase Storage remove failed: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|