create-nextblock 0.14.0 → 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/next-env.d.ts +1 -0
- package/templates/nextblock-template/package.json +6 -6
- 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,115 +1,115 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
|
|
3
|
-
import { createClient } from "@nextblock-cms/db/server";
|
|
4
|
-
import { revalidatePath } from "next/cache";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
applyPromotionsImport,
|
|
8
|
-
dryRunPromotionsImport,
|
|
9
|
-
exportPromotionsCsv,
|
|
10
|
-
getPromotionsTemplateCsv,
|
|
11
|
-
type PromotionImportSummary,
|
|
12
|
-
type PromotionKind,
|
|
13
|
-
type PromotionTransferResult,
|
|
14
|
-
} from "../../../lib/promotions/server";
|
|
15
|
-
|
|
16
|
-
async function assertAdmin() {
|
|
17
|
-
const supabase = createClient();
|
|
18
|
-
const {
|
|
19
|
-
data: { user },
|
|
20
|
-
} = await supabase.auth.getUser();
|
|
21
|
-
|
|
22
|
-
if (!user) {
|
|
23
|
-
throw new Error("Not authenticated.");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const { data: profile } = await supabase
|
|
27
|
-
.from("profiles")
|
|
28
|
-
.select("role")
|
|
29
|
-
.eq("id", user.id)
|
|
30
|
-
.single();
|
|
31
|
-
|
|
32
|
-
if (profile?.role !== "ADMIN") {
|
|
33
|
-
throw new Error("Admin access required.");
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function dateStamp() {
|
|
38
|
-
return new Date().toISOString().slice(0, 10);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function kindLabel(kind: PromotionKind) {
|
|
42
|
-
return kind === "sale" ? "promotions" : "price-changes";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function getPromotionsTemplateAction(
|
|
46
|
-
kind: PromotionKind
|
|
47
|
-
): Promise<PromotionTransferResult> {
|
|
48
|
-
try {
|
|
49
|
-
await assertAdmin();
|
|
50
|
-
return {
|
|
51
|
-
success: true,
|
|
52
|
-
fileName: `nextblock-${kindLabel(kind)}-import-template.csv`,
|
|
53
|
-
mimeType: "text/csv",
|
|
54
|
-
content: getPromotionsTemplateCsv(kind),
|
|
55
|
-
};
|
|
56
|
-
} catch (error) {
|
|
57
|
-
return {
|
|
58
|
-
success: false,
|
|
59
|
-
error: error instanceof Error ? error.message : "Failed to build template.",
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export async function exportPromotionsAction(
|
|
65
|
-
kind: PromotionKind
|
|
66
|
-
): Promise<PromotionTransferResult> {
|
|
67
|
-
try {
|
|
68
|
-
await assertAdmin();
|
|
69
|
-
return {
|
|
70
|
-
success: true,
|
|
71
|
-
fileName: `nextblock-${kindLabel(kind)}-${dateStamp()}.csv`,
|
|
72
|
-
mimeType: "text/csv",
|
|
73
|
-
content: await exportPromotionsCsv(kind),
|
|
74
|
-
};
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return {
|
|
77
|
-
success: false,
|
|
78
|
-
error: error instanceof Error ? error.message : "Failed to export CSV.",
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export async function dryRunPromotionsImportAction(params: {
|
|
84
|
-
kind: PromotionKind;
|
|
85
|
-
content: string;
|
|
86
|
-
}): Promise<{ success: boolean; summary?: PromotionImportSummary; error?: string }> {
|
|
87
|
-
try {
|
|
88
|
-
await assertAdmin();
|
|
89
|
-
const summary = await dryRunPromotionsImport(params.kind, params.content);
|
|
90
|
-
return { success: true, summary };
|
|
91
|
-
} catch (error) {
|
|
92
|
-
return {
|
|
93
|
-
success: false,
|
|
94
|
-
error: error instanceof Error ? error.message : "Failed to validate import.",
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export async function applyPromotionsImportAction(params: {
|
|
100
|
-
kind: PromotionKind;
|
|
101
|
-
content: string;
|
|
102
|
-
}): Promise<{ success: boolean; summary?: PromotionImportSummary; error?: string }> {
|
|
103
|
-
try {
|
|
104
|
-
await assertAdmin();
|
|
105
|
-
const summary = await applyPromotionsImport(params.kind, params.content);
|
|
106
|
-
revalidatePath("/cms/promotions");
|
|
107
|
-
revalidatePath("/cms/products");
|
|
108
|
-
return { success: true, summary };
|
|
109
|
-
} catch (error) {
|
|
110
|
-
return {
|
|
111
|
-
success: false,
|
|
112
|
-
error: error instanceof Error ? error.message : "Failed to apply import.",
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
4
|
+
import { revalidatePath } from "next/cache";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
applyPromotionsImport,
|
|
8
|
+
dryRunPromotionsImport,
|
|
9
|
+
exportPromotionsCsv,
|
|
10
|
+
getPromotionsTemplateCsv,
|
|
11
|
+
type PromotionImportSummary,
|
|
12
|
+
type PromotionKind,
|
|
13
|
+
type PromotionTransferResult,
|
|
14
|
+
} from "../../../lib/promotions/server";
|
|
15
|
+
|
|
16
|
+
async function assertAdmin() {
|
|
17
|
+
const supabase = createClient();
|
|
18
|
+
const {
|
|
19
|
+
data: { user },
|
|
20
|
+
} = await supabase.auth.getUser();
|
|
21
|
+
|
|
22
|
+
if (!user) {
|
|
23
|
+
throw new Error("Not authenticated.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { data: profile } = await supabase
|
|
27
|
+
.from("profiles")
|
|
28
|
+
.select("role")
|
|
29
|
+
.eq("id", user.id)
|
|
30
|
+
.single();
|
|
31
|
+
|
|
32
|
+
if (profile?.role !== "ADMIN") {
|
|
33
|
+
throw new Error("Admin access required.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function dateStamp() {
|
|
38
|
+
return new Date().toISOString().slice(0, 10);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function kindLabel(kind: PromotionKind) {
|
|
42
|
+
return kind === "sale" ? "promotions" : "price-changes";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function getPromotionsTemplateAction(
|
|
46
|
+
kind: PromotionKind
|
|
47
|
+
): Promise<PromotionTransferResult> {
|
|
48
|
+
try {
|
|
49
|
+
await assertAdmin();
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
fileName: `nextblock-${kindLabel(kind)}-import-template.csv`,
|
|
53
|
+
mimeType: "text/csv",
|
|
54
|
+
content: getPromotionsTemplateCsv(kind),
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: error instanceof Error ? error.message : "Failed to build template.",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function exportPromotionsAction(
|
|
65
|
+
kind: PromotionKind
|
|
66
|
+
): Promise<PromotionTransferResult> {
|
|
67
|
+
try {
|
|
68
|
+
await assertAdmin();
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
fileName: `nextblock-${kindLabel(kind)}-${dateStamp()}.csv`,
|
|
72
|
+
mimeType: "text/csv",
|
|
73
|
+
content: await exportPromotionsCsv(kind),
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: error instanceof Error ? error.message : "Failed to export CSV.",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function dryRunPromotionsImportAction(params: {
|
|
84
|
+
kind: PromotionKind;
|
|
85
|
+
content: string;
|
|
86
|
+
}): Promise<{ success: boolean; summary?: PromotionImportSummary; error?: string }> {
|
|
87
|
+
try {
|
|
88
|
+
await assertAdmin();
|
|
89
|
+
const summary = await dryRunPromotionsImport(params.kind, params.content);
|
|
90
|
+
return { success: true, summary };
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: error instanceof Error ? error.message : "Failed to validate import.",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function applyPromotionsImportAction(params: {
|
|
100
|
+
kind: PromotionKind;
|
|
101
|
+
content: string;
|
|
102
|
+
}): Promise<{ success: boolean; summary?: PromotionImportSummary; error?: string }> {
|
|
103
|
+
try {
|
|
104
|
+
await assertAdmin();
|
|
105
|
+
const summary = await applyPromotionsImport(params.kind, params.content);
|
|
106
|
+
revalidatePath("/cms/promotions");
|
|
107
|
+
revalidatePath("/cms/products");
|
|
108
|
+
return { success: true, summary };
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: error instanceof Error ? error.message : "Failed to apply import.",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { createClient, verifyPackageOnline } from "@nextblock-cms/db/server";
|
|
2
|
-
import { redirect } from "next/navigation";
|
|
3
|
-
|
|
4
|
-
import { PromotionsWorkspace } from "./PromotionsWorkspace";
|
|
5
|
-
|
|
6
|
-
export const dynamic = "force-dynamic";
|
|
7
|
-
|
|
8
|
-
export default async function PromotionsPage() {
|
|
9
|
-
const supabase = createClient();
|
|
10
|
-
const {
|
|
11
|
-
data: { user },
|
|
12
|
-
} = await supabase.auth.getUser();
|
|
13
|
-
|
|
14
|
-
if (!user) {
|
|
15
|
-
redirect("/sign-in?redirect=/cms/promotions");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const { data: profile } = await supabase
|
|
19
|
-
.from("profiles")
|
|
20
|
-
.select("role")
|
|
21
|
-
.eq("id", user.id)
|
|
22
|
-
.single();
|
|
23
|
-
|
|
24
|
-
if (profile?.role !== "ADMIN") {
|
|
25
|
-
redirect("/unauthorized?reason=admin_required");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const isEcommerceActive = await verifyPackageOnline("ecommerce").catch(() => false);
|
|
29
|
-
|
|
30
|
-
return <PromotionsWorkspace isEcommerceActive={isEcommerceActive} />;
|
|
31
|
-
}
|
|
1
|
+
import { createClient, verifyPackageOnline } from "@nextblock-cms/db/server";
|
|
2
|
+
import { redirect } from "next/navigation";
|
|
3
|
+
|
|
4
|
+
import { PromotionsWorkspace } from "./PromotionsWorkspace";
|
|
5
|
+
|
|
6
|
+
export const dynamic = "force-dynamic";
|
|
7
|
+
|
|
8
|
+
export default async function PromotionsPage() {
|
|
9
|
+
const supabase = createClient();
|
|
10
|
+
const {
|
|
11
|
+
data: { user },
|
|
12
|
+
} = await supabase.auth.getUser();
|
|
13
|
+
|
|
14
|
+
if (!user) {
|
|
15
|
+
redirect("/sign-in?redirect=/cms/promotions");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { data: profile } = await supabase
|
|
19
|
+
.from("profiles")
|
|
20
|
+
.select("role")
|
|
21
|
+
.eq("id", user.id)
|
|
22
|
+
.single();
|
|
23
|
+
|
|
24
|
+
if (profile?.role !== "ADMIN") {
|
|
25
|
+
redirect("/unauthorized?reason=admin_required");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isEcommerceActive = await verifyPackageOnline("ecommerce").catch(() => false);
|
|
29
|
+
|
|
30
|
+
return <PromotionsWorkspace isEcommerceActive={isEcommerceActive} />;
|
|
31
|
+
}
|
|
@@ -150,7 +150,7 @@ export async function restorePageToVersion(pageId: number, targetVersion: number
|
|
|
150
150
|
// Fallback for missing Version 1: use empty content with current meta
|
|
151
151
|
const { data: pageMeta } = await supabase
|
|
152
152
|
.from('pages')
|
|
153
|
-
.select('title, slug, language_id, status, meta_title, meta_description, feature_image_id')
|
|
153
|
+
.select('title, slug, language_id, status, meta_title, meta_description, feature_image_id')
|
|
154
154
|
.eq('id', pageId)
|
|
155
155
|
.single();
|
|
156
156
|
if (!pageMeta) return { error: 'Page not found.' } as const;
|
|
@@ -201,11 +201,11 @@ export async function restorePageToVersion(pageId: number, targetVersion: number
|
|
|
201
201
|
title: content.meta.title,
|
|
202
202
|
slug: content.meta.slug,
|
|
203
203
|
language_id: content.meta.language_id,
|
|
204
|
-
status: content.meta.status,
|
|
205
|
-
meta_title: content.meta.meta_title,
|
|
206
|
-
meta_description: content.meta.meta_description,
|
|
207
|
-
feature_image_id: content.meta.feature_image_id,
|
|
208
|
-
version: newVersion,
|
|
204
|
+
status: content.meta.status,
|
|
205
|
+
meta_title: content.meta.meta_title,
|
|
206
|
+
meta_description: content.meta.meta_description,
|
|
207
|
+
feature_image_id: content.meta.feature_image_id,
|
|
208
|
+
version: newVersion,
|
|
209
209
|
})
|
|
210
210
|
.eq('id', pageId);
|
|
211
211
|
if (updatePageError) return { error: `Failed to update page: ${updatePageError.message}` } as const;
|
|
@@ -262,7 +262,7 @@ export async function restorePostToVersion(postId: number, targetVersion: number
|
|
|
262
262
|
} else if (targetVersion === 1) {
|
|
263
263
|
const { data: postMeta } = await supabase
|
|
264
264
|
.from('posts')
|
|
265
|
-
.select('title, slug, language_id, status, meta_title, meta_description, label, excerpt, subtitle, published_at, feature_image_id')
|
|
265
|
+
.select('title, slug, language_id, status, meta_title, meta_description, label, excerpt, subtitle, published_at, feature_image_id')
|
|
266
266
|
.eq('id', postId)
|
|
267
267
|
.single();
|
|
268
268
|
if (!postMeta) return { error: 'Post not found.' } as const;
|
|
@@ -310,16 +310,16 @@ export async function restorePostToVersion(postId: number, targetVersion: number
|
|
|
310
310
|
.update({
|
|
311
311
|
title: content.meta.title,
|
|
312
312
|
slug: content.meta.slug,
|
|
313
|
-
language_id: content.meta.language_id,
|
|
314
|
-
status: content.meta.status,
|
|
315
|
-
meta_title: content.meta.meta_title,
|
|
316
|
-
meta_description: content.meta.meta_description,
|
|
317
|
-
label: content.meta.label,
|
|
318
|
-
excerpt: content.meta.excerpt,
|
|
319
|
-
subtitle: content.meta.subtitle,
|
|
320
|
-
published_at: content.meta.published_at,
|
|
321
|
-
feature_image_id: content.meta.feature_image_id,
|
|
322
|
-
version: newVersion,
|
|
313
|
+
language_id: content.meta.language_id,
|
|
314
|
+
status: content.meta.status,
|
|
315
|
+
meta_title: content.meta.meta_title,
|
|
316
|
+
meta_description: content.meta.meta_description,
|
|
317
|
+
label: content.meta.label,
|
|
318
|
+
excerpt: content.meta.excerpt,
|
|
319
|
+
subtitle: content.meta.subtitle,
|
|
320
|
+
published_at: content.meta.published_at,
|
|
321
|
+
feature_image_id: content.meta.feature_image_id,
|
|
322
|
+
version: newVersion,
|
|
323
323
|
})
|
|
324
324
|
.eq('id', postId);
|
|
325
325
|
if (updatePostError) return { error: `Failed to update post: ${updatePostError.message}` } as const;
|
|
@@ -374,7 +374,7 @@ export async function reconstructPageVersionContent(pageId: number, targetVersio
|
|
|
374
374
|
} else if (targetVersion === 1) {
|
|
375
375
|
const { data: pageMeta } = await supabase
|
|
376
376
|
.from('pages')
|
|
377
|
-
.select('title, slug, language_id, status, meta_title, meta_description, feature_image_id')
|
|
377
|
+
.select('title, slug, language_id, status, meta_title, meta_description, feature_image_id')
|
|
378
378
|
.eq('id', pageId)
|
|
379
379
|
.single();
|
|
380
380
|
if (!pageMeta) return { error: 'Page not found.' } as const;
|
|
@@ -433,7 +433,7 @@ export async function reconstructPostVersionContent(postId: number, targetVersio
|
|
|
433
433
|
} else if (targetVersion === 1) {
|
|
434
434
|
const { data: postMeta } = await supabase
|
|
435
435
|
.from('posts')
|
|
436
|
-
.select('title, slug, language_id, status, meta_title, meta_description, label, excerpt, subtitle, published_at, feature_image_id')
|
|
436
|
+
.select('title, slug, language_id, status, meta_title, meta_description, label, excerpt, subtitle, published_at, feature_image_id')
|
|
437
437
|
.eq('id', postId)
|
|
438
438
|
.single();
|
|
439
439
|
if (!postMeta) return { error: 'Post not found.' } as const;
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
// apps/nextblock/app/cms/revisions/utils.ts
|
|
2
|
-
import { createClient } from "@nextblock-cms/db/server";
|
|
3
|
-
import type { Database, Json } from "@nextblock-cms/db";
|
|
4
|
-
|
|
5
|
-
type BlockRow = Database['public']['Tables']['blocks']['Row'];
|
|
6
|
-
|
|
7
|
-
export interface PageMetaContent {
|
|
8
|
-
title: string;
|
|
9
|
-
slug: string;
|
|
10
|
-
language_id: number;
|
|
11
|
-
status: Database['public']['Enums']['page_status'];
|
|
12
|
-
meta_title: string | null;
|
|
13
|
-
meta_description: string | null;
|
|
14
|
-
feature_image_id: string | null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface PostMetaContent extends PageMetaContent {
|
|
18
|
-
label: string | null;
|
|
19
|
-
excerpt: string | null;
|
|
20
|
-
subtitle: string | null;
|
|
21
|
-
published_at: string | null;
|
|
22
|
-
feature_image_id: string | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface SimpleBlockContent {
|
|
26
|
-
language_id: number;
|
|
27
|
-
block_type: BlockRow['block_type'];
|
|
28
|
-
content: Json;
|
|
29
|
-
order: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface FullPageContent {
|
|
33
|
-
meta: PageMetaContent;
|
|
34
|
-
blocks: SimpleBlockContent[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface FullPostContent {
|
|
38
|
-
meta: PostMetaContent;
|
|
39
|
-
blocks: SimpleBlockContent[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function getFullPageContent(
|
|
43
|
-
pageId: number,
|
|
44
|
-
opts?: { overrideBlockId?: number; overrideBlockContent?: unknown; excludeDeletedBlockId?: number }
|
|
45
|
-
): Promise<FullPageContent | null> {
|
|
46
|
-
const supabase = createClient();
|
|
47
|
-
const { data: page, error: pageError } = await supabase
|
|
48
|
-
.from('pages')
|
|
49
|
-
.select('id, title, slug, language_id, status, meta_title, meta_description, feature_image_id')
|
|
50
|
-
.eq('id', pageId)
|
|
51
|
-
.single();
|
|
52
|
-
if (pageError || !page) return null;
|
|
53
|
-
|
|
54
|
-
const { data: blocks, error: blocksError } = await supabase
|
|
55
|
-
.from('blocks')
|
|
56
|
-
.select('id, language_id, block_type, content, order, page_id, post_id')
|
|
57
|
-
.eq('page_id', pageId)
|
|
58
|
-
.order('order', { ascending: true });
|
|
59
|
-
if (blocksError) return null;
|
|
60
|
-
|
|
61
|
-
const processed = (blocks || [])
|
|
62
|
-
.filter(b => opts?.excludeDeletedBlockId ? b.id !== opts.excludeDeletedBlockId : true)
|
|
63
|
-
.map(b => ({
|
|
64
|
-
language_id: b.language_id,
|
|
65
|
-
block_type: b.block_type,
|
|
66
|
-
content: (opts?.overrideBlockId && b.id === opts.overrideBlockId)
|
|
67
|
-
? (opts.overrideBlockContent as Json)
|
|
68
|
-
: (b.content as Json),
|
|
69
|
-
order: b.order,
|
|
70
|
-
} satisfies SimpleBlockContent));
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
meta: {
|
|
74
|
-
title: page.title,
|
|
75
|
-
slug: page.slug,
|
|
76
|
-
language_id: page.language_id,
|
|
77
|
-
status: page.status,
|
|
78
|
-
meta_title: page.meta_title,
|
|
79
|
-
meta_description: page.meta_description,
|
|
80
|
-
feature_image_id: page.feature_image_id ?? null,
|
|
81
|
-
},
|
|
82
|
-
blocks: processed,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function getFullPostContent(
|
|
87
|
-
postId: number,
|
|
88
|
-
opts?: { overrideBlockId?: number; overrideBlockContent?: unknown; excludeDeletedBlockId?: number }
|
|
89
|
-
): Promise<FullPostContent | null> {
|
|
90
|
-
const supabase = createClient();
|
|
91
|
-
const { data: post, error: postError } = await supabase
|
|
92
|
-
.from('posts')
|
|
93
|
-
.select('id, title, slug, language_id, status, meta_title, meta_description, label, excerpt, subtitle, published_at, feature_image_id')
|
|
94
|
-
.eq('id', postId)
|
|
95
|
-
.single();
|
|
96
|
-
if (postError || !post) return null;
|
|
97
|
-
|
|
98
|
-
const { data: blocks, error: blocksError } = await supabase
|
|
99
|
-
.from('blocks')
|
|
100
|
-
.select('id, language_id, block_type, content, order, page_id, post_id')
|
|
101
|
-
.eq('post_id', postId)
|
|
102
|
-
.order('order', { ascending: true });
|
|
103
|
-
if (blocksError) return null;
|
|
104
|
-
|
|
105
|
-
const processed = (blocks || [])
|
|
106
|
-
.filter(b => opts?.excludeDeletedBlockId ? b.id !== opts.excludeDeletedBlockId : true)
|
|
107
|
-
.map(b => ({
|
|
108
|
-
language_id: b.language_id,
|
|
109
|
-
block_type: b.block_type,
|
|
110
|
-
content: (opts?.overrideBlockId && b.id === opts.overrideBlockId)
|
|
111
|
-
? (opts.overrideBlockContent as Json)
|
|
112
|
-
: (b.content as Json),
|
|
113
|
-
order: b.order,
|
|
114
|
-
} satisfies SimpleBlockContent));
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
meta: {
|
|
118
|
-
title: post.title,
|
|
119
|
-
slug: post.slug,
|
|
120
|
-
language_id: post.language_id,
|
|
121
|
-
status: post.status,
|
|
122
|
-
meta_title: post.meta_title,
|
|
123
|
-
meta_description: post.meta_description,
|
|
124
|
-
label: post.label,
|
|
125
|
-
excerpt: post.excerpt,
|
|
126
|
-
subtitle: post.subtitle,
|
|
127
|
-
published_at: post.published_at ? new Date(post.published_at).toISOString() : null,
|
|
128
|
-
feature_image_id: post.feature_image_id ?? null,
|
|
129
|
-
},
|
|
130
|
-
blocks: processed,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
1
|
+
// apps/nextblock/app/cms/revisions/utils.ts
|
|
2
|
+
import { createClient } from "@nextblock-cms/db/server";
|
|
3
|
+
import type { Database, Json } from "@nextblock-cms/db";
|
|
4
|
+
|
|
5
|
+
type BlockRow = Database['public']['Tables']['blocks']['Row'];
|
|
6
|
+
|
|
7
|
+
export interface PageMetaContent {
|
|
8
|
+
title: string;
|
|
9
|
+
slug: string;
|
|
10
|
+
language_id: number;
|
|
11
|
+
status: Database['public']['Enums']['page_status'];
|
|
12
|
+
meta_title: string | null;
|
|
13
|
+
meta_description: string | null;
|
|
14
|
+
feature_image_id: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PostMetaContent extends PageMetaContent {
|
|
18
|
+
label: string | null;
|
|
19
|
+
excerpt: string | null;
|
|
20
|
+
subtitle: string | null;
|
|
21
|
+
published_at: string | null;
|
|
22
|
+
feature_image_id: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SimpleBlockContent {
|
|
26
|
+
language_id: number;
|
|
27
|
+
block_type: BlockRow['block_type'];
|
|
28
|
+
content: Json;
|
|
29
|
+
order: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FullPageContent {
|
|
33
|
+
meta: PageMetaContent;
|
|
34
|
+
blocks: SimpleBlockContent[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FullPostContent {
|
|
38
|
+
meta: PostMetaContent;
|
|
39
|
+
blocks: SimpleBlockContent[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function getFullPageContent(
|
|
43
|
+
pageId: number,
|
|
44
|
+
opts?: { overrideBlockId?: number; overrideBlockContent?: unknown; excludeDeletedBlockId?: number }
|
|
45
|
+
): Promise<FullPageContent | null> {
|
|
46
|
+
const supabase = createClient();
|
|
47
|
+
const { data: page, error: pageError } = await supabase
|
|
48
|
+
.from('pages')
|
|
49
|
+
.select('id, title, slug, language_id, status, meta_title, meta_description, feature_image_id')
|
|
50
|
+
.eq('id', pageId)
|
|
51
|
+
.single();
|
|
52
|
+
if (pageError || !page) return null;
|
|
53
|
+
|
|
54
|
+
const { data: blocks, error: blocksError } = await supabase
|
|
55
|
+
.from('blocks')
|
|
56
|
+
.select('id, language_id, block_type, content, order, page_id, post_id')
|
|
57
|
+
.eq('page_id', pageId)
|
|
58
|
+
.order('order', { ascending: true });
|
|
59
|
+
if (blocksError) return null;
|
|
60
|
+
|
|
61
|
+
const processed = (blocks || [])
|
|
62
|
+
.filter(b => opts?.excludeDeletedBlockId ? b.id !== opts.excludeDeletedBlockId : true)
|
|
63
|
+
.map(b => ({
|
|
64
|
+
language_id: b.language_id,
|
|
65
|
+
block_type: b.block_type,
|
|
66
|
+
content: (opts?.overrideBlockId && b.id === opts.overrideBlockId)
|
|
67
|
+
? (opts.overrideBlockContent as Json)
|
|
68
|
+
: (b.content as Json),
|
|
69
|
+
order: b.order,
|
|
70
|
+
} satisfies SimpleBlockContent));
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
meta: {
|
|
74
|
+
title: page.title,
|
|
75
|
+
slug: page.slug,
|
|
76
|
+
language_id: page.language_id,
|
|
77
|
+
status: page.status,
|
|
78
|
+
meta_title: page.meta_title,
|
|
79
|
+
meta_description: page.meta_description,
|
|
80
|
+
feature_image_id: page.feature_image_id ?? null,
|
|
81
|
+
},
|
|
82
|
+
blocks: processed,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function getFullPostContent(
|
|
87
|
+
postId: number,
|
|
88
|
+
opts?: { overrideBlockId?: number; overrideBlockContent?: unknown; excludeDeletedBlockId?: number }
|
|
89
|
+
): Promise<FullPostContent | null> {
|
|
90
|
+
const supabase = createClient();
|
|
91
|
+
const { data: post, error: postError } = await supabase
|
|
92
|
+
.from('posts')
|
|
93
|
+
.select('id, title, slug, language_id, status, meta_title, meta_description, label, excerpt, subtitle, published_at, feature_image_id')
|
|
94
|
+
.eq('id', postId)
|
|
95
|
+
.single();
|
|
96
|
+
if (postError || !post) return null;
|
|
97
|
+
|
|
98
|
+
const { data: blocks, error: blocksError } = await supabase
|
|
99
|
+
.from('blocks')
|
|
100
|
+
.select('id, language_id, block_type, content, order, page_id, post_id')
|
|
101
|
+
.eq('post_id', postId)
|
|
102
|
+
.order('order', { ascending: true });
|
|
103
|
+
if (blocksError) return null;
|
|
104
|
+
|
|
105
|
+
const processed = (blocks || [])
|
|
106
|
+
.filter(b => opts?.excludeDeletedBlockId ? b.id !== opts.excludeDeletedBlockId : true)
|
|
107
|
+
.map(b => ({
|
|
108
|
+
language_id: b.language_id,
|
|
109
|
+
block_type: b.block_type,
|
|
110
|
+
content: (opts?.overrideBlockId && b.id === opts.overrideBlockId)
|
|
111
|
+
? (opts.overrideBlockContent as Json)
|
|
112
|
+
: (b.content as Json),
|
|
113
|
+
order: b.order,
|
|
114
|
+
} satisfies SimpleBlockContent));
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
meta: {
|
|
118
|
+
title: post.title,
|
|
119
|
+
slug: post.slug,
|
|
120
|
+
language_id: post.language_id,
|
|
121
|
+
status: post.status,
|
|
122
|
+
meta_title: post.meta_title,
|
|
123
|
+
meta_description: post.meta_description,
|
|
124
|
+
label: post.label,
|
|
125
|
+
excerpt: post.excerpt,
|
|
126
|
+
subtitle: post.subtitle,
|
|
127
|
+
published_at: post.published_at ? new Date(post.published_at).toISOString() : null,
|
|
128
|
+
feature_image_id: post.feature_image_id ?? null,
|
|
129
|
+
},
|
|
130
|
+
blocks: processed,
|
|
131
|
+
};
|
|
132
|
+
}
|