@endora-commerce/mod-custom-fields 0.0.0-stage → 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 +58 -2
- package/dist/admin/api/custom-fields-client.d.ts +10 -0
- package/dist/admin/api/custom-fields-client.d.ts.map +1 -0
- package/dist/admin/api/custom-fields-client.js +11 -0
- package/dist/admin/api/custom-fields-client.js.map +1 -0
- package/dist/admin/index.d.ts +44 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +51 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CustomFieldsPage.d.ts +10 -0
- package/dist/admin/pages/CustomFieldsPage.d.ts.map +1 -0
- package/dist/admin/pages/CustomFieldsPage.js +118 -0
- package/dist/admin/pages/CustomFieldsPage.js.map +1 -0
- package/dist/backend/commands/definition-commands.d.ts +22 -0
- package/dist/backend/commands/definition-commands.d.ts.map +1 -0
- package/dist/backend/commands/definition-commands.js +115 -0
- package/dist/backend/commands/definition-commands.js.map +1 -0
- package/dist/backend/entities/custom-field-definition.entity.d.ts +37 -0
- package/dist/backend/entities/custom-field-definition.entity.d.ts.map +1 -0
- package/dist/backend/entities/custom-field-definition.entity.js +98 -0
- package/dist/backend/entities/custom-field-definition.entity.js.map +1 -0
- package/dist/backend/entities/custom-field-option.entity.d.ts +25 -0
- package/dist/backend/entities/custom-field-option.entity.d.ts.map +1 -0
- package/dist/backend/entities/custom-field-option.entity.js +79 -0
- package/dist/backend/entities/custom-field-option.entity.js.map +1 -0
- package/dist/backend/index.d.ts +79 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +94 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +14 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +179 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/services/custom-field-definition-apply.d.ts +63 -0
- package/dist/backend/services/custom-field-definition-apply.d.ts.map +1 -0
- package/dist/backend/services/custom-field-definition-apply.js +221 -0
- package/dist/backend/services/custom-field-definition-apply.js.map +1 -0
- package/dist/backend/services/custom-field-definition.service.d.ts +68 -0
- package/dist/backend/services/custom-field-definition.service.d.ts.map +1 -0
- package/dist/backend/services/custom-field-definition.service.js +205 -0
- package/dist/backend/services/custom-field-definition.service.js.map +1 -0
- package/dist/backend/services/custom-field-definitions-cache.d.ts +46 -0
- package/dist/backend/services/custom-field-definitions-cache.d.ts.map +1 -0
- package/dist/backend/services/custom-field-definitions-cache.js +82 -0
- package/dist/backend/services/custom-field-definitions-cache.js.map +1 -0
- package/dist/backend/services/custom-field-read-port.d.ts +61 -0
- package/dist/backend/services/custom-field-read-port.d.ts.map +1 -0
- package/dist/backend/services/custom-field-read-port.js +82 -0
- package/dist/backend/services/custom-field-read-port.js.map +1 -0
- package/dist/backend/services/custom-field-registry.d.ts +41 -0
- package/dist/backend/services/custom-field-registry.d.ts.map +1 -0
- package/dist/backend/services/custom-field-registry.js +24 -0
- package/dist/backend/services/custom-field-registry.js.map +1 -0
- package/dist/backend/services/custom-field-value.service.d.ts +56 -0
- package/dist/backend/services/custom-field-value.service.d.ts.map +1 -0
- package/dist/backend/services/custom-field-value.service.js +175 -0
- package/dist/backend/services/custom-field-value.service.js.map +1 -0
- package/dist/manifest.d.ts +177 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +130 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260718T200338_custom_fields_init.d.ts +20 -0
- package/dist/migrations/20260718T200338_custom_fields_init.d.ts.map +1 -0
- package/dist/migrations/20260718T200338_custom_fields_init.js +60 -0
- package/dist/migrations/20260718T200338_custom_fields_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/dist/ports/index.d.ts +123 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/i18n/en.json +15 -0
- package/i18n/pl.json +15 -0
- package/package.json +99 -3
- package/tailwind.css +14 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* CustomFieldDefinition — an operator-defined field for one host entity type
|
|
15
|
+
* (feature 055, FR-001/FR-002).
|
|
16
|
+
*
|
|
17
|
+
* Entity-agnostic by construction (FR-006): it carries a `key`, a per-locale
|
|
18
|
+
* `label` + `labelDefault`, a `valueType`, a `required` flag, ordering, and an
|
|
19
|
+
* opaque `config` JSONB for host-capability opt-in that the generic core stores
|
|
20
|
+
* but never interprets. It deliberately omits the catalog-only capability flags
|
|
21
|
+
* that made the product-attribute registry un-reusable — those stay behind host
|
|
22
|
+
* extension points. Platform-global (`@GlobalEntity`), like the product-attribute
|
|
23
|
+
* registry; the VALUES live on the host row's JSONB column and inherit that
|
|
24
|
+
* host's tenant scope.
|
|
25
|
+
*
|
|
26
|
+
* Unique on `(entity_type, key)` so a key is a clean identifier within an
|
|
27
|
+
* entity type. Options for `select` / `multiselect` live on
|
|
28
|
+
* `custom_field_options` (FK ON DELETE CASCADE).
|
|
29
|
+
*/
|
|
30
|
+
let CustomFieldDefinition = class CustomFieldDefinition {
|
|
31
|
+
[OptionalProps];
|
|
32
|
+
id = randomUUID();
|
|
33
|
+
entityType;
|
|
34
|
+
key;
|
|
35
|
+
/** Per-locale label map (BCP-47-keyed). Empty `{}` is allowed when only `labelDefault` is filled. */
|
|
36
|
+
label = {};
|
|
37
|
+
/** Fallback label used when the active locale is missing from `label` (US1 scenario 4). */
|
|
38
|
+
labelDefault;
|
|
39
|
+
valueType;
|
|
40
|
+
required = false;
|
|
41
|
+
sortOrder = 0;
|
|
42
|
+
/** Opaque host-capability flags (FR-006). The core stores but never reads this for meaning. */
|
|
43
|
+
config = {};
|
|
44
|
+
createdAt = new Date();
|
|
45
|
+
updatedAt = new Date();
|
|
46
|
+
};
|
|
47
|
+
__decorate([
|
|
48
|
+
PrimaryKey({ type: 'uuid' }),
|
|
49
|
+
__metadata("design:type", String)
|
|
50
|
+
], CustomFieldDefinition.prototype, "id", void 0);
|
|
51
|
+
__decorate([
|
|
52
|
+
Property({ type: 'string', length: 32 }),
|
|
53
|
+
Index(),
|
|
54
|
+
__metadata("design:type", String)
|
|
55
|
+
], CustomFieldDefinition.prototype, "entityType", void 0);
|
|
56
|
+
__decorate([
|
|
57
|
+
Property({ type: 'string', length: 64 }),
|
|
58
|
+
__metadata("design:type", String)
|
|
59
|
+
], CustomFieldDefinition.prototype, "key", void 0);
|
|
60
|
+
__decorate([
|
|
61
|
+
Property({ type: 'json' }),
|
|
62
|
+
__metadata("design:type", Object)
|
|
63
|
+
], CustomFieldDefinition.prototype, "label", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
Property({ type: 'string', length: 200 }),
|
|
66
|
+
__metadata("design:type", String)
|
|
67
|
+
], CustomFieldDefinition.prototype, "labelDefault", void 0);
|
|
68
|
+
__decorate([
|
|
69
|
+
Property({ type: 'string', length: 16 }),
|
|
70
|
+
__metadata("design:type", String)
|
|
71
|
+
], CustomFieldDefinition.prototype, "valueType", void 0);
|
|
72
|
+
__decorate([
|
|
73
|
+
Property({ type: 'boolean' }),
|
|
74
|
+
__metadata("design:type", Boolean)
|
|
75
|
+
], CustomFieldDefinition.prototype, "required", void 0);
|
|
76
|
+
__decorate([
|
|
77
|
+
Property({ type: 'integer' }),
|
|
78
|
+
__metadata("design:type", Number)
|
|
79
|
+
], CustomFieldDefinition.prototype, "sortOrder", void 0);
|
|
80
|
+
__decorate([
|
|
81
|
+
Property({ type: 'json' }),
|
|
82
|
+
__metadata("design:type", Object)
|
|
83
|
+
], CustomFieldDefinition.prototype, "config", void 0);
|
|
84
|
+
__decorate([
|
|
85
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
86
|
+
__metadata("design:type", Date)
|
|
87
|
+
], CustomFieldDefinition.prototype, "createdAt", void 0);
|
|
88
|
+
__decorate([
|
|
89
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
90
|
+
__metadata("design:type", Date)
|
|
91
|
+
], CustomFieldDefinition.prototype, "updatedAt", void 0);
|
|
92
|
+
CustomFieldDefinition = __decorate([
|
|
93
|
+
GlobalEntity(),
|
|
94
|
+
Entity({ tableName: 'custom_field_definitions' }),
|
|
95
|
+
Unique({ properties: ['entityType', 'key'] })
|
|
96
|
+
], CustomFieldDefinition);
|
|
97
|
+
export { CustomFieldDefinition };
|
|
98
|
+
//# sourceMappingURL=custom-field-definition.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-field-definition.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/custom-field-definition.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGpC;;;;;;;;;;;;;;;;GAgBG;AAII,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,CAAC,aAAa,CAAC,CAOF;IAGb,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,UAAU,CAAuB;IAGjC,GAAG,CAAU;IAEb,qGAAqG;IAErG,KAAK,GAA2B,EAAE,CAAC;IAEnC,2FAA2F;IAE3F,YAAY,CAAU;IAGtB,SAAS,CAAwB;IAGjC,QAAQ,GAAY,KAAK,CAAC;IAG1B,SAAS,GAAW,CAAC,CAAC;IAEtB,+FAA+F;IAE/F,MAAM,GAA4B,EAAE,CAAC;IAGrC,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAnCC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;iDACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,KAAK,EAAE;;yDACyB;AAGjC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;kDAC5B;AAIb;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oDACQ;AAInC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;2DACpB;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;wDACR;AAGjC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;uDACJ;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;wDACR;AAItB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;qDACU;AAGrC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;wDAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;wDAAc;AA7ClB,qBAAqB;IAHjC,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,0BAA0B,EAAE,CAAC;IACjD,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;GACjC,qBAAqB,CA8CjC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* CustomFieldOption — one selectable value for a `select` / `multiselect`
|
|
4
|
+
* custom-field definition (feature 055, FR-002). Shape lifted from the catalog
|
|
5
|
+
* `AttributeOption`: stable `value`, per-locale label + default fallback,
|
|
6
|
+
* `isDefault`, `sortOrder`.
|
|
7
|
+
*
|
|
8
|
+
* Unique on `(definitionId, value)`. FK-by-id to `custom_field_definitions`
|
|
9
|
+
* with ON DELETE CASCADE (enforced at migration level, mirroring
|
|
10
|
+
* `attribute_options`). Platform-global (`@GlobalEntity`).
|
|
11
|
+
*/
|
|
12
|
+
export declare class CustomFieldOption {
|
|
13
|
+
[OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'isDefault' | 'sortOrder' | 'label';
|
|
14
|
+
id: string;
|
|
15
|
+
definitionId: string;
|
|
16
|
+
value: string;
|
|
17
|
+
/** Per-locale label map (BCP-47-keyed). Empty `{}` is allowed when only `labelDefault` is filled. */
|
|
18
|
+
label: Record<string, string>;
|
|
19
|
+
labelDefault: string;
|
|
20
|
+
isDefault: boolean;
|
|
21
|
+
sortOrder: number;
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
updatedAt: Date;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=custom-field-option.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-field-option.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/custom-field-option.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAI7F;;;;;;;;;GASG;AACH,qBAGa,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,OAAO,CAAC;IAGZ,EAAE,EAAE,MAAM,CAAgB;IAI1B,YAAY,EAAG,MAAM,CAAC;IAGtB,KAAK,EAAG,MAAM,CAAC;IAEf,qGAAqG;IAErG,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAGnC,YAAY,EAAG,MAAM,CAAC;IAGtB,SAAS,EAAE,OAAO,CAAS;IAG3B,SAAS,EAAE,MAAM,CAAK;IAGtB,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* CustomFieldOption — one selectable value for a `select` / `multiselect`
|
|
15
|
+
* custom-field definition (feature 055, FR-002). Shape lifted from the catalog
|
|
16
|
+
* `AttributeOption`: stable `value`, per-locale label + default fallback,
|
|
17
|
+
* `isDefault`, `sortOrder`.
|
|
18
|
+
*
|
|
19
|
+
* Unique on `(definitionId, value)`. FK-by-id to `custom_field_definitions`
|
|
20
|
+
* with ON DELETE CASCADE (enforced at migration level, mirroring
|
|
21
|
+
* `attribute_options`). Platform-global (`@GlobalEntity`).
|
|
22
|
+
*/
|
|
23
|
+
let CustomFieldOption = class CustomFieldOption {
|
|
24
|
+
[OptionalProps];
|
|
25
|
+
id = randomUUID();
|
|
26
|
+
definitionId;
|
|
27
|
+
value;
|
|
28
|
+
/** Per-locale label map (BCP-47-keyed). Empty `{}` is allowed when only `labelDefault` is filled. */
|
|
29
|
+
label = {};
|
|
30
|
+
labelDefault;
|
|
31
|
+
isDefault = false;
|
|
32
|
+
sortOrder = 0;
|
|
33
|
+
createdAt = new Date();
|
|
34
|
+
updatedAt = new Date();
|
|
35
|
+
};
|
|
36
|
+
__decorate([
|
|
37
|
+
PrimaryKey({ type: 'uuid' }),
|
|
38
|
+
__metadata("design:type", String)
|
|
39
|
+
], CustomFieldOption.prototype, "id", void 0);
|
|
40
|
+
__decorate([
|
|
41
|
+
Property({ type: 'uuid' }),
|
|
42
|
+
Index(),
|
|
43
|
+
__metadata("design:type", String)
|
|
44
|
+
], CustomFieldOption.prototype, "definitionId", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
Property({ type: 'string', length: 200 }),
|
|
47
|
+
__metadata("design:type", String)
|
|
48
|
+
], CustomFieldOption.prototype, "value", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
Property({ type: 'json' }),
|
|
51
|
+
__metadata("design:type", Object)
|
|
52
|
+
], CustomFieldOption.prototype, "label", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
Property({ type: 'string', length: 200 }),
|
|
55
|
+
__metadata("design:type", String)
|
|
56
|
+
], CustomFieldOption.prototype, "labelDefault", void 0);
|
|
57
|
+
__decorate([
|
|
58
|
+
Property({ type: 'boolean' }),
|
|
59
|
+
__metadata("design:type", Boolean)
|
|
60
|
+
], CustomFieldOption.prototype, "isDefault", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
Property({ type: 'integer' }),
|
|
63
|
+
__metadata("design:type", Number)
|
|
64
|
+
], CustomFieldOption.prototype, "sortOrder", void 0);
|
|
65
|
+
__decorate([
|
|
66
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
67
|
+
__metadata("design:type", Date)
|
|
68
|
+
], CustomFieldOption.prototype, "createdAt", void 0);
|
|
69
|
+
__decorate([
|
|
70
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
71
|
+
__metadata("design:type", Date)
|
|
72
|
+
], CustomFieldOption.prototype, "updatedAt", void 0);
|
|
73
|
+
CustomFieldOption = __decorate([
|
|
74
|
+
GlobalEntity(),
|
|
75
|
+
Entity({ tableName: 'custom_field_options' }),
|
|
76
|
+
Unique({ properties: ['definitionId', 'value'] })
|
|
77
|
+
], CustomFieldOption);
|
|
78
|
+
export { CustomFieldOption };
|
|
79
|
+
//# sourceMappingURL=custom-field-option.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-field-option.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/custom-field-option.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;GASG;AAII,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAMH;IAGZ,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,YAAY,CAAU;IAGtB,KAAK,CAAU;IAEf,qGAAqG;IAErG,KAAK,GAA2B,EAAE,CAAC;IAGnC,YAAY,CAAU;IAGtB,SAAS,GAAY,KAAK,CAAC;IAG3B,SAAS,GAAW,CAAC,CAAC;IAGtB,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AA3BC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;6CACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;uDACc;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;gDAC3B;AAIf;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;gDACQ;AAGnC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;uDACpB;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;oDACH;AAG3B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;oDACR;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;oDAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;oDAAc;AApClB,iBAAiB;IAH7B,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC;GACrC,iBAAiB,CAqC7B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
import type { CommandBus } from '@endora-commerce/platform/commands';
|
|
4
|
+
import type { ModuleContext } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
6
|
+
import { CustomFieldDefinitionsCache } from './services/custom-field-definitions-cache.js';
|
|
7
|
+
import { CustomFieldDefinitionService } from './services/custom-field-definition.service.js';
|
|
8
|
+
import { CustomFieldValueService } from './services/custom-field-value.service.js';
|
|
9
|
+
import { CustomFieldDefinition } from './entities/custom-field-definition.entity.js';
|
|
10
|
+
import { CustomFieldOption } from './entities/custom-field-option.entity.js';
|
|
11
|
+
/**
|
|
12
|
+
* `custom_fields` — the module that cannot be switched off (feature 072, wave 1,
|
|
13
|
+
* T087).
|
|
14
|
+
*
|
|
15
|
+
* Mechanically this is one of the plainer conversions: one factory call per
|
|
16
|
+
* root, one admin route file, three services with a cycle between two of them.
|
|
17
|
+
* What it forced was a decision the wave had so far been able to defer.
|
|
18
|
+
*
|
|
19
|
+
* **Why the activation control is `nonDeactivatable`.** Every other converted
|
|
20
|
+
* module got a Setting an operator can flip. This one gets a locked control and
|
|
21
|
+
* a reason, because the alternative is a switch that half-works. Custom-field
|
|
22
|
+
* *values* live in the host's own JSONB column — `orders.custom_field_values`,
|
|
23
|
+
* `products.attribute_values` — so switching the module off does not hide the
|
|
24
|
+
* data; it removes the definitions that give the data meaning. `validateAndMerge`
|
|
25
|
+
* with no definitions silently ignores every incoming key and `project` returns
|
|
26
|
+
* an empty bag, so a host write would drop the operator's custom fields and a
|
|
27
|
+
* host read would stop showing them, with a 200 either way. `catalog`,
|
|
28
|
+
* `product_feeds` and `pim_ergonode` already declare this module a hard
|
|
29
|
+
* dependency for exactly that reason; `catalog`'s manifest says outright that
|
|
30
|
+
* it "must not hard-uninstall it under a live catalog". Declaring the reality
|
|
31
|
+
* beats shipping a control whose off position corrupts.
|
|
32
|
+
*
|
|
33
|
+
* **The cache is the module's spine, and there is one.** `getForEntity` caches
|
|
34
|
+
* per entity type for 5 s, invalidated across processes on a Redis channel. The
|
|
35
|
+
* admin service publishes an invalidation after every definition write, and the
|
|
36
|
+
* value service reads through the same instance — which is what makes a write
|
|
37
|
+
* visible to the next validation instead of up to five seconds later. The two
|
|
38
|
+
* registrations below therefore resolve one `CustomFieldDefinitionService`, and
|
|
39
|
+
* `customFieldValueService` is built *from* it rather than beside it.
|
|
40
|
+
*
|
|
41
|
+
* **The hard-uninstall cleanup stays in `manifest.ts`.** That is the only place
|
|
42
|
+
* the lifecycle orchestrator looks: it reads `uninstallHook` off the registry
|
|
43
|
+
* entry the composer generates into the manifest index. There is no
|
|
44
|
+
* container-side equivalent — `ctx.onInstall`/`ctx.onUninstall` existed, were
|
|
45
|
+
* never run by anything, and were deleted by D-46 precisely because a hook
|
|
46
|
+
* placed there would compile, pass every test, and quietly stop dropping
|
|
47
|
+
* definitions on a hard uninstall.
|
|
48
|
+
*/
|
|
49
|
+
export interface CustomFieldsCradle {
|
|
50
|
+
readonly emFactory: () => EntityManager;
|
|
51
|
+
readonly commandBus: CommandBus;
|
|
52
|
+
readonly requireAdmin: RequireAdminFactory;
|
|
53
|
+
readonly redis: Redis;
|
|
54
|
+
/** The shared subscriber connection; see the note on `onBoot` below. */
|
|
55
|
+
readonly redisSubscriber: Redis;
|
|
56
|
+
readonly customFieldDefinitionsCache: CustomFieldDefinitionsCache;
|
|
57
|
+
readonly customFieldServices: CustomFieldServices;
|
|
58
|
+
readonly customFieldDefinitionService: CustomFieldDefinitionService;
|
|
59
|
+
readonly customFieldValueService: CustomFieldValueService;
|
|
60
|
+
}
|
|
61
|
+
interface CustomFieldServices {
|
|
62
|
+
readonly definitionService: CustomFieldDefinitionService;
|
|
63
|
+
readonly valueService: CustomFieldValueService;
|
|
64
|
+
}
|
|
65
|
+
export declare function registerModule(ctx: ModuleContext): void;
|
|
66
|
+
/**
|
|
67
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
68
|
+
* array and **no named class export** (D-168).
|
|
69
|
+
*
|
|
70
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
71
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
72
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
73
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
74
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
75
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
76
|
+
*/
|
|
77
|
+
export declare const entities: (typeof CustomFieldDefinition | typeof CustomFieldOption)[];
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAC3F,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAGnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC;IAChC,QAAQ,CAAC,2BAA2B,EAAE,2BAA2B,CAAC;IAClE,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD,QAAQ,CAAC,4BAA4B,EAAE,4BAA4B,CAAC;IACpE,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;CAC3D;AAED,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,4BAA4B,CAAC;IACzD,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;CAChD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAkGvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,6DAGpB,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CustomFieldDefinitionsCache } from './services/custom-field-definitions-cache.js';
|
|
2
|
+
import { CustomFieldDefinitionService } from './services/custom-field-definition.service.js';
|
|
3
|
+
import { CustomFieldValueService } from './services/custom-field-value.service.js';
|
|
4
|
+
import { CustomFieldDefinitionReadService } from './services/custom-field-read-port.js';
|
|
5
|
+
import { registerCustomFieldsAdminRoutes } from './routes.admin.js';
|
|
6
|
+
import { CustomFieldDefinition } from './entities/custom-field-definition.entity.js';
|
|
7
|
+
import { CustomFieldOption } from './entities/custom-field-option.entity.js';
|
|
8
|
+
export function registerModule(ctx) {
|
|
9
|
+
ctx.di.register({
|
|
10
|
+
// Internal: the definition and value services are the module's public
|
|
11
|
+
// surface, and the cache is how they agree. Nothing outside resolves it.
|
|
12
|
+
customFieldDefinitionsCache: ctx
|
|
13
|
+
.asFunction(({ redis }) => new CustomFieldDefinitionsCache(redis))
|
|
14
|
+
.singleton(),
|
|
15
|
+
// The two services are **one registration** because they are one object
|
|
16
|
+
// graph: the definition service holds the value service to run its
|
|
17
|
+
// change-guards, and the value service reads definitions back through it.
|
|
18
|
+
// Registering them separately means the guard's value service and the one
|
|
19
|
+
// hosts validate against are different instances — harmless today, since
|
|
20
|
+
// the value service is a stateless wrapper, and exactly the divergence that
|
|
21
|
+
// stops being harmless the first time one of them gains state.
|
|
22
|
+
customFieldServices: ctx
|
|
23
|
+
.asFunction(({ emFactory, customFieldDefinitionsCache, commandBus, }) => {
|
|
24
|
+
const definitionService = new CustomFieldDefinitionService(emFactory, customFieldDefinitionsCache, commandBus);
|
|
25
|
+
const valueService = new CustomFieldValueService(definitionService);
|
|
26
|
+
definitionService.setValueService(valueService);
|
|
27
|
+
return { definitionService, valueService };
|
|
28
|
+
})
|
|
29
|
+
.singleton(),
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* The module's own CRUD surface — its admin routes resolve it, and `catalog`
|
|
33
|
+
* reaches it for one thing only: the transactional apply seam
|
|
34
|
+
* (`CustomFieldDefinitionApplyApi`, declared in this module's `ports/`
|
|
35
|
+
* directory), whose every `apply*` method takes the caller's `EntityManager`
|
|
36
|
+
* so that a product attribute and its definition are written under one
|
|
37
|
+
* transaction. D-77 ruled that seam permanent and FR-034 keeps a MikroORM
|
|
38
|
+
* type out of `@endora-commerce/contracts`, so it is the one name this module publishes
|
|
39
|
+
* no contract for; it is ledgered as such in
|
|
40
|
+
* `RESOLUTIONS_OF_UNPUBLISHED_NAMES`. Every definition **read** goes through
|
|
41
|
+
* `customFieldDefinitionReadPort` below — the per-entity lists since issue
|
|
42
|
+
* #209, and the single definition by id since feature 080's T053(b).
|
|
43
|
+
*/
|
|
44
|
+
ctx.di.providePort('customFieldDefinitionService', ctx
|
|
45
|
+
.asFunction(({ customFieldServices }) => customFieldServices.definitionService)
|
|
46
|
+
.singleton());
|
|
47
|
+
ctx.di.providePort('customFieldValueService', ctx
|
|
48
|
+
.asFunction(({ customFieldServices }) => customFieldServices.valueService)
|
|
49
|
+
.singleton());
|
|
50
|
+
/**
|
|
51
|
+
* Feature 075, Phase P — the definition read model, without the two ORM
|
|
52
|
+
* entities `CachedDefinition` carries.
|
|
53
|
+
*
|
|
54
|
+
* `customFieldDefinitionService` above stays for the reason its own note
|
|
55
|
+
* gives. This is the read seven modules actually make — `catalog`'s composed
|
|
56
|
+
* attribute read model most of all, because since feature 061 the definition
|
|
57
|
+
* half of a product attribute *is* a custom-field definition, and since issue
|
|
58
|
+
* #209 that read names this port rather than the CRUD registration.
|
|
59
|
+
*/
|
|
60
|
+
ctx.di.providePort('customFieldDefinitionReadPort', ctx
|
|
61
|
+
.asFunction(({ customFieldServices }) => new CustomFieldDefinitionReadService(customFieldServices.definitionService))
|
|
62
|
+
.singleton());
|
|
63
|
+
ctx.onBoot(async () => {
|
|
64
|
+
// The subscriber is one connection shared by the whole composition —
|
|
65
|
+
// ioredis puts a connection in subscriber mode, so a per-module one would
|
|
66
|
+
// cost a socket per module for no benefit. `start` is idempotent and only
|
|
67
|
+
// adds a listener for this module's channel.
|
|
68
|
+
const { customFieldDefinitionsCache, redisSubscriber } = ctx.cradle();
|
|
69
|
+
await customFieldDefinitionsCache.start(redisSubscriber);
|
|
70
|
+
});
|
|
71
|
+
ctx.routes(async (app) => {
|
|
72
|
+
const { customFieldDefinitionService, requireAdmin } = ctx.cradle();
|
|
73
|
+
await registerCustomFieldsAdminRoutes(app, {
|
|
74
|
+
definitionService: customFieldDefinitionService,
|
|
75
|
+
requireAdmin,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
81
|
+
* array and **no named class export** (D-168).
|
|
82
|
+
*
|
|
83
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
84
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
85
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
86
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
87
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
88
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
89
|
+
*/
|
|
90
|
+
export const entities = [
|
|
91
|
+
CustomFieldDefinition,
|
|
92
|
+
CustomFieldOption,
|
|
93
|
+
];
|
|
94
|
+
//# 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,14 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import { type CustomFieldDefinitionService } from './services/custom-field-definition.service.js';
|
|
3
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
4
|
+
export interface CustomFieldsAdminDeps {
|
|
5
|
+
definitionService: CustomFieldDefinitionService;
|
|
6
|
+
requireAdmin: RequireAdminFactory;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Admin API for custom-field definitions + options (feature 055). All mutations
|
|
10
|
+
* run through the service, which dispatches Commands (Principle XIII). Reads are
|
|
11
|
+
* cached per entity type.
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerCustomFieldsAdminRoutes(app: FastifyInstance, deps: CustomFieldsAdminDeps): Promise<void>;
|
|
14
|
+
//# 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;AAY/C,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,+CAA+C,CAAC;AAMvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,4BAA4B,CAAC;IAChD,YAAY,EAAE,mBAAmB,CAAC;CACnC;AA8DD;;;;GAIG;AACH,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA4Jf"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { ERROR_CODES, createCustomFieldDefinitionSchema, customFieldOptionSchema, supportedEntityTypeSchema, updateCustomFieldDefinitionSchema, } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
import { CustomFieldDefinitionError, } from './services/custom-field-definition.service.js';
|
|
4
|
+
import { isSupportedEntityType, SUPPORTED_ENTITIES, } from './services/custom-field-registry.js';
|
|
5
|
+
/** Map a service error to the HTTP envelope. */
|
|
6
|
+
function toHttp(err) {
|
|
7
|
+
if (err instanceof CustomFieldDefinitionError) {
|
|
8
|
+
switch (err.code) {
|
|
9
|
+
case 'not_found':
|
|
10
|
+
throw new HttpError(404, ERROR_CODES.CUSTOM_FIELD_NOT_FOUND, err.message);
|
|
11
|
+
case 'duplicate_key':
|
|
12
|
+
throw new HttpError(409, ERROR_CODES.CUSTOM_FIELD_KEY_CONFLICT, err.message);
|
|
13
|
+
case 'value_type_locked':
|
|
14
|
+
case 'option_in_use':
|
|
15
|
+
throw new HttpError(409, ERROR_CODES.CUSTOM_FIELD_DEFINITION_INVALID, err.message);
|
|
16
|
+
default:
|
|
17
|
+
throw new HttpError(422, ERROR_CODES.CUSTOM_FIELD_DEFINITION_INVALID, err.message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Feature 061 — generic host-managed refusal. When an entity type's registry
|
|
24
|
+
* entry declares `managedBy`, its definitions are mutated only through the
|
|
25
|
+
* owning host module's own surface; the generic mutation routes refuse with
|
|
26
|
+
* 409. Entity-agnostic: only the marker's presence is checked, never which
|
|
27
|
+
* module manages.
|
|
28
|
+
*/
|
|
29
|
+
function assertNotHostManaged(entityType) {
|
|
30
|
+
const meta = isSupportedEntityType(entityType) ? SUPPORTED_ENTITIES[entityType] : undefined;
|
|
31
|
+
if (meta?.managedBy) {
|
|
32
|
+
throw new HttpError(409, ERROR_CODES.CUSTOM_FIELD_HOST_MANAGED, `Definitions for entity type "${entityType}" are managed by the "${meta.managedBy.moduleId}" module and cannot be modified here.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function serialize({ definition, options }) {
|
|
36
|
+
return {
|
|
37
|
+
id: definition.id,
|
|
38
|
+
entityType: definition.entityType,
|
|
39
|
+
key: definition.key,
|
|
40
|
+
label: definition.label,
|
|
41
|
+
labelDefault: definition.labelDefault,
|
|
42
|
+
valueType: definition.valueType,
|
|
43
|
+
required: definition.required,
|
|
44
|
+
sortOrder: definition.sortOrder,
|
|
45
|
+
config: definition.config,
|
|
46
|
+
options: options.map((o) => ({
|
|
47
|
+
id: o.id,
|
|
48
|
+
value: o.value,
|
|
49
|
+
label: o.label,
|
|
50
|
+
labelDefault: o.labelDefault,
|
|
51
|
+
isDefault: o.isDefault,
|
|
52
|
+
sortOrder: o.sortOrder,
|
|
53
|
+
})),
|
|
54
|
+
createdAt: definition.createdAt.toISOString(),
|
|
55
|
+
updatedAt: definition.updatedAt.toISOString(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Admin API for custom-field definitions + options (feature 055). All mutations
|
|
60
|
+
* run through the service, which dispatches Commands (Principle XIII). Reads are
|
|
61
|
+
* cached per entity type.
|
|
62
|
+
*/
|
|
63
|
+
export async function registerCustomFieldsAdminRoutes(app, deps) {
|
|
64
|
+
const { definitionService, requireAdmin } = deps;
|
|
65
|
+
/** Refuse mutating an existing definition whose entity type is host-managed (feature 061). */
|
|
66
|
+
async function assertDefinitionNotHostManaged(id) {
|
|
67
|
+
const def = await definitionService.getById(id);
|
|
68
|
+
if (def)
|
|
69
|
+
assertNotHostManaged(def.definition.entityType);
|
|
70
|
+
}
|
|
71
|
+
// Feature 061 T037 — supported entity types + host-managed metadata, so the
|
|
72
|
+
// generic admin UI can render managed types read-only with a link to the
|
|
73
|
+
// owning module's surface (no hard-coded entity list in the frontend).
|
|
74
|
+
app.get('/api/v1/admin/custom-fields/entity-types', { preHandler: requireAdmin('custom_fields:read') }, async () => {
|
|
75
|
+
const data = Object.entries(SUPPORTED_ENTITIES).map(([entityType, meta]) => ({
|
|
76
|
+
entityType,
|
|
77
|
+
labelKey: meta.labelKey,
|
|
78
|
+
...(meta.managedBy
|
|
79
|
+
? {
|
|
80
|
+
managedBy: {
|
|
81
|
+
moduleId: meta.managedBy.moduleId,
|
|
82
|
+
labelKey: meta.managedBy.labelKey,
|
|
83
|
+
route: meta.managedBy.route,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
: {}),
|
|
87
|
+
}));
|
|
88
|
+
return { data };
|
|
89
|
+
});
|
|
90
|
+
app.get('/api/v1/admin/custom-fields/definitions', { preHandler: requireAdmin('custom_fields:read') }, async (request) => {
|
|
91
|
+
const raw = request.query.entityType;
|
|
92
|
+
const entityType = raw ? supportedEntityTypeSchema.parse(raw) : undefined;
|
|
93
|
+
const defs = await definitionService.listAll(entityType);
|
|
94
|
+
return { data: defs.map(serialize) };
|
|
95
|
+
});
|
|
96
|
+
app.get('/api/v1/admin/custom-fields/definitions/:id', { preHandler: requireAdmin('custom_fields:read') }, async (request) => {
|
|
97
|
+
const def = await definitionService.getById(request.params.id);
|
|
98
|
+
if (!def)
|
|
99
|
+
throw new HttpError(404, ERROR_CODES.CUSTOM_FIELD_NOT_FOUND, 'Custom field not found.');
|
|
100
|
+
return { data: serialize(def) };
|
|
101
|
+
});
|
|
102
|
+
app.post('/api/v1/admin/custom-fields/definitions', { preHandler: requireAdmin('custom_fields:write'), schema: { body: createCustomFieldDefinitionSchema } }, async (request, reply) => {
|
|
103
|
+
const body = createCustomFieldDefinitionSchema.parse(request.body);
|
|
104
|
+
assertNotHostManaged(body.entityType);
|
|
105
|
+
try {
|
|
106
|
+
const created = await definitionService.create(body);
|
|
107
|
+
const def = await definitionService.getById(created.id);
|
|
108
|
+
reply.status(201);
|
|
109
|
+
return { data: serialize(def) };
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
toHttp(err);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
app.patch('/api/v1/admin/custom-fields/definitions/:id', { preHandler: requireAdmin('custom_fields:write'), schema: { body: updateCustomFieldDefinitionSchema } }, async (request) => {
|
|
116
|
+
const body = updateCustomFieldDefinitionSchema.parse(request.body);
|
|
117
|
+
await assertDefinitionNotHostManaged(request.params.id);
|
|
118
|
+
try {
|
|
119
|
+
const updated = await definitionService.update(request.params.id, body);
|
|
120
|
+
const def = await definitionService.getById(updated.id);
|
|
121
|
+
return { data: serialize(def) };
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
toHttp(err);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
app.delete('/api/v1/admin/custom-fields/definitions/:id', { preHandler: requireAdmin('custom_fields:write') }, async (request, reply) => {
|
|
128
|
+
await assertDefinitionNotHostManaged(request.params.id);
|
|
129
|
+
try {
|
|
130
|
+
await definitionService.delete(request.params.id);
|
|
131
|
+
reply.status(204);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
toHttp(err);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// ---- Option sub-routes (select / multiselect) ---------------------------
|
|
139
|
+
app.post('/api/v1/admin/custom-fields/definitions/:id/options', { preHandler: requireAdmin('custom_fields:write'), schema: { body: customFieldOptionSchema } }, async (request, reply) => {
|
|
140
|
+
const body = customFieldOptionSchema.parse(request.body);
|
|
141
|
+
await assertDefinitionNotHostManaged(request.params.id);
|
|
142
|
+
try {
|
|
143
|
+
const opt = await definitionService.createOption(request.params.id, body);
|
|
144
|
+
reply.status(201);
|
|
145
|
+
return { data: { id: opt.id, value: opt.value } };
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
toHttp(err);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
app.patch('/api/v1/admin/custom-fields/definitions/:id/options/:optionId', { preHandler: requireAdmin('custom_fields:write'), schema: { body: customFieldOptionSchema.partial() } }, async (request) => {
|
|
152
|
+
const body = customFieldOptionSchema.partial().parse(request.body);
|
|
153
|
+
await assertDefinitionNotHostManaged(request.params.id);
|
|
154
|
+
try {
|
|
155
|
+
const opt = await definitionService.updateOption(request.params.id, request.params.optionId, {
|
|
156
|
+
...(body.label !== undefined ? { label: body.label } : {}),
|
|
157
|
+
...(body.labelDefault !== undefined ? { labelDefault: body.labelDefault } : {}),
|
|
158
|
+
...(body.isDefault !== undefined ? { isDefault: body.isDefault } : {}),
|
|
159
|
+
...(body.sortOrder !== undefined ? { sortOrder: body.sortOrder } : {}),
|
|
160
|
+
});
|
|
161
|
+
return { data: { id: opt.id, value: opt.value } };
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
toHttp(err);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
app.delete('/api/v1/admin/custom-fields/definitions/:id/options/:optionId', { preHandler: requireAdmin('custom_fields:write') }, async (request, reply) => {
|
|
168
|
+
await assertDefinitionNotHostManaged(request.params.id);
|
|
169
|
+
try {
|
|
170
|
+
await definitionService.deleteOption(request.params.id, request.params.optionId);
|
|
171
|
+
reply.status(204);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
toHttp(err);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
//# 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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"}
|