@endora-commerce/mod-comparisons 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 +61 -2
- package/dist/admin/api/comparisons-client.d.ts +24 -0
- package/dist/admin/api/comparisons-client.d.ts.map +1 -0
- package/dist/admin/api/comparisons-client.js +15 -0
- package/dist/admin/api/comparisons-client.js.map +1 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +49 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/ComparisonDetailPage.d.ts +19 -0
- package/dist/admin/pages/ComparisonDetailPage.d.ts.map +1 -0
- package/dist/admin/pages/ComparisonDetailPage.js +62 -0
- package/dist/admin/pages/ComparisonDetailPage.js.map +1 -0
- package/dist/admin/pages/ComparisonsListPage.d.ts +10 -0
- package/dist/admin/pages/ComparisonsListPage.d.ts.map +1 -0
- package/dist/admin/pages/ComparisonsListPage.js +71 -0
- package/dist/admin/pages/ComparisonsListPage.js.map +1 -0
- package/dist/backend/entities/comparison-product.entity.d.ts +22 -0
- package/dist/backend/entities/comparison-product.entity.d.ts.map +1 -0
- package/dist/backend/entities/comparison-product.entity.js +54 -0
- package/dist/backend/entities/comparison-product.entity.js.map +1 -0
- package/dist/backend/entities/comparison.entity.d.ts +61 -0
- package/dist/backend/entities/comparison.entity.d.ts.map +1 -0
- package/dist/backend/entities/comparison.entity.js +115 -0
- package/dist/backend/entities/comparison.entity.js.map +1 -0
- package/dist/backend/index.d.ts +61 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +117 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/request-actor.d.ts +22 -0
- package/dist/backend/request-actor.d.ts.map +1 -0
- package/dist/backend/request-actor.js +24 -0
- package/dist/backend/request-actor.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +22 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +41 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.public.d.ts +33 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +215 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/routes.share.d.ts +27 -0
- package/dist/backend/routes.share.d.ts.map +1 -0
- package/dist/backend/routes.share.js +49 -0
- package/dist/backend/routes.share.js.map +1 -0
- package/dist/backend/services/anonymous-token-cookie.d.ts +23 -0
- package/dist/backend/services/anonymous-token-cookie.d.ts.map +1 -0
- package/dist/backend/services/anonymous-token-cookie.js +33 -0
- package/dist/backend/services/anonymous-token-cookie.js.map +1 -0
- package/dist/backend/services/asset-byte-fetcher.d.ts +37 -0
- package/dist/backend/services/asset-byte-fetcher.d.ts.map +1 -0
- package/dist/backend/services/asset-byte-fetcher.js +70 -0
- package/dist/backend/services/asset-byte-fetcher.js.map +1 -0
- package/dist/backend/services/comparable-attribute-projection.d.ts +57 -0
- package/dist/backend/services/comparable-attribute-projection.d.ts.map +1 -0
- package/dist/backend/services/comparable-attribute-projection.js +136 -0
- package/dist/backend/services/comparable-attribute-projection.js.map +1 -0
- package/dist/backend/services/comparison-admin.service.d.ts +31 -0
- package/dist/backend/services/comparison-admin.service.d.ts.map +1 -0
- package/dist/backend/services/comparison-admin.service.js +184 -0
- package/dist/backend/services/comparison-admin.service.js.map +1 -0
- package/dist/backend/services/comparison-pdf-renderer.d.ts +57 -0
- package/dist/backend/services/comparison-pdf-renderer.d.ts.map +1 -0
- package/dist/backend/services/comparison-pdf-renderer.js +211 -0
- package/dist/backend/services/comparison-pdf-renderer.js.map +1 -0
- package/dist/backend/services/comparison-service.d.ts +339 -0
- package/dist/backend/services/comparison-service.d.ts.map +1 -0
- package/dist/backend/services/comparison-service.js +588 -0
- package/dist/backend/services/comparison-service.js.map +1 -0
- package/dist/backend/services/share-token-generator.d.ts +20 -0
- package/dist/backend/services/share-token-generator.d.ts.map +1 -0
- package/dist/backend/services/share-token-generator.js +23 -0
- package/dist/backend/services/share-token-generator.js.map +1 -0
- package/dist/manifest.d.ts +199 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +179 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260501T185834_comparisons_init.d.ts +32 -0
- package/dist/migrations/20260501T185834_comparisons_init.d.ts.map +1 -0
- package/dist/migrations/20260501T185834_comparisons_init.js +102 -0
- package/dist/migrations/20260501T185834_comparisons_init.js.map +1 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts +84 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts.map +1 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.js +147 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.js.map +1 -0
- package/dist/migrations/index.d.ts +28 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +31 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/comparisons.md +278 -0
- package/i18n/en.json +51 -0
- package/i18n/pl.json +51 -0
- package/package.json +102 -3
- package/tailwind.css +14 -0
|
@@ -0,0 +1,115 @@
|
|
|
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 { CustomerScoped } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* Comparison — feature 007 / T011 / data-model.md §1.1.
|
|
15
|
+
*
|
|
16
|
+
* A customer-owned set of product references with a chosen display mode,
|
|
17
|
+
* an owner (customer account or anonymous browser session), an associated
|
|
18
|
+
* sales channel, and a stable shareable URL token. Owner is *exclusively*
|
|
19
|
+
* one of the two — the DB CHECK constraint
|
|
20
|
+
* `comparisons_owner_xor_chk` enforces it; the service layer also
|
|
21
|
+
* validates before insert.
|
|
22
|
+
*
|
|
23
|
+
* `displayMode` persists across re-opens so the customer's chosen mode is
|
|
24
|
+
* remembered. The default `'all'` matches spec FR-008.
|
|
25
|
+
*
|
|
26
|
+
* State transitions: live → deleted (hard delete; `comparison_products`
|
|
27
|
+
* cascades). No soft-delete: spec FR-014 requires the share-token endpoint
|
|
28
|
+
* to resolve to a clear "no longer exists" state, which a missing row
|
|
29
|
+
* already satisfies.
|
|
30
|
+
*/
|
|
31
|
+
let Comparison = class Comparison {
|
|
32
|
+
[OptionalProps];
|
|
33
|
+
id = randomUUID();
|
|
34
|
+
shareToken;
|
|
35
|
+
/**
|
|
36
|
+
* Set when the owner is authenticated. Mutually exclusive with
|
|
37
|
+
* `anonymousToken` (DB CHECK + service-side guard).
|
|
38
|
+
*/
|
|
39
|
+
customerAccountId;
|
|
40
|
+
/**
|
|
41
|
+
* The organisation that owns this comparison — feature 087 Group B, ruling
|
|
42
|
+
* D-187.
|
|
43
|
+
*
|
|
44
|
+
* Derived from {@link customerAccountId}'s account and stamped at the write,
|
|
45
|
+
* never resolved on read: `customerOrganizationColumn` asks the ORM's own
|
|
46
|
+
* metadata whether this property exists, so its presence is what makes
|
|
47
|
+
* `customerFilterCond`'s `allowed-set` arm **grant** on this table rather
|
|
48
|
+
* than refuse it whole. A sales representative assigned to the buyer's
|
|
49
|
+
* organisation sees this row because this column is here.
|
|
50
|
+
*
|
|
51
|
+
* Nullable, and the constraint is an implication rather than an equivalence:
|
|
52
|
+
* `comparisons_organization_attribution_chk` requires an organisation of a
|
|
53
|
+
* row that names an account, and says nothing about a row that names none.
|
|
54
|
+
* An **anonymous** comparison is a representable state (FR-011) — this table
|
|
55
|
+
* enforces the owner XOR, so half of it is anonymous by construction — and
|
|
56
|
+
* who such a row belongs to is R-6's open question, which a constraint must
|
|
57
|
+
* not answer.
|
|
58
|
+
*/
|
|
59
|
+
organizationId;
|
|
60
|
+
/**
|
|
61
|
+
* Set when the owner is an anonymous browser session. Same encoding as
|
|
62
|
+
* `shareToken` (16 random bytes, base64url) but a separate value so a
|
|
63
|
+
* leaked anonymous-cookie token does not double as a public share link.
|
|
64
|
+
*/
|
|
65
|
+
anonymousToken;
|
|
66
|
+
salesChannelId;
|
|
67
|
+
displayMode = 'all';
|
|
68
|
+
createdAt = new Date();
|
|
69
|
+
updatedAt = new Date();
|
|
70
|
+
};
|
|
71
|
+
__decorate([
|
|
72
|
+
PrimaryKey({ type: 'uuid' }),
|
|
73
|
+
__metadata("design:type", String)
|
|
74
|
+
], Comparison.prototype, "id", void 0);
|
|
75
|
+
__decorate([
|
|
76
|
+
Property({ type: 'string', length: 32 }),
|
|
77
|
+
Unique(),
|
|
78
|
+
__metadata("design:type", String)
|
|
79
|
+
], Comparison.prototype, "shareToken", void 0);
|
|
80
|
+
__decorate([
|
|
81
|
+
Property({ type: 'uuid', nullable: true }),
|
|
82
|
+
__metadata("design:type", Object)
|
|
83
|
+
], Comparison.prototype, "customerAccountId", void 0);
|
|
84
|
+
__decorate([
|
|
85
|
+
Property({ type: 'uuid', nullable: true }),
|
|
86
|
+
Index(),
|
|
87
|
+
__metadata("design:type", Object)
|
|
88
|
+
], Comparison.prototype, "organizationId", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
Property({ type: 'string', length: 32, nullable: true }),
|
|
91
|
+
__metadata("design:type", Object)
|
|
92
|
+
], Comparison.prototype, "anonymousToken", void 0);
|
|
93
|
+
__decorate([
|
|
94
|
+
Property({ type: 'uuid' }),
|
|
95
|
+
Index(),
|
|
96
|
+
__metadata("design:type", String)
|
|
97
|
+
], Comparison.prototype, "salesChannelId", void 0);
|
|
98
|
+
__decorate([
|
|
99
|
+
Property({ type: 'string', length: 16 }),
|
|
100
|
+
__metadata("design:type", String)
|
|
101
|
+
], Comparison.prototype, "displayMode", void 0);
|
|
102
|
+
__decorate([
|
|
103
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
104
|
+
__metadata("design:type", Date)
|
|
105
|
+
], Comparison.prototype, "createdAt", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
Property({ type: 'datetime', onUpdate: () => new Date() }),
|
|
108
|
+
__metadata("design:type", Date)
|
|
109
|
+
], Comparison.prototype, "updatedAt", void 0);
|
|
110
|
+
Comparison = __decorate([
|
|
111
|
+
CustomerScoped(),
|
|
112
|
+
Entity({ tableName: 'comparisons' })
|
|
113
|
+
], Comparison);
|
|
114
|
+
export { Comparison };
|
|
115
|
+
//# sourceMappingURL=comparison.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/comparison.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,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAIpC;;;;;;;;;;;;;;;;;GAiBG;AAGI,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,CAAC,aAAa,CAAC,CAOM;IAGrB,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,UAAU,CAAU;IAEpB;;;OAGG;IAEH,iBAAiB,CAAiB;IAElC;;;;;;;;;;;;;;;;;;OAkBG;IAGH,cAAc,CAAiB;IAE/B;;;;OAIG;IAEH,cAAc,CAAiB;IAI/B,cAAc,CAAU;IAGxB,WAAW,GAA0B,KAAK,CAAC;IAG3C,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAxDC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;sCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,MAAM,EAAE;;8CACW;AAOpB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qDACT;AAuBlC;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1C,KAAK,EAAE;;kDACuB;AAQ/B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDAC1B;AAI/B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;kDACgB;AAGxB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;+CACE;AAG3C;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;6CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;6CAAc;AAlElB,UAAU;IAFtB,cAAc,EAAE;IAChB,MAAM,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;GACxB,UAAU,CAmEtB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { ModuleContext } from '@endora-commerce/platform/kernel';
|
|
3
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
4
|
+
import type { SettingsReadPort } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import { ComparisonService } from './services/comparison-service.js';
|
|
6
|
+
import { ComparisonAdminService } from './services/comparison-admin.service.js';
|
|
7
|
+
import { ShareTokenGenerator } from './services/share-token-generator.js';
|
|
8
|
+
import { ComparisonProduct } from './entities/comparison-product.entity.js';
|
|
9
|
+
import { Comparison } from './entities/comparison.entity.js';
|
|
10
|
+
/**
|
|
11
|
+
* `comparisons` — the module that made a whole service to throw it away
|
|
12
|
+
* (feature 072, wave 2, T111).
|
|
13
|
+
*
|
|
14
|
+
* `ComparisonService`'s second constructor parameter was `_catalogQuery:
|
|
15
|
+
* CatalogQueryService` — underscore-prefixed, unused, kept with the comment
|
|
16
|
+
* "constructor contract kept; attribute reads moved to `catalogAttributes`".
|
|
17
|
+
* To feed it, each composition root built an entire `CatalogQueryService`,
|
|
18
|
+
* one of four per composition, and handed it to a parameter that discards it.
|
|
19
|
+
* The parameter is gone and so is the construction; `catalogAttributeRead` is
|
|
20
|
+
* what this module actually reads attributes through, and it stays.
|
|
21
|
+
*
|
|
22
|
+
* **`requireAdmin` becomes required, and this one failed *safe* before.** Where
|
|
23
|
+
* `cms` and `assets_library` defaulted an absent gate to a permissive no-op,
|
|
24
|
+
* this module's plugin read
|
|
25
|
+
*
|
|
26
|
+
* if (options.requireAdmin) { await registerComparisonsAdminRoutes(…) }
|
|
27
|
+
*
|
|
28
|
+
* so an omitted gate removed the admin surface rather than opening it. That is
|
|
29
|
+
* the right direction to fail in, and it is still worth removing: whether an
|
|
30
|
+
* API exists at all should not be a side effect of an argument being passed.
|
|
31
|
+
* Both roots passed one, so the surface is unchanged.
|
|
32
|
+
*
|
|
33
|
+
* `settingsService` stops being optional too. Absent, `compare.max_products`
|
|
34
|
+
* silently fell back to a constant on every call, which is the quiet form —
|
|
35
|
+
* an operator's configured limit ignored with no indication.
|
|
36
|
+
*
|
|
37
|
+
* `comparisonService` is a **port**: the login flow adopts an anonymous
|
|
38
|
+
* comparison through it, across a module boundary.
|
|
39
|
+
*/
|
|
40
|
+
export interface ComparisonsCradle {
|
|
41
|
+
readonly emFactory: () => EntityManager;
|
|
42
|
+
readonly requireAdmin: RequireAdminFactory;
|
|
43
|
+
readonly settingsReadPort: SettingsReadPort;
|
|
44
|
+
readonly comparisonService: ComparisonService;
|
|
45
|
+
readonly comparisonAdminService: ComparisonAdminService;
|
|
46
|
+
readonly comparisonShareTokens: ShareTokenGenerator;
|
|
47
|
+
}
|
|
48
|
+
export declare function registerModule(ctx: ModuleContext): void;
|
|
49
|
+
/**
|
|
50
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
51
|
+
* array and **no named class export** (D-168).
|
|
52
|
+
*
|
|
53
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
54
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
55
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
56
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
57
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
58
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
59
|
+
*/
|
|
60
|
+
export declare const entities: (typeof Comparison | typeof ComparisonProduct)[];
|
|
61
|
+
//# 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;AAS3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAK1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;IACxD,QAAQ,CAAC,qBAAqB,EAAE,mBAAmB,CAAC;CACrD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA8GvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,kDAGpB,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { lazyPort } from '@endora-commerce/platform/kernel';
|
|
2
|
+
import { ComparisonService } from './services/comparison-service.js';
|
|
3
|
+
import { ComparisonAdminService } from './services/comparison-admin.service.js';
|
|
4
|
+
import { ComparableAttributeProjection } from './services/comparable-attribute-projection.js';
|
|
5
|
+
import { ShareTokenGenerator } from './services/share-token-generator.js';
|
|
6
|
+
import { ComparisonPdfRenderer } from './services/comparison-pdf-renderer.js';
|
|
7
|
+
import { registerComparisonsPublicRoutes } from './routes.public.js';
|
|
8
|
+
import { registerComparisonsShareRoutes } from './routes.share.js';
|
|
9
|
+
import { registerComparisonsAdminRoutes } from './routes.admin.js';
|
|
10
|
+
import { ComparisonProduct } from './entities/comparison-product.entity.js';
|
|
11
|
+
import { Comparison } from './entities/comparison.entity.js';
|
|
12
|
+
export function registerModule(ctx) {
|
|
13
|
+
ctx.di.providePort('comparisonService', ctx
|
|
14
|
+
.asFunction(({ emFactory }) => new ComparisonService(emFactory, new ComparableAttributeProjection(), ctx.cradle().comparisonShareTokens,
|
|
15
|
+
// Feature 075, Phase C — the products a comparison holds are
|
|
16
|
+
// `catalog`'s rows. They used to be read with `em.find(Product, …)`
|
|
17
|
+
// from inside this module, which no gate can see; over the port the
|
|
18
|
+
// same read answers 503 when `catalog` is off, which is the
|
|
19
|
+
// binding dependency this manifest declares.
|
|
20
|
+
lazyPort(ctx, 'catalogProductReadPort'),
|
|
21
|
+
// Issue #259 — the channel assortment gate on the add seam. A
|
|
22
|
+
// kernel registration, so there is no module edge to declare and
|
|
23
|
+
// nothing to switch off underneath it.
|
|
24
|
+
lazyPort(ctx, 'salesChannelMembershipPort'),
|
|
25
|
+
// Feature 075 / D-87 — the base image of a column, over `catalog`'s
|
|
26
|
+
// batch gallery read. It used to be a three-table join written
|
|
27
|
+
// here, reaching two of `catalog`'s tables and one of
|
|
28
|
+
// `assets_library`'s; a raw statement names no import specifier, so
|
|
29
|
+
// nothing in the tree could see it and no gate could stop it. The
|
|
30
|
+
// edge binds — `catalog` is already a dependency of this manifest,
|
|
31
|
+
// and a comparison whose products are unreadable has no column to
|
|
32
|
+
// put an image in.
|
|
33
|
+
lazyPort(ctx, 'galleryService'), lazyPort(ctx, 'settingsReadPort'),
|
|
34
|
+
// The attribute read model, now `catalog`'s published port rather
|
|
35
|
+
// than a hand-made adapter over a name a root registered.
|
|
36
|
+
lazyPort(ctx, 'catalogAttributeReadPort'),
|
|
37
|
+
// Issue #132 — a comparison column is a listing and prices through
|
|
38
|
+
// the engine, not off the catalogue's legacy attribute.
|
|
39
|
+
lazyPort(ctx, 'pricingService'),
|
|
40
|
+
// The viewer's organisation, for the customer group the pricing
|
|
41
|
+
// engine selects a group-targeted list by. The edge binds: a
|
|
42
|
+
// comparison that cannot read the buyer's organisation would quote
|
|
43
|
+
// the channel price to a buyer who has negotiated one, which is
|
|
44
|
+
// silently wrong rather than visibly refused.
|
|
45
|
+
lazyPort(ctx, 'organizationDetailsPort'),
|
|
46
|
+
// Feature 087 Group B / D-187 — the organisation an owned
|
|
47
|
+
// comparison is stamped with, derived from the owning account
|
|
48
|
+
// rather than from the ambient request context. Both write paths
|
|
49
|
+
// need it and only one of them has a request:
|
|
50
|
+
// `adoptAnonymousComparison` runs inside the post-login hook. The
|
|
51
|
+
// edge binds — a comparison this module cannot attribute is a row
|
|
52
|
+
// `comparisons_organization_attribution_chk` refuses, so failing
|
|
53
|
+
// closed at the port is the same answer arriving earlier and with
|
|
54
|
+
// the account named. Nothing to declare: the same port is already
|
|
55
|
+
// resolved for `comparisonAdminService` below, and
|
|
56
|
+
// `check:port-dependencies` reads the manifest's transitive
|
|
57
|
+
// closure.
|
|
58
|
+
lazyPort(ctx, 'customerAccountReadPort')))
|
|
59
|
+
.singleton());
|
|
60
|
+
ctx.di.register({
|
|
61
|
+
// One generator, registered rather than constructed twice. It happens to be
|
|
62
|
+
// stateless today — `randomBytes(16)` and nothing else — so two instances
|
|
63
|
+
// would agree; registering it means that stays true by construction rather
|
|
64
|
+
// than by the class never gaining a key.
|
|
65
|
+
comparisonShareTokens: ctx.asFunction(() => new ShareTokenGenerator()).singleton(),
|
|
66
|
+
comparisonAdminService: ctx
|
|
67
|
+
.asFunction(({ emFactory }) => new ComparisonAdminService(emFactory,
|
|
68
|
+
// Resolved per call, not captured. `comparisonService` is a
|
|
69
|
+
// transient port gate and Awilix's strict mode refuses a singleton
|
|
70
|
+
// holding one — rightly, since a captured port keeps answering
|
|
71
|
+
// after its module is switched off. `buildOwnerView` is the only
|
|
72
|
+
// method this service reaches.
|
|
73
|
+
{
|
|
74
|
+
buildOwnerView: (...args) => ctx.cradle().comparisonService.buildOwnerView(...args),
|
|
75
|
+
},
|
|
76
|
+
// Feature 075, Phase C — the owner column's e-mail address is
|
|
77
|
+
// `customer_accounts`' row, read over its port instead of out of
|
|
78
|
+
// its table.
|
|
79
|
+
lazyPort(ctx, 'customerAccountReadPort')))
|
|
80
|
+
.singleton(),
|
|
81
|
+
});
|
|
82
|
+
ctx.routes(async (app) => {
|
|
83
|
+
const { requireAdmin, comparisonShareTokens } = ctx.cradle();
|
|
84
|
+
// The module's own two ports, read lazily. Route *registration* runs
|
|
85
|
+
// whatever the module's effective state is — `defineModuleRoutes` gates
|
|
86
|
+
// requests, not the wiring — so destructuring a gated port here asked the
|
|
87
|
+
// gate inside `buildServer`, and an operator who had switched `comparisons`
|
|
88
|
+
// off stopped the backend from starting rather than stopping its routes.
|
|
89
|
+
const comparisonService = lazyPort(ctx, 'comparisonService');
|
|
90
|
+
await registerComparisonsPublicRoutes(app, {
|
|
91
|
+
comparisonService,
|
|
92
|
+
tokens: comparisonShareTokens,
|
|
93
|
+
pdfRenderer: new ComparisonPdfRenderer(),
|
|
94
|
+
});
|
|
95
|
+
await registerComparisonsShareRoutes(app, { comparisonService });
|
|
96
|
+
await registerComparisonsAdminRoutes(app, {
|
|
97
|
+
adminService: lazyPort(ctx, 'comparisonAdminService'),
|
|
98
|
+
requireAdmin,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
104
|
+
* array and **no named class export** (D-168).
|
|
105
|
+
*
|
|
106
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
107
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
108
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
109
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
110
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
111
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
112
|
+
*/
|
|
113
|
+
export const entities = [
|
|
114
|
+
ComparisonProduct,
|
|
115
|
+
Comparison,
|
|
116
|
+
];
|
|
117
|
+
//# 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,22 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
/**
|
|
3
|
+
* The signed-in customer behind a request, or `null`.
|
|
4
|
+
*
|
|
5
|
+
* `request.actor` is not on `FastifyRequest`: it is a declaration-merging
|
|
6
|
+
* augmentation the `auth` module contributes (`modules/auth/plugin.ts`). Inside
|
|
7
|
+
* `backend/src` that augmentation arrived ambiently through the host's own
|
|
8
|
+
* `types` graph, so nothing in this module ever named it. A package compiles
|
|
9
|
+
* against its own manifest and `auth` publishes nothing a package can name — it
|
|
10
|
+
* is the one module still holding an unpublished platform reach — so the
|
|
11
|
+
* property is simply not there (TS2339).
|
|
12
|
+
*
|
|
13
|
+
* So the shape is read structurally, in **one** place rather than at each of the
|
|
14
|
+
* three call sites, and it is the same door `admin_notifications` already uses
|
|
15
|
+
* for the admin half of the same augmentation. It narrows rather than asserts:
|
|
16
|
+
* a request carrying no actor, or an actor of another kind, answers `null`, so
|
|
17
|
+
* the anonymous `compare_token` path is reached exactly as before.
|
|
18
|
+
*/
|
|
19
|
+
export declare function customerActor(request: FastifyRequest): {
|
|
20
|
+
customerAccountId: string;
|
|
21
|
+
} | null;
|
|
22
|
+
//# sourceMappingURL=request-actor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-actor.d.ts","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM3F"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed-in customer behind a request, or `null`.
|
|
3
|
+
*
|
|
4
|
+
* `request.actor` is not on `FastifyRequest`: it is a declaration-merging
|
|
5
|
+
* augmentation the `auth` module contributes (`modules/auth/plugin.ts`). Inside
|
|
6
|
+
* `backend/src` that augmentation arrived ambiently through the host's own
|
|
7
|
+
* `types` graph, so nothing in this module ever named it. A package compiles
|
|
8
|
+
* against its own manifest and `auth` publishes nothing a package can name — it
|
|
9
|
+
* is the one module still holding an unpublished platform reach — so the
|
|
10
|
+
* property is simply not there (TS2339).
|
|
11
|
+
*
|
|
12
|
+
* So the shape is read structurally, in **one** place rather than at each of the
|
|
13
|
+
* three call sites, and it is the same door `admin_notifications` already uses
|
|
14
|
+
* for the admin half of the same augmentation. It narrows rather than asserts:
|
|
15
|
+
* a request carrying no actor, or an actor of another kind, answers `null`, so
|
|
16
|
+
* the anonymous `compare_token` path is reached exactly as before.
|
|
17
|
+
*/
|
|
18
|
+
export function customerActor(request) {
|
|
19
|
+
const { actor } = request;
|
|
20
|
+
if (actor?.kind !== 'customer' || typeof actor.customerAccountId !== 'string')
|
|
21
|
+
return null;
|
|
22
|
+
return { customerAccountId: actor.customerAccountId };
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=request-actor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-actor.js","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,OAAuB;IACnD,MAAM,EAAE,KAAK,EAAE,GAAG,OAEjB,CAAC;IACF,IAAI,KAAK,EAAE,IAAI,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,iBAAiB,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3F,OAAO,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { ComparisonAdminService } from './services/comparison-admin.service.js';
|
|
3
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
4
|
+
/**
|
|
5
|
+
* Admin HTTP surface — feature 007 / US5 / T064.
|
|
6
|
+
*
|
|
7
|
+
* Mounts under `/api/v1/admin/comparisons/*` and is GET-only:
|
|
8
|
+
*
|
|
9
|
+
* - GET /api/v1/admin/comparisons — list with filters
|
|
10
|
+
* - GET /api/v1/admin/comparisons/:id — single-comparison detail
|
|
11
|
+
*
|
|
12
|
+
* Spec FR-022: "MUST allow an administrator to open any single
|
|
13
|
+
* Comparison and inspect ... without offering any controls that mutate
|
|
14
|
+
* the Comparison." Translated literally — no PATCH, no POST, no
|
|
15
|
+
* DELETE under this prefix.
|
|
16
|
+
*/
|
|
17
|
+
export interface ComparisonsAdminDeps {
|
|
18
|
+
adminService: ComparisonAdminService;
|
|
19
|
+
requireAdmin: RequireAdminFactory;
|
|
20
|
+
}
|
|
21
|
+
export declare function registerComparisonsAdminRoutes(app: FastifyInstance, deps: ComparisonsAdminDeps): Promise<void>;
|
|
22
|
+
//# 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;AAO/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,sBAAsB,CAAC;IACrC,YAAY,EAAE,mBAAmB,CAAC;CACnC;AAED,wBAAsB,8BAA8B,CAClD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ERROR_CODES, comparisonAdminListQuerySchema, } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
import { ComparisonNotFoundError } from './services/comparison-service.js';
|
|
4
|
+
export async function registerComparisonsAdminRoutes(app, deps) {
|
|
5
|
+
const { adminService, requireAdmin } = deps;
|
|
6
|
+
app.get('/api/v1/admin/comparisons', { preHandler: requireAdmin('comparisons:read') }, async (request) => {
|
|
7
|
+
const query = parseListQuery(request.query);
|
|
8
|
+
const { data, nextCursor, limit } = await adminService.list(query);
|
|
9
|
+
return { data, meta: { limit, nextCursor } };
|
|
10
|
+
});
|
|
11
|
+
app.get('/api/v1/admin/comparisons/:id', { preHandler: requireAdmin('comparisons:read') }, async (request) => {
|
|
12
|
+
try {
|
|
13
|
+
return { data: await adminService.detail(request.params.id) };
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err instanceof ComparisonNotFoundError) {
|
|
17
|
+
throw new HttpError(404, ERROR_CODES.COMPARISON_NOT_FOUND, 'Comparison not found.');
|
|
18
|
+
}
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function parseListQuery(input) {
|
|
24
|
+
// Coerce numeric strings (Fastify hands query params as strings) before Zod.
|
|
25
|
+
const raw = (input ?? {});
|
|
26
|
+
const coerced = {
|
|
27
|
+
...raw,
|
|
28
|
+
...(typeof raw['limit'] === 'string'
|
|
29
|
+
? { limit: Number(raw['limit']) }
|
|
30
|
+
: {}),
|
|
31
|
+
};
|
|
32
|
+
const parsed = comparisonAdminListQuerySchema.safeParse(coerced);
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Validation failed.', parsed.error.issues.map((i) => ({
|
|
35
|
+
path: i.path.map(String).join('.'),
|
|
36
|
+
issue: i.message,
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
39
|
+
return parsed.data;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=routes.admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAuB3E,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,GAAoB,EACpB,IAA0B;IAE1B,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE5C,GAAG,CAAC,GAAG,CACL,2BAA2B,EAC3B,EAAE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,GAAG,CACL,+BAA+B,EAC/B,EAAE,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC,EAAE,EAChD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,IAAI,CAAC;YACH,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;gBAC3C,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,oBAAoB,EAChC,uBAAuB,CACxB,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,6EAA6E;IAC7E,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;IACrD,MAAM,OAAO,GAAG;QACd,GAAG,GAAG;QACN,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ;YAClC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE;YACjC,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;IACF,MAAM,MAAM,GAAG,8BAA8B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,iBAAiB,EAC7B,oBAAoB,EACpB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAClC,KAAK,EAAE,CAAC,CAAC,OAAO;SACjB,CAAC,CAAC,CACJ,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import { type ComparisonService } from './services/comparison-service.js';
|
|
3
|
+
import type { ComparisonPdfRenderer } from './services/comparison-pdf-renderer.js';
|
|
4
|
+
import { COMPARE_TOKEN_COOKIE } from './services/anonymous-token-cookie.js';
|
|
5
|
+
import type { ShareTokenGenerator } from './services/share-token-generator.js';
|
|
6
|
+
/**
|
|
7
|
+
* Public HTTP surface — feature 007 / US1 (T027).
|
|
8
|
+
*
|
|
9
|
+
* Routes mounted under `/api/v1/comparisons/me*` (project convention is
|
|
10
|
+
* `/api/v1/<module>/<resource>`; the contract README's `/public/`
|
|
11
|
+
* prefix follows spec wording but the codebase strips it — same as
|
|
12
|
+
* `catalog`, `search`, `sales_channels`).
|
|
13
|
+
*
|
|
14
|
+
* - GET /me — read; 204 when none exists
|
|
15
|
+
* - POST /me/products — add (creates on first call,
|
|
16
|
+
* mints `compare_token` cookie)
|
|
17
|
+
* - DELETE /me/products/:productId — remove
|
|
18
|
+
* - PATCH /me — update display mode
|
|
19
|
+
* - DELETE /me — hard-delete the comparison
|
|
20
|
+
*
|
|
21
|
+
* Owner resolution: authenticated customer wins over anonymous cookie.
|
|
22
|
+
* On a write where neither is present, a fresh anonymous token is
|
|
23
|
+
* minted and the `compare_token` cookie is set on the response (R-2).
|
|
24
|
+
*/
|
|
25
|
+
export interface ComparisonsPublicDeps {
|
|
26
|
+
comparisonService: ComparisonService;
|
|
27
|
+
tokens: ShareTokenGenerator;
|
|
28
|
+
/** Optional — when omitted, `GET /me/pdf` is not mounted (foundation tests). */
|
|
29
|
+
pdfRenderer?: ComparisonPdfRenderer;
|
|
30
|
+
}
|
|
31
|
+
export declare function registerComparisonsPublicRoutes(app: FastifyInstance, deps: ComparisonsPublicDeps): Promise<void>;
|
|
32
|
+
export { COMPARE_TOKEN_COOKIE };
|
|
33
|
+
//# sourceMappingURL=routes.public.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.public.d.ts","sourceRoot":"","sources":["../../src/backend/routes.public.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAU7E,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EACL,oBAAoB,EAGrB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAI/E;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,gFAAgF;IAChF,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAED,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA6Kf;AAoGD,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|