@endora-commerce/mod-megamenu 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 +59 -0
- package/dist/admin/api/megamenu-client.d.ts +28 -0
- package/dist/admin/api/megamenu-client.d.ts.map +1 -0
- package/dist/admin/api/megamenu-client.js +43 -0
- package/dist/admin/api/megamenu-client.js.map +1 -0
- package/dist/admin/components/BindingsPanel.d.ts +10 -0
- package/dist/admin/components/BindingsPanel.d.ts.map +1 -0
- package/dist/admin/components/BindingsPanel.js +120 -0
- package/dist/admin/components/BindingsPanel.js.map +1 -0
- package/dist/admin/components/MenuItemConfigPanel.d.ts +22 -0
- package/dist/admin/components/MenuItemConfigPanel.d.ts.map +1 -0
- package/dist/admin/components/MenuItemConfigPanel.js +77 -0
- package/dist/admin/components/MenuItemConfigPanel.js.map +1 -0
- package/dist/admin/components/MenuItemTree.d.ts +13 -0
- package/dist/admin/components/MenuItemTree.d.ts.map +1 -0
- package/dist/admin/components/MenuItemTree.js +46 -0
- package/dist/admin/components/MenuItemTree.js.map +1 -0
- package/dist/admin/index.d.ts +31 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +47 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/MegamenuEditor.d.ts +4 -0
- package/dist/admin/pages/MegamenuEditor.d.ts.map +1 -0
- package/dist/admin/pages/MegamenuEditor.js +151 -0
- package/dist/admin/pages/MegamenuEditor.js.map +1 -0
- package/dist/admin/pages/MegamenuListPage.d.ts +4 -0
- package/dist/admin/pages/MegamenuListPage.d.ts.map +1 -0
- package/dist/admin/pages/MegamenuListPage.js +51 -0
- package/dist/admin/pages/MegamenuListPage.js.map +1 -0
- package/dist/backend/entities/megamenu-binding.entity.d.ts +18 -0
- package/dist/backend/entities/megamenu-binding.entity.d.ts.map +1 -0
- package/dist/backend/entities/megamenu-binding.entity.js +61 -0
- package/dist/backend/entities/megamenu-binding.entity.js.map +1 -0
- package/dist/backend/entities/megamenu-item.entity.d.ts +27 -0
- package/dist/backend/entities/megamenu-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/megamenu-item.entity.js +83 -0
- package/dist/backend/entities/megamenu-item.entity.js.map +1 -0
- package/dist/backend/entities/megamenu.entity.d.ts +17 -0
- package/dist/backend/entities/megamenu.entity.d.ts.map +1 -0
- package/dist/backend/entities/megamenu.entity.js +57 -0
- package/dist/backend/entities/megamenu.entity.js.map +1 -0
- package/dist/backend/index.d.ts +116 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +147 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +10 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +50 -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 +26 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/asset-references.d.ts +4 -0
- package/dist/backend/services/asset-references.d.ts.map +1 -0
- package/dist/backend/services/asset-references.js +36 -0
- package/dist/backend/services/asset-references.js.map +1 -0
- package/dist/backend/services/cms-references.d.ts +6 -0
- package/dist/backend/services/cms-references.d.ts.map +1 -0
- package/dist/backend/services/cms-references.js +39 -0
- package/dist/backend/services/cms-references.js.map +1 -0
- package/dist/backend/services/cross-module-ports.d.ts +112 -0
- package/dist/backend/services/cross-module-ports.d.ts.map +1 -0
- package/dist/backend/services/cross-module-ports.js +88 -0
- package/dist/backend/services/cross-module-ports.js.map +1 -0
- package/dist/backend/services/megamenu-cache.d.ts +36 -0
- package/dist/backend/services/megamenu-cache.d.ts.map +1 -0
- package/dist/backend/services/megamenu-cache.js +70 -0
- package/dist/backend/services/megamenu-cache.js.map +1 -0
- package/dist/backend/services/megamenu-item-service.d.ts +39 -0
- package/dist/backend/services/megamenu-item-service.d.ts.map +1 -0
- package/dist/backend/services/megamenu-item-service.js +151 -0
- package/dist/backend/services/megamenu-item-service.js.map +1 -0
- package/dist/backend/services/megamenu-language-reference.d.ts +12 -0
- package/dist/backend/services/megamenu-language-reference.d.ts.map +1 -0
- package/dist/backend/services/megamenu-language-reference.js +24 -0
- package/dist/backend/services/megamenu-language-reference.js.map +1 -0
- package/dist/backend/services/megamenu-reference-registry.d.ts +24 -0
- package/dist/backend/services/megamenu-reference-registry.d.ts.map +1 -0
- package/dist/backend/services/megamenu-reference-registry.js +69 -0
- package/dist/backend/services/megamenu-reference-registry.js.map +1 -0
- package/dist/backend/services/megamenu-service.d.ts +70 -0
- package/dist/backend/services/megamenu-service.d.ts.map +1 -0
- package/dist/backend/services/megamenu-service.js +299 -0
- package/dist/backend/services/megamenu-service.js.map +1 -0
- package/dist/backend/services/storefront-resolver.d.ts +46 -0
- package/dist/backend/services/storefront-resolver.d.ts.map +1 -0
- package/dist/backend/services/storefront-resolver.js +162 -0
- package/dist/backend/services/storefront-resolver.js.map +1 -0
- package/dist/backend/services/target-validator.d.ts +24 -0
- package/dist/backend/services/target-validator.d.ts.map +1 -0
- package/dist/backend/services/target-validator.js +105 -0
- package/dist/backend/services/target-validator.js.map +1 -0
- package/dist/manifest.d.ts +167 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +170 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260505T193836_megamenu_init.d.ts +23 -0
- package/dist/migrations/20260505T193836_megamenu_init.d.ts.map +1 -0
- package/dist/migrations/20260505T193836_megamenu_init.js +91 -0
- package/dist/migrations/20260505T193836_megamenu_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/megamenu/index.md +159 -0
- package/i18n/en.json +96 -0
- package/i18n/pl.json +96 -0
- package/package.json +99 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"megamenu-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/megamenu-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAGL,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,eAAe,IAAI,kBAAkB,EAC1C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,KAAK,WAAW,GAAG;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,MAAM,CAAC;CAC3B,CAAC;AAuBF;;;;;GAKG;AACH,qBAAa,eAAe;IAExB,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,aAAa,YAAA,EACrB,mBAAmB,CAAC,EAAE,mBAAmB,YAAA;IAOtD,IAAI,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QAAC,UAAU,EAAE,IAAI,CAAA;KAAE,CAAC;IAQ9D,MAAM,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;IAY7D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAMxC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IA8BvE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBjC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAS3D,UAAU,CACd,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC;YA2ChB,gBAAgB;IAmBxB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5F;;;;;OAKG;IACG,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,uBAAuB,CAAC;IA0E7B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BnF,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAM5D,gBAAgB;IAK9B,OAAO,CAAC,aAAa;IAMrB;;;;;;;;;;;OAWG;YACW,YAAY;YAIZ,UAAU;YASV,aAAa;YAOb,SAAS;YAcT,QAAQ;IAStB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,GAAG;CAGZ;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,gBAAgB,EAAE,CAEvD"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { DictionaryReferenceError, ERROR_CODES, } from '@endora-commerce/contracts';
|
|
3
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
4
|
+
import { SalesChannel } from '@endora-commerce/platform/kernel';
|
|
5
|
+
/**
|
|
6
|
+
* Megamenu CRUD + binding lifecycle. The activation transaction enforces
|
|
7
|
+
* "exactly one active megamenu per (channel, language)" via the partial
|
|
8
|
+
* unique index on `megamenu_bindings(sales_channel_id, language) WHERE
|
|
9
|
+
* active = true` (R3 / FR-008).
|
|
10
|
+
*/
|
|
11
|
+
export class MegamenuService {
|
|
12
|
+
emFactory;
|
|
13
|
+
cache;
|
|
14
|
+
dictionaryValidator;
|
|
15
|
+
constructor(emFactory, cache, dictionaryValidator) {
|
|
16
|
+
this.emFactory = emFactory;
|
|
17
|
+
this.cache = cache;
|
|
18
|
+
this.dictionaryValidator = dictionaryValidator;
|
|
19
|
+
}
|
|
20
|
+
// ────────────────────────────────────────────────────────────────────
|
|
21
|
+
// CRUD
|
|
22
|
+
// ────────────────────────────────────────────────────────────────────
|
|
23
|
+
async list() {
|
|
24
|
+
const rows = (await this.emFactory().execute(`select * from megamenus order by updated_at desc limit 100`));
|
|
25
|
+
const data = await Promise.all(rows.map((row) => this.toSummary(row)));
|
|
26
|
+
return { data, nextCursor: null };
|
|
27
|
+
}
|
|
28
|
+
async create(input) {
|
|
29
|
+
const em = this.emFactory();
|
|
30
|
+
const id = randomUUID();
|
|
31
|
+
const now = new Date();
|
|
32
|
+
await em.execute(`insert into megamenus (id, name, description, version, created_at, updated_at)
|
|
33
|
+
values (?, ?, ?, 1, ?, ?)`, [id, input.name, input.description ?? null, now, now]);
|
|
34
|
+
return this.get(id);
|
|
35
|
+
}
|
|
36
|
+
async get(id) {
|
|
37
|
+
const row = await this.findRow(id);
|
|
38
|
+
if (!row)
|
|
39
|
+
throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'Megamenu not found.');
|
|
40
|
+
return this.toDetail(row);
|
|
41
|
+
}
|
|
42
|
+
async patch(id, input) {
|
|
43
|
+
const em = this.emFactory();
|
|
44
|
+
await em.transactional(async (tx) => {
|
|
45
|
+
const row = await this.findRow(id, tx);
|
|
46
|
+
if (!row)
|
|
47
|
+
throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'Megamenu not found.');
|
|
48
|
+
this.assertVersion(row.version, input.version);
|
|
49
|
+
const sets = [];
|
|
50
|
+
const params = [];
|
|
51
|
+
if (input.name !== undefined) {
|
|
52
|
+
sets.push('name = ?');
|
|
53
|
+
params.push(input.name);
|
|
54
|
+
}
|
|
55
|
+
if (input.description !== undefined) {
|
|
56
|
+
sets.push('description = ?');
|
|
57
|
+
params.push(input.description);
|
|
58
|
+
}
|
|
59
|
+
if (sets.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
params.push(id);
|
|
62
|
+
await tx.execute(`update megamenus
|
|
63
|
+
set ${sets.join(', ')}, version = version + 1, updated_at = now()
|
|
64
|
+
where id = ?`, params);
|
|
65
|
+
});
|
|
66
|
+
if (this.cache)
|
|
67
|
+
await this.cache.invalidateAll();
|
|
68
|
+
return this.get(id);
|
|
69
|
+
}
|
|
70
|
+
async delete(id) {
|
|
71
|
+
const em = this.emFactory();
|
|
72
|
+
const activeRows = (await em.execute(`select 1 from megamenu_bindings where megamenu_id = ? and active = true limit 1`, [id]));
|
|
73
|
+
if (activeRows.length > 0) {
|
|
74
|
+
throw new HttpError(409, ERROR_CODES.MEGAMENU_HAS_ACTIVE_BINDINGS, 'Megamenu has at least one active binding; deactivate before deleting.');
|
|
75
|
+
}
|
|
76
|
+
await em.execute('delete from megamenus where id = ?', [id]);
|
|
77
|
+
if (this.cache)
|
|
78
|
+
await this.cache.invalidateAll();
|
|
79
|
+
}
|
|
80
|
+
// ────────────────────────────────────────────────────────────────────
|
|
81
|
+
// Bindings
|
|
82
|
+
// ────────────────────────────────────────────────────────────────────
|
|
83
|
+
async listBindings(menuId) {
|
|
84
|
+
await this.assertMenuExists(menuId);
|
|
85
|
+
const rows = (await this.emFactory().execute(`select * from megamenu_bindings where megamenu_id = ? order by created_at asc`, [menuId]));
|
|
86
|
+
return rows.map((row) => this.toBindingDto(row));
|
|
87
|
+
}
|
|
88
|
+
async addBinding(menuId, salesChannelId, language) {
|
|
89
|
+
const em = this.emFactory();
|
|
90
|
+
await this.assertMenuExists(menuId);
|
|
91
|
+
await this.validateLanguage(language);
|
|
92
|
+
const channel = await this.fetchChannel(em, salesChannelId);
|
|
93
|
+
if (!channel) {
|
|
94
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Sales channel ${salesChannelId} not found.`);
|
|
95
|
+
}
|
|
96
|
+
if (!channel.languages.includes(language)) {
|
|
97
|
+
throw new HttpError(400, ERROR_CODES.MEGAMENU_LANGUAGE_NOT_IN_CHANNEL_SCOPE, `Language "${language}" is not in the channel's configured language set.`);
|
|
98
|
+
}
|
|
99
|
+
const existing = (await em.execute(`select 1 from megamenu_bindings
|
|
100
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]));
|
|
101
|
+
if (existing.length > 0) {
|
|
102
|
+
throw new HttpError(409, ERROR_CODES.MEGAMENU_BINDING_ALREADY_EXISTS, 'Binding for this (channel, language) already exists.');
|
|
103
|
+
}
|
|
104
|
+
const now = new Date();
|
|
105
|
+
await em.execute(`insert into megamenu_bindings
|
|
106
|
+
(megamenu_id, sales_channel_id, language, active, version, created_at, updated_at)
|
|
107
|
+
values (?, ?, ?, false, 1, ?, ?)`, [menuId, salesChannelId, language, now, now]);
|
|
108
|
+
if (this.cache)
|
|
109
|
+
await this.cache.invalidateScope(channel.code, language);
|
|
110
|
+
const rows = (await em.execute(`select * from megamenu_bindings
|
|
111
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]));
|
|
112
|
+
return this.toBindingDto(rows[0]);
|
|
113
|
+
}
|
|
114
|
+
async validateLanguage(language) {
|
|
115
|
+
if (!this.dictionaryValidator)
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
await this.dictionaryValidator.validateLanguageCode(language, 'create-or-change');
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err instanceof DictionaryReferenceError) {
|
|
122
|
+
throw new HttpError(409, err.code, err.code === 'DICTIONARY_ENTRY_INACTIVE'
|
|
123
|
+
? `Language code ${err.entryCode} is no longer available for megamenu bindings.`
|
|
124
|
+
: `Language code ${err.entryCode} is not recognised.`, [{ path: 'language', issue: err.code }]);
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async removeBinding(menuId, salesChannelId, language) {
|
|
130
|
+
const em = this.emFactory();
|
|
131
|
+
await this.assertMenuExists(menuId);
|
|
132
|
+
const channel = await this.fetchChannel(em, salesChannelId);
|
|
133
|
+
await em.execute(`delete from megamenu_bindings
|
|
134
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]);
|
|
135
|
+
// DELETE is idempotent — a missing row is fine. Cache invalidation
|
|
136
|
+
// runs unconditionally so a stale entry never lingers.
|
|
137
|
+
if (this.cache && channel)
|
|
138
|
+
await this.cache.invalidateScope(channel.code, language);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Atomic activation swap (FR-008 / R3): deactivate the prior holder
|
|
142
|
+
* for the scope (if any) inside the same transaction, then upsert the
|
|
143
|
+
* new active row. The partial unique index refuses dual-active state
|
|
144
|
+
* even if this method's body regresses.
|
|
145
|
+
*/
|
|
146
|
+
async activate(menuId, salesChannelId, language) {
|
|
147
|
+
const em = this.emFactory();
|
|
148
|
+
let response;
|
|
149
|
+
await em.transactional(async (tx) => {
|
|
150
|
+
// Verify the binding exists.
|
|
151
|
+
const binding = (await tx.execute(`select * from megamenu_bindings
|
|
152
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]));
|
|
153
|
+
if (binding.length === 0) {
|
|
154
|
+
throw new HttpError(404, ERROR_CODES.MEGAMENU_BINDING_NOT_FOUND, 'Binding not found.');
|
|
155
|
+
}
|
|
156
|
+
// Refuse on empty tree (R10).
|
|
157
|
+
const items = (await tx.execute(`select 1 from megamenu_items where megamenu_id = ? limit 1`, [menuId]));
|
|
158
|
+
if (items.length === 0) {
|
|
159
|
+
throw new HttpError(400, ERROR_CODES.MEGAMENU_EMPTY_TREE, 'Megamenu has no items; populate the tree before activating.');
|
|
160
|
+
}
|
|
161
|
+
// Identify any prior holder for the (channel, language) pair.
|
|
162
|
+
const prior = (await tx.execute(`select b.megamenu_id, m.name
|
|
163
|
+
from megamenu_bindings b
|
|
164
|
+
join megamenus m on m.id = b.megamenu_id
|
|
165
|
+
where b.sales_channel_id = ? and b.language = ? and b.active = true
|
|
166
|
+
and b.megamenu_id <> ?
|
|
167
|
+
limit 1`, [salesChannelId, language, menuId]));
|
|
168
|
+
// Deactivate any existing active binding in the scope (this menu or another).
|
|
169
|
+
await tx.execute(`update megamenu_bindings
|
|
170
|
+
set active = false, version = version + 1, updated_at = now()
|
|
171
|
+
where sales_channel_id = ? and language = ? and active = true`, [salesChannelId, language]);
|
|
172
|
+
// Activate the requested binding.
|
|
173
|
+
await tx.execute(`update megamenu_bindings
|
|
174
|
+
set active = true, version = version + 1, updated_at = now()
|
|
175
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]);
|
|
176
|
+
response = {
|
|
177
|
+
activated: { megamenuId: menuId, salesChannelId, language },
|
|
178
|
+
previouslyActive: prior.length > 0 && prior[0]
|
|
179
|
+
? { megamenuId: prior[0].megamenu_id, name: prior[0].name }
|
|
180
|
+
: null,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
if (this.cache) {
|
|
184
|
+
const channel = await this.fetchChannel(em, salesChannelId);
|
|
185
|
+
if (channel)
|
|
186
|
+
await this.cache.invalidateScope(channel.code, language);
|
|
187
|
+
}
|
|
188
|
+
return response;
|
|
189
|
+
}
|
|
190
|
+
async deactivate(menuId, salesChannelId, language) {
|
|
191
|
+
const em = this.emFactory();
|
|
192
|
+
const existing = (await em.execute(`select 1 from megamenu_bindings
|
|
193
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ? limit 1`, [menuId, salesChannelId, language]));
|
|
194
|
+
if (existing.length === 0) {
|
|
195
|
+
throw new HttpError(404, ERROR_CODES.MEGAMENU_BINDING_NOT_FOUND, 'Binding not found.');
|
|
196
|
+
}
|
|
197
|
+
await em.execute(`update megamenu_bindings
|
|
198
|
+
set active = false, version = version + 1, updated_at = now()
|
|
199
|
+
where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]);
|
|
200
|
+
if (this.cache) {
|
|
201
|
+
const channel = await this.fetchChannel(em, salesChannelId);
|
|
202
|
+
if (channel)
|
|
203
|
+
await this.cache.invalidateScope(channel.code, language);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ────────────────────────────────────────────────────────────────────
|
|
207
|
+
// Helpers
|
|
208
|
+
// ────────────────────────────────────────────────────────────────────
|
|
209
|
+
async findRow(id, em) {
|
|
210
|
+
const targetEm = (em ?? this.emFactory());
|
|
211
|
+
const rows = (await targetEm.execute('select * from megamenus where id = ?', [id]));
|
|
212
|
+
return rows[0] ?? null;
|
|
213
|
+
}
|
|
214
|
+
async assertMenuExists(id) {
|
|
215
|
+
const row = await this.findRow(id);
|
|
216
|
+
if (!row)
|
|
217
|
+
throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'Megamenu not found.');
|
|
218
|
+
}
|
|
219
|
+
assertVersion(current, supplied) {
|
|
220
|
+
if (supplied !== undefined && supplied !== current) {
|
|
221
|
+
throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Megamenu was updated concurrently.');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The kernel's own entity, not `select … from sales_channels` (feature 075,
|
|
226
|
+
* D-87). `sales_channels` is the kernel's table since feature 072 moved the
|
|
227
|
+
* resolution machinery there, and a module relating into the kernel by ORM is
|
|
228
|
+
* the sanctioned access.
|
|
229
|
+
*
|
|
230
|
+
* Read through the caller's `em`, not through
|
|
231
|
+
* `salesChannelResolutionPort.getById`: every caller here is inside
|
|
232
|
+
* `em.transactional`, and that accessor forks its own EntityManager, so the
|
|
233
|
+
* binding checks below would validate against rows outside the transaction
|
|
234
|
+
* they are about to write into.
|
|
235
|
+
*/
|
|
236
|
+
async fetchChannel(em, id) {
|
|
237
|
+
return em.findOne(SalesChannel, { id });
|
|
238
|
+
}
|
|
239
|
+
async fetchItems(menuId) {
|
|
240
|
+
return (await this.emFactory().execute(`select * from megamenu_items
|
|
241
|
+
where megamenu_id = ?
|
|
242
|
+
order by parent_id nulls first, position asc, id asc`, [menuId]));
|
|
243
|
+
}
|
|
244
|
+
async fetchBindings(menuId) {
|
|
245
|
+
return (await this.emFactory().execute(`select * from megamenu_bindings where megamenu_id = ? order by created_at asc`, [menuId]));
|
|
246
|
+
}
|
|
247
|
+
async toSummary(row) {
|
|
248
|
+
const bindings = (await this.fetchBindings(row.id)).map((b) => this.toBindingDto(b));
|
|
249
|
+
return {
|
|
250
|
+
id: row.id,
|
|
251
|
+
name: row.name,
|
|
252
|
+
description: row.description,
|
|
253
|
+
version: row.version,
|
|
254
|
+
bindings,
|
|
255
|
+
activeIn: bindings.filter((b) => b.active).length,
|
|
256
|
+
createdAt: this.iso(row.created_at),
|
|
257
|
+
updatedAt: this.iso(row.updated_at),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async toDetail(row) {
|
|
261
|
+
const summary = await this.toSummary(row);
|
|
262
|
+
const itemRows = await this.fetchItems(row.id);
|
|
263
|
+
return {
|
|
264
|
+
...summary,
|
|
265
|
+
items: itemRows.map((item) => this.toItemDto(item)),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
toBindingDto(row) {
|
|
269
|
+
return {
|
|
270
|
+
salesChannelId: row.sales_channel_id,
|
|
271
|
+
language: row.language,
|
|
272
|
+
active: row.active,
|
|
273
|
+
version: row.version,
|
|
274
|
+
createdAt: this.iso(row.created_at),
|
|
275
|
+
updatedAt: this.iso(row.updated_at),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
toItemDto(row) {
|
|
279
|
+
// The Zod discriminated union enforces shape on the way in; the way
|
|
280
|
+
// out we trust the persisted data and re-shape into the contract type.
|
|
281
|
+
return {
|
|
282
|
+
id: row.id,
|
|
283
|
+
parentId: row.parent_id,
|
|
284
|
+
position: row.position,
|
|
285
|
+
kind: row.kind,
|
|
286
|
+
labels: row.labels,
|
|
287
|
+
descriptions: row.descriptions,
|
|
288
|
+
target: row.target,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
iso(value) {
|
|
292
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/** Small helper for tests / smoke flows that need to assemble a tree. */
|
|
296
|
+
export function emptyResolvedItems() {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=megamenu-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"megamenu-service.js","sourceRoot":"","sources":["../../../src/backend/services/megamenu-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { ResolvedMegamenu } from '@endora-commerce/contracts';
|
|
3
|
+
import type { MegamenuCache } from './megamenu-cache.js';
|
|
4
|
+
import { type MegamenuCrossModulePorts } from './cross-module-ports.js';
|
|
5
|
+
/** Request-resolved sales channel handed in by the resolver middleware. */
|
|
6
|
+
export type ResolvedChannel = {
|
|
7
|
+
id: string;
|
|
8
|
+
code: string;
|
|
9
|
+
defaultLanguage: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Storefront resolution for the Megamenu module — feature 015 / T028.
|
|
13
|
+
*
|
|
14
|
+
* `resolveByChannelAndLanguage` is the single read endpoint behind
|
|
15
|
+
* `GET /api/v1/megamenu/by-channel`. It looks up the active binding via
|
|
16
|
+
* the partial unique index, fetches every item in one query, batches
|
|
17
|
+
* cross-module URL resolution, and assembles a recursive payload.
|
|
18
|
+
* Optional Redis read-through wraps the whole call.
|
|
19
|
+
*/
|
|
20
|
+
export declare class StorefrontResolver {
|
|
21
|
+
private readonly emFactory;
|
|
22
|
+
/**
|
|
23
|
+
* The four cross-module reads a resolved menu needs, as their owners'
|
|
24
|
+
* published ports (`specs/110-instance-repository/` T118c). This was
|
|
25
|
+
* `StorefrontDeps`, an interface this module exported so a composition root
|
|
26
|
+
* could build four closures against it — twice, and not identically.
|
|
27
|
+
*/
|
|
28
|
+
private readonly ports;
|
|
29
|
+
private readonly cache?;
|
|
30
|
+
constructor(emFactory: () => EntityManager,
|
|
31
|
+
/**
|
|
32
|
+
* The four cross-module reads a resolved menu needs, as their owners'
|
|
33
|
+
* published ports (`specs/110-instance-repository/` T118c). This was
|
|
34
|
+
* `StorefrontDeps`, an interface this module exported so a composition root
|
|
35
|
+
* could build four closures against it — twice, and not identically.
|
|
36
|
+
*/
|
|
37
|
+
ports: MegamenuCrossModulePorts, cache?: MegamenuCache | undefined);
|
|
38
|
+
resolveByChannelAndLanguage(input: {
|
|
39
|
+
resolvedChannel: ResolvedChannel;
|
|
40
|
+
language?: string | undefined;
|
|
41
|
+
}): Promise<ResolvedMegamenu | null>;
|
|
42
|
+
private buildOne;
|
|
43
|
+
private resolveIcon;
|
|
44
|
+
private resolveLabel;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=storefront-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storefront-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/storefront-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAOjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAKL,KAAK,wBAAwB,EAC9B,MAAM,yBAAyB,CAAC;AAQjC,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAqBpF;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBARN,SAAS,EAAE,MAAM,aAAa;IAC/C;;;;;OAKG;IACc,KAAK,EAAE,wBAAwB,EAC/B,KAAK,CAAC,EAAE,aAAa,YAAA;IAGlC,2BAA2B,CAAC,KAAK,EAAE;QACvC,eAAe,EAAE,eAAe,CAAC;QACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC/B,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;YA6DtB,QAAQ;YA4ER,WAAW;IAUzB,OAAO,CAAC,YAAY;CAiBrB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { resolveCategoryUrl, resolveCmsPageUrl, resolveMenuAsset, resolveMenuBlock, } from './cross-module-ports.js';
|
|
2
|
+
function toChannelRow(channel) {
|
|
3
|
+
return { id: channel.id, code: channel.code, default_language: channel.defaultLanguage };
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Storefront resolution for the Megamenu module — feature 015 / T028.
|
|
7
|
+
*
|
|
8
|
+
* `resolveByChannelAndLanguage` is the single read endpoint behind
|
|
9
|
+
* `GET /api/v1/megamenu/by-channel`. It looks up the active binding via
|
|
10
|
+
* the partial unique index, fetches every item in one query, batches
|
|
11
|
+
* cross-module URL resolution, and assembles a recursive payload.
|
|
12
|
+
* Optional Redis read-through wraps the whole call.
|
|
13
|
+
*/
|
|
14
|
+
export class StorefrontResolver {
|
|
15
|
+
emFactory;
|
|
16
|
+
ports;
|
|
17
|
+
cache;
|
|
18
|
+
constructor(emFactory,
|
|
19
|
+
/**
|
|
20
|
+
* The four cross-module reads a resolved menu needs, as their owners'
|
|
21
|
+
* published ports (`specs/110-instance-repository/` T118c). This was
|
|
22
|
+
* `StorefrontDeps`, an interface this module exported so a composition root
|
|
23
|
+
* could build four closures against it — twice, and not identically.
|
|
24
|
+
*/
|
|
25
|
+
ports, cache) {
|
|
26
|
+
this.emFactory = emFactory;
|
|
27
|
+
this.ports = ports;
|
|
28
|
+
this.cache = cache;
|
|
29
|
+
}
|
|
30
|
+
async resolveByChannelAndLanguage(input) {
|
|
31
|
+
const em = this.emFactory();
|
|
32
|
+
const channel = toChannelRow(input.resolvedChannel);
|
|
33
|
+
const requestedLanguage = input.language ?? channel.default_language;
|
|
34
|
+
if (this.cache) {
|
|
35
|
+
const cached = await this.cache.get(channel.code, requestedLanguage);
|
|
36
|
+
if (cached)
|
|
37
|
+
return cached;
|
|
38
|
+
}
|
|
39
|
+
const menuRows = (await em.execute(`select m.id::text as id, m.name as name
|
|
40
|
+
from megamenu_bindings b
|
|
41
|
+
join megamenus m on m.id = b.megamenu_id
|
|
42
|
+
where b.sales_channel_id = ? and b.language = ? and b.active = true
|
|
43
|
+
limit 1`, [channel.id, requestedLanguage]));
|
|
44
|
+
const active = menuRows[0];
|
|
45
|
+
if (!active)
|
|
46
|
+
return null;
|
|
47
|
+
const itemRows = (await em.execute(`select id::text, parent_id::text, position, kind, labels, descriptions, target
|
|
48
|
+
from megamenu_items
|
|
49
|
+
where megamenu_id = ?
|
|
50
|
+
order by parent_id nulls first, position asc, id asc`, [active.id]));
|
|
51
|
+
const byParent = new Map();
|
|
52
|
+
for (const row of itemRows) {
|
|
53
|
+
const list = byParent.get(row.parent_id) ?? [];
|
|
54
|
+
list.push(row);
|
|
55
|
+
byParent.set(row.parent_id, list);
|
|
56
|
+
}
|
|
57
|
+
const buildSubtree = async (parentId) => {
|
|
58
|
+
const children = byParent.get(parentId) ?? [];
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const row of children) {
|
|
61
|
+
const built = await this.buildOne(row, requestedLanguage, channel.default_language, buildSubtree);
|
|
62
|
+
if (built)
|
|
63
|
+
out.push(built);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
};
|
|
67
|
+
const tree = await buildSubtree(null);
|
|
68
|
+
const payload = {
|
|
69
|
+
megamenuId: active.id,
|
|
70
|
+
name: active.name,
|
|
71
|
+
language: requestedLanguage,
|
|
72
|
+
items: tree,
|
|
73
|
+
};
|
|
74
|
+
if (this.cache) {
|
|
75
|
+
await this.cache.set(channel.code, requestedLanguage, payload);
|
|
76
|
+
}
|
|
77
|
+
return payload;
|
|
78
|
+
}
|
|
79
|
+
async buildOne(row, language, fallbackLanguage, buildSubtree) {
|
|
80
|
+
const label = this.resolveLabel(row.labels, language, fallbackLanguage);
|
|
81
|
+
if (label === null)
|
|
82
|
+
return null;
|
|
83
|
+
const description = this.resolveLabel(row.descriptions ?? {}, language, fallbackLanguage);
|
|
84
|
+
const children = await buildSubtree(row.id);
|
|
85
|
+
const base = {
|
|
86
|
+
id: row.id,
|
|
87
|
+
parentId: row.parent_id,
|
|
88
|
+
kind: row.kind,
|
|
89
|
+
label,
|
|
90
|
+
...(description !== null ? { description } : {}),
|
|
91
|
+
children,
|
|
92
|
+
};
|
|
93
|
+
switch (row.kind) {
|
|
94
|
+
case 'category-link': {
|
|
95
|
+
const target = row.target;
|
|
96
|
+
const url = await resolveCategoryUrl(this.ports.categories, target.categoryId);
|
|
97
|
+
if (!url)
|
|
98
|
+
return null;
|
|
99
|
+
const icon = await this.resolveIcon(target.iconAssetId, target.iconPosition);
|
|
100
|
+
return { ...base, url, ...(icon ? { icon } : {}) };
|
|
101
|
+
}
|
|
102
|
+
case 'cms-page-link': {
|
|
103
|
+
const target = row.target;
|
|
104
|
+
const url = await resolveCmsPageUrl(this.ports.pages, target.pageId);
|
|
105
|
+
if (!url)
|
|
106
|
+
return null;
|
|
107
|
+
const icon = await this.resolveIcon(target.iconAssetId, target.iconPosition);
|
|
108
|
+
return { ...base, url, ...(icon ? { icon } : {}) };
|
|
109
|
+
}
|
|
110
|
+
case 'external-link': {
|
|
111
|
+
const target = row.target;
|
|
112
|
+
const icon = await this.resolveIcon(target.iconAssetId, target.iconPosition);
|
|
113
|
+
return { ...base, url: target.url, ...(icon ? { icon } : {}) };
|
|
114
|
+
}
|
|
115
|
+
case 'button': {
|
|
116
|
+
const target = row.target;
|
|
117
|
+
return { ...base, url: target.url, variant: target.variant };
|
|
118
|
+
}
|
|
119
|
+
case 'asset': {
|
|
120
|
+
const target = row.target;
|
|
121
|
+
if (!target.assetId)
|
|
122
|
+
return null;
|
|
123
|
+
const resolved = await resolveMenuAsset(this.ports, target.assetId);
|
|
124
|
+
if (!resolved)
|
|
125
|
+
return null;
|
|
126
|
+
return {
|
|
127
|
+
...base,
|
|
128
|
+
asset: { id: target.assetId, kind: resolved.kind, url: resolved.url, label: resolved.label },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
case 'cms-block-embed': {
|
|
132
|
+
const target = row.target;
|
|
133
|
+
const block = await resolveMenuBlock(this.ports.blocks, target.blockId, language);
|
|
134
|
+
if (!block)
|
|
135
|
+
return null;
|
|
136
|
+
return { ...base, block, embedSide: target.embedSide };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async resolveIcon(iconAssetId, iconPosition) {
|
|
141
|
+
if (!iconAssetId)
|
|
142
|
+
return null;
|
|
143
|
+
const asset = await resolveMenuAsset(this.ports, iconAssetId);
|
|
144
|
+
if (!asset)
|
|
145
|
+
return null;
|
|
146
|
+
return { assetId: iconAssetId, url: asset.url, position: iconPosition ?? 'left' };
|
|
147
|
+
}
|
|
148
|
+
resolveLabel(labels, language, fallbackLanguage) {
|
|
149
|
+
const direct = labels[language];
|
|
150
|
+
if (typeof direct === 'string' && direct.length > 0) {
|
|
151
|
+
return direct;
|
|
152
|
+
}
|
|
153
|
+
if (language !== fallbackLanguage) {
|
|
154
|
+
const fallback = labels[fallbackLanguage];
|
|
155
|
+
if (typeof fallback === 'string' && fallback.length > 0) {
|
|
156
|
+
return fallback;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=storefront-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storefront-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/storefront-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type MegamenuItem } from '@endora-commerce/contracts';
|
|
2
|
+
import type { MegamenuCrossModulePorts } from './cross-module-ports.js';
|
|
3
|
+
/**
|
|
4
|
+
* Validates a single MegamenuItem against the cross-module ports. Throws
|
|
5
|
+
* `HttpError` on the first failure so callers (`MegamenuItemService.setTree`)
|
|
6
|
+
* can re-raise unchanged.
|
|
7
|
+
*
|
|
8
|
+
* The four checks used to arrive as `TargetValidatorDeps`, an interface this
|
|
9
|
+
* module exported for a composition root to build closures against
|
|
10
|
+
* (`specs/110-instance-repository/` T118c). They are their owners' published read
|
|
11
|
+
* ports now, resolved by `backend/index.ts` and declared in this module's
|
|
12
|
+
* manifest.
|
|
13
|
+
*
|
|
14
|
+
* **`_channelIds` is threaded and read by nothing, which is what the roots' SQL
|
|
15
|
+
* did.** `setTree` computes the union of the menu's bound channels and passes it
|
|
16
|
+
* here; neither root's closure ever looked at it, so `MEGAMENU_TARGET_OUT_OF_SCOPE`
|
|
17
|
+
* has only ever meant *the target does not exist*. That is a documented v1
|
|
18
|
+
* narrowing — `megamenu-item-service.ts` says so at the call site, and the scope
|
|
19
|
+
* refusal is US3's — and the parameter is kept so US3 lands inside this function
|
|
20
|
+
* rather than re-plumbing its caller. Naming it `_` is the only change: the
|
|
21
|
+
* ignoring used to happen two files away in a root nobody reads.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateTarget(item: MegamenuItem, _channelIds: string[], ports: MegamenuCrossModulePorts): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=target-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target-validator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/target-validator.ts"],"names":[],"mappings":"AAQA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAIxE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAwGf"}
|