@carlonicora/nextjs-jsonapi 5.2.0 → 5.4.0
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/{BlockNoteEditor-EGY4ZAAM.js → BlockNoteEditor-4YCAIEPT.js} +23 -22
- package/dist/BlockNoteEditor-4YCAIEPT.js.map +1 -0
- package/dist/{BlockNoteEditor-2QJVK2HH.mjs → BlockNoteEditor-YQL2SGFR.mjs} +8 -7
- package/dist/BlockNoteEditor-YQL2SGFR.mjs.map +1 -0
- package/dist/{BlockNoteViewer-6BQXJ4TB.mjs → BlockNoteViewer-AEJKJ27Q.mjs} +2 -2
- package/dist/{BlockNoteViewer-I2MMFH3S.js → BlockNoteViewer-YL3J7J2Y.js} +3 -3
- package/dist/{BlockNoteViewer-I2MMFH3S.js.map → BlockNoteViewer-YL3J7J2Y.js.map} +1 -1
- package/dist/BlogCategoryFields-DO48vOTE.d.mts +14 -0
- package/dist/BlogCategoryFields-DO48vOTE.d.ts +14 -0
- package/dist/BlogPostInterface-BB_4wcDR.d.mts +124 -0
- package/dist/BlogPostInterface-bWAT1rfR.d.ts +124 -0
- package/dist/billing/index.js +441 -441
- package/dist/billing/index.mjs +3 -3
- package/dist/blog-public.types-BbrzsC1c.d.mts +30 -0
- package/dist/blog-public.types-BbrzsC1c.d.ts +30 -0
- package/dist/{chunk-3VEQ5JZJ.js → chunk-3NLAXVIN.js} +860 -849
- package/dist/{chunk-3VEQ5JZJ.js.map → chunk-3NLAXVIN.js.map} +1 -1
- package/dist/{chunk-YMUPTXOT.mjs → chunk-3YW35YHQ.mjs} +1 -101
- package/dist/chunk-3YW35YHQ.mjs.map +1 -0
- package/dist/{chunk-EXICQC4A.js → chunk-5PPG6PL7.js} +3 -3
- package/dist/{chunk-EXICQC4A.js.map → chunk-5PPG6PL7.js.map} +1 -1
- package/dist/{chunk-NPKFMRH5.mjs → chunk-6DC2WVVB.mjs} +2 -2
- package/dist/{chunk-2Z3ZCYXX.mjs → chunk-EUKTBOI2.mjs} +19 -8
- package/dist/chunk-EUKTBOI2.mjs.map +1 -0
- package/dist/{chunk-THKYH65N.js → chunk-KSYQEKJX.js} +11 -111
- package/dist/chunk-KSYQEKJX.js.map +1 -0
- package/dist/{chunk-S5MQDWWC.js → chunk-NFBOSPNJ.js} +1451 -176
- package/dist/chunk-NFBOSPNJ.js.map +1 -0
- package/dist/{chunk-JEZQTZQ4.mjs → chunk-QMYQRPQC.mjs} +2 -2
- package/dist/{chunk-HNOWSFM5.mjs → chunk-WC63PJSC.mjs} +1278 -3
- package/dist/chunk-WC63PJSC.mjs.map +1 -0
- package/dist/chunk-XCHSX3VC.mjs +107 -0
- package/dist/chunk-XCHSX3VC.mjs.map +1 -0
- package/dist/{chunk-75MLT74B.js → chunk-YNCQSWQW.js} +8 -8
- package/dist/{chunk-75MLT74B.js.map → chunk-YNCQSWQW.js.map} +1 -1
- package/dist/chunk-YW4ZCEWB.js +107 -0
- package/dist/chunk-YW4ZCEWB.js.map +1 -0
- package/dist/client/index.d.mts +6 -39
- package/dist/client/index.d.ts +6 -39
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +15 -4
- package/dist/components/index.d.ts +15 -4
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +400 -3
- package/dist/core/index.d.ts +400 -3
- package/dist/core/index.js +64 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +63 -1
- package/dist/email-automation-enrollment.fields-BZvF14qx.d.mts +82 -0
- package/dist/email-automation-enrollment.fields-Cp3VMzFq.d.ts +82 -0
- package/dist/email-automations/index.css +30 -0
- package/dist/email-automations/index.css.map +1 -0
- package/dist/email-automations/index.d.mts +303 -0
- package/dist/email-automations/index.d.ts +303 -0
- package/dist/email-automations/index.js +1705 -0
- package/dist/email-automations/index.js.map +1 -0
- package/dist/email-automations/index.mjs +1705 -0
- package/dist/email-automations/index.mjs.map +1 -0
- package/dist/features/analytics/index.js +122 -122
- package/dist/features/analytics/index.mjs +4 -4
- package/dist/features/analytics/server.js +6 -5
- package/dist/features/analytics/server.js.map +1 -1
- package/dist/features/analytics/server.mjs +4 -3
- package/dist/features/analytics/server.mjs.map +1 -1
- package/dist/features/blog/index.css +30 -0
- package/dist/features/blog/index.css.map +1 -0
- package/dist/features/blog/index.d.mts +266 -0
- package/dist/features/blog/index.d.ts +266 -0
- package/dist/features/blog/index.js +1799 -0
- package/dist/features/blog/index.js.map +1 -0
- package/dist/features/blog/index.mjs +1799 -0
- package/dist/features/blog/index.mjs.map +1 -0
- package/dist/features/blog/server.d.mts +33 -0
- package/dist/features/blog/server.d.ts +33 -0
- package/dist/features/blog/server.js +139 -0
- package/dist/features/blog/server.js.map +1 -0
- package/dist/features/blog/server.mjs +139 -0
- package/dist/features/blog/server.mjs.map +1 -0
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.js +125 -125
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +65 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +64 -2
- package/dist/{s3.service-CY-jgOAM.d.ts → s3.service-Bz_K-tjz.d.ts} +11 -0
- package/dist/{s3.service-Sri0QP7Z.d.mts → s3.service-D5zNCEW3.d.mts} +11 -0
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +8 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +12 -10
- package/dist/types-70acd-8C.d.mts +39 -0
- package/dist/types-l8SFleXi.d.ts +39 -0
- package/messages/en.json +210 -0
- package/messages/it.json +210 -0
- package/package.json +21 -1
- package/src/components/editors/BlockNoteEditor.tsx +13 -3
- package/src/components/forms/FormBlockNote.tsx +4 -0
- package/src/components/forms/FormFieldWrapper.tsx +1 -1
- package/src/components/forms/FormTextarea.tsx +2 -1
- package/src/components/navigations/ContentTitle.tsx +14 -3
- package/src/core/index.ts +12 -0
- package/src/core/registry/ModuleRegistry.ts +11 -0
- package/src/email-automations/index.ts +5 -0
- package/src/features/blog/BlogModules.ts +49 -0
- package/src/features/blog/components/admin/BlogAdminContainer.tsx +32 -0
- package/src/features/blog/components/admin/BlogCategoriesList.tsx +56 -0
- package/src/features/blog/components/admin/BlogCategoryEditor.tsx +230 -0
- package/src/features/blog/components/admin/BlogCoverImageField.tsx +123 -0
- package/src/features/blog/components/admin/BlogLayoutSetting.tsx +86 -0
- package/src/features/blog/components/admin/BlogPostComparison.tsx +54 -0
- package/src/features/blog/components/admin/BlogPostContainer.tsx +102 -0
- package/src/features/blog/components/admin/BlogPostDeleter.tsx +35 -0
- package/src/features/blog/components/admin/BlogPostEditor.tsx +207 -0
- package/src/features/blog/components/admin/BlogPostPublishButton.tsx +50 -0
- package/src/features/blog/components/admin/BlogPostTranslationEditor.tsx +110 -0
- package/src/features/blog/components/admin/BlogPostTranslationPanel.tsx +136 -0
- package/src/features/blog/components/admin/BlogPostsList.tsx +58 -0
- package/src/features/blog/components/admin/BlogTranslationStatus.tsx +104 -0
- package/src/features/blog/components/admin/__tests__/BlogPostTranslationPanel.spec.tsx +100 -0
- package/src/features/blog/components/admin/__tests__/BlogPostsList.spec.tsx +30 -0
- package/src/features/blog/components/admin/index.ts +14 -0
- package/src/features/blog/components/public/BlogCategoryChips.tsx +59 -0
- package/src/features/blog/components/public/BlogList.tsx +21 -0
- package/src/features/blog/components/public/BlogListColumn.tsx +53 -0
- package/src/features/blog/components/public/BlogListFeatured.tsx +57 -0
- package/src/features/blog/components/public/BlogListParts.tsx +74 -0
- package/src/features/blog/components/public/BlogListRail.tsx +62 -0
- package/src/features/blog/components/public/BlogPostCard.tsx +132 -0
- package/src/features/blog/components/public/BlogPostMeta.tsx +49 -0
- package/src/features/blog/components/public/BlogPostView.tsx +74 -0
- package/src/features/blog/components/public/BlogSearchBox.tsx +52 -0
- package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +173 -0
- package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +128 -0
- package/src/features/blog/components/public/blogPublicHelpers.ts +30 -0
- package/src/features/blog/components/public/index.ts +3 -0
- package/src/features/blog/contexts/BlogPostContext.tsx +66 -0
- package/src/features/blog/contexts/index.ts +1 -0
- package/src/features/blog/data/BlogCategory.ts +93 -0
- package/src/features/blog/data/BlogCategoryFields.ts +4 -0
- package/src/features/blog/data/BlogCategoryInterface.ts +24 -0
- package/src/features/blog/data/BlogCategoryService.ts +54 -0
- package/src/features/blog/data/BlogCategoryTranslation.ts +90 -0
- package/src/features/blog/data/BlogCategoryTranslationInterface.ts +22 -0
- package/src/features/blog/data/BlogCategoryTranslationService.ts +30 -0
- package/src/features/blog/data/BlogPost.spec.ts +123 -0
- package/src/features/blog/data/BlogPost.ts +159 -0
- package/src/features/blog/data/BlogPostFields.ts +7 -0
- package/src/features/blog/data/BlogPostInterface.ts +39 -0
- package/src/features/blog/data/BlogPostService.ts +82 -0
- package/src/features/blog/data/BlogPostTranslation.spec.ts +73 -0
- package/src/features/blog/data/BlogPostTranslation.ts +110 -0
- package/src/features/blog/data/BlogPostTranslationInterface.ts +27 -0
- package/src/features/blog/data/BlogPostTranslationService.ts +30 -0
- package/src/features/blog/data/BlogSettings.ts +37 -0
- package/src/features/blog/data/BlogSettingsInterface.ts +11 -0
- package/src/features/blog/data/BlogSettingsService.ts +24 -0
- package/src/features/blog/data/index.ts +17 -0
- package/src/features/blog/hooks/index.ts +3 -0
- package/src/features/blog/hooks/useBlogCategoryNames.ts +34 -0
- package/src/features/blog/hooks/useBlogCategoryTableStructure.tsx +84 -0
- package/src/features/blog/hooks/useBlogPostTableStructure.tsx +126 -0
- package/src/features/blog/index.ts +15 -0
- package/src/features/blog/server/fetchers.ts +120 -0
- package/src/features/blog/server/toPublic.spec.ts +91 -0
- package/src/features/blog/server/toPublic.ts +36 -0
- package/src/features/blog/server-entry.ts +17 -0
- package/src/features/blog/types/blog-public.types.ts +34 -0
- package/src/features/blog/types/blog.constants.ts +40 -0
- package/src/features/email-automation/EmailAutomationEnrollmentModule.ts +11 -0
- package/src/features/email-automation/EmailAutomationModule.ts +30 -0
- package/src/features/email-automation/EmailAutomationStepModule.ts +11 -0
- package/src/features/email-automation/EmailAutomationTriggerModule.ts +11 -0
- package/src/features/email-automation/components/__tests__/EmailAutomationEditor.spec.tsx +104 -0
- package/src/features/email-automation/components/canvas/EmailAutomationActiveSwitch.tsx +49 -0
- package/src/features/email-automation/components/canvas/EmailAutomationCanvas.tsx +474 -0
- package/src/features/email-automation/components/canvas/EmailAutomationCanvasInspector.tsx +89 -0
- package/src/features/email-automation/components/canvas/EmailAutomationCanvasTab.tsx +32 -0
- package/src/features/email-automation/components/canvas/__tests__/buildCanvasGraph.spec.ts +67 -0
- package/src/features/email-automation/components/canvas/buildCanvasGraph.ts +141 -0
- package/src/features/email-automation/components/canvas/edges/DelayEdge.tsx +68 -0
- package/src/features/email-automation/components/canvas/nodes/EmailStepNode.tsx +43 -0
- package/src/features/email-automation/components/canvas/nodes/TriggerNode.tsx +23 -0
- package/src/features/email-automation/components/canvas/shared/CanvasToolbar.tsx +53 -0
- package/src/features/email-automation/components/canvas/shared/EmptyCanvas.tsx +11 -0
- package/src/features/email-automation/components/canvas/shared/__tests__/autoLayout.spec.ts +42 -0
- package/src/features/email-automation/components/canvas/shared/__tests__/longPress.spec.ts +51 -0
- package/src/features/email-automation/components/canvas/shared/autoLayout.ts +55 -0
- package/src/features/email-automation/components/canvas/shared/index.ts +4 -0
- package/src/features/email-automation/components/canvas/shared/longPress.ts +57 -0
- package/src/features/email-automation/components/containers/EmailAutomationContainer.tsx +80 -0
- package/src/features/email-automation/components/containers/EmailAutomationsListContainer.tsx +27 -0
- package/src/features/email-automation/components/forms/EmailAutomationEditor.tsx +153 -0
- package/src/features/email-automation/components/forms/EmailStepEditor.tsx +151 -0
- package/src/features/email-automation/components/index.ts +15 -0
- package/src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx +90 -0
- package/src/features/email-automation/components/lists/EmailAutomationsList.tsx +76 -0
- package/src/features/email-automation/components/unsubscribe/EmailUnsubscribe.tsx +99 -0
- package/src/features/email-automation/components/unsubscribe/__tests__/EmailUnsubscribe.spec.tsx +55 -0
- package/src/features/email-automation/contexts/EmailAutomationContext.tsx +175 -0
- package/src/features/email-automation/data/EmailAutomation.ts +87 -0
- package/src/features/email-automation/data/EmailAutomationEnrollment.ts +105 -0
- package/src/features/email-automation/data/EmailAutomationEnrollmentInterface.ts +18 -0
- package/src/features/email-automation/data/EmailAutomationEnrollmentService.ts +31 -0
- package/src/features/email-automation/data/EmailAutomationInterface.ts +22 -0
- package/src/features/email-automation/data/EmailAutomationService.ts +106 -0
- package/src/features/email-automation/data/EmailAutomationStep.ts +104 -0
- package/src/features/email-automation/data/EmailAutomationStepInterface.ts +23 -0
- package/src/features/email-automation/data/EmailAutomationStepService.ts +80 -0
- package/src/features/email-automation/data/EmailAutomationTrigger.ts +28 -0
- package/src/features/email-automation/data/EmailAutomationTriggerInterface.ts +6 -0
- package/src/features/email-automation/data/__tests__/email-automation-models.spec.ts +103 -0
- package/src/features/email-automation/data/email-automation-enrollment.fields.ts +8 -0
- package/src/features/email-automation/data/email-automation.fields.ts +9 -0
- package/src/features/email-automation/data/index.ts +13 -0
- package/src/features/email-automation/hooks/index.ts +2 -0
- package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +126 -0
- package/src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx +119 -0
- package/src/features/email-automation/index.ts +5 -0
- package/src/features/s3/data/s3.service.ts +17 -0
- package/dist/BlockNoteEditor-2QJVK2HH.mjs.map +0 -1
- package/dist/BlockNoteEditor-EGY4ZAAM.js.map +0 -1
- package/dist/chunk-2Z3ZCYXX.mjs.map +0 -1
- package/dist/chunk-HNOWSFM5.mjs.map +0 -1
- package/dist/chunk-S5MQDWWC.js.map +0 -1
- package/dist/chunk-THKYH65N.js.map +0 -1
- package/dist/chunk-YMUPTXOT.mjs.map +0 -1
- /package/dist/{BlockNoteViewer-6BQXJ4TB.mjs.map → BlockNoteViewer-AEJKJ27Q.mjs.map} +0 -0
- /package/dist/{chunk-NPKFMRH5.mjs.map → chunk-6DC2WVVB.mjs.map} +0 -0
- /package/dist/{chunk-JEZQTZQ4.mjs.map → chunk-QMYQRPQC.mjs.map} +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { BlogPostInterface, BlogPostService } from "../data";
|
|
5
|
+
|
|
6
|
+
/** How often the post page re-reads the post while a translation job runs. */
|
|
7
|
+
const TRANSLATION_POLL_MS = 5000;
|
|
8
|
+
|
|
9
|
+
export interface BlogPostContextType {
|
|
10
|
+
post: BlogPostInterface | undefined;
|
|
11
|
+
reload: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const BlogPostContext = createContext<BlogPostContextType | undefined>(undefined);
|
|
15
|
+
|
|
16
|
+
export type BlogPostProviderProps = {
|
|
17
|
+
postId: string;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Loads one post (with its translations, categories and author) for the admin
|
|
23
|
+
* post page. While any translation is `pending` or `in_progress` it re-reads
|
|
24
|
+
* the post every 5 s, so the language tabs move from "Translating…" to the
|
|
25
|
+
* translated text without a manual refresh; it stops once none are running.
|
|
26
|
+
*/
|
|
27
|
+
export const BlogPostProvider = ({ postId, children }: BlogPostProviderProps) => {
|
|
28
|
+
const [post, setPost] = useState<BlogPostInterface | undefined>(undefined);
|
|
29
|
+
|
|
30
|
+
const reload = useCallback(async () => {
|
|
31
|
+
const loaded = await BlogPostService.findOne({ id: postId });
|
|
32
|
+
setPost(loaded);
|
|
33
|
+
}, [postId]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
void reload();
|
|
37
|
+
}, [reload]);
|
|
38
|
+
|
|
39
|
+
const isTranslating = useMemo(
|
|
40
|
+
() =>
|
|
41
|
+
(post?.translations ?? []).some(
|
|
42
|
+
(translation) => translation.aiStatus === "pending" || translation.aiStatus === "in_progress",
|
|
43
|
+
),
|
|
44
|
+
[post],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!isTranslating) return;
|
|
49
|
+
const handle = setInterval(() => {
|
|
50
|
+
reload().catch(() => {});
|
|
51
|
+
}, TRANSLATION_POLL_MS);
|
|
52
|
+
return () => clearInterval(handle);
|
|
53
|
+
}, [isTranslating, reload]);
|
|
54
|
+
|
|
55
|
+
const value = useMemo<BlogPostContextType>(() => ({ post, reload }), [post, reload]);
|
|
56
|
+
|
|
57
|
+
return <BlogPostContext.Provider value={value}>{children}</BlogPostContext.Provider>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const useBlogPost = (): BlogPostContextType => {
|
|
61
|
+
const context = useContext(BlogPostContext);
|
|
62
|
+
if (context === undefined) {
|
|
63
|
+
throw new Error("useBlogPost must be used within a BlogPostProvider");
|
|
64
|
+
}
|
|
65
|
+
return context;
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./BlogPostContext";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { BlogCategoryInput, BlogCategoryInterface } from "./BlogCategoryInterface";
|
|
3
|
+
import { BlogCategoryTranslationInterface } from "./BlogCategoryTranslationInterface";
|
|
4
|
+
|
|
5
|
+
export class BlogCategory extends AbstractApiData implements BlogCategoryInterface {
|
|
6
|
+
private _originalLanguage?: string;
|
|
7
|
+
private _image?: string;
|
|
8
|
+
private _translations?: BlogCategoryTranslationInterface[];
|
|
9
|
+
|
|
10
|
+
private _language?: string;
|
|
11
|
+
private _name?: string;
|
|
12
|
+
private _slug?: string;
|
|
13
|
+
private _description?: string;
|
|
14
|
+
|
|
15
|
+
get originalLanguage(): string {
|
|
16
|
+
return this._originalLanguage ?? "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get image(): string | undefined {
|
|
20
|
+
return this._image;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get translations(): BlogCategoryTranslationInterface[] {
|
|
24
|
+
return this._translations ?? [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get original(): BlogCategoryTranslationInterface | undefined {
|
|
28
|
+
return this.translations.find((translation) => translation.isOriginal);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get language(): string | undefined {
|
|
32
|
+
return this._language;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get name(): string | undefined {
|
|
36
|
+
return this._name;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get slug(): string | undefined {
|
|
40
|
+
return this._slug;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get description(): string | undefined {
|
|
44
|
+
return this._description;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
48
|
+
super.rehydrate(data);
|
|
49
|
+
|
|
50
|
+
const attributes = data.jsonApi.attributes ?? {};
|
|
51
|
+
const meta = data.jsonApi.meta ?? {};
|
|
52
|
+
// The resolved-language values are computed on the API. Depending on the
|
|
53
|
+
// descriptor they arrive as attributes or as meta, so both are read.
|
|
54
|
+
const resolved = (key: string) => attributes[key] ?? meta[key] ?? undefined;
|
|
55
|
+
|
|
56
|
+
this._originalLanguage = attributes.originalLanguage;
|
|
57
|
+
this._image = attributes.image ?? undefined;
|
|
58
|
+
|
|
59
|
+
this._language = resolved("language");
|
|
60
|
+
this._name = resolved("name");
|
|
61
|
+
this._slug = resolved("slug");
|
|
62
|
+
this._description = resolved("description");
|
|
63
|
+
|
|
64
|
+
this._translations = this._readIncluded(
|
|
65
|
+
data,
|
|
66
|
+
"translations",
|
|
67
|
+
Modules.BlogCategoryTranslation,
|
|
68
|
+
) as BlogCategoryTranslationInterface[];
|
|
69
|
+
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createJsonApi(data: BlogCategoryInput) {
|
|
74
|
+
const response: any = {
|
|
75
|
+
data: {
|
|
76
|
+
type: Modules.BlogCategory.name,
|
|
77
|
+
id: data.id,
|
|
78
|
+
attributes: {},
|
|
79
|
+
meta: {},
|
|
80
|
+
relationships: {},
|
|
81
|
+
},
|
|
82
|
+
included: [],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (data.originalLanguage !== undefined) response.data.attributes.originalLanguage = data.originalLanguage;
|
|
86
|
+
if (data.name !== undefined) response.data.attributes.name = data.name;
|
|
87
|
+
if (data.slug !== undefined) response.data.attributes.slug = data.slug;
|
|
88
|
+
if (data.description !== undefined) response.data.attributes.description = data.description;
|
|
89
|
+
if (data.image !== undefined) response.data.attributes.image = data.image;
|
|
90
|
+
|
|
91
|
+
return response;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
import { BlogCategoryTranslationInterface } from "./BlogCategoryTranslationInterface";
|
|
3
|
+
|
|
4
|
+
export type BlogCategoryInput = {
|
|
5
|
+
id: string;
|
|
6
|
+
originalLanguage?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
slug?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
image?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface BlogCategoryInterface extends ApiDataInterface {
|
|
14
|
+
get originalLanguage(): string;
|
|
15
|
+
get image(): string | undefined;
|
|
16
|
+
get translations(): BlogCategoryTranslationInterface[];
|
|
17
|
+
get original(): BlogCategoryTranslationInterface | undefined;
|
|
18
|
+
|
|
19
|
+
// Resolved for one language by the public and list queries.
|
|
20
|
+
get language(): string | undefined;
|
|
21
|
+
get name(): string | undefined;
|
|
22
|
+
get slug(): string | undefined;
|
|
23
|
+
get description(): string | undefined;
|
|
24
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef } from "../../../core";
|
|
2
|
+
import { BlogCategoryInput, BlogCategoryInterface } from "./BlogCategoryInterface";
|
|
3
|
+
|
|
4
|
+
export class BlogCategoryService extends AbstractService {
|
|
5
|
+
static async findMany(
|
|
6
|
+
params: { search?: string; fetchAll?: boolean; next?: NextRef } = {},
|
|
7
|
+
): Promise<BlogCategoryInterface[]> {
|
|
8
|
+
const endpoint = new EndpointCreator({ endpoint: Modules.BlogCategory });
|
|
9
|
+
|
|
10
|
+
if (params.fetchAll) endpoint.addAdditionalParam("fetchAll", "true");
|
|
11
|
+
if (params.search) endpoint.addAdditionalParam("search", params.search);
|
|
12
|
+
|
|
13
|
+
return this.callApi<BlogCategoryInterface[]>({
|
|
14
|
+
type: Modules.BlogCategory,
|
|
15
|
+
method: HttpMethod.GET,
|
|
16
|
+
endpoint: endpoint.generate(),
|
|
17
|
+
next: params.next,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static async findOne(params: { id: string }): Promise<BlogCategoryInterface> {
|
|
22
|
+
return this.callApi<BlogCategoryInterface>({
|
|
23
|
+
type: Modules.BlogCategory,
|
|
24
|
+
method: HttpMethod.GET,
|
|
25
|
+
endpoint: new EndpointCreator({ endpoint: Modules.BlogCategory, id: params.id }).generate(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static async create(params: BlogCategoryInput): Promise<BlogCategoryInterface> {
|
|
30
|
+
return this.callApi<BlogCategoryInterface>({
|
|
31
|
+
type: Modules.BlogCategory,
|
|
32
|
+
method: HttpMethod.POST,
|
|
33
|
+
endpoint: new EndpointCreator({ endpoint: Modules.BlogCategory }).generate(),
|
|
34
|
+
input: params,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static async update(params: BlogCategoryInput): Promise<BlogCategoryInterface> {
|
|
39
|
+
return this.callApi<BlogCategoryInterface>({
|
|
40
|
+
type: Modules.BlogCategory,
|
|
41
|
+
method: HttpMethod.PUT,
|
|
42
|
+
endpoint: new EndpointCreator({ endpoint: Modules.BlogCategory, id: params.id }).generate(),
|
|
43
|
+
input: params,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static async delete(params: { id: string }): Promise<void> {
|
|
48
|
+
await this.callApi({
|
|
49
|
+
type: Modules.BlogCategory,
|
|
50
|
+
method: HttpMethod.DELETE,
|
|
51
|
+
endpoint: new EndpointCreator({ endpoint: Modules.BlogCategory, id: params.id }).generate(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { BlogAiStatus, BlogTranslationSource } from "../types/blog.constants";
|
|
3
|
+
import { BlogCategoryInterface } from "./BlogCategoryInterface";
|
|
4
|
+
import { BlogCategoryTranslationInput, BlogCategoryTranslationInterface } from "./BlogCategoryTranslationInterface";
|
|
5
|
+
|
|
6
|
+
export class BlogCategoryTranslation extends AbstractApiData implements BlogCategoryTranslationInterface {
|
|
7
|
+
private _language?: string;
|
|
8
|
+
private _isOriginal?: boolean;
|
|
9
|
+
private _source?: BlogTranslationSource;
|
|
10
|
+
private _aiStatus?: BlogAiStatus;
|
|
11
|
+
private _name?: string;
|
|
12
|
+
private _slug?: string;
|
|
13
|
+
private _description?: string;
|
|
14
|
+
private _translatedAt?: Date;
|
|
15
|
+
private _category?: BlogCategoryInterface;
|
|
16
|
+
|
|
17
|
+
get language(): string {
|
|
18
|
+
return this._language ?? "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get isOriginal(): boolean {
|
|
22
|
+
return this._isOriginal ?? false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get source(): BlogTranslationSource {
|
|
26
|
+
return this._source ?? "ai";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get aiStatus(): BlogAiStatus {
|
|
30
|
+
return this._aiStatus ?? "pending";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get name(): string {
|
|
34
|
+
return this._name ?? "";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get slug(): string {
|
|
38
|
+
return this._slug ?? "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get description(): string | undefined {
|
|
42
|
+
return this._description;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get translatedAt(): Date | undefined {
|
|
46
|
+
return this._translatedAt;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get category(): BlogCategoryInterface | undefined {
|
|
50
|
+
return this._category;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
54
|
+
super.rehydrate(data);
|
|
55
|
+
|
|
56
|
+
const attributes = data.jsonApi.attributes ?? {};
|
|
57
|
+
|
|
58
|
+
this._language = attributes.language;
|
|
59
|
+
this._isOriginal = attributes.isOriginal === true;
|
|
60
|
+
this._source = attributes.source;
|
|
61
|
+
this._aiStatus = attributes.aiStatus;
|
|
62
|
+
this._name = attributes.name ?? undefined;
|
|
63
|
+
this._slug = attributes.slug ?? undefined;
|
|
64
|
+
this._description = attributes.description ?? undefined;
|
|
65
|
+
this._translatedAt = attributes.translatedAt ? new Date(attributes.translatedAt) : undefined;
|
|
66
|
+
|
|
67
|
+
this._category = this._readIncluded(data, "category", Modules.BlogCategory) as BlogCategoryInterface;
|
|
68
|
+
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createJsonApi(data: BlogCategoryTranslationInput) {
|
|
73
|
+
const response: any = {
|
|
74
|
+
data: {
|
|
75
|
+
type: Modules.BlogCategoryTranslation.name,
|
|
76
|
+
id: data.id,
|
|
77
|
+
attributes: {},
|
|
78
|
+
meta: {},
|
|
79
|
+
relationships: {},
|
|
80
|
+
},
|
|
81
|
+
included: [],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (data.name !== undefined) response.data.attributes.name = data.name;
|
|
85
|
+
if (data.slug !== undefined) response.data.attributes.slug = data.slug;
|
|
86
|
+
if (data.description !== undefined) response.data.attributes.description = data.description;
|
|
87
|
+
|
|
88
|
+
return response;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
import { BlogAiStatus, BlogTranslationSource } from "../types/blog.constants";
|
|
3
|
+
import { BlogCategoryInterface } from "./BlogCategoryInterface";
|
|
4
|
+
|
|
5
|
+
export type BlogCategoryTranslationInput = {
|
|
6
|
+
id: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
slug?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface BlogCategoryTranslationInterface extends ApiDataInterface {
|
|
13
|
+
get language(): string;
|
|
14
|
+
get isOriginal(): boolean;
|
|
15
|
+
get source(): BlogTranslationSource;
|
|
16
|
+
get aiStatus(): BlogAiStatus;
|
|
17
|
+
get name(): string;
|
|
18
|
+
get slug(): string;
|
|
19
|
+
get description(): string | undefined;
|
|
20
|
+
get translatedAt(): Date | undefined;
|
|
21
|
+
get category(): BlogCategoryInterface | undefined;
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
|
|
2
|
+
import { BLOG_ACTION } from "../types/blog.constants";
|
|
3
|
+
import { BlogCategoryTranslationInput, BlogCategoryTranslationInterface } from "./BlogCategoryTranslationInterface";
|
|
4
|
+
|
|
5
|
+
export class BlogCategoryTranslationService extends AbstractService {
|
|
6
|
+
static async update(params: BlogCategoryTranslationInput): Promise<BlogCategoryTranslationInterface> {
|
|
7
|
+
return this.callApi<BlogCategoryTranslationInterface>({
|
|
8
|
+
type: Modules.BlogCategoryTranslation,
|
|
9
|
+
method: HttpMethod.PUT,
|
|
10
|
+
endpoint: new EndpointCreator({ endpoint: Modules.BlogCategoryTranslation, id: params.id }).generate(),
|
|
11
|
+
input: params,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* POST blog-category-translations/:id/retranslate. Re-queues this one
|
|
17
|
+
* language from the current original. Replies 204.
|
|
18
|
+
*/
|
|
19
|
+
static async retranslate(params: { id: string }): Promise<void> {
|
|
20
|
+
await this.callApi({
|
|
21
|
+
type: Modules.BlogCategoryTranslation,
|
|
22
|
+
method: HttpMethod.POST,
|
|
23
|
+
endpoint: new EndpointCreator({
|
|
24
|
+
endpoint: Modules.BlogCategoryTranslation,
|
|
25
|
+
id: params.id,
|
|
26
|
+
childEndpoint: BLOG_ACTION.retranslate,
|
|
27
|
+
}).generate(),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import { User } from "../../user/data/user";
|
|
3
|
+
import { ModuleRegistry } from "../../../core/registry/ModuleRegistry";
|
|
4
|
+
import { ModuleFactory, ModuleWithPermissions } from "../../../permissions/types";
|
|
5
|
+
import {
|
|
6
|
+
BlogCategoryModule,
|
|
7
|
+
BlogCategoryTranslationModule,
|
|
8
|
+
BlogPostModule,
|
|
9
|
+
BlogPostTranslationModule,
|
|
10
|
+
BlogSettingsModule,
|
|
11
|
+
} from "../BlogModules";
|
|
12
|
+
import { BlogPost } from "./BlogPost";
|
|
13
|
+
import { BlogPostTranslation } from "./BlogPostTranslation";
|
|
14
|
+
import { BlogCategory } from "./BlogCategory";
|
|
15
|
+
import { DataClassRegistry } from "../../../core/registry/DataClassRegistry";
|
|
16
|
+
import { Modules } from "../../../core/registry/ModuleRegistry";
|
|
17
|
+
|
|
18
|
+
// createJsonApi reads Modules.X.name (resolved lazily via ModuleRegistry), so
|
|
19
|
+
// the modules must be registered before a model serialises — mirrors the
|
|
20
|
+
// email-automation-models.spec setup convention in this package.
|
|
21
|
+
const moduleFactory: ModuleFactory = (params) =>
|
|
22
|
+
({
|
|
23
|
+
pageUrl: params.pageUrl,
|
|
24
|
+
name: params.name,
|
|
25
|
+
model: params.model,
|
|
26
|
+
}) as ModuleWithPermissions;
|
|
27
|
+
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
ModuleRegistry.register("BlogPost", BlogPostModule(moduleFactory));
|
|
30
|
+
ModuleRegistry.register("BlogPostTranslation", BlogPostTranslationModule(moduleFactory));
|
|
31
|
+
ModuleRegistry.register("BlogCategory", BlogCategoryModule(moduleFactory));
|
|
32
|
+
ModuleRegistry.register("BlogCategoryTranslation", BlogCategoryTranslationModule(moduleFactory));
|
|
33
|
+
ModuleRegistry.register("BlogSettings", BlogSettingsModule(moduleFactory));
|
|
34
|
+
ModuleRegistry.register("User", { name: "users", model: User } as ModuleWithPermissions);
|
|
35
|
+
DataClassRegistry.registerObjectClass(Modules.BlogPostTranslation, BlogPostTranslation);
|
|
36
|
+
DataClassRegistry.registerObjectClass(Modules.BlogCategory, BlogCategory);
|
|
37
|
+
DataClassRegistry.registerObjectClass(Modules.User, User);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const blocks = [{ type: "paragraph", content: [{ type: "text", text: "Hello", styles: {} }] }];
|
|
41
|
+
|
|
42
|
+
describe("BlogPost", () => {
|
|
43
|
+
it("BlogPost.createJsonApi stringifies the body and sends categories", () => {
|
|
44
|
+
const p = new BlogPost().createJsonApi({
|
|
45
|
+
id: "p",
|
|
46
|
+
originalLanguage: "en",
|
|
47
|
+
title: "T",
|
|
48
|
+
body: [{ type: "paragraph" }],
|
|
49
|
+
categoryIds: ["c1"],
|
|
50
|
+
});
|
|
51
|
+
expect(p.data.type).toBe("blog-posts");
|
|
52
|
+
expect(typeof p.data.attributes.body).toBe("string");
|
|
53
|
+
expect(JSON.parse(p.data.attributes.body)).toEqual([{ type: "paragraph" }]);
|
|
54
|
+
expect(p.data.relationships.categories.data).toEqual([{ type: "blog-categories", id: "c1" }]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("never sends server-owned attributes", () => {
|
|
58
|
+
const p = new BlogPost().createJsonApi({ id: "p", title: "T" });
|
|
59
|
+
expect(p.data.attributes).toEqual({ title: "T" });
|
|
60
|
+
expect("categories" in p.data.relationships).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("BlogPost.rehydrate parses publishedAt as a Date and the resolved body as blocks", () => {
|
|
64
|
+
const post = new BlogPost().rehydrate({
|
|
65
|
+
jsonApi: {
|
|
66
|
+
id: "p",
|
|
67
|
+
type: "blog-posts",
|
|
68
|
+
attributes: {
|
|
69
|
+
status: "published",
|
|
70
|
+
originalLanguage: "en",
|
|
71
|
+
publishedAt: "2026-10-12T09:00:00.000Z",
|
|
72
|
+
language: "it",
|
|
73
|
+
title: "Titolo",
|
|
74
|
+
slug: "titolo",
|
|
75
|
+
body: JSON.stringify(blocks),
|
|
76
|
+
readingMinutes: 3,
|
|
77
|
+
authorName: "Ada",
|
|
78
|
+
categoryIds: ["c1"],
|
|
79
|
+
},
|
|
80
|
+
relationships: {
|
|
81
|
+
translations: { data: [{ type: "blog-post-translations", id: "t1" }] },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
included: [
|
|
85
|
+
{
|
|
86
|
+
id: "t1",
|
|
87
|
+
type: "blog-post-translations",
|
|
88
|
+
attributes: { language: "en", isOriginal: true, source: "original", aiStatus: "completed", title: "Title" },
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
} as any);
|
|
92
|
+
|
|
93
|
+
expect(post.publishedAt).toBeInstanceOf(Date);
|
|
94
|
+
expect(post.publishedAt?.toISOString()).toBe("2026-10-12T09:00:00.000Z");
|
|
95
|
+
expect(Array.isArray(post.body)).toBe(true);
|
|
96
|
+
expect(post.body).toEqual(blocks);
|
|
97
|
+
expect(post.status).toBe("published");
|
|
98
|
+
expect(post.language).toBe("it");
|
|
99
|
+
expect(post.title).toBe("Titolo");
|
|
100
|
+
expect(post.readingMinutes).toBe(3);
|
|
101
|
+
expect(post.authorName).toBe("Ada");
|
|
102
|
+
expect(post.categoryIds).toEqual(["c1"]);
|
|
103
|
+
expect(post.translations).toHaveLength(1);
|
|
104
|
+
expect(post.original?.title).toBe("Title");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("reads resolved values sent as meta", () => {
|
|
108
|
+
const post = new BlogPost().rehydrate({
|
|
109
|
+
jsonApi: {
|
|
110
|
+
id: "p",
|
|
111
|
+
type: "blog-posts",
|
|
112
|
+
attributes: { status: "draft", originalLanguage: "en" },
|
|
113
|
+
meta: { title: "From meta", categoryIds: ["c2"] },
|
|
114
|
+
},
|
|
115
|
+
included: [],
|
|
116
|
+
} as any);
|
|
117
|
+
|
|
118
|
+
expect(post.title).toBe("From meta");
|
|
119
|
+
expect(post.categoryIds).toEqual(["c2"]);
|
|
120
|
+
expect(post.publishedAt).toBeUndefined();
|
|
121
|
+
expect(post.body).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { UserInterface } from "../../user/data/user.interface";
|
|
3
|
+
import { BlogPostStatus } from "../types/blog.constants";
|
|
4
|
+
import { BlogCategoryInterface } from "./BlogCategoryInterface";
|
|
5
|
+
import { BlogPostInput, BlogPostInterface } from "./BlogPostInterface";
|
|
6
|
+
import { BlogPostTranslationInterface } from "./BlogPostTranslationInterface";
|
|
7
|
+
|
|
8
|
+
export class BlogPost extends AbstractApiData implements BlogPostInterface {
|
|
9
|
+
private _status?: BlogPostStatus;
|
|
10
|
+
private _originalLanguage?: string;
|
|
11
|
+
private _coverImage?: string;
|
|
12
|
+
private _publishedAt?: Date;
|
|
13
|
+
private _author?: UserInterface;
|
|
14
|
+
private _categories?: BlogCategoryInterface[];
|
|
15
|
+
private _translations?: BlogPostTranslationInterface[];
|
|
16
|
+
|
|
17
|
+
private _language?: string;
|
|
18
|
+
private _title?: string;
|
|
19
|
+
private _slug?: string;
|
|
20
|
+
private _summary?: string;
|
|
21
|
+
private _body?: any;
|
|
22
|
+
private _coverImageAlt?: string;
|
|
23
|
+
private _readingMinutes?: number;
|
|
24
|
+
private _authorName?: string;
|
|
25
|
+
private _categoryIds?: string[];
|
|
26
|
+
|
|
27
|
+
get status(): BlogPostStatus {
|
|
28
|
+
return this._status ?? "draft";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get originalLanguage(): string {
|
|
32
|
+
return this._originalLanguage ?? "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get coverImage(): string | undefined {
|
|
36
|
+
return this._coverImage;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get publishedAt(): Date | undefined {
|
|
40
|
+
return this._publishedAt;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get author(): UserInterface | undefined {
|
|
44
|
+
return this._author;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get categories(): BlogCategoryInterface[] {
|
|
48
|
+
return this._categories ?? [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get translations(): BlogPostTranslationInterface[] {
|
|
52
|
+
return this._translations ?? [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get original(): BlogPostTranslationInterface | undefined {
|
|
56
|
+
return this.translations.find((translation) => translation.isOriginal);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get language(): string | undefined {
|
|
60
|
+
return this._language;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get title(): string | undefined {
|
|
64
|
+
return this._title;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get slug(): string | undefined {
|
|
68
|
+
return this._slug;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get summary(): string | undefined {
|
|
72
|
+
return this._summary;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get body(): any {
|
|
76
|
+
return this._body ?? [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get coverImageAlt(): string | undefined {
|
|
80
|
+
return this._coverImageAlt;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get readingMinutes(): number | undefined {
|
|
84
|
+
return this._readingMinutes;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get authorName(): string | undefined {
|
|
88
|
+
return this._authorName;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get categoryIds(): string[] {
|
|
92
|
+
return this._categoryIds ?? [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
96
|
+
super.rehydrate(data);
|
|
97
|
+
|
|
98
|
+
const attributes = data.jsonApi.attributes ?? {};
|
|
99
|
+
const meta = data.jsonApi.meta ?? {};
|
|
100
|
+
// The resolved-language values are computed on the API. Depending on the
|
|
101
|
+
// descriptor they arrive as attributes or as meta, so both are read.
|
|
102
|
+
const resolved = (key: string) => attributes[key] ?? meta[key] ?? undefined;
|
|
103
|
+
|
|
104
|
+
this._status = attributes.status;
|
|
105
|
+
this._originalLanguage = attributes.originalLanguage;
|
|
106
|
+
this._coverImage = attributes.coverImage ?? undefined;
|
|
107
|
+
this._publishedAt = attributes.publishedAt ? new Date(attributes.publishedAt) : undefined;
|
|
108
|
+
|
|
109
|
+
this._language = resolved("language");
|
|
110
|
+
this._title = resolved("title");
|
|
111
|
+
this._slug = resolved("slug");
|
|
112
|
+
this._summary = resolved("summary");
|
|
113
|
+
const body = resolved("body");
|
|
114
|
+
this._body = body ? JSON.parse(body) : undefined;
|
|
115
|
+
this._coverImageAlt = resolved("coverImageAlt");
|
|
116
|
+
this._readingMinutes = resolved("readingMinutes");
|
|
117
|
+
this._authorName = resolved("authorName");
|
|
118
|
+
this._categoryIds = resolved("categoryIds") ?? [];
|
|
119
|
+
|
|
120
|
+
this._author = this._readIncluded(data, "author", Modules.User) as UserInterface;
|
|
121
|
+
this._categories = this._readIncluded(data, "categories", Modules.BlogCategory) as BlogCategoryInterface[];
|
|
122
|
+
this._translations = this._readIncluded(
|
|
123
|
+
data,
|
|
124
|
+
"translations",
|
|
125
|
+
Modules.BlogPostTranslation,
|
|
126
|
+
) as BlogPostTranslationInterface[];
|
|
127
|
+
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
createJsonApi(data: BlogPostInput) {
|
|
132
|
+
const response: any = {
|
|
133
|
+
data: {
|
|
134
|
+
type: Modules.BlogPost.name,
|
|
135
|
+
id: data.id,
|
|
136
|
+
attributes: {},
|
|
137
|
+
meta: {},
|
|
138
|
+
relationships: {},
|
|
139
|
+
},
|
|
140
|
+
included: [],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (data.originalLanguage !== undefined) response.data.attributes.originalLanguage = data.originalLanguage;
|
|
144
|
+
if (data.title !== undefined) response.data.attributes.title = data.title;
|
|
145
|
+
if (data.slug !== undefined) response.data.attributes.slug = data.slug;
|
|
146
|
+
if (data.summary !== undefined) response.data.attributes.summary = data.summary;
|
|
147
|
+
if (data.body !== undefined) response.data.attributes.body = JSON.stringify(data.body);
|
|
148
|
+
if (data.coverImage !== undefined) response.data.attributes.coverImage = data.coverImage;
|
|
149
|
+
if (data.coverImageAlt !== undefined) response.data.attributes.coverImageAlt = data.coverImageAlt;
|
|
150
|
+
|
|
151
|
+
if (data.categoryIds !== undefined) {
|
|
152
|
+
response.data.relationships.categories = {
|
|
153
|
+
data: data.categoryIds.map((id) => ({ type: Modules.BlogCategory.name, id })),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
}
|