@endora-commerce/mod-blog 0.100.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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/admin/api/blog-client.d.ts +70 -0
  4. package/dist/admin/api/blog-client.d.ts.map +1 -0
  5. package/dist/admin/api/blog-client.js +137 -0
  6. package/dist/admin/api/blog-client.js.map +1 -0
  7. package/dist/admin/components/CategoryTreeNode.d.ts +25 -0
  8. package/dist/admin/components/CategoryTreeNode.d.ts.map +1 -0
  9. package/dist/admin/components/CategoryTreeNode.js +14 -0
  10. package/dist/admin/components/CategoryTreeNode.js.map +1 -0
  11. package/dist/admin/components/PostStatusBadge.d.ts +7 -0
  12. package/dist/admin/components/PostStatusBadge.d.ts.map +1 -0
  13. package/dist/admin/components/PostStatusBadge.js +16 -0
  14. package/dist/admin/components/PostStatusBadge.js.map +1 -0
  15. package/dist/admin/components/RelatedPostsPicker.d.ts +11 -0
  16. package/dist/admin/components/RelatedPostsPicker.d.ts.map +1 -0
  17. package/dist/admin/components/RelatedPostsPicker.js +77 -0
  18. package/dist/admin/components/RelatedPostsPicker.js.map +1 -0
  19. package/dist/admin/components/RelatedProductsPicker.d.ts +9 -0
  20. package/dist/admin/components/RelatedProductsPicker.d.ts.map +1 -0
  21. package/dist/admin/components/RelatedProductsPicker.js +66 -0
  22. package/dist/admin/components/RelatedProductsPicker.js.map +1 -0
  23. package/dist/admin/components/TagPicker.d.ts +9 -0
  24. package/dist/admin/components/TagPicker.d.ts.map +1 -0
  25. package/dist/admin/components/TagPicker.js +66 -0
  26. package/dist/admin/components/TagPicker.js.map +1 -0
  27. package/dist/admin/index.d.ts +33 -0
  28. package/dist/admin/index.d.ts.map +1 -0
  29. package/dist/admin/index.js +114 -0
  30. package/dist/admin/index.js.map +1 -0
  31. package/dist/admin/pages/BlogCategoryEditor.d.ts +9 -0
  32. package/dist/admin/pages/BlogCategoryEditor.d.ts.map +1 -0
  33. package/dist/admin/pages/BlogCategoryEditor.js +196 -0
  34. package/dist/admin/pages/BlogCategoryEditor.js.map +1 -0
  35. package/dist/admin/pages/BlogCategoryTreePage.d.ts +9 -0
  36. package/dist/admin/pages/BlogCategoryTreePage.d.ts.map +1 -0
  37. package/dist/admin/pages/BlogCategoryTreePage.js +122 -0
  38. package/dist/admin/pages/BlogCategoryTreePage.js.map +1 -0
  39. package/dist/admin/pages/BlogPostEditor.d.ts +9 -0
  40. package/dist/admin/pages/BlogPostEditor.d.ts.map +1 -0
  41. package/dist/admin/pages/BlogPostEditor.js +314 -0
  42. package/dist/admin/pages/BlogPostEditor.js.map +1 -0
  43. package/dist/admin/pages/BlogPostListPage.d.ts +9 -0
  44. package/dist/admin/pages/BlogPostListPage.d.ts.map +1 -0
  45. package/dist/admin/pages/BlogPostListPage.js +75 -0
  46. package/dist/admin/pages/BlogPostListPage.js.map +1 -0
  47. package/dist/admin/pages/BlogTagListPage.d.ts +9 -0
  48. package/dist/admin/pages/BlogTagListPage.d.ts.map +1 -0
  49. package/dist/admin/pages/BlogTagListPage.js +145 -0
  50. package/dist/admin/pages/BlogTagListPage.js.map +1 -0
  51. package/dist/backend/entities/blog-category-language.entity.d.ts +11 -0
  52. package/dist/backend/entities/blog-category-language.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/blog-category-language.entity.js +35 -0
  54. package/dist/backend/entities/blog-category-language.entity.js.map +1 -0
  55. package/dist/backend/entities/blog-category-sales-channel.entity.d.ts +17 -0
  56. package/dist/backend/entities/blog-category-sales-channel.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/blog-category-sales-channel.entity.js +48 -0
  58. package/dist/backend/entities/blog-category-sales-channel.entity.js.map +1 -0
  59. package/dist/backend/entities/blog-category.entity.d.ts +32 -0
  60. package/dist/backend/entities/blog-category.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/blog-category.entity.js +113 -0
  62. package/dist/backend/entities/blog-category.entity.js.map +1 -0
  63. package/dist/backend/entities/blog-post-category.entity.d.ts +10 -0
  64. package/dist/backend/entities/blog-post-category.entity.d.ts.map +1 -0
  65. package/dist/backend/entities/blog-post-category.entity.js +34 -0
  66. package/dist/backend/entities/blog-post-category.entity.js.map +1 -0
  67. package/dist/backend/entities/blog-post-language.entity.d.ts +9 -0
  68. package/dist/backend/entities/blog-post-language.entity.d.ts.map +1 -0
  69. package/dist/backend/entities/blog-post-language.entity.js +33 -0
  70. package/dist/backend/entities/blog-post-language.entity.js.map +1 -0
  71. package/dist/backend/entities/blog-post-related-post.entity.d.ts +15 -0
  72. package/dist/backend/entities/blog-post-related-post.entity.d.ts.map +1 -0
  73. package/dist/backend/entities/blog-post-related-post.entity.js +42 -0
  74. package/dist/backend/entities/blog-post-related-post.entity.js.map +1 -0
  75. package/dist/backend/entities/blog-post-related-product.entity.d.ts +14 -0
  76. package/dist/backend/entities/blog-post-related-product.entity.d.ts.map +1 -0
  77. package/dist/backend/entities/blog-post-related-product.entity.js +41 -0
  78. package/dist/backend/entities/blog-post-related-product.entity.js.map +1 -0
  79. package/dist/backend/entities/blog-post-sales-channel.entity.d.ts +16 -0
  80. package/dist/backend/entities/blog-post-sales-channel.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/blog-post-sales-channel.entity.js +47 -0
  82. package/dist/backend/entities/blog-post-sales-channel.entity.js.map +1 -0
  83. package/dist/backend/entities/blog-post-tag.entity.d.ts +14 -0
  84. package/dist/backend/entities/blog-post-tag.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/blog-post-tag.entity.js +41 -0
  86. package/dist/backend/entities/blog-post-tag.entity.js.map +1 -0
  87. package/dist/backend/entities/blog-post.entity.d.ts +31 -0
  88. package/dist/backend/entities/blog-post.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/blog-post.entity.js +106 -0
  90. package/dist/backend/entities/blog-post.entity.js.map +1 -0
  91. package/dist/backend/entities/blog-tag.entity.d.ts +18 -0
  92. package/dist/backend/entities/blog-tag.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/blog-tag.entity.js +66 -0
  94. package/dist/backend/entities/blog-tag.entity.js.map +1 -0
  95. package/dist/backend/index.d.ts +100 -0
  96. package/dist/backend/index.d.ts.map +1 -0
  97. package/dist/backend/index.js +182 -0
  98. package/dist/backend/index.js.map +1 -0
  99. package/dist/backend/routes.admin.d.ts +12 -0
  100. package/dist/backend/routes.admin.d.ts.map +1 -0
  101. package/dist/backend/routes.admin.js +148 -0
  102. package/dist/backend/routes.admin.js.map +1 -0
  103. package/dist/backend/routes.storefront.d.ts +6 -0
  104. package/dist/backend/routes.storefront.d.ts.map +1 -0
  105. package/dist/backend/routes.storefront.js +72 -0
  106. package/dist/backend/routes.storefront.js.map +1 -0
  107. package/dist/backend/services/blog-asset-references.d.ts +4 -0
  108. package/dist/backend/services/blog-asset-references.d.ts.map +1 -0
  109. package/dist/backend/services/blog-asset-references.js +79 -0
  110. package/dist/backend/services/blog-asset-references.js.map +1 -0
  111. package/dist/backend/services/blog-cache.d.ts +55 -0
  112. package/dist/backend/services/blog-cache.d.ts.map +1 -0
  113. package/dist/backend/services/blog-cache.js +111 -0
  114. package/dist/backend/services/blog-cache.js.map +1 -0
  115. package/dist/backend/services/blog-category-service.d.ts +53 -0
  116. package/dist/backend/services/blog-category-service.d.ts.map +1 -0
  117. package/dist/backend/services/blog-category-service.js +446 -0
  118. package/dist/backend/services/blog-category-service.js.map +1 -0
  119. package/dist/backend/services/blog-language-reference.d.ts +18 -0
  120. package/dist/backend/services/blog-language-reference.d.ts.map +1 -0
  121. package/dist/backend/services/blog-language-reference.js +43 -0
  122. package/dist/backend/services/blog-language-reference.js.map +1 -0
  123. package/dist/backend/services/blog-post-service.d.ts +79 -0
  124. package/dist/backend/services/blog-post-service.d.ts.map +1 -0
  125. package/dist/backend/services/blog-post-service.js +539 -0
  126. package/dist/backend/services/blog-post-service.js.map +1 -0
  127. package/dist/backend/services/blog-settings-resolver.d.ts +36 -0
  128. package/dist/backend/services/blog-settings-resolver.d.ts.map +1 -0
  129. package/dist/backend/services/blog-settings-resolver.js +68 -0
  130. package/dist/backend/services/blog-settings-resolver.js.map +1 -0
  131. package/dist/backend/services/blog-slug-collision.d.ts +35 -0
  132. package/dist/backend/services/blog-slug-collision.d.ts.map +1 -0
  133. package/dist/backend/services/blog-slug-collision.js +84 -0
  134. package/dist/backend/services/blog-slug-collision.js.map +1 -0
  135. package/dist/backend/services/blog-storefront-resolver.d.ts +107 -0
  136. package/dist/backend/services/blog-storefront-resolver.d.ts.map +1 -0
  137. package/dist/backend/services/blog-storefront-resolver.js +492 -0
  138. package/dist/backend/services/blog-storefront-resolver.js.map +1 -0
  139. package/dist/backend/services/blog-tag-service.d.ts +45 -0
  140. package/dist/backend/services/blog-tag-service.d.ts.map +1 -0
  141. package/dist/backend/services/blog-tag-service.js +198 -0
  142. package/dist/backend/services/blog-tag-service.js.map +1 -0
  143. package/dist/backend/services/seed-default-category.d.ts +25 -0
  144. package/dist/backend/services/seed-default-category.d.ts.map +1 -0
  145. package/dist/backend/services/seed-default-category.js +48 -0
  146. package/dist/backend/services/seed-default-category.js.map +1 -0
  147. package/dist/backend/services/seed-roles.d.ts +41 -0
  148. package/dist/backend/services/seed-roles.d.ts.map +1 -0
  149. package/dist/backend/services/seed-roles.js +98 -0
  150. package/dist/backend/services/seed-roles.js.map +1 -0
  151. package/dist/manifest.d.ts +226 -0
  152. package/dist/manifest.d.ts.map +1 -0
  153. package/dist/manifest.js +236 -0
  154. package/dist/manifest.js.map +1 -0
  155. package/dist/migrations/20260506T081055_blog_init.d.ts +33 -0
  156. package/dist/migrations/20260506T081055_blog_init.d.ts.map +1 -0
  157. package/dist/migrations/20260506T081055_blog_init.js +260 -0
  158. package/dist/migrations/20260506T081055_blog_init.js.map +1 -0
  159. package/dist/migrations/20260903T101744_blog_namespace_block_names.d.ts +6 -0
  160. package/dist/migrations/20260903T101744_blog_namespace_block_names.d.ts.map +1 -0
  161. package/dist/migrations/20260903T101744_blog_namespace_block_names.js +58 -0
  162. package/dist/migrations/20260903T101744_blog_namespace_block_names.js.map +1 -0
  163. package/dist/migrations/index.d.ts +34 -0
  164. package/dist/migrations/index.d.ts.map +1 -0
  165. package/dist/migrations/index.js +37 -0
  166. package/dist/migrations/index.js.map +1 -0
  167. package/docs/blog/index.md +199 -0
  168. package/i18n/en.json +166 -0
  169. package/i18n/pl.json +166 -0
  170. package/package.json +108 -0
  171. package/tailwind.css +14 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Built-in settings manifest for the Blog module — feature 016 / R7.
3
+ *
4
+ * Reserves the `blog` group and registers four settings the module
5
+ * consumes through `settings.service.getValue(...)`:
6
+ *
7
+ * - blog.enabled (boolean, default true)
8
+ * - blog.url_prefix (string, default 'blog') — single URL segment
9
+ * - blog.latest_count (number, default 5) — index "latest N"
10
+ * - blog.posts_per_page (number, default 12) — pagination page size
11
+ *
12
+ * Range bounds (`latest_count` ≥ 1, `posts_per_page` ≥ 1) and the
13
+ * url-prefix shape + reserved-segments check live in
14
+ * `services/blog-settings-resolver.ts` (the Settings module's value-type
15
+ * registry only validates primitive shapes).
16
+ */
17
+ export declare const BLOG_SETTING_CODES: {
18
+ /**
19
+ * Feature 074 — the operator-activation control, and a **different switch**
20
+ * from `ENABLED` below.
21
+ *
22
+ * `blog.enabled` is channel-scoped by design ("disable the blog for a
23
+ * specific audience while leaving it on globally") and governs storefront
24
+ * URLs only; the admin blog screens keep working under it. Activation is
25
+ * platform-wide by construction — `resolveActivation` stops at
26
+ * `global_value` → `default_value` and never joins the per-channel tier
27
+ * (Constitution XII) — and it governs the whole module. Adopting the
28
+ * existing code would have collapsed the two, and would have switched the
29
+ * module off in every deployment that had set the storefront switch to
30
+ * false globally, which is the state change FR-012 forbids on merge.
31
+ */
32
+ readonly ACTIVATION: "blog.activation";
33
+ readonly ENABLED: "blog.enabled";
34
+ readonly URL_PREFIX: "blog.url_prefix";
35
+ readonly LATEST_COUNT: "blog.latest_count";
36
+ readonly POSTS_PER_PAGE: "blog.posts_per_page";
37
+ };
38
+ export declare const BLOG_DEFAULT_ENABLED = true;
39
+ export declare const BLOG_DEFAULT_URL_PREFIX = "blog";
40
+ export declare const BLOG_DEFAULT_LATEST_COUNT = 5;
41
+ export declare const BLOG_DEFAULT_POSTS_PER_PAGE = 12;
42
+ /** Module-lifecycle manifest (feature 018). */
43
+ export declare const manifest: {
44
+ id: string;
45
+ name: string;
46
+ version: string;
47
+ dependencies: string[];
48
+ description?: string | undefined;
49
+ acknowledgedDependencies?: {
50
+ moduleId: string;
51
+ port: string;
52
+ reason: string;
53
+ }[] | undefined;
54
+ nonBindingDependencies?: {
55
+ moduleId: string;
56
+ name: string;
57
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
58
+ reason: string;
59
+ whenAbsent?: string | undefined;
60
+ }[] | undefined;
61
+ activation?: {
62
+ settingCode: string;
63
+ default: boolean;
64
+ } | {
65
+ nonDeactivatable: true;
66
+ reason: string;
67
+ } | undefined;
68
+ settings?: {
69
+ moduleCode: string;
70
+ groups: {
71
+ code: string;
72
+ name: string;
73
+ salesChannelCodes?: string[] | undefined;
74
+ isSystemProtected?: boolean | undefined;
75
+ }[];
76
+ settings: {
77
+ code: string;
78
+ name: string;
79
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
80
+ defaultValue: unknown;
81
+ description?: string | undefined;
82
+ groupCode?: string | undefined;
83
+ previousDefaultValues?: unknown[] | undefined;
84
+ salesChannelCodes?: string[] | undefined;
85
+ enumOptions?: string[] | undefined;
86
+ configurationType?: string | undefined;
87
+ hidden?: boolean | undefined;
88
+ }[];
89
+ } | undefined;
90
+ i18n?: {
91
+ bundlesDir: string;
92
+ } | undefined;
93
+ docs?: false | {
94
+ dir: string;
95
+ } | undefined;
96
+ demo?: false | {
97
+ summary: string;
98
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
99
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
100
+ after?: readonly string[] | undefined;
101
+ package?: string | undefined;
102
+ } | undefined;
103
+ actions?: {
104
+ id: string;
105
+ labelKey: string;
106
+ icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
107
+ targetRoute: string;
108
+ keywords: string[];
109
+ weight: number;
110
+ descriptionKey?: string | undefined;
111
+ requiredPermission?: string | undefined;
112
+ }[] | undefined;
113
+ permissions?: {
114
+ code: string;
115
+ label: string;
116
+ module?: string | undefined;
117
+ description?: string | undefined;
118
+ requires?: string[] | undefined;
119
+ }[] | undefined;
120
+ transactionalEmails?: {
121
+ code: string;
122
+ name: string;
123
+ variables: {
124
+ key: string;
125
+ label: string;
126
+ sampleValue?: string | undefined;
127
+ description?: string | undefined;
128
+ }[];
129
+ description?: string | undefined;
130
+ group?: string | undefined;
131
+ }[] | undefined;
132
+ capabilities?: string[] | undefined;
133
+ exclusiveCapabilities?: {
134
+ key: string;
135
+ errorCode: string;
136
+ }[] | undefined;
137
+ errorCodes?: {
138
+ code: string;
139
+ tokens?: string[] | undefined;
140
+ }[] | undefined;
141
+ blocks?: {
142
+ name: string;
143
+ labelKey: string;
144
+ category: string;
145
+ contexts: ("cms" | "invoice" | "email" | "newsletter")[];
146
+ fields: Record<string, {
147
+ type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
148
+ label?: string | undefined;
149
+ required?: boolean | undefined;
150
+ options?: {
151
+ label: string;
152
+ value: string | number;
153
+ }[] | undefined;
154
+ refKind?: string | undefined;
155
+ }>;
156
+ descriptionKey?: string | undefined;
157
+ defaultProps?: Record<string, unknown> | undefined;
158
+ responsiveFields?: string[] | undefined;
159
+ previewIcon?: string | undefined;
160
+ weight?: number | undefined;
161
+ }[] | undefined;
162
+ blockCategories?: {
163
+ key: string;
164
+ titleKey: string;
165
+ contexts: ("cms" | "invoice" | "email" | "newsletter")[];
166
+ weight?: number | undefined;
167
+ visible?: boolean | undefined;
168
+ }[] | undefined;
169
+ env?: {
170
+ name: string;
171
+ describes: {
172
+ en: string;
173
+ pl: string;
174
+ };
175
+ requirement: {
176
+ kind: "required";
177
+ } | {
178
+ kind: "requiredWhen";
179
+ input: string;
180
+ equals: string;
181
+ } | {
182
+ kind: "optional";
183
+ without: {
184
+ en: string;
185
+ pl: string;
186
+ };
187
+ };
188
+ secret: boolean;
189
+ generable: boolean;
190
+ owner: {
191
+ kind: "platform";
192
+ } | {
193
+ kind: "application";
194
+ application: "admin" | "backend" | "storefront";
195
+ } | {
196
+ kind: "module";
197
+ moduleId: string;
198
+ };
199
+ consumers: ("admin" | "backend" | "storefront")[];
200
+ addressOf: "admin" | "backend" | "storefront" | null;
201
+ }[] | undefined;
202
+ };
203
+ /** Legacy export retained for backward compatibility. */
204
+ export declare const blogManifest: {
205
+ moduleCode: string;
206
+ groups: {
207
+ code: string;
208
+ name: string;
209
+ salesChannelCodes?: string[] | undefined;
210
+ isSystemProtected?: boolean | undefined;
211
+ }[];
212
+ settings: {
213
+ code: string;
214
+ name: string;
215
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
216
+ defaultValue: unknown;
217
+ description?: string | undefined;
218
+ groupCode?: string | undefined;
219
+ previousDefaultValues?: unknown[] | undefined;
220
+ salesChannelCodes?: string[] | undefined;
221
+ enumOptions?: string[] | undefined;
222
+ configurationType?: string | undefined;
223
+ hidden?: boolean | undefined;
224
+ }[];
225
+ };
226
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB;IAC7B;;;;;;;;;;;;;OAaG;;;;;;CAMK,CAAC;AAEX,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,uBAAuB,SAAS,CAAC;AAC9C,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,2BAA2B,KAAK,CAAC;AA8D9C,+CAA+C;AAC/C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwInB,CAAC;AAEH,yDAAyD;AACzD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;CAAW,CAAC"}
@@ -0,0 +1,236 @@
1
+ import { defineModuleManifest, defineModuleSettingsManifest, } from '@endora-commerce/contracts';
2
+ /**
3
+ * Built-in settings manifest for the Blog module — feature 016 / R7.
4
+ *
5
+ * Reserves the `blog` group and registers four settings the module
6
+ * consumes through `settings.service.getValue(...)`:
7
+ *
8
+ * - blog.enabled (boolean, default true)
9
+ * - blog.url_prefix (string, default 'blog') — single URL segment
10
+ * - blog.latest_count (number, default 5) — index "latest N"
11
+ * - blog.posts_per_page (number, default 12) — pagination page size
12
+ *
13
+ * Range bounds (`latest_count` ≥ 1, `posts_per_page` ≥ 1) and the
14
+ * url-prefix shape + reserved-segments check live in
15
+ * `services/blog-settings-resolver.ts` (the Settings module's value-type
16
+ * registry only validates primitive shapes).
17
+ */
18
+ export const BLOG_SETTING_CODES = {
19
+ /**
20
+ * Feature 074 — the operator-activation control, and a **different switch**
21
+ * from `ENABLED` below.
22
+ *
23
+ * `blog.enabled` is channel-scoped by design ("disable the blog for a
24
+ * specific audience while leaving it on globally") and governs storefront
25
+ * URLs only; the admin blog screens keep working under it. Activation is
26
+ * platform-wide by construction — `resolveActivation` stops at
27
+ * `global_value` → `default_value` and never joins the per-channel tier
28
+ * (Constitution XII) — and it governs the whole module. Adopting the
29
+ * existing code would have collapsed the two, and would have switched the
30
+ * module off in every deployment that had set the storefront switch to
31
+ * false globally, which is the state change FR-012 forbids on merge.
32
+ */
33
+ ACTIVATION: 'blog.activation',
34
+ ENABLED: 'blog.enabled',
35
+ URL_PREFIX: 'blog.url_prefix',
36
+ LATEST_COUNT: 'blog.latest_count',
37
+ POSTS_PER_PAGE: 'blog.posts_per_page',
38
+ };
39
+ export const BLOG_DEFAULT_ENABLED = true;
40
+ export const BLOG_DEFAULT_URL_PREFIX = 'blog';
41
+ export const BLOG_DEFAULT_LATEST_COUNT = 5;
42
+ export const BLOG_DEFAULT_POSTS_PER_PAGE = 12;
43
+ const settings = defineModuleSettingsManifest({
44
+ moduleCode: 'blog',
45
+ groups: [
46
+ {
47
+ code: 'blog',
48
+ name: 'Blog',
49
+ },
50
+ ],
51
+ settings: [
52
+ {
53
+ // Feature 074 — the operator's activation control. Platform-wide, and
54
+ // never channel-scoped: the reconciler's channel scope is additive-only,
55
+ // so a channel-scoped first commit would have no way back.
56
+ code: BLOG_SETTING_CODES.ACTIVATION,
57
+ name: 'Blog enabled',
58
+ description: 'Switches the blog on or off as a whole: the storefront blog, its admin screens and its API. Nothing is dropped — every post, category, tag and media reference stays in the database and comes back exactly as it was when you switch it on again.',
59
+ groupCode: 'blog',
60
+ valueType: 'boolean',
61
+ defaultValue: true,
62
+ },
63
+ {
64
+ code: BLOG_SETTING_CODES.ENABLED,
65
+ name: 'Blog storefront enabled',
66
+ description: 'When false, every blog URL on the storefront returns 404 in this scope. Set per Sales Channel to disable the blog for a specific audience while leaving it on globally.',
67
+ groupCode: 'blog',
68
+ valueType: 'boolean',
69
+ defaultValue: BLOG_DEFAULT_ENABLED,
70
+ },
71
+ {
72
+ code: BLOG_SETTING_CODES.URL_PREFIX,
73
+ name: 'Blog URL prefix',
74
+ description: "Single URL segment under which the blog is served (e.g. 'blog' → /blog, 'aktualnosci' → /aktualnosci). Must match ^[a-z0-9-]+$ and avoid reserved segments.",
75
+ groupCode: 'blog',
76
+ valueType: 'string',
77
+ defaultValue: BLOG_DEFAULT_URL_PREFIX,
78
+ },
79
+ {
80
+ code: BLOG_SETTING_CODES.LATEST_COUNT,
81
+ name: 'Latest posts on the blog index',
82
+ description: 'How many newest posts the blog index page shows. Sensible range 1..20.',
83
+ groupCode: 'blog',
84
+ valueType: 'number',
85
+ defaultValue: BLOG_DEFAULT_LATEST_COUNT,
86
+ },
87
+ {
88
+ code: BLOG_SETTING_CODES.POSTS_PER_PAGE,
89
+ name: 'Posts per page in Category and Tag views',
90
+ description: 'Page size for the paginated post list on Category and Tag pages. Sensible range 1..100.',
91
+ groupCode: 'blog',
92
+ valueType: 'number',
93
+ defaultValue: BLOG_DEFAULT_POSTS_PER_PAGE,
94
+ },
95
+ ],
96
+ });
97
+ /** Module-lifecycle manifest (feature 018). */
98
+ export const manifest = defineModuleManifest({
99
+ id: 'blog',
100
+ name: 'Blog',
101
+ description: 'Multi-channel, multi-language blog with categories, tags, and Page Builder posts.',
102
+ version: '1.0.0',
103
+ dependencies: [
104
+ // Two seams, both in the boot seeder, both into `admin_roles`.
105
+ //
106
+ // `systemRoleCodePort` (feature 075, Phase C) registers this module's two
107
+ // role codes as deletion-protected, where the seeder used to import
108
+ // `admin_roles`' module-level function. That registry is ungated.
109
+ //
110
+ // `adminRolePort` (feature 075, the boundary drain) is where the two role
111
+ // rows are written, in place of the three raw SQL statements that named
112
+ // `admin_roles`' own table. That one is gated, so it fails closed: with no
113
+ // `admin_roles` in the composition the seeder throws, `runBootHooks`
114
+ // re-throws as `ModuleCompositionError` and the platform exits rather than
115
+ // serving with two roles nobody granted. `admin_roles` is non-deactivatable,
116
+ // so no operator flip reaches that state — only a deployment that never
117
+ // shipped the module, which `composeModules` refuses up front. The
118
+ // declaration therefore buys install and migration order rather than a
119
+ // flip-time refusal.
120
+ 'admin_roles',
121
+ 'assets_library',
122
+ // Feature 072 (T061) — blog resolves the `requireAdmin` port, which `auth`
123
+ // owns. Every admin route in this module is gated by it, so `auth` being
124
+ // absent is not a degraded blog, it is a blog whose admin surface cannot be
125
+ // guarded. The edge existed before this declaration; it simply existed
126
+ // nowhere the lifecycle, the migration order or an operator could see it.
127
+ 'auth',
128
+ 'catalog',
129
+ 'cms',
130
+ 'dictionaries',
131
+ // `languages` owns `languageReferenceRegistry`, the registry this module
132
+ // contributes its two "which languages do posts and categories carry"
133
+ // descriptors to (feature 077, D-87). `languages` used to ask the question
134
+ // itself, with two `count(*)` statements naming this module's tables.
135
+ 'languages',
136
+ 'sales_channels',
137
+ 'settings',
138
+ ],
139
+ settings,
140
+ // Feature 074 (Constitution XVII) — a control this module never had. Before
141
+ // it, an operator could not decline the blog at all: a module with no
142
+ // activation declaration resolves as activated. Editorial content is an
143
+ // additional capability in ruling 1's sense — the catalogue stands without it
144
+ // — so it is operator-controlled, and the default is `true` so that merging
145
+ // this changes no deployment's state (FR-012).
146
+ activation: { settingCode: BLOG_SETTING_CODES.ACTIVATION, default: true },
147
+ /**
148
+ * The nineteen error codes this module owns — feature 090 Phase 3
149
+ * (`specs/090-module-owned-error-codes/contracts/error-code-declaration.md`
150
+ * §1.1). This is where the sentence for each is looked up from: `errors.<CODE>`
151
+ * in this module's own `i18n/{en,pl}.json`, which holds all nineteen in both
152
+ * languages. None of them is a `check-error-translations.ts`
153
+ * `UNTRANSLATED_ERROR_CODES` entry.
154
+ *
155
+ * The list is answer-preserving, not a judgement (§6.2 and §6.5), and it was
156
+ * not written by hand: it is the verbatim output of the runbook's step-1
157
+ * derivation over the frozen capture at
158
+ * `backend/test/fixtures/error-code-routing/chain-answers.ts`, which records
159
+ * what the prefix chain in `@endora-commerce/mod-i18n` answered at
160
+ * `49f3c6817`. Re-routing a code to a better owner is
161
+ * `specs/082-error-code-ownership/rulings.md` §9's remaining work and is
162
+ * deliberately not done here.
163
+ *
164
+ * **No code here is a shadow, and that is worth stating rather than leaving a
165
+ * reader to check.** Trap T1 exists because the chain is an ordered `if` and
166
+ * an earlier rule can claim a code a later one names — it cost `inventory` two
167
+ * codes and gave `catalog` four. `blog` has a single rule, `BLOG_`, no misc
168
+ * set, and nothing above it claims a `BLOG_`-prefixed code: the nineteen the
169
+ * chain answers with are exactly the nineteen `BLOG_`-prefixed members of
170
+ * `ERROR_CODES`, so the source reading and the answer reading coincide for this
171
+ * module. The derivation was still run from the answer, because "no shadow
172
+ * reaches me" is a conclusion of reading the whole chain and not a premise a
173
+ * migrating author is entitled to.
174
+ *
175
+ * **Five of the nineteen are raised nowhere in the tree** —
176
+ * `BLOG_ASSET_KIND_MISMATCH`, `BLOG_CATEGORY_NO_CHANNEL`,
177
+ * `BLOG_POST_NO_CHANNEL`, `BLOG_POST_NO_LANGUAGE` and `BLOG_SLUG_INVALID`. Each
178
+ * is a member of `ERROR_CODES` with a sentence in both languages that no
179
+ * `throw` in `backend/src` or `packages` can produce; the refusals they were
180
+ * written for are delivered by the schemas instead, as `VALIDATION_FAILED`
181
+ * (`test/contract/blog/admin-posts.contract.test.ts` says so in its own
182
+ * comment). Ownership follows the capture and not the raise sites, so they are
183
+ * declared: dropping them would red the progress test as `undeclared` and
184
+ * would move an answer this merge request is not allowed to move. Whether the
185
+ * codes should exist at all is a separate question from who owns them.
186
+ *
187
+ * The inverse is also true here and is T2's shape: this module raises
188
+ * `VERSION_CONFLICT` (three sites) and `INTERNAL` (two), which the chain routes
189
+ * to `core`, so they are not declared here and the sentences stay where they
190
+ * are.
191
+ *
192
+ * No `tokens`: no code here carries a refusal discriminator. Derived from the
193
+ * raise sites per the runbook's §5 — every `errors.<CODE>.<token>` key is a
194
+ * value the envelope reads out of `details.code`, no `new HttpError` in this
195
+ * module passes a `code` member at all, and the module's bundles hold no
196
+ * `errors.BLOG_*.<token>` key in the other direction.
197
+ */
198
+ errorCodes: [
199
+ { code: 'BLOG_ASSET_KIND_MISMATCH' },
200
+ { code: 'BLOG_CATEGORY_CYCLE' },
201
+ { code: 'BLOG_CATEGORY_HAS_CHILDREN' },
202
+ { code: 'BLOG_CATEGORY_IN_USE' },
203
+ { code: 'BLOG_CATEGORY_NOT_FOUND' },
204
+ { code: 'BLOG_CATEGORY_NO_CHANNEL' },
205
+ { code: 'BLOG_CATEGORY_PROTECTED' },
206
+ { code: 'BLOG_DISABLED' },
207
+ { code: 'BLOG_POST_NOT_FOUND' },
208
+ { code: 'BLOG_POST_NO_CHANNEL' },
209
+ { code: 'BLOG_POST_NO_LANGUAGE' },
210
+ { code: 'BLOG_RELATED_POST_SELF_REFERENCE' },
211
+ { code: 'BLOG_SLUG_INVALID' },
212
+ { code: 'BLOG_SLUG_TAKEN' },
213
+ { code: 'BLOG_TAG_CODE_TAKEN' },
214
+ { code: 'BLOG_TAG_IN_USE' },
215
+ { code: 'BLOG_TAG_NOT_FOUND' },
216
+ { code: 'BLOG_URL_PREFIX_INVALID' },
217
+ { code: 'BLOG_URL_PREFIX_RESERVED' },
218
+ ],
219
+ i18n: { bundlesDir: 'i18n' },
220
+ docs: { dir: 'docs' },
221
+ actions: [
222
+ {
223
+ id: 'new-post',
224
+ labelKey: 'actions.newPost.label',
225
+ descriptionKey: 'actions.newPost.description',
226
+ icon: 'BookOpen',
227
+ targetRoute: '/blog/posts/new',
228
+ requiredPermission: 'blog.write',
229
+ keywords: ['post', 'new', 'create', 'wpis', 'nowy'],
230
+ weight: 140,
231
+ },
232
+ ],
233
+ });
234
+ /** Legacy export retained for backward compatibility. */
235
+ export const blogManifest = settings;
236
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Blog module — feature 016 / data-model.md.
4
+ *
5
+ * Schema changes (one atomic migration):
6
+ * - new tables: blog_categories + blog_category_sales_channels +
7
+ * blog_category_languages + blog_posts + blog_post_sales_channels +
8
+ * blog_post_languages + blog_post_categories + blog_post_tags +
9
+ * blog_post_related_posts + blog_post_related_products + blog_tags.
10
+ * - GIN index on blog_posts.content (jsonb_path_ops) so the asset-reference
11
+ * scan stays fast (R13).
12
+ * - btree index on blog_posts(status, published_at DESC) drives the
13
+ * storefront's "newest first" listing.
14
+ * - btree index on blog_categories(parent_id, position) drives the
15
+ * per-tree fetch.
16
+ * - PARTIAL unique indexes on blog_post_sales_channels(sales_channel_id,
17
+ * slug) WHERE deleted_at IS NULL and the analogous one on
18
+ * blog_category_sales_channels — defensive guards for the cross-table
19
+ * uniqueness contract documented in research.md § R3 (the cross-table
20
+ * check itself runs at the service layer with a pg_advisory_xact_lock).
21
+ * - PARTIAL unique on blog_tags(code) WHERE deleted_at IS NULL — global
22
+ * tag-code uniqueness (R14).
23
+ *
24
+ * No seed rows; the seeded `Default` Category and the two seeded admin
25
+ * roles are inserted by runtime reconcilers in `services/seed-default-
26
+ * category.ts` and `services/seed-roles.ts` so admin edits across deploys
27
+ * stay safe (R8 + R11).
28
+ */
29
+ export declare class Migration20260506T081055BlogInit extends Migration {
30
+ up(): Promise<void>;
31
+ down(): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=20260506T081055_blog_init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260506T081055_blog_init.d.ts","sourceRoot":"","sources":["../../src/migrations/20260506T081055_blog_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,gCAAiC,SAAQ,SAAS;IAC9C,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IA6PnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAarC"}