@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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/api/blog-client.d.ts +70 -0
- package/dist/admin/api/blog-client.d.ts.map +1 -0
- package/dist/admin/api/blog-client.js +137 -0
- package/dist/admin/api/blog-client.js.map +1 -0
- package/dist/admin/components/CategoryTreeNode.d.ts +25 -0
- package/dist/admin/components/CategoryTreeNode.d.ts.map +1 -0
- package/dist/admin/components/CategoryTreeNode.js +14 -0
- package/dist/admin/components/CategoryTreeNode.js.map +1 -0
- package/dist/admin/components/PostStatusBadge.d.ts +7 -0
- package/dist/admin/components/PostStatusBadge.d.ts.map +1 -0
- package/dist/admin/components/PostStatusBadge.js +16 -0
- package/dist/admin/components/PostStatusBadge.js.map +1 -0
- package/dist/admin/components/RelatedPostsPicker.d.ts +11 -0
- package/dist/admin/components/RelatedPostsPicker.d.ts.map +1 -0
- package/dist/admin/components/RelatedPostsPicker.js +77 -0
- package/dist/admin/components/RelatedPostsPicker.js.map +1 -0
- package/dist/admin/components/RelatedProductsPicker.d.ts +9 -0
- package/dist/admin/components/RelatedProductsPicker.d.ts.map +1 -0
- package/dist/admin/components/RelatedProductsPicker.js +66 -0
- package/dist/admin/components/RelatedProductsPicker.js.map +1 -0
- package/dist/admin/components/TagPicker.d.ts +9 -0
- package/dist/admin/components/TagPicker.d.ts.map +1 -0
- package/dist/admin/components/TagPicker.js +66 -0
- package/dist/admin/components/TagPicker.js.map +1 -0
- package/dist/admin/index.d.ts +33 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +114 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/BlogCategoryEditor.d.ts +9 -0
- package/dist/admin/pages/BlogCategoryEditor.d.ts.map +1 -0
- package/dist/admin/pages/BlogCategoryEditor.js +196 -0
- package/dist/admin/pages/BlogCategoryEditor.js.map +1 -0
- package/dist/admin/pages/BlogCategoryTreePage.d.ts +9 -0
- package/dist/admin/pages/BlogCategoryTreePage.d.ts.map +1 -0
- package/dist/admin/pages/BlogCategoryTreePage.js +122 -0
- package/dist/admin/pages/BlogCategoryTreePage.js.map +1 -0
- package/dist/admin/pages/BlogPostEditor.d.ts +9 -0
- package/dist/admin/pages/BlogPostEditor.d.ts.map +1 -0
- package/dist/admin/pages/BlogPostEditor.js +314 -0
- package/dist/admin/pages/BlogPostEditor.js.map +1 -0
- package/dist/admin/pages/BlogPostListPage.d.ts +9 -0
- package/dist/admin/pages/BlogPostListPage.d.ts.map +1 -0
- package/dist/admin/pages/BlogPostListPage.js +75 -0
- package/dist/admin/pages/BlogPostListPage.js.map +1 -0
- package/dist/admin/pages/BlogTagListPage.d.ts +9 -0
- package/dist/admin/pages/BlogTagListPage.d.ts.map +1 -0
- package/dist/admin/pages/BlogTagListPage.js +145 -0
- package/dist/admin/pages/BlogTagListPage.js.map +1 -0
- package/dist/backend/entities/blog-category-language.entity.d.ts +11 -0
- package/dist/backend/entities/blog-category-language.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-category-language.entity.js +35 -0
- package/dist/backend/entities/blog-category-language.entity.js.map +1 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.d.ts +17 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.js +48 -0
- package/dist/backend/entities/blog-category-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/blog-category.entity.d.ts +32 -0
- package/dist/backend/entities/blog-category.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-category.entity.js +113 -0
- package/dist/backend/entities/blog-category.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-category.entity.d.ts +10 -0
- package/dist/backend/entities/blog-post-category.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-category.entity.js +34 -0
- package/dist/backend/entities/blog-post-category.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-language.entity.d.ts +9 -0
- package/dist/backend/entities/blog-post-language.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-language.entity.js +33 -0
- package/dist/backend/entities/blog-post-language.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-related-post.entity.d.ts +15 -0
- package/dist/backend/entities/blog-post-related-post.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-related-post.entity.js +42 -0
- package/dist/backend/entities/blog-post-related-post.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-related-product.entity.d.ts +14 -0
- package/dist/backend/entities/blog-post-related-product.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-related-product.entity.js +41 -0
- package/dist/backend/entities/blog-post-related-product.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.d.ts +16 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.js +47 -0
- package/dist/backend/entities/blog-post-sales-channel.entity.js.map +1 -0
- package/dist/backend/entities/blog-post-tag.entity.d.ts +14 -0
- package/dist/backend/entities/blog-post-tag.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post-tag.entity.js +41 -0
- package/dist/backend/entities/blog-post-tag.entity.js.map +1 -0
- package/dist/backend/entities/blog-post.entity.d.ts +31 -0
- package/dist/backend/entities/blog-post.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-post.entity.js +106 -0
- package/dist/backend/entities/blog-post.entity.js.map +1 -0
- package/dist/backend/entities/blog-tag.entity.d.ts +18 -0
- package/dist/backend/entities/blog-tag.entity.d.ts.map +1 -0
- package/dist/backend/entities/blog-tag.entity.js +66 -0
- package/dist/backend/entities/blog-tag.entity.js.map +1 -0
- package/dist/backend/index.d.ts +100 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +182 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +12 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +148 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +6 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +72 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/blog-asset-references.d.ts +4 -0
- package/dist/backend/services/blog-asset-references.d.ts.map +1 -0
- package/dist/backend/services/blog-asset-references.js +79 -0
- package/dist/backend/services/blog-asset-references.js.map +1 -0
- package/dist/backend/services/blog-cache.d.ts +55 -0
- package/dist/backend/services/blog-cache.d.ts.map +1 -0
- package/dist/backend/services/blog-cache.js +111 -0
- package/dist/backend/services/blog-cache.js.map +1 -0
- package/dist/backend/services/blog-category-service.d.ts +53 -0
- package/dist/backend/services/blog-category-service.d.ts.map +1 -0
- package/dist/backend/services/blog-category-service.js +446 -0
- package/dist/backend/services/blog-category-service.js.map +1 -0
- package/dist/backend/services/blog-language-reference.d.ts +18 -0
- package/dist/backend/services/blog-language-reference.d.ts.map +1 -0
- package/dist/backend/services/blog-language-reference.js +43 -0
- package/dist/backend/services/blog-language-reference.js.map +1 -0
- package/dist/backend/services/blog-post-service.d.ts +79 -0
- package/dist/backend/services/blog-post-service.d.ts.map +1 -0
- package/dist/backend/services/blog-post-service.js +539 -0
- package/dist/backend/services/blog-post-service.js.map +1 -0
- package/dist/backend/services/blog-settings-resolver.d.ts +36 -0
- package/dist/backend/services/blog-settings-resolver.d.ts.map +1 -0
- package/dist/backend/services/blog-settings-resolver.js +68 -0
- package/dist/backend/services/blog-settings-resolver.js.map +1 -0
- package/dist/backend/services/blog-slug-collision.d.ts +35 -0
- package/dist/backend/services/blog-slug-collision.d.ts.map +1 -0
- package/dist/backend/services/blog-slug-collision.js +84 -0
- package/dist/backend/services/blog-slug-collision.js.map +1 -0
- package/dist/backend/services/blog-storefront-resolver.d.ts +107 -0
- package/dist/backend/services/blog-storefront-resolver.d.ts.map +1 -0
- package/dist/backend/services/blog-storefront-resolver.js +492 -0
- package/dist/backend/services/blog-storefront-resolver.js.map +1 -0
- package/dist/backend/services/blog-tag-service.d.ts +45 -0
- package/dist/backend/services/blog-tag-service.d.ts.map +1 -0
- package/dist/backend/services/blog-tag-service.js +198 -0
- package/dist/backend/services/blog-tag-service.js.map +1 -0
- package/dist/backend/services/seed-default-category.d.ts +25 -0
- package/dist/backend/services/seed-default-category.d.ts.map +1 -0
- package/dist/backend/services/seed-default-category.js +48 -0
- package/dist/backend/services/seed-default-category.js.map +1 -0
- package/dist/backend/services/seed-roles.d.ts +41 -0
- package/dist/backend/services/seed-roles.d.ts.map +1 -0
- package/dist/backend/services/seed-roles.js +98 -0
- package/dist/backend/services/seed-roles.js.map +1 -0
- package/dist/manifest.d.ts +226 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +236 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260506T081055_blog_init.d.ts +33 -0
- package/dist/migrations/20260506T081055_blog_init.d.ts.map +1 -0
- package/dist/migrations/20260506T081055_blog_init.js +260 -0
- package/dist/migrations/20260506T081055_blog_init.js.map +1 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.d.ts +6 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.d.ts.map +1 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.js +58 -0
- package/dist/migrations/20260903T101744_blog_namespace_block_names.js.map +1 -0
- package/dist/migrations/index.d.ts +34 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +37 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/blog/index.md +199 -0
- package/i18n/en.json +166 -0
- package/i18n/pl.json +166 -0
- package/package.json +108 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storefront read-through cache for the Blog module — feature 016 / T020.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors feature 014's `CmsCache` and feature 015's `MegamenuCache` shape
|
|
5
|
+
* exactly: same key-prefix scheme, same default TTL, same SCAN-based
|
|
6
|
+
* coarse invalidation. Keys live under `blog:v1:<channelCode>:<language>`.
|
|
7
|
+
*
|
|
8
|
+
* Per `research.md` § R9 the cache holds four kinds of entry:
|
|
9
|
+
*
|
|
10
|
+
* blog:v1:<channel>:<language>:index
|
|
11
|
+
* blog:v1:<channel>:<language>:category:<slug>:p<page>
|
|
12
|
+
* blog:v1:<channel>:<language>:post:<slug>
|
|
13
|
+
* blog:v1:<channel>:<language>:tag:<code>:p<page>
|
|
14
|
+
*
|
|
15
|
+
* Invalidation is per-write (publishing a Post invalidates that post's
|
|
16
|
+
* key, every category page that contained it, every tag page that
|
|
17
|
+
* contained it, and the index for every channel the post is in). Settings
|
|
18
|
+
* writes invalidate the whole channel keyspace via `invalidateChannel`.
|
|
19
|
+
*/
|
|
20
|
+
export const BLOG_CACHE_KEY_PREFIX = 'blog:v1:';
|
|
21
|
+
export const BLOG_CACHE_DEFAULT_TTL_SECONDS = 5 * 60;
|
|
22
|
+
function channelLanguagePrefix(channelCode, language) {
|
|
23
|
+
return `${BLOG_CACHE_KEY_PREFIX}${channelCode}:${language}`;
|
|
24
|
+
}
|
|
25
|
+
export class BlogCacheService {
|
|
26
|
+
redis;
|
|
27
|
+
ttlSeconds;
|
|
28
|
+
enabled;
|
|
29
|
+
constructor(redis, options = {}) {
|
|
30
|
+
this.redis = redis;
|
|
31
|
+
this.ttlSeconds = options.ttlSeconds ?? BLOG_CACHE_DEFAULT_TTL_SECONDS;
|
|
32
|
+
this.enabled = (options.enabled ?? true) && this.ttlSeconds > 0;
|
|
33
|
+
}
|
|
34
|
+
static composeIndexKey(channelCode, language) {
|
|
35
|
+
return `${channelLanguagePrefix(channelCode, language)}:index`;
|
|
36
|
+
}
|
|
37
|
+
static composeCategoryKey(channelCode, language, slug, page) {
|
|
38
|
+
return `${channelLanguagePrefix(channelCode, language)}:category:${slug}:p${page}`;
|
|
39
|
+
}
|
|
40
|
+
static composePostKey(channelCode, language, slug) {
|
|
41
|
+
return `${channelLanguagePrefix(channelCode, language)}:post:${slug}`;
|
|
42
|
+
}
|
|
43
|
+
static composeTagKey(channelCode, language, code, page) {
|
|
44
|
+
return `${channelLanguagePrefix(channelCode, language)}:tag:${code}:p${page}`;
|
|
45
|
+
}
|
|
46
|
+
async get(key) {
|
|
47
|
+
if (!this.enabled)
|
|
48
|
+
return null;
|
|
49
|
+
const raw = await this.redis.get(key);
|
|
50
|
+
if (raw === null)
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
await this.redis.del(key);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async set(key, payload) {
|
|
61
|
+
if (!this.enabled)
|
|
62
|
+
return;
|
|
63
|
+
await this.redis.set(key, JSON.stringify(payload), 'EX', this.ttlSeconds);
|
|
64
|
+
}
|
|
65
|
+
/** Drop one key (used after the post-by-slug write). */
|
|
66
|
+
async invalidateKey(key) {
|
|
67
|
+
if (!this.enabled)
|
|
68
|
+
return;
|
|
69
|
+
await this.redis.del(key);
|
|
70
|
+
}
|
|
71
|
+
/** Drop every key under a channel + language scope. */
|
|
72
|
+
async invalidateScope(channelCode, language) {
|
|
73
|
+
await this.scanDelete(`${channelLanguagePrefix(channelCode, language)}:*`);
|
|
74
|
+
}
|
|
75
|
+
/** Drop every key under a channel (covers every language). Used on settings writes. */
|
|
76
|
+
async invalidateChannel(channelCode) {
|
|
77
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*`);
|
|
78
|
+
}
|
|
79
|
+
/** Drop the entire blog keyspace. Used by tests and by global config changes. */
|
|
80
|
+
async invalidateAll() {
|
|
81
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}*`);
|
|
82
|
+
}
|
|
83
|
+
/** Drop every entry that mentions a specific category slug across every language of a channel. */
|
|
84
|
+
async invalidateCategory(channelCode, slug) {
|
|
85
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*:category:${slug}:*`);
|
|
86
|
+
}
|
|
87
|
+
/** Drop every entry that mentions a specific post slug across every language of a channel. */
|
|
88
|
+
async invalidatePost(channelCode, slug) {
|
|
89
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*:post:${slug}`);
|
|
90
|
+
// Indices and category/tag pages may include the post — coarsely drop them.
|
|
91
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*:index`);
|
|
92
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*:category:*`);
|
|
93
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*:tag:*`);
|
|
94
|
+
}
|
|
95
|
+
/** Drop every entry that mentions a specific tag code across every language of a channel. */
|
|
96
|
+
async invalidateTag(channelCode, code) {
|
|
97
|
+
await this.scanDelete(`${BLOG_CACHE_KEY_PREFIX}${channelCode}:*:tag:${code}:*`);
|
|
98
|
+
}
|
|
99
|
+
async scanDelete(pattern) {
|
|
100
|
+
if (!this.enabled)
|
|
101
|
+
return;
|
|
102
|
+
let cursor = '0';
|
|
103
|
+
do {
|
|
104
|
+
const [next, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 200);
|
|
105
|
+
cursor = next;
|
|
106
|
+
if (keys.length > 0)
|
|
107
|
+
await this.redis.del(...keys);
|
|
108
|
+
} while (cursor !== '0');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=blog-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog-cache.js","sourceRoot":"","sources":["../../../src/backend/services/blog-cache.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC;AAChD,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,GAAG,EAAE,CAAC;AAQrD,SAAS,qBAAqB,CAAC,WAAmB,EAAE,QAAgB;IAClE,OAAO,GAAG,qBAAqB,GAAG,WAAW,IAAI,QAAQ,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,OAAO,gBAAgB;IAIE;IAHZ,UAAU,CAAS;IACnB,OAAO,CAAU;IAElC,YAA6B,KAAY,EAAE,UAA4B,EAAE;QAA5C,UAAK,GAAL,KAAK,CAAO;QACvC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,8BAA8B,CAAC;QACvE,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,WAAmB,EAAE,QAAgB;QAC1D,OAAO,GAAG,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACjE,CAAC;IAED,MAAM,CAAC,kBAAkB,CACvB,WAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,IAAY;QAEZ,OAAO,GAAG,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,aAAa,IAAI,KAAK,IAAI,EAAE,CAAC;IACrF,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,WAAmB,EAAE,QAAgB,EAAE,IAAY;QACvE,OAAO,GAAG,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,CAAC,aAAa,CAClB,WAAmB,EACnB,QAAgB,EAChB,IAAY,EACZ,IAAY;QAEZ,OAAO,GAAG,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,IAAI,KAAK,IAAI,EAAE,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,OAAU;QAClC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5E,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,eAAe,CAAC,WAAmB,EAAE,QAAgB;QACzD,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,iBAAiB,CAAC,WAAmB;QACzC,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,IAAI,CAAC,CAAC;IACpE,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,kBAAkB,CAAC,WAAmB,EAAE,IAAY;QACxD,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,eAAe,IAAI,IAAI,CAAC,CAAC;IACvF,CAAC;IAED,8FAA8F;IAC9F,KAAK,CAAC,cAAc,CAAC,WAAmB,EAAE,IAAY;QACpD,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,WAAW,IAAI,EAAE,CAAC,CAAC;QAC/E,4EAA4E;QAC5E,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,UAAU,CAAC,CAAC;QACxE,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,eAAe,CAAC,CAAC;QAC7E,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,UAAU,CAAC,CAAC;IAC1E,CAAC;IAED,6FAA6F;IAC7F,KAAK,CAAC,aAAa,CAAC,WAAmB,EAAE,IAAY;QACnD,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,qBAAqB,GAAG,WAAW,UAAU,IAAI,IAAI,CAAC,CAAC;IAClF,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,OAAe;QACtC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,MAAM,GAAG,GAAG,CAAC;QACjB,GAAG,CAAC;YACF,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;YACnF,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QACrD,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import { type BlogCategoryDetail, type BlogCategoryTreeMove, type BlogCategoryTreeNode, type CreateBlogCategoryRequest, type DictionaryValidator, type PatchBlogCategoryRequest, type PutBlogCategoryDescriptionRequest } from '@endora-commerce/contracts';
|
|
3
|
+
import type { BlogCacheService } from './blog-cache.js';
|
|
4
|
+
/**
|
|
5
|
+
* BlogCategoryService — feature 016 / T042.
|
|
6
|
+
*
|
|
7
|
+
* Owns the full BlogCategory CRUD + tree-mutation contract documented in
|
|
8
|
+
* `contracts/blog-admin-http.contract.md`. The service runs every write
|
|
9
|
+
* inside an `em.transactional(...)` block; the slug-collision helper
|
|
10
|
+
* acquires per-(channel, slug) advisory locks so a concurrent write
|
|
11
|
+
* cannot squeeze a duplicate past the partial unique indexes.
|
|
12
|
+
*
|
|
13
|
+
* The deletion-protection contract (FR-005 / FR-006 / FR-007 / R11) is
|
|
14
|
+
* enforced in `softDelete`:
|
|
15
|
+
* - is_system=true → 409 BLOG_CATEGORY_PROTECTED;
|
|
16
|
+
* - has at least one referencing Post → 409 BLOG_CATEGORY_IN_USE;
|
|
17
|
+
* - has at least one child Category → 409 BLOG_CATEGORY_HAS_CHILDREN.
|
|
18
|
+
*
|
|
19
|
+
* Cycle prevention runs on every save by walking parent_id up to root
|
|
20
|
+
* and refusing if the target id already appears on the path.
|
|
21
|
+
*/
|
|
22
|
+
export declare class BlogCategoryService {
|
|
23
|
+
private readonly emFactory;
|
|
24
|
+
private readonly cache?;
|
|
25
|
+
private readonly dictionaryValidator?;
|
|
26
|
+
constructor(emFactory: () => EntityManager, cache?: BlogCacheService | undefined, dictionaryValidator?: DictionaryValidator | undefined);
|
|
27
|
+
getTree(): Promise<{
|
|
28
|
+
tree: BlogCategoryTreeNode[];
|
|
29
|
+
}>;
|
|
30
|
+
getById(id: string): Promise<BlogCategoryDetail>;
|
|
31
|
+
create(input: CreateBlogCategoryRequest): Promise<BlogCategoryDetail>;
|
|
32
|
+
patch(id: string, input: PatchBlogCategoryRequest): Promise<BlogCategoryDetail>;
|
|
33
|
+
setDescription(id: string, input: PutBlogCategoryDescriptionRequest): Promise<BlogCategoryDetail>;
|
|
34
|
+
applyTreeMoves(moves: BlogCategoryTreeMove[]): Promise<{
|
|
35
|
+
tree: BlogCategoryTreeNode[];
|
|
36
|
+
}>;
|
|
37
|
+
softDelete(id: string, version: number | undefined): Promise<void>;
|
|
38
|
+
private findRow;
|
|
39
|
+
private assertParentExists;
|
|
40
|
+
private assertNoCycle;
|
|
41
|
+
private assertVersion;
|
|
42
|
+
private getByIdInTx;
|
|
43
|
+
private toIso;
|
|
44
|
+
private loadChannelIds;
|
|
45
|
+
private loadLanguages;
|
|
46
|
+
private validateLanguages;
|
|
47
|
+
private loadChannelsByCategory;
|
|
48
|
+
private loadLanguagesByCategory;
|
|
49
|
+
private replaceChannels;
|
|
50
|
+
private replaceLanguages;
|
|
51
|
+
private invalidateCache;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=blog-category-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog-category-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/blog-category-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,iCAAiC,EACvC,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAsBxD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;gBAFpB,SAAS,EAAE,MAAM,aAAa,EAC9B,KAAK,CAAC,EAAE,gBAAgB,YAAA,EACxB,mBAAmB,CAAC,EAAE,mBAAmB,YAAA;IAOtD,OAAO,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,oBAAoB,EAAE,CAAA;KAAE,CAAC;IAyCpD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIhD,MAAM,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsErE,KAAK,CACT,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,kBAAkB,CAAC;IAmGxB,cAAc,CAClB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,kBAAkB,CAAC;IA0BxB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,oBAAoB,EAAE,CAAA;KAAE,CAAC;IA6CxF,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;YA8D1D,OAAO;YAQP,kBAAkB;YAclB,aAAa;IA0B3B,OAAO,CAAC,aAAa;YAUP,WAAW;IAiDzB,OAAO,CAAC,KAAK;YAIC,cAAc;YAQd,aAAa;YAQb,iBAAiB;YA4BjB,sBAAsB;YAmBtB,uBAAuB;YAkBvB,eAAe;YAiBf,gBAAgB;YAgBhB,eAAe;CAI9B"}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { DictionaryReferenceError, ERROR_CODES, } from '@endora-commerce/contracts';
|
|
3
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
4
|
+
import { assertSlugAvailable } from './blog-slug-collision.js';
|
|
5
|
+
/**
|
|
6
|
+
* BlogCategoryService — feature 016 / T042.
|
|
7
|
+
*
|
|
8
|
+
* Owns the full BlogCategory CRUD + tree-mutation contract documented in
|
|
9
|
+
* `contracts/blog-admin-http.contract.md`. The service runs every write
|
|
10
|
+
* inside an `em.transactional(...)` block; the slug-collision helper
|
|
11
|
+
* acquires per-(channel, slug) advisory locks so a concurrent write
|
|
12
|
+
* cannot squeeze a duplicate past the partial unique indexes.
|
|
13
|
+
*
|
|
14
|
+
* The deletion-protection contract (FR-005 / FR-006 / FR-007 / R11) is
|
|
15
|
+
* enforced in `softDelete`:
|
|
16
|
+
* - is_system=true → 409 BLOG_CATEGORY_PROTECTED;
|
|
17
|
+
* - has at least one referencing Post → 409 BLOG_CATEGORY_IN_USE;
|
|
18
|
+
* - has at least one child Category → 409 BLOG_CATEGORY_HAS_CHILDREN.
|
|
19
|
+
*
|
|
20
|
+
* Cycle prevention runs on every save by walking parent_id up to root
|
|
21
|
+
* and refusing if the target id already appears on the path.
|
|
22
|
+
*/
|
|
23
|
+
export class BlogCategoryService {
|
|
24
|
+
emFactory;
|
|
25
|
+
cache;
|
|
26
|
+
dictionaryValidator;
|
|
27
|
+
constructor(emFactory, cache, dictionaryValidator) {
|
|
28
|
+
this.emFactory = emFactory;
|
|
29
|
+
this.cache = cache;
|
|
30
|
+
this.dictionaryValidator = dictionaryValidator;
|
|
31
|
+
}
|
|
32
|
+
// ────────────────────────────────────────────────────────────────────
|
|
33
|
+
// CRUD + tree
|
|
34
|
+
// ────────────────────────────────────────────────────────────────────
|
|
35
|
+
async getTree() {
|
|
36
|
+
const em = this.emFactory();
|
|
37
|
+
const rows = (await em.execute(`select * from blog_categories where deleted_at is null order by parent_id nulls first, position`));
|
|
38
|
+
const channelsByCategory = await this.loadChannelsByCategory(rows.map((r) => r.id));
|
|
39
|
+
const languagesByCategory = await this.loadLanguagesByCategory(rows.map((r) => r.id));
|
|
40
|
+
const nodes = new Map();
|
|
41
|
+
const tree = [];
|
|
42
|
+
for (const r of rows) {
|
|
43
|
+
const node = {
|
|
44
|
+
id: r.id,
|
|
45
|
+
parentId: r.parent_id,
|
|
46
|
+
position: r.position,
|
|
47
|
+
name: r.name,
|
|
48
|
+
slug: r.slug,
|
|
49
|
+
enabled: r.enabled,
|
|
50
|
+
isSystem: r.is_system,
|
|
51
|
+
salesChannelIds: channelsByCategory.get(r.id) ?? [],
|
|
52
|
+
languages: languagesByCategory.get(r.id) ?? [],
|
|
53
|
+
mainImageAssetId: r.main_image_asset_id,
|
|
54
|
+
version: r.version,
|
|
55
|
+
children: [],
|
|
56
|
+
};
|
|
57
|
+
nodes.set(r.id, node);
|
|
58
|
+
}
|
|
59
|
+
for (const r of rows) {
|
|
60
|
+
const node = nodes.get(r.id);
|
|
61
|
+
if (r.parent_id === null) {
|
|
62
|
+
tree.push(node);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const parent = nodes.get(r.parent_id);
|
|
66
|
+
if (parent)
|
|
67
|
+
parent.children.push(node);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { tree };
|
|
71
|
+
}
|
|
72
|
+
async getById(id) {
|
|
73
|
+
return this.getByIdInTx(this.emFactory(), id);
|
|
74
|
+
}
|
|
75
|
+
async create(input) {
|
|
76
|
+
const em = this.emFactory();
|
|
77
|
+
const id = randomUUID();
|
|
78
|
+
const now = new Date();
|
|
79
|
+
return em.transactional(async (tx) => {
|
|
80
|
+
// Slug-collision guard runs first so we don't pollute the row.
|
|
81
|
+
await assertSlugAvailable(tx, {
|
|
82
|
+
slug: input.slug,
|
|
83
|
+
salesChannelIds: input.salesChannelIds,
|
|
84
|
+
kind: 'category',
|
|
85
|
+
});
|
|
86
|
+
await this.validateLanguages(input.languages);
|
|
87
|
+
// Cycle check — when creating, parentId can only point at an
|
|
88
|
+
// existing Category. The parent's own ancestry is fine; we only
|
|
89
|
+
// need to verify the parent exists and is not soft-deleted.
|
|
90
|
+
if (input.parentId !== null) {
|
|
91
|
+
await this.assertParentExists(tx, input.parentId);
|
|
92
|
+
}
|
|
93
|
+
// Compute next position for the parent slot.
|
|
94
|
+
const positionRows = (await tx.execute(`select coalesce(max(position) + 1, 0) as next_pos
|
|
95
|
+
from blog_categories
|
|
96
|
+
where deleted_at is null and ${input.parentId === null ? 'parent_id is null' : 'parent_id = ?'}`, input.parentId === null ? [] : [input.parentId]));
|
|
97
|
+
const position = Number(positionRows[0]?.next_pos ?? 0);
|
|
98
|
+
await tx.execute(`insert into blog_categories
|
|
99
|
+
(id, parent_id, position, name, slug, enabled, description,
|
|
100
|
+
main_image_asset_id, meta_title, meta_description, meta_keywords,
|
|
101
|
+
is_system, version, created_at, updated_at)
|
|
102
|
+
values (?, ?, ?, ?::jsonb, ?, ?, null, ?, ?::jsonb, ?::jsonb, ?::jsonb, false, 1, ?, ?)`, [
|
|
103
|
+
id,
|
|
104
|
+
input.parentId,
|
|
105
|
+
position,
|
|
106
|
+
JSON.stringify(input.name),
|
|
107
|
+
input.slug,
|
|
108
|
+
input.enabled ?? true,
|
|
109
|
+
input.mainImageAssetId ?? null,
|
|
110
|
+
input.metaTitle ? JSON.stringify(input.metaTitle) : null,
|
|
111
|
+
input.metaDescription ? JSON.stringify(input.metaDescription) : null,
|
|
112
|
+
input.metaKeywords ? JSON.stringify(input.metaKeywords) : null,
|
|
113
|
+
now,
|
|
114
|
+
now,
|
|
115
|
+
]);
|
|
116
|
+
for (const channelId of input.salesChannelIds) {
|
|
117
|
+
await tx.execute(`insert into blog_category_sales_channels (blog_category_id, sales_channel_id, slug)
|
|
118
|
+
values (?, ?, ?)`, [id, channelId, input.slug]);
|
|
119
|
+
}
|
|
120
|
+
for (const language of input.languages) {
|
|
121
|
+
await tx.execute(`insert into blog_category_languages (blog_category_id, language) values (?, ?)`, [id, language]);
|
|
122
|
+
}
|
|
123
|
+
return this.getByIdInTx(tx, id);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async patch(id, input) {
|
|
127
|
+
const em = this.emFactory();
|
|
128
|
+
return em.transactional(async (tx) => {
|
|
129
|
+
const existing = await this.findRow(tx, id);
|
|
130
|
+
if (!existing) {
|
|
131
|
+
throw new HttpError(404, ERROR_CODES.BLOG_CATEGORY_NOT_FOUND, 'Blog category not found.');
|
|
132
|
+
}
|
|
133
|
+
this.assertVersion(existing.version, input.version);
|
|
134
|
+
// Slug-collision guard whenever slug or channel scope changes.
|
|
135
|
+
const slugChange = input.slug !== undefined && input.slug !== existing.slug;
|
|
136
|
+
const channelChange = input.salesChannelIds !== undefined;
|
|
137
|
+
if (slugChange || channelChange) {
|
|
138
|
+
const targetSlug = input.slug ?? existing.slug;
|
|
139
|
+
const targetChannels = input.salesChannelIds ?? (await this.loadChannelIds(tx, id));
|
|
140
|
+
await assertSlugAvailable(tx, {
|
|
141
|
+
slug: targetSlug,
|
|
142
|
+
salesChannelIds: targetChannels,
|
|
143
|
+
kind: 'category',
|
|
144
|
+
excludeId: id,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// parentId change → cycle check.
|
|
148
|
+
if (input.parentId !== undefined && input.parentId !== existing.parent_id) {
|
|
149
|
+
if (input.parentId !== null) {
|
|
150
|
+
await this.assertParentExists(tx, input.parentId);
|
|
151
|
+
await this.assertNoCycle(tx, id, input.parentId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const sets = ['version = version + 1', 'updated_at = now()'];
|
|
155
|
+
const params = [];
|
|
156
|
+
if (input.name !== undefined) {
|
|
157
|
+
sets.push('name = ?::jsonb');
|
|
158
|
+
params.push(JSON.stringify(input.name));
|
|
159
|
+
}
|
|
160
|
+
if (input.slug !== undefined) {
|
|
161
|
+
sets.push('slug = ?');
|
|
162
|
+
params.push(input.slug);
|
|
163
|
+
}
|
|
164
|
+
if (input.enabled !== undefined) {
|
|
165
|
+
sets.push('enabled = ?');
|
|
166
|
+
params.push(input.enabled);
|
|
167
|
+
}
|
|
168
|
+
if (input.mainImageAssetId !== undefined) {
|
|
169
|
+
sets.push('main_image_asset_id = ?');
|
|
170
|
+
params.push(input.mainImageAssetId);
|
|
171
|
+
}
|
|
172
|
+
if (input.parentId !== undefined) {
|
|
173
|
+
sets.push('parent_id = ?');
|
|
174
|
+
params.push(input.parentId);
|
|
175
|
+
}
|
|
176
|
+
if (input.metaTitle !== undefined) {
|
|
177
|
+
sets.push('meta_title = ?::jsonb');
|
|
178
|
+
params.push(input.metaTitle ? JSON.stringify(input.metaTitle) : null);
|
|
179
|
+
}
|
|
180
|
+
if (input.metaDescription !== undefined) {
|
|
181
|
+
sets.push('meta_description = ?::jsonb');
|
|
182
|
+
params.push(input.metaDescription ? JSON.stringify(input.metaDescription) : null);
|
|
183
|
+
}
|
|
184
|
+
if (input.metaKeywords !== undefined) {
|
|
185
|
+
sets.push('meta_keywords = ?::jsonb');
|
|
186
|
+
params.push(input.metaKeywords ? JSON.stringify(input.metaKeywords) : null);
|
|
187
|
+
}
|
|
188
|
+
if (sets.length > 2) {
|
|
189
|
+
params.push(id);
|
|
190
|
+
await tx.execute(`update blog_categories set ${sets.join(', ')} where id = ?`, params);
|
|
191
|
+
}
|
|
192
|
+
if (slugChange) {
|
|
193
|
+
await tx.execute(`update blog_category_sales_channels set slug = ? where blog_category_id = ?`, [input.slug, id]);
|
|
194
|
+
}
|
|
195
|
+
if (channelChange) {
|
|
196
|
+
await this.replaceChannels(tx, id, input.salesChannelIds, input.slug ?? existing.slug);
|
|
197
|
+
}
|
|
198
|
+
if (input.languages !== undefined) {
|
|
199
|
+
const currentLanguages = await this.loadLanguages(tx, id);
|
|
200
|
+
await this.validateLanguages(input.languages, currentLanguages);
|
|
201
|
+
await this.replaceLanguages(tx, id, input.languages);
|
|
202
|
+
}
|
|
203
|
+
const detail = await this.getByIdInTx(tx, id);
|
|
204
|
+
await this.invalidateCache(existing.slug, detail.slug);
|
|
205
|
+
return detail;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async setDescription(id, input) {
|
|
209
|
+
const em = this.emFactory();
|
|
210
|
+
return em.transactional(async (tx) => {
|
|
211
|
+
const existing = await this.findRow(tx, id);
|
|
212
|
+
if (!existing) {
|
|
213
|
+
throw new HttpError(404, ERROR_CODES.BLOG_CATEGORY_NOT_FOUND, 'Blog category not found.');
|
|
214
|
+
}
|
|
215
|
+
this.assertVersion(existing.version, input.version);
|
|
216
|
+
await tx.execute(`update blog_categories
|
|
217
|
+
set description = ?::jsonb,
|
|
218
|
+
version = version + 1,
|
|
219
|
+
updated_at = now()
|
|
220
|
+
where id = ?`, [input.description ? JSON.stringify(input.description) : null, id]);
|
|
221
|
+
const detail = await this.getByIdInTx(tx, id);
|
|
222
|
+
await this.invalidateCache(existing.slug, detail.slug);
|
|
223
|
+
return detail;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async applyTreeMoves(moves) {
|
|
227
|
+
if (moves.length === 0)
|
|
228
|
+
return this.getTree();
|
|
229
|
+
const em = this.emFactory();
|
|
230
|
+
await em.transactional(async (tx) => {
|
|
231
|
+
// Cycle prevention runs against the post-move state.
|
|
232
|
+
// Load the current parent_id map so we can simulate the moves and
|
|
233
|
+
// refuse the whole batch if any move introduces a cycle.
|
|
234
|
+
const currentRows = (await tx.execute(`select id::text as id, parent_id::text as parent_id from blog_categories where deleted_at is null`));
|
|
235
|
+
const parentMap = new Map();
|
|
236
|
+
for (const r of currentRows)
|
|
237
|
+
parentMap.set(r.id, r.parent_id);
|
|
238
|
+
for (const m of moves)
|
|
239
|
+
parentMap.set(m.id, m.parentId);
|
|
240
|
+
for (const m of moves) {
|
|
241
|
+
if (m.parentId === null)
|
|
242
|
+
continue;
|
|
243
|
+
// Walk up from the proposed parent looking for the moved id.
|
|
244
|
+
let cursor = m.parentId;
|
|
245
|
+
const visited = new Set();
|
|
246
|
+
while (cursor !== null) {
|
|
247
|
+
if (cursor === m.id) {
|
|
248
|
+
throw new HttpError(422, ERROR_CODES.BLOG_CATEGORY_CYCLE, `Move would introduce a cycle: ${m.id} cannot be its own ancestor.`);
|
|
249
|
+
}
|
|
250
|
+
if (visited.has(cursor))
|
|
251
|
+
break;
|
|
252
|
+
visited.add(cursor);
|
|
253
|
+
cursor = parentMap.get(cursor) ?? null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const m of moves) {
|
|
257
|
+
await tx.execute(`update blog_categories
|
|
258
|
+
set parent_id = ?, position = ?, version = version + 1, updated_at = now()
|
|
259
|
+
where id = ? and deleted_at is null`, [m.parentId, m.position, m.id]);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
if (this.cache)
|
|
263
|
+
await this.cache.invalidateAll();
|
|
264
|
+
return this.getTree();
|
|
265
|
+
}
|
|
266
|
+
async softDelete(id, version) {
|
|
267
|
+
const em = this.emFactory();
|
|
268
|
+
await em.transactional(async (tx) => {
|
|
269
|
+
const existing = await this.findRow(tx, id);
|
|
270
|
+
if (!existing) {
|
|
271
|
+
throw new HttpError(404, ERROR_CODES.BLOG_CATEGORY_NOT_FOUND, 'Blog category not found.');
|
|
272
|
+
}
|
|
273
|
+
this.assertVersion(existing.version, version);
|
|
274
|
+
if (existing.is_system) {
|
|
275
|
+
throw new HttpError(409, ERROR_CODES.BLOG_CATEGORY_PROTECTED, 'The seeded Default category cannot be deleted.');
|
|
276
|
+
}
|
|
277
|
+
// Block on referencing Posts.
|
|
278
|
+
const refRows = (await tx.execute(`select count(*)::int as n from blog_post_categories where blog_category_id = ?`, [id]));
|
|
279
|
+
if ((refRows[0]?.n ?? 0) > 0) {
|
|
280
|
+
throw new HttpError(409, ERROR_CODES.BLOG_CATEGORY_IN_USE, 'Category has at least one referencing post; reassign or archive those posts before deletion.');
|
|
281
|
+
}
|
|
282
|
+
// Block on child Categories.
|
|
283
|
+
const childRows = (await tx.execute(`select count(*)::int as n from blog_categories where parent_id = ? and deleted_at is null`, [id]));
|
|
284
|
+
if ((childRows[0]?.n ?? 0) > 0) {
|
|
285
|
+
throw new HttpError(409, ERROR_CODES.BLOG_CATEGORY_HAS_CHILDREN, 'Category has at least one child; reparent or delete children first.');
|
|
286
|
+
}
|
|
287
|
+
await tx.execute(`update blog_categories set deleted_at = now(), updated_at = now() where id = ?`, [id]);
|
|
288
|
+
await tx.execute(`update blog_category_sales_channels set deleted_at = now() where blog_category_id = ?`, [id]);
|
|
289
|
+
});
|
|
290
|
+
if (this.cache)
|
|
291
|
+
await this.cache.invalidateAll();
|
|
292
|
+
}
|
|
293
|
+
// ────────────────────────────────────────────────────────────────────
|
|
294
|
+
// Internals
|
|
295
|
+
// ────────────────────────────────────────────────────────────────────
|
|
296
|
+
async findRow(em, id) {
|
|
297
|
+
const rows = (await em.execute(`select * from blog_categories where id = ? and deleted_at is null limit 1`, [id]));
|
|
298
|
+
return rows[0] ?? null;
|
|
299
|
+
}
|
|
300
|
+
async assertParentExists(em, parentId) {
|
|
301
|
+
const rows = (await em.execute(`select 1 from blog_categories where id = ? and deleted_at is null limit 1`, [parentId]));
|
|
302
|
+
if (rows.length === 0) {
|
|
303
|
+
throw new HttpError(404, ERROR_CODES.BLOG_CATEGORY_NOT_FOUND, `Parent category ${parentId} not found.`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async assertNoCycle(em, selfId, parentId) {
|
|
307
|
+
const rows = (await em.execute(`select id::text as id, parent_id::text as parent_id from blog_categories where deleted_at is null`));
|
|
308
|
+
const map = new Map();
|
|
309
|
+
for (const r of rows)
|
|
310
|
+
map.set(r.id, r.parent_id);
|
|
311
|
+
let cursor = parentId;
|
|
312
|
+
const visited = new Set();
|
|
313
|
+
while (cursor !== null) {
|
|
314
|
+
if (cursor === selfId) {
|
|
315
|
+
throw new HttpError(422, ERROR_CODES.BLOG_CATEGORY_CYCLE, `Setting parent ${parentId} would create a cycle for category ${selfId}.`);
|
|
316
|
+
}
|
|
317
|
+
if (visited.has(cursor))
|
|
318
|
+
break;
|
|
319
|
+
visited.add(cursor);
|
|
320
|
+
cursor = map.get(cursor) ?? null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
assertVersion(have, sent) {
|
|
324
|
+
if (sent !== undefined && sent !== have) {
|
|
325
|
+
throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, `Blog category was updated concurrently (have v${have}, request v${sent}).`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async getByIdInTx(em, id) {
|
|
329
|
+
const row = await this.findRow(em, id);
|
|
330
|
+
if (!row) {
|
|
331
|
+
throw new HttpError(404, ERROR_CODES.BLOG_CATEGORY_NOT_FOUND, 'Blog category not found.');
|
|
332
|
+
}
|
|
333
|
+
const [channels, languages] = (await Promise.all([
|
|
334
|
+
em.execute(`select sales_channel_id::text as id from blog_category_sales_channels where blog_category_id = ? and deleted_at is null`, [id]),
|
|
335
|
+
em.execute(`select language from blog_category_languages where blog_category_id = ?`, [id]),
|
|
336
|
+
]));
|
|
337
|
+
return {
|
|
338
|
+
id: row.id,
|
|
339
|
+
parentId: row.parent_id,
|
|
340
|
+
position: row.position,
|
|
341
|
+
name: row.name,
|
|
342
|
+
slug: row.slug,
|
|
343
|
+
enabled: row.enabled,
|
|
344
|
+
isSystem: row.is_system,
|
|
345
|
+
salesChannelIds: channels.map((c) => c.id),
|
|
346
|
+
languages: languages.map((l) => l.language),
|
|
347
|
+
mainImageAssetId: row.main_image_asset_id,
|
|
348
|
+
metaTitle: row.meta_title,
|
|
349
|
+
metaDescription: row.meta_description,
|
|
350
|
+
metaKeywords: row.meta_keywords,
|
|
351
|
+
description: row.description
|
|
352
|
+
? {
|
|
353
|
+
schema_version: typeof row.description.schema_version === 'number'
|
|
354
|
+
? row.description.schema_version
|
|
355
|
+
: 1,
|
|
356
|
+
languages: row.description.languages ?? {},
|
|
357
|
+
}
|
|
358
|
+
: null,
|
|
359
|
+
version: row.version,
|
|
360
|
+
createdAt: this.toIso(row.created_at),
|
|
361
|
+
updatedAt: this.toIso(row.updated_at),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
toIso(value) {
|
|
365
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
366
|
+
}
|
|
367
|
+
async loadChannelIds(em, categoryId) {
|
|
368
|
+
const rows = (await em.execute(`select sales_channel_id::text as id from blog_category_sales_channels where blog_category_id = ? and deleted_at is null`, [categoryId]));
|
|
369
|
+
return rows.map((r) => r.id);
|
|
370
|
+
}
|
|
371
|
+
async loadLanguages(em, categoryId) {
|
|
372
|
+
const rows = (await em.execute(`select language from blog_category_languages where blog_category_id = ?`, [categoryId]));
|
|
373
|
+
return rows.map((r) => r.language);
|
|
374
|
+
}
|
|
375
|
+
async validateLanguages(languages, currentLanguages = []) {
|
|
376
|
+
if (!this.dictionaryValidator)
|
|
377
|
+
return;
|
|
378
|
+
const current = new Set(currentLanguages);
|
|
379
|
+
for (const language of languages) {
|
|
380
|
+
try {
|
|
381
|
+
await this.dictionaryValidator.validateLanguageCode(language, current.has(language) ? 'unchanged' : 'create-or-change');
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
if (err instanceof DictionaryReferenceError) {
|
|
385
|
+
throw new HttpError(409, err.code, err.code === 'DICTIONARY_ENTRY_INACTIVE'
|
|
386
|
+
? `Language code ${err.entryCode} is no longer available for blog categories.`
|
|
387
|
+
: `Language code ${err.entryCode} is not recognised.`, [{ path: 'languages', issue: err.code }]);
|
|
388
|
+
}
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async loadChannelsByCategory(ids) {
|
|
394
|
+
const out = new Map();
|
|
395
|
+
if (ids.length === 0)
|
|
396
|
+
return out;
|
|
397
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
398
|
+
const rows = (await this.emFactory().execute(`select blog_category_id::text as cid, sales_channel_id::text as id
|
|
399
|
+
from blog_category_sales_channels
|
|
400
|
+
where blog_category_id in (${placeholders})
|
|
401
|
+
and deleted_at is null`, ids));
|
|
402
|
+
for (const r of rows) {
|
|
403
|
+
const list = out.get(r.cid) ?? [];
|
|
404
|
+
list.push(r.id);
|
|
405
|
+
out.set(r.cid, list);
|
|
406
|
+
}
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
async loadLanguagesByCategory(ids) {
|
|
410
|
+
const out = new Map();
|
|
411
|
+
if (ids.length === 0)
|
|
412
|
+
return out;
|
|
413
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
414
|
+
const rows = (await this.emFactory().execute(`select blog_category_id::text as cid, language
|
|
415
|
+
from blog_category_languages
|
|
416
|
+
where blog_category_id in (${placeholders})`, ids));
|
|
417
|
+
for (const r of rows) {
|
|
418
|
+
const list = out.get(r.cid) ?? [];
|
|
419
|
+
list.push(r.language);
|
|
420
|
+
out.set(r.cid, list);
|
|
421
|
+
}
|
|
422
|
+
return out;
|
|
423
|
+
}
|
|
424
|
+
async replaceChannels(em, categoryId, channelIds, slug) {
|
|
425
|
+
await em.execute(`delete from blog_category_sales_channels where blog_category_id = ?`, [
|
|
426
|
+
categoryId,
|
|
427
|
+
]);
|
|
428
|
+
for (const id of channelIds) {
|
|
429
|
+
await em.execute(`insert into blog_category_sales_channels (blog_category_id, sales_channel_id, slug) values (?, ?, ?)`, [categoryId, id, slug]);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async replaceLanguages(em, categoryId, languages) {
|
|
433
|
+
await em.execute(`delete from blog_category_languages where blog_category_id = ?`, [
|
|
434
|
+
categoryId,
|
|
435
|
+
]);
|
|
436
|
+
for (const lang of languages) {
|
|
437
|
+
await em.execute(`insert into blog_category_languages (blog_category_id, language) values (?, ?)`, [categoryId, lang]);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async invalidateCache(_oldSlug, _newSlug) {
|
|
441
|
+
if (!this.cache)
|
|
442
|
+
return;
|
|
443
|
+
await this.cache.invalidateAll();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=blog-category-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog-category-service.js","sourceRoot":"","sources":["../../../src/backend/services/blog-category-service.ts"],"names":[],"mappings":"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"}
|