@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,182 @@
1
+ import { effectiveState, lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { BlogCacheService } from './services/blog-cache.js';
3
+ import { BlogSettingsResolver, } from './services/blog-settings-resolver.js';
4
+ import { BlogCategoryService } from './services/blog-category-service.js';
5
+ import { BlogPostService } from './services/blog-post-service.js';
6
+ import { BlogTagService } from './services/blog-tag-service.js';
7
+ import { BlogStorefrontResolver, } from './services/blog-storefront-resolver.js';
8
+ import { registerBlogAssetReferences } from './services/blog-asset-references.js';
9
+ import { seedDefaultCategory } from './services/seed-default-category.js';
10
+ import { seedBlogRoles } from './services/seed-roles.js';
11
+ import { registerBlogAdminRoutes } from './routes.admin.js';
12
+ import { registerBlogStorefrontRoutes } from './routes.storefront.js';
13
+ import { registerBlogLanguageReferences } from './services/blog-language-reference.js';
14
+ import { BlogCategory } from './entities/blog-category.entity.js';
15
+ import { BlogCategoryLanguage } from './entities/blog-category-language.entity.js';
16
+ import { BlogCategorySalesChannel } from './entities/blog-category-sales-channel.entity.js';
17
+ import { BlogPost } from './entities/blog-post.entity.js';
18
+ import { BlogPostCategory } from './entities/blog-post-category.entity.js';
19
+ import { BlogPostLanguage } from './entities/blog-post-language.entity.js';
20
+ import { BlogPostRelatedPost } from './entities/blog-post-related-post.entity.js';
21
+ import { BlogPostRelatedProduct } from './entities/blog-post-related-product.entity.js';
22
+ import { BlogPostSalesChannel } from './entities/blog-post-sales-channel.entity.js';
23
+ import { BlogPostTag } from './entities/blog-post-tag.entity.js';
24
+ import { BlogTag } from './entities/blog-tag.entity.js';
25
+ export function registerModule(ctx) {
26
+ ctx.di.register({
27
+ // Singletons, matching what the composition root constructed once at boot.
28
+ // They take `emFactory` rather than an `EntityManager`, so a single
29
+ // instance is correct in every request and every job: the fork happens per
30
+ // call and the tenant filter reads the ambient context at query time.
31
+ blogCacheService: ctx
32
+ .asFunction(({ redis }) => redis ? new BlogCacheService(redis, {}) : undefined)
33
+ .singleton(),
34
+ blogSettingsResolver: ctx
35
+ .asFunction(() => new BlogSettingsResolver(lazyPort(ctx, 'settingsReadPort')))
36
+ .singleton(),
37
+ blogPostService: ctx
38
+ .asFunction(({ emFactory, blogCacheService }) => new BlogPostService(emFactory, blogCacheService, lazyPort(ctx, 'dictionaryValidator')))
39
+ .singleton(),
40
+ blogCategoryService: ctx
41
+ .asFunction(({ emFactory, blogCacheService }) => new BlogCategoryService(emFactory, blogCacheService, lazyPort(ctx, 'dictionaryValidator')))
42
+ .singleton(),
43
+ blogTagService: ctx
44
+ .asFunction(({ emFactory, blogCacheService }) => new BlogTagService(emFactory, blogCacheService))
45
+ .singleton(),
46
+ blogStorefrontResolver: ctx
47
+ .asFunction(({ emFactory, blogSettingsResolver, blogStorefrontDeps, blogCacheService }) => new BlogStorefrontResolver(emFactory, blogSettingsResolver, blogStorefrontDeps ?? {}, blogCacheService))
48
+ .singleton(),
49
+ });
50
+ ctx.routes(async (app) => {
51
+ const { blogPostService, blogCategoryService, blogTagService, requireAdmin } = ctx.cradle();
52
+ await registerBlogAdminRoutes(app, {
53
+ postService: blogPostService,
54
+ categoryService: blogCategoryService,
55
+ tagService: blogTagService,
56
+ ...(requireAdmin ? { requireAdmin } : {}),
57
+ });
58
+ });
59
+ ctx.routes(async (app) => {
60
+ const { blogStorefrontResolver } = ctx.cradle();
61
+ await registerBlogStorefrontRoutes(app, { storefrontResolver: blogStorefrontResolver });
62
+ });
63
+ // R9 — a `blog.*` settings write wipes the storefront blog cache, so the next
64
+ // read picks up the new value.
65
+ ctx.subscribe('settings.value_changed', async (payload) => {
66
+ const code = payload?.settingCode;
67
+ if (code === undefined || !code.startsWith('blog.'))
68
+ return;
69
+ await ctx.cradle().blogCacheService?.invalidateAll();
70
+ });
71
+ /**
72
+ * The two asset-reference edges, contributed **unprobed** (D-68).
73
+ *
74
+ * Registered before any blog write can happen, so the Library's soft-delete
75
+ * path sees them from the first request on — and it keeps seeing them while
76
+ * `blog` is switched off, deliberately. `assets_library` consults the registry
77
+ * to refuse deleting an asset something still points at, and a deactivated
78
+ * module's posts and categories still embed assets: probing here would let an
79
+ * operator delete an asset a deactivated post references, and the damage would
80
+ * only surface as a broken image when `blog` comes back on. Off is meant to be
81
+ * non-destructive and reversible (Constitution XVII), which is why the
82
+ * registry's own enumeration policy is `honoured` rather than `skip` for these
83
+ * descriptors — a scanner is integrity, not a surface.
84
+ *
85
+ * A second hook rather than a line in the one below, because the two answer
86
+ * different questions: this one must run whatever the module's state, that one
87
+ * must not run while it is off. `product_feeds/backend.ts` is the shipped
88
+ * example of the same pair.
89
+ */
90
+ ctx.onBoot(() => {
91
+ const { assetReferenceRegistry, emFactory } = ctx.cradle();
92
+ registerBlogAssetReferences(assetReferenceRegistry, emFactory);
93
+ });
94
+ // Idempotent boot reconcilers (R11 / R8): on a rerun they preserve every
95
+ // admin edit and only fill in what is missing.
96
+ ctx.onBoot(async () => {
97
+ // Presence is decided here — first, and outside anything that could catch it
98
+ // (issue #146, D-68). These two seeds write rows: a category and two roles.
99
+ // A switched-off module writing at every boot is "behaves as if never
100
+ // installed" failing, and a boot hook has no caller to answer, so the
101
+ // question is asked rather than thrown. `runBootHooks` re-throws as
102
+ // `ModuleCompositionError`, which `index.ts` turns into `process.exit(1)`.
103
+ if (!effectiveState.isPresent('blog'))
104
+ return;
105
+ const { emFactory } = ctx.cradle();
106
+ await seedDefaultCategory(emFactory);
107
+ // Two ports into `admin_roles`, and they are different kinds of seam.
108
+ //
109
+ // `adminRolePort` is a **call**: it is where the two role rows are written
110
+ // since feature 075 drained this module's cross-module-import shard, in
111
+ // place of three raw SQL statements against `admin_roles`' own table. It is
112
+ // gated, so the seam fails closed — but `admin_roles` declares itself
113
+ // non-deactivatable, so the state that gate answers "no" in is one an
114
+ // operator cannot reach: the only way this composition runs without it is a
115
+ // deployment that never shipped it, and `composeModules` refuses that
116
+ // before the first module registers. Resolving it here rather than in a
117
+ // factory keeps it lazy, so composition asks the gate nothing.
118
+ //
119
+ // `systemRoleCodePort` is `admin_roles`' ungated contribution registry, so
120
+ // resolving it asks no gate at all — the probe above is this module's own
121
+ // answer about its own seeds, not a question about either seam's owner.
122
+ await seedBlogRoles(lazyPort(ctx, 'adminRolePort'), lazyPort(ctx, 'systemRoleCodePort'));
123
+ });
124
+ /**
125
+ * This module's rows carry a language code, so it answers "who still points at
126
+ * this language?" about its own tables (feature 077, D-87), where the owner used
127
+ * to count them with SQL naming this module's tables.
128
+ *
129
+ * A **contribution** hook: it pushes an inert descriptor into `languageReferenceRegistry`,
130
+ * an ungated registry, and carries no presence probe (D-62/D-68). Probing
131
+ * would be wrong in the dangerous direction — a switched-off module still owns
132
+ * the rows, so its language must still refuse the delete, which is the
133
+ * enumeration policy the registry states.
134
+ */
135
+ ctx.onBoot(() => {
136
+ registerBlogLanguageReferences(lazyPort(ctx, 'languageReferenceRegistry'), ctx.cradle().emFactory);
137
+ });
138
+ }
139
+ /**
140
+ * The module's persisted entity classes, on the `./backend` subpath, as one
141
+ * array and **no named class export** (D-168).
142
+ *
143
+ * This is the shape the platform already reads, in two places: the boot-time
144
+ * loader (`src/packages/package-runtime.ts`, `exported['entities']`) and the
145
+ * static declaration reader (`scripts/lib/package-declarations.ts`), which is
146
+ * the third source of the `table→owner` map and the package pass of
147
+ * `check-entity-tenant-classification`. Until D-168 this file published eleven
148
+ * `export *` lines instead, which satisfied only the committed host registry —
149
+ * the one path that stops being taken the day this module is *installed*
150
+ * rather than linked, at which point the loader read `undefined`, returned
151
+ * `[]`, and registered zero entities without a word.
152
+ *
153
+ * The absence of a named export is the load-bearing half. With it,
154
+ * `import type { BlogPost } from '@endora-commerce/mod-blog/backend'` compiles
155
+ * in any consumer — ours, a deployment's, a stranger's — and only a check whose
156
+ * population is *this* repository could ever object. Without it, that import is
157
+ * TS2305 in the consumer's own tree, which is Principle I holding by
158
+ * construction. Nothing legitimate is lost: D-32 already forbids an ORM
159
+ * relation from another module into these classes, and every other cross-module
160
+ * read goes through a contract type. An `EntityClassLike[]` is a value the
161
+ * host's ORM registers and nothing else can usefully name.
162
+ *
163
+ * Identity matters more here than anywhere else in the package: MikroORM keys
164
+ * its metadata on the class, so a consumer that reached these files by a second
165
+ * specifier would register a second `BlogPost` and lose one of them at
166
+ * discovery (D-160.6, measured). One array behind one declared subpath is the
167
+ * only way in.
168
+ */
169
+ export const entities = [
170
+ BlogCategory,
171
+ BlogCategoryLanguage,
172
+ BlogCategorySalesChannel,
173
+ BlogPost,
174
+ BlogPostCategory,
175
+ BlogPostLanguage,
176
+ BlogPostRelatedPost,
177
+ BlogPostRelatedProduct,
178
+ BlogPostSalesChannel,
179
+ BlogPostTag,
180
+ BlogTag,
181
+ ];
182
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { BlogCategoryService } from './services/blog-category-service.js';
3
+ import type { BlogPostService } from './services/blog-post-service.js';
4
+ import type { BlogTagService } from './services/blog-tag-service.js';
5
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ export declare function registerBlogAdminRoutes(app: FastifyInstance, deps: {
7
+ postService: BlogPostService;
8
+ categoryService: BlogCategoryService;
9
+ tagService: BlogTagService;
10
+ requireAdmin?: RequireAdminFactory;
11
+ }): Promise<void>;
12
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAgB/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AA2C5E,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE;IACJ,WAAW,EAAE,eAAe,CAAC;IAC7B,eAAe,EAAE,mBAAmB,CAAC;IACrC,UAAU,EAAE,cAAc,CAAC;IAC3B,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC,GACA,OAAO,CAAC,IAAI,CAAC,CA6Of"}
@@ -0,0 +1,148 @@
1
+ import { createBlogCategoryRequestSchema, createBlogPostRequestSchema, createBlogTagRequestSchema, patchBlogCategoryRequestSchema, patchBlogPostRequestSchema, patchBlogTagRequestSchema, putBlogCategoryDescriptionRequestSchema, putBlogCategoryTreeMovesRequestSchema, putBlogPostContentRequestSchema, setBlogPostRelatedPostsRequestSchema, setBlogPostRelatedProductsRequestSchema, setBlogPostTagsRequestSchema, } from '@endora-commerce/contracts';
2
+ import { z } from 'zod';
3
+ const versionOnlySchema = z.object({ version: z.number().int() });
4
+ const versionQuerySchema = z.object({
5
+ version: z.coerce.number().int().optional(),
6
+ });
7
+ /**
8
+ * DELETE doesn't carry a body (Fetch API limitation in some browsers),
9
+ * so the admin client can pass version as a query parameter. The backend
10
+ * accepts either form: body wins when present, query is the fallback.
11
+ */
12
+ function readDeleteVersion(body, query) {
13
+ if (body && typeof body === 'object') {
14
+ const bv = body.version;
15
+ if (typeof bv === 'number')
16
+ return bv;
17
+ }
18
+ const q = versionQuerySchema.safeParse(query ?? {});
19
+ if (q.success)
20
+ return q.data.version;
21
+ return undefined;
22
+ }
23
+ const listFiltersSchema = z.object({
24
+ q: z.string().optional(),
25
+ status: z.enum(['draft', 'published', 'archived']).optional(),
26
+ salesChannelId: z.string().uuid().optional(),
27
+ categoryId: z.string().uuid().optional(),
28
+ tagId: z.string().uuid().optional(),
29
+ language: z.string().min(2).optional(),
30
+ page: z.coerce.number().int().positive().optional(),
31
+ perPage: z.coerce.number().int().positive().max(100).optional(),
32
+ });
33
+ const tagListFiltersSchema = z.object({
34
+ q: z.string().optional(),
35
+ page: z.coerce.number().int().positive().optional(),
36
+ perPage: z.coerce.number().int().positive().max(100).optional(),
37
+ });
38
+ export async function registerBlogAdminRoutes(app, deps) {
39
+ const requireRead = deps.requireAdmin?.('blog.read') ?? (async () => { });
40
+ const requireWrite = deps.requireAdmin?.('blog.write') ?? (async () => { });
41
+ // ── Posts ──────────────────────────────────────────────────────────
42
+ app.get('/api/v1/admin/blog/posts', { preHandler: requireRead }, async (request) => {
43
+ const filters = listFiltersSchema.parse(request.query ?? {});
44
+ return deps.postService.list(filters);
45
+ });
46
+ app.post('/api/v1/admin/blog/posts', { preHandler: requireWrite }, async (request, reply) => {
47
+ const body = createBlogPostRequestSchema.parse(request.body);
48
+ const created = await deps.postService.create(body);
49
+ return reply.code(201).send({ data: created });
50
+ });
51
+ app.get('/api/v1/admin/blog/posts/:id', { preHandler: requireRead }, async (request) => ({ data: await deps.postService.getById(request.params.id) }));
52
+ app.patch('/api/v1/admin/blog/posts/:id', { preHandler: requireWrite }, async (request) => {
53
+ const body = patchBlogPostRequestSchema.parse(request.body);
54
+ return { data: await deps.postService.patch(request.params.id, body) };
55
+ });
56
+ app.put('/api/v1/admin/blog/posts/:id/content', { preHandler: requireWrite }, async (request) => {
57
+ const body = putBlogPostContentRequestSchema.parse(request.body);
58
+ return { data: await deps.postService.setContent(request.params.id, body) };
59
+ });
60
+ app.put('/api/v1/admin/blog/posts/:id/tags', { preHandler: requireWrite }, async (request) => {
61
+ const body = setBlogPostTagsRequestSchema.parse(request.body);
62
+ return {
63
+ data: await deps.postService.setTags(request.params.id, body.tagIds, body.version),
64
+ };
65
+ });
66
+ app.put('/api/v1/admin/blog/posts/:id/related-posts', { preHandler: requireWrite }, async (request) => {
67
+ const body = setBlogPostRelatedPostsRequestSchema.parse(request.body);
68
+ return {
69
+ data: await deps.postService.setRelatedPosts(request.params.id, body.relatedPostIds, body.version),
70
+ };
71
+ });
72
+ app.put('/api/v1/admin/blog/posts/:id/related-products', { preHandler: requireWrite }, async (request) => {
73
+ const body = setBlogPostRelatedProductsRequestSchema.parse(request.body);
74
+ return {
75
+ data: await deps.postService.setRelatedProducts(request.params.id, body.productIds, body.version),
76
+ };
77
+ });
78
+ app.post('/api/v1/admin/blog/posts/:id/publish', { preHandler: requireWrite }, async (request) => {
79
+ const body = versionOnlySchema.parse(request.body ?? {});
80
+ return { data: await deps.postService.publish(request.params.id, body.version) };
81
+ });
82
+ app.post('/api/v1/admin/blog/posts/:id/unpublish', { preHandler: requireWrite }, async (request) => {
83
+ const body = versionOnlySchema.parse(request.body ?? {});
84
+ return { data: await deps.postService.unpublish(request.params.id, body.version) };
85
+ });
86
+ app.post('/api/v1/admin/blog/posts/:id/archive', { preHandler: requireWrite }, async (request) => {
87
+ const body = versionOnlySchema.parse(request.body ?? {});
88
+ return { data: await deps.postService.archive(request.params.id, body.version) };
89
+ });
90
+ app.delete('/api/v1/admin/blog/posts/:id', { preHandler: requireWrite }, async (request, reply) => {
91
+ const version = readDeleteVersion(request.body, request.query) ??
92
+ versionOnlySchema.parse(request.body ?? { version: 0 }).version;
93
+ const result = await deps.postService.softDelete(request.params.id, version);
94
+ return reply.code(200).send({ data: result });
95
+ });
96
+ app.get('/api/v1/admin/blog/posts/:id/inbound-references', { preHandler: requireRead }, async (request) => ({
97
+ data: await deps.postService.getInboundReferences(request.params.id),
98
+ }));
99
+ // ── Categories ─────────────────────────────────────────────────────
100
+ app.get('/api/v1/admin/blog/categories', { preHandler: requireRead }, async () => deps.categoryService.getTree());
101
+ app.get('/api/v1/admin/blog/categories/:id', { preHandler: requireRead }, async (request) => ({ data: await deps.categoryService.getById(request.params.id) }));
102
+ app.post('/api/v1/admin/blog/categories', { preHandler: requireWrite }, async (request, reply) => {
103
+ const body = createBlogCategoryRequestSchema.parse(request.body);
104
+ const created = await deps.categoryService.create(body);
105
+ return reply.code(201).send({ data: created });
106
+ });
107
+ app.patch('/api/v1/admin/blog/categories/:id', { preHandler: requireWrite }, async (request) => {
108
+ const body = patchBlogCategoryRequestSchema.parse(request.body);
109
+ return { data: await deps.categoryService.patch(request.params.id, body) };
110
+ });
111
+ app.put('/api/v1/admin/blog/categories/:id/description', { preHandler: requireWrite }, async (request) => {
112
+ const body = putBlogCategoryDescriptionRequestSchema.parse(request.body);
113
+ return { data: await deps.categoryService.setDescription(request.params.id, body) };
114
+ });
115
+ app.put('/api/v1/admin/blog/categories/tree', { preHandler: requireWrite }, async (request) => {
116
+ const body = putBlogCategoryTreeMovesRequestSchema.parse(request.body);
117
+ return await deps.categoryService.applyTreeMoves(body.moves);
118
+ });
119
+ app.delete('/api/v1/admin/blog/categories/:id', { preHandler: requireWrite }, async (request, reply) => {
120
+ const version = readDeleteVersion(request.body, request.query);
121
+ await deps.categoryService.softDelete(request.params.id, version);
122
+ return reply.code(204).send();
123
+ });
124
+ // ── Tags ───────────────────────────────────────────────────────────
125
+ app.get('/api/v1/admin/blog/tags', { preHandler: requireRead }, async (request) => {
126
+ const filters = tagListFiltersSchema.parse(request.query ?? {});
127
+ return deps.tagService.list(filters);
128
+ });
129
+ app.get('/api/v1/admin/blog/tags/:id', { preHandler: requireRead }, async (request) => ({ data: await deps.tagService.getById(request.params.id) }));
130
+ app.post('/api/v1/admin/blog/tags', { preHandler: requireWrite }, async (request, reply) => {
131
+ const body = createBlogTagRequestSchema.parse(request.body);
132
+ const created = await deps.tagService.create(body);
133
+ return reply.code(201).send({ data: created });
134
+ });
135
+ app.patch('/api/v1/admin/blog/tags/:id', { preHandler: requireWrite }, async (request) => {
136
+ const body = patchBlogTagRequestSchema.parse(request.body);
137
+ return { data: await deps.tagService.patch(request.params.id, body) };
138
+ });
139
+ app.delete('/api/v1/admin/blog/tags/:id', { preHandler: requireWrite }, async (request, reply) => {
140
+ const version = readDeleteVersion(request.body, request.query);
141
+ await deps.tagService.softDelete(request.params.id, version);
142
+ return reply.code(204).send();
143
+ });
144
+ app.get('/api/v1/admin/blog/tags/:id/inbound-references', { preHandler: requireRead }, async (request) => ({
145
+ data: await deps.tagService.getInboundReferences(request.params.id),
146
+ }));
147
+ }
148
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { BlogStorefrontResolver } from './services/blog-storefront-resolver.js';
3
+ export declare function registerBlogStorefrontRoutes(app: FastifyInstance, deps: {
4
+ storefrontResolver: BlogStorefrontResolver;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=routes.storefront.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.d.ts","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAK/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAYrF,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE;IAAE,kBAAkB,EAAE,sBAAsB,CAAA;CAAE,GACnD,OAAO,CAAC,IAAI,CAAC,CAgDf"}
@@ -0,0 +1,72 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { z } from 'zod';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { getResolvedChannel } from '@endora-commerce/platform/kernel';
5
+ const bySlugQuerySchema = z.object({
6
+ slug: z.string().min(1).max(160),
7
+ page: z.coerce.number().int().positive().optional(),
8
+ });
9
+ const tagByCodeQuerySchema = z.object({
10
+ code: z.string().min(1).max(64),
11
+ page: z.coerce.number().int().positive().optional(),
12
+ });
13
+ export async function registerBlogStorefrontRoutes(app, deps) {
14
+ app.get('/api/v1/blog/by-channel', async (request) => {
15
+ const { language } = readContext(request);
16
+ const resolved = await deps.storefrontResolver.getIndex({
17
+ resolvedChannel: resolvedChannel(request),
18
+ ...(language !== undefined ? { language } : {}),
19
+ });
20
+ if (!resolved) {
21
+ throw new HttpError(404, ERROR_CODES.BLOG_DISABLED, 'Blog is disabled or not configured.');
22
+ }
23
+ return { data: resolved };
24
+ });
25
+ app.get('/api/v1/blog/by-slug', async (request) => {
26
+ const query = bySlugQuerySchema.parse(request.query ?? {});
27
+ const { language } = readContext(request);
28
+ const resolved = await deps.storefrontResolver.getBySlug(query.slug, {
29
+ resolvedChannel: resolvedChannel(request),
30
+ ...(language !== undefined ? { language } : {}),
31
+ ...(query.page !== undefined ? { page: query.page } : {}),
32
+ });
33
+ if (!resolved) {
34
+ throw new HttpError(404, ERROR_CODES.BLOG_POST_NOT_FOUND, 'No blog Post or Category at that slug, or the blog is disabled in this scope.');
35
+ }
36
+ return { data: resolved };
37
+ });
38
+ app.get('/api/v1/blog/tag-by-code', async (request) => {
39
+ const query = tagByCodeQuerySchema.parse(request.query ?? {});
40
+ const { language } = readContext(request);
41
+ const resolved = await deps.storefrontResolver.getTagByCode(query.code, {
42
+ resolvedChannel: resolvedChannel(request),
43
+ ...(language !== undefined ? { language } : {}),
44
+ ...(query.page !== undefined ? { page: query.page } : {}),
45
+ });
46
+ if (!resolved) {
47
+ throw new HttpError(404, ERROR_CODES.BLOG_TAG_NOT_FOUND, 'No blog Tag at that code, or the blog is disabled in this scope.');
48
+ }
49
+ return { data: resolved };
50
+ });
51
+ }
52
+ function readContext(request) {
53
+ const headers = request.headers;
54
+ const language = readHeader(headers['x-blog-language']) ??
55
+ readHeader(headers['accept-language'])?.split(',')[0]?.trim();
56
+ return { language };
57
+ }
58
+ /**
59
+ * Read the sales channel already resolved by the sales-channel resolver
60
+ * middleware for this request. Feature 053: routes no longer re-parse the
61
+ * `x-sales-channel` header nor re-query `sales_channels`.
62
+ */
63
+ function resolvedChannel(request) {
64
+ const ch = getResolvedChannel(request);
65
+ return { id: ch.id, code: ch.code, defaultLanguage: ch.defaultLanguage };
66
+ }
67
+ function readHeader(value) {
68
+ if (Array.isArray(value))
69
+ return value[0];
70
+ return value;
71
+ }
72
+ //# sourceMappingURL=routes.storefront.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.js","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AssetReferenceRegistryPort } from '@endora-commerce/contracts';
3
+ export declare function registerBlogAssetReferences(registry: AssetReferenceRegistryPort, emFactory: () => EntityManager): void;
4
+ //# sourceMappingURL=blog-asset-references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-asset-references.d.ts","sourceRoot":"","sources":["../../../src/backend/services/blog-asset-references.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,4BAA4B,CAAC;AAEpC,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,0BAA0B,EACpC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAGN"}
@@ -0,0 +1,79 @@
1
+ // Blog → AssetReferenceRegistry descriptor — feature 016 / T018.
2
+ //
3
+ // Registers two reference edges with the Library Asset reference registry
4
+ // so the Library's soft-delete path (feature 013 / FR-030) blocks deletion
5
+ // of any asset still pointed at by:
6
+ //
7
+ // - blog_categories.main_image_asset_id (column-level reference)
8
+ // - jsonb references inside blog_posts.content or
9
+ // blog_categories.description (Page Builder asset embeds)
10
+ //
11
+ // The jsonb edge uses `jsonb_path_exists` driven by the GIN index
12
+ // `idx_blog_posts_content_refs` and the matching index on
13
+ // `blog_categories.description` (created via the GIN's content path).
14
+ export function registerBlogAssetReferences(registry, emFactory) {
15
+ registry.register(blogMainImageReferenceDescriptor(emFactory));
16
+ registry.register(blogContentTreeReferenceDescriptor(emFactory));
17
+ }
18
+ function blogMainImageReferenceDescriptor(emFactory) {
19
+ return {
20
+ ownerModuleId: 'blog',
21
+ async findReferences(assetIds) {
22
+ if (assetIds.length === 0)
23
+ return [];
24
+ const em = emFactory();
25
+ const placeholders = assetIds.map(() => '?').join(', ');
26
+ const rows = (await em.execute(`select id::text as id, slug
27
+ from blog_categories
28
+ where main_image_asset_id in (${placeholders})
29
+ and deleted_at is null`, [...assetIds]));
30
+ return rows.map((r) => ({
31
+ kind: 'blog_category_main_image',
32
+ entityId: r.id,
33
+ label: `Blog category "${r.slug}"`,
34
+ }));
35
+ },
36
+ };
37
+ }
38
+ function blogContentTreeReferenceDescriptor(emFactory) {
39
+ return {
40
+ ownerModuleId: 'blog',
41
+ async findReferences(assetIds) {
42
+ if (assetIds.length === 0)
43
+ return [];
44
+ const em = emFactory();
45
+ const out = [];
46
+ for (const aidRaw of assetIds) {
47
+ // The asset id is a UUID; this regex is a defensive pre-filter to
48
+ // keep the literal-injected jsonpath safe (the same idiom feature
49
+ // 014's CMS asset-references uses).
50
+ const aid = aidRaw.replace(/[^0-9a-fA-F-]/g, '');
51
+ const path = `'$.** ? (@ == "${aid}")'::jsonpath`;
52
+ const rows = (await em.execute(`select id::text as id, slug, 'blog_post' as kind
53
+ from blog_posts
54
+ where deleted_at is null
55
+ and jsonb_path_exists(content, ${path})
56
+ union all
57
+ select id::text as id, slug, 'blog_category' as kind
58
+ from blog_categories
59
+ where deleted_at is null
60
+ and description is not null
61
+ and jsonb_path_exists(description, ${path})`));
62
+ for (const r of rows) {
63
+ const label = r.kind === 'blog_post'
64
+ ? `Blog post "${r.slug}"`
65
+ : `Blog category description "${r.slug}"`;
66
+ out.push({
67
+ kind: r.kind === 'blog_post'
68
+ ? 'blog_post_content'
69
+ : 'blog_category_description',
70
+ entityId: r.id,
71
+ label,
72
+ });
73
+ }
74
+ }
75
+ return out;
76
+ },
77
+ };
78
+ }
79
+ //# sourceMappingURL=blog-asset-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-asset-references.js","sourceRoot":"","sources":["../../../src/backend/services/blog-asset-references.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,oCAAoC;AACpC,EAAE;AACF,sEAAsE;AACtE,oDAAoD;AACpD,8DAA8D;AAC9D,EAAE;AACF,kEAAkE;AAClE,0DAA0D;AAC1D,sEAAsE;AAStE,MAAM,UAAU,2BAA2B,CACzC,QAAoC,EACpC,SAA8B;IAE9B,QAAQ,CAAC,QAAQ,CAAC,gCAAgC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/D,QAAQ,CAAC,QAAQ,CAAC,kCAAkC,CAAC,SAAS,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,gCAAgC,CACvC,SAA8B;IAE9B,OAAO;QACL,aAAa,EAAE,MAAM;QACrB,KAAK,CAAC,cAAc,CAAC,QAAkB;YACrC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACrC,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;YACvB,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAC5B;;0CAEkC,YAAY;mCACnB,EAC3B,CAAC,GAAG,QAAQ,CAAC,CACd,CAAwC,CAAC;YAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtB,IAAI,EAAE,0BAA0B;gBAChC,QAAQ,EAAE,CAAC,CAAC,EAAE;gBACd,KAAK,EAAE,kBAAkB,CAAC,CAAC,IAAI,GAAG;aACnC,CAAC,CAAC,CAAC;QACN,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,kCAAkC,CACzC,SAA8B;IAE9B,OAAO;QACL,aAAa,EAAE,MAAM;QACrB,KAAK,CAAC,cAAc,CAAC,QAAkB;YACrC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACrC,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;YACvB,MAAM,GAAG,GAAqB,EAAE,CAAC;YACjC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;gBAC9B,kEAAkE;gBAClE,kEAAkE;gBAClE,oCAAoC;gBACpC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;gBACjD,MAAM,IAAI,GAAG,kBAAkB,GAAG,eAAe,CAAC;gBAElD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAC5B;;;+CAGqC,IAAI;;;;;;mDAMA,IAAI,GAAG,CACjD,CAAsD,CAAC;gBAExD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;oBACrB,MAAM,KAAK,GACT,CAAC,CAAC,IAAI,KAAK,WAAW;wBACpB,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,GAAG;wBACzB,CAAC,CAAC,8BAA8B,CAAC,CAAC,IAAI,GAAG,CAAC;oBAC9C,GAAG,CAAC,IAAI,CAAC;wBACP,IAAI,EACF,CAAC,CAAC,IAAI,KAAK,WAAW;4BACpB,CAAC,CAAC,mBAAmB;4BACrB,CAAC,CAAC,2BAA2B;wBACjC,QAAQ,EAAE,CAAC,CAAC,EAAE;wBACd,KAAK;qBACN,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { Redis } from 'ioredis';
2
+ /**
3
+ * Storefront read-through cache for the Blog module — feature 016 / T020.
4
+ *
5
+ * Mirrors feature 014's `CmsCache` and feature 015's `MegamenuCache` shape
6
+ * exactly: same key-prefix scheme, same default TTL, same SCAN-based
7
+ * coarse invalidation. Keys live under `blog:v1:<channelCode>:<language>`.
8
+ *
9
+ * Per `research.md` § R9 the cache holds four kinds of entry:
10
+ *
11
+ * blog:v1:<channel>:<language>:index
12
+ * blog:v1:<channel>:<language>:category:<slug>:p<page>
13
+ * blog:v1:<channel>:<language>:post:<slug>
14
+ * blog:v1:<channel>:<language>:tag:<code>:p<page>
15
+ *
16
+ * Invalidation is per-write (publishing a Post invalidates that post's
17
+ * key, every category page that contained it, every tag page that
18
+ * contained it, and the index for every channel the post is in). Settings
19
+ * writes invalidate the whole channel keyspace via `invalidateChannel`.
20
+ */
21
+ export declare const BLOG_CACHE_KEY_PREFIX = "blog:v1:";
22
+ export declare const BLOG_CACHE_DEFAULT_TTL_SECONDS: number;
23
+ export interface BlogCacheOptions {
24
+ ttlSeconds?: number;
25
+ /** When false, get/set are short-circuit no-ops (useful for tests). */
26
+ enabled?: boolean;
27
+ }
28
+ export declare class BlogCacheService {
29
+ private readonly redis;
30
+ private readonly ttlSeconds;
31
+ private readonly enabled;
32
+ constructor(redis: Redis, options?: BlogCacheOptions);
33
+ static composeIndexKey(channelCode: string, language: string): string;
34
+ static composeCategoryKey(channelCode: string, language: string, slug: string, page: number): string;
35
+ static composePostKey(channelCode: string, language: string, slug: string): string;
36
+ static composeTagKey(channelCode: string, language: string, code: string, page: number): string;
37
+ get<T>(key: string): Promise<T | null>;
38
+ set<T>(key: string, payload: T): Promise<void>;
39
+ /** Drop one key (used after the post-by-slug write). */
40
+ invalidateKey(key: string): Promise<void>;
41
+ /** Drop every key under a channel + language scope. */
42
+ invalidateScope(channelCode: string, language: string): Promise<void>;
43
+ /** Drop every key under a channel (covers every language). Used on settings writes. */
44
+ invalidateChannel(channelCode: string): Promise<void>;
45
+ /** Drop the entire blog keyspace. Used by tests and by global config changes. */
46
+ invalidateAll(): Promise<void>;
47
+ /** Drop every entry that mentions a specific category slug across every language of a channel. */
48
+ invalidateCategory(channelCode: string, slug: string): Promise<void>;
49
+ /** Drop every entry that mentions a specific post slug across every language of a channel. */
50
+ invalidatePost(channelCode: string, slug: string): Promise<void>;
51
+ /** Drop every entry that mentions a specific tag code across every language of a channel. */
52
+ invalidateTag(channelCode: string, code: string): Promise<void>;
53
+ private scanDelete;
54
+ }
55
+ //# sourceMappingURL=blog-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog-cache.d.ts","sourceRoot":"","sources":["../../../src/backend/services/blog-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAChD,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAMD,qBAAa,gBAAgB;IAIf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEL,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,gBAAqB;IAKzE,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIrE,MAAM,CAAC,kBAAkB,CACvB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM;IAIT,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAIlF,MAAM,CAAC,aAAa,CAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM;IAIH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAYtC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,wDAAwD;IAClD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,uDAAuD;IACjD,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E,uFAAuF;IACjF,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,iFAAiF;IAC3E,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,kGAAkG;IAC5F,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1E,8FAA8F;IACxF,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtE,6FAA6F;IACvF,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIvD,UAAU;CASzB"}