@endora-commerce/mod-pwa 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 +63 -0
- package/dist/admin/api/pwa-client.d.ts +15 -0
- package/dist/admin/api/pwa-client.d.ts.map +1 -0
- package/dist/admin/api/pwa-client.js +56 -0
- package/dist/admin/api/pwa-client.js.map +1 -0
- package/dist/admin/components/PushAudienceRuleBuilder.d.ts +19 -0
- package/dist/admin/components/PushAudienceRuleBuilder.d.ts.map +1 -0
- package/dist/admin/components/PushAudienceRuleBuilder.js +211 -0
- package/dist/admin/components/PushAudienceRuleBuilder.js.map +1 -0
- package/dist/admin/index.d.ts +39 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +61 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/PwaPage.d.ts +16 -0
- package/dist/admin/pages/PwaPage.d.ts.map +1 -0
- package/dist/admin/pages/PwaPage.js +207 -0
- package/dist/admin/pages/PwaPage.js.map +1 -0
- package/dist/backend/entities/push-message-delivery.entity.d.ts +18 -0
- package/dist/backend/entities/push-message-delivery.entity.d.ts.map +1 -0
- package/dist/backend/entities/push-message-delivery.entity.js +68 -0
- package/dist/backend/entities/push-message-delivery.entity.js.map +1 -0
- package/dist/backend/entities/push-message.entity.d.ts +26 -0
- package/dist/backend/entities/push-message.entity.d.ts.map +1 -0
- package/dist/backend/entities/push-message.entity.js +102 -0
- package/dist/backend/entities/push-message.entity.js.map +1 -0
- package/dist/backend/entities/push-subscription.entity.d.ts +49 -0
- package/dist/backend/entities/push-subscription.entity.d.ts.map +1 -0
- package/dist/backend/entities/push-subscription.entity.js +117 -0
- package/dist/backend/entities/push-subscription.entity.js.map +1 -0
- package/dist/backend/entities/pwa-icon-rendition.entity.d.ts +21 -0
- package/dist/backend/entities/pwa-icon-rendition.entity.d.ts.map +1 -0
- package/dist/backend/entities/pwa-icon-rendition.entity.js +69 -0
- package/dist/backend/entities/pwa-icon-rendition.entity.js.map +1 -0
- package/dist/backend/index.d.ts +100 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +107 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +119 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +109 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/request-actor.d.ts +24 -0
- package/dist/backend/request-actor.d.ts.map +1 -0
- package/dist/backend/request-actor.js +29 -0
- package/dist/backend/request-actor.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +52 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +222 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +29 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +66 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/cross-module-context.d.ts +90 -0
- package/dist/backend/services/cross-module-context.d.ts.map +1 -0
- package/dist/backend/services/cross-module-context.js +84 -0
- package/dist/backend/services/cross-module-context.js.map +1 -0
- package/dist/backend/services/providers/web-push-provider.d.ts +31 -0
- package/dist/backend/services/providers/web-push-provider.d.ts.map +1 -0
- package/dist/backend/services/providers/web-push-provider.js +72 -0
- package/dist/backend/services/providers/web-push-provider.js.map +1 -0
- package/dist/backend/services/push-audience-evaluator.d.ts +24 -0
- package/dist/backend/services/push-audience-evaluator.d.ts.map +1 -0
- package/dist/backend/services/push-audience-evaluator.js +29 -0
- package/dist/backend/services/push-audience-evaluator.js.map +1 -0
- package/dist/backend/services/push-delivery-queue.d.ts +14 -0
- package/dist/backend/services/push-delivery-queue.d.ts.map +1 -0
- package/dist/backend/services/push-delivery-queue.js +30 -0
- package/dist/backend/services/push-delivery-queue.js.map +1 -0
- package/dist/backend/services/push-event-subscriber.d.ts +86 -0
- package/dist/backend/services/push-event-subscriber.d.ts.map +1 -0
- package/dist/backend/services/push-event-subscriber.js +109 -0
- package/dist/backend/services/push-event-subscriber.js.map +1 -0
- package/dist/backend/services/push-message-service.d.ts +58 -0
- package/dist/backend/services/push-message-service.d.ts.map +1 -0
- package/dist/backend/services/push-message-service.js +145 -0
- package/dist/backend/services/push-message-service.js.map +1 -0
- package/dist/backend/services/push-provider-registry.d.ts +17 -0
- package/dist/backend/services/push-provider-registry.d.ts.map +1 -0
- package/dist/backend/services/push-provider-registry.js +32 -0
- package/dist/backend/services/push-provider-registry.js.map +1 -0
- package/dist/backend/services/push-subscription-service.d.ts +67 -0
- package/dist/backend/services/push-subscription-service.d.ts.map +1 -0
- package/dist/backend/services/push-subscription-service.js +165 -0
- package/dist/backend/services/push-subscription-service.js.map +1 -0
- package/dist/backend/services/pwa-config-resolver.d.ts +29 -0
- package/dist/backend/services/pwa-config-resolver.d.ts.map +1 -0
- package/dist/backend/services/pwa-config-resolver.js +92 -0
- package/dist/backend/services/pwa-config-resolver.js.map +1 -0
- package/dist/backend/services/pwa-icon-service.d.ts +44 -0
- package/dist/backend/services/pwa-icon-service.d.ts.map +1 -0
- package/dist/backend/services/pwa-icon-service.js +105 -0
- package/dist/backend/services/pwa-icon-service.js.map +1 -0
- package/dist/backend/workers/push-delivery-worker.d.ts +17 -0
- package/dist/backend/workers/push-delivery-worker.d.ts.map +1 -0
- package/dist/backend/workers/push-delivery-worker.js +105 -0
- package/dist/backend/workers/push-delivery-worker.js.map +1 -0
- package/dist/manifest.d.ts +184 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +207 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260625T144228_pwa_init.d.ts +18 -0
- package/dist/migrations/20260625T144228_pwa_init.d.ts.map +1 -0
- package/dist/migrations/20260625T144228_pwa_init.js +105 -0
- package/dist/migrations/20260625T144228_pwa_init.js.map +1 -0
- package/dist/migrations/20260830T172022_pwa_organization_attribution.d.ts +102 -0
- package/dist/migrations/20260830T172022_pwa_organization_attribution.d.ts.map +1 -0
- package/dist/migrations/20260830T172022_pwa_organization_attribution.js +169 -0
- package/dist/migrations/20260830T172022_pwa_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/pwa.md +93 -0
- package/i18n/en.json +43 -0
- package/i18n/pl.json +43 -0
- package/package.json +105 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { PwaConfigResolver } from './services/pwa-config-resolver.js';
|
|
3
|
+
import { PwaIconService } from './services/pwa-icon-service.js';
|
|
4
|
+
import { PushSubscriptionService } from './services/push-subscription-service.js';
|
|
5
|
+
import { PushMessageService } from './services/push-message-service.js';
|
|
6
|
+
import { PushProviderRegistry } from './services/push-provider-registry.js';
|
|
7
|
+
import { WebPushProvider } from './services/providers/web-push-provider.js';
|
|
8
|
+
import { createPushEventHandlers, } from './services/push-event-subscriber.js';
|
|
9
|
+
import { createPushDeliveryQueue, createPushDeliveryWorker, } from './services/push-delivery-queue.js';
|
|
10
|
+
import { makePushDeliveryProcessor } from './workers/push-delivery-worker.js';
|
|
11
|
+
import { registerPwaStorefrontRoutes, } from './routes.storefront.js';
|
|
12
|
+
import { registerPwaAdminRoutes, } from './routes.admin.js';
|
|
13
|
+
const BoolSchema = z.boolean();
|
|
14
|
+
export function pwaModule(options) {
|
|
15
|
+
const configResolver = new PwaConfigResolver(options.settings, options.emFactory);
|
|
16
|
+
const iconService = new PwaIconService(options.emFactory, options.assetUpload);
|
|
17
|
+
// Feature 087 Group B / D-187 — the subscription registry stamps the
|
|
18
|
+
// organisation an owned device belongs to, derived from the owning account
|
|
19
|
+
// through the gated port this module already resolves. Passed rather than
|
|
20
|
+
// optional: `push_subscriptions_organization_attribution_chk` refuses a row
|
|
21
|
+
// this service could not attribute, so a composition with no way to answer
|
|
22
|
+
// the question cannot write the table at all.
|
|
23
|
+
const subscriptionService = new PushSubscriptionService(options.emFactory, options.customerAccounts);
|
|
24
|
+
const providerRegistry = new PushProviderRegistry();
|
|
25
|
+
providerRegistry.register(new WebPushProvider(options.settings, options.vapidSubject));
|
|
26
|
+
const deliveryQueue = createPushDeliveryQueue(options.redis);
|
|
27
|
+
const messageService = new PushMessageService(options.emFactory, deliveryQueue, options.customerAccounts, options.organizationDetails);
|
|
28
|
+
// Where the two auto-triggers write a skipped or failed push. Composition
|
|
29
|
+
// runs before `buildServer`, so `options.log` is the best a root can offer at
|
|
30
|
+
// construction time; `app.log` is the log the platform actually collects, and
|
|
31
|
+
// the plugin below moves the holder onto it. Nothing can emit an in-process
|
|
32
|
+
// event before that plugin has run, so the fallback is a type obligation
|
|
33
|
+
// rather than a path.
|
|
34
|
+
let eventLogger = options.log;
|
|
35
|
+
// Auto-triggered push (FR-024) — producer only; enqueues, never sends inline.
|
|
36
|
+
// `backend.ts` registers these two through `ctx.subscribe`, so they stop with
|
|
37
|
+
// the module (issue #107).
|
|
38
|
+
const pushEventHandlers = createPushEventHandlers({
|
|
39
|
+
messageService,
|
|
40
|
+
log: () => eventLogger,
|
|
41
|
+
resolveOrderTarget: options.resolveOrderTarget,
|
|
42
|
+
...(options.resolveQuoteTarget ? { resolveQuoteTarget: options.resolveQuoteTarget } : {}),
|
|
43
|
+
isPushEnabled: async (salesChannelId) => {
|
|
44
|
+
try {
|
|
45
|
+
return await options.settings.get('pwa.push_enabled', salesChannelId, BoolSchema);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// Push-delivery consumer (Principle X). Separable entrypoint; co-located by
|
|
53
|
+
// default given low push volume, split-out-able under load via BACKEND_ROLE.
|
|
54
|
+
//
|
|
55
|
+
// It is **handed to `backend.ts`** rather than started here, and that is the
|
|
56
|
+
// Constitution XVII repair rather than tidiness: this line used to read
|
|
57
|
+
// `createPushDeliveryWorker(options.redis, processor);`, the value dropped on
|
|
58
|
+
// the floor. A worker outside `ctx.worker` is in no per-module registry, so
|
|
59
|
+
// `pauseWorkersFor('pwa')` reached nothing and the presence reconcile had
|
|
60
|
+
// nothing to reconcile — an operator who switched `pwa` off went on having
|
|
61
|
+
// push notifications delivered to their customers' devices, on both axes,
|
|
62
|
+
// while the module's own admin and storefront surfaces refused. That is what
|
|
63
|
+
// made it invisible, and it is the same shape issue #107 found for
|
|
64
|
+
// subscribers; `check:subscribe-seam` now refuses it.
|
|
65
|
+
const workers = options.runWorkers
|
|
66
|
+
? [
|
|
67
|
+
createPushDeliveryWorker(options.redis, makePushDeliveryProcessor({
|
|
68
|
+
emFactory: options.emFactory,
|
|
69
|
+
registry: providerRegistry,
|
|
70
|
+
})),
|
|
71
|
+
]
|
|
72
|
+
: [];
|
|
73
|
+
const handle = {
|
|
74
|
+
pushEventHandlers,
|
|
75
|
+
configResolver,
|
|
76
|
+
iconService,
|
|
77
|
+
subscriptionService,
|
|
78
|
+
messageService,
|
|
79
|
+
providerRegistry,
|
|
80
|
+
deliveryQueue,
|
|
81
|
+
};
|
|
82
|
+
const plugin = async (app) => {
|
|
83
|
+
eventLogger = app.log;
|
|
84
|
+
await registerPwaStorefrontRoutes(app, {
|
|
85
|
+
configResolver,
|
|
86
|
+
iconService,
|
|
87
|
+
subscriptionService,
|
|
88
|
+
resolveAssetUrl: options.resolveAssetUrl,
|
|
89
|
+
});
|
|
90
|
+
await registerPwaAdminRoutes(app, {
|
|
91
|
+
requireAdmin: options.requireAdmin,
|
|
92
|
+
emFactory: options.emFactory,
|
|
93
|
+
configResolver,
|
|
94
|
+
iconService,
|
|
95
|
+
subscriptionService,
|
|
96
|
+
messageService,
|
|
97
|
+
settingsWrite: options.settingsWrite,
|
|
98
|
+
customerAccounts: options.customerAccounts,
|
|
99
|
+
organizationDetails: options.organizationDetails,
|
|
100
|
+
customerGroups: options.customerGroups,
|
|
101
|
+
settingsRead: options.settings,
|
|
102
|
+
resolveScopeChannelId: async (salesChannelId) => salesChannelId ?? (await options.defaultChannelId()),
|
|
103
|
+
channelCodeForId: options.channelCodeForId,
|
|
104
|
+
resolveAuditContext: options.resolveAuditContext,
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
return { plugin, handle, workers };
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
import '@endora-commerce/platform/http';
|
|
3
|
+
/**
|
|
4
|
+
* The signed-in customer behind a storefront request, or `null`.
|
|
5
|
+
*
|
|
6
|
+
* A push subscription is legitimately anonymous — the same browser can register
|
|
7
|
+
* a device with nobody signed in — so this narrows rather than refuses, and it
|
|
8
|
+
* is deliberately **not** the platform's `customerAccountIdResolver`, which
|
|
9
|
+
* throws 401 for a caller who is not a customer. Feature 087 Group B / D-187
|
|
10
|
+
* turns the answer into the row's attribution: an owned device carries its
|
|
11
|
+
* account and that account's organisation, and an ownerless one carries
|
|
12
|
+
* neither.
|
|
13
|
+
*
|
|
14
|
+
* It reads `request.actor` directly, which is the drain of a
|
|
15
|
+
* `resolveCustomerAccountId` option both composition roots supplied
|
|
16
|
+
* (`specs/110-instance-repository/` T118c). That option was the older shape,
|
|
17
|
+
* from when the augmentation was `auth`'s and a packaged module could not name
|
|
18
|
+
* it; a knob a root fills on a module's behalf is a knob that drifts between the
|
|
19
|
+
* two roots, and this one had — production read `request.actor` and the harness
|
|
20
|
+
* `request.testActor`. `registerTestAuth` mirrors each resolved actor onto both
|
|
21
|
+
* properties, so one read answers correctly under either composition.
|
|
22
|
+
*/
|
|
23
|
+
export declare function callingCustomerAccountId(request: FastifyRequest): string | null;
|
|
24
|
+
//# 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;AAK9C,OAAO,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAE/E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The `request.actor` augmentation, which is the platform's own since
|
|
2
|
+
// `specs/110-instance-repository/` T118b — a side-effect import of the `./http`
|
|
3
|
+
// barrel is how the `declare module 'fastify'` block reaches this package's
|
|
4
|
+
// program, and reaching a published barrel is not a `whole-file-reach`.
|
|
5
|
+
import '@endora-commerce/platform/http';
|
|
6
|
+
/**
|
|
7
|
+
* The signed-in customer behind a storefront request, or `null`.
|
|
8
|
+
*
|
|
9
|
+
* A push subscription is legitimately anonymous — the same browser can register
|
|
10
|
+
* a device with nobody signed in — so this narrows rather than refuses, and it
|
|
11
|
+
* is deliberately **not** the platform's `customerAccountIdResolver`, which
|
|
12
|
+
* throws 401 for a caller who is not a customer. Feature 087 Group B / D-187
|
|
13
|
+
* turns the answer into the row's attribution: an owned device carries its
|
|
14
|
+
* account and that account's organisation, and an ownerless one carries
|
|
15
|
+
* neither.
|
|
16
|
+
*
|
|
17
|
+
* It reads `request.actor` directly, which is the drain of a
|
|
18
|
+
* `resolveCustomerAccountId` option both composition roots supplied
|
|
19
|
+
* (`specs/110-instance-repository/` T118c). That option was the older shape,
|
|
20
|
+
* from when the augmentation was `auth`'s and a packaged module could not name
|
|
21
|
+
* it; a knob a root fills on a module's behalf is a knob that drifts between the
|
|
22
|
+
* two roots, and this one had — production read `request.actor` and the harness
|
|
23
|
+
* `request.testActor`. `registerTestAuth` mirrors each resolved actor onto both
|
|
24
|
+
* properties, so one read answers correctly under either composition.
|
|
25
|
+
*/
|
|
26
|
+
export function callingCustomerAccountId(request) {
|
|
27
|
+
return request.actor.kind === 'customer' ? request.actor.customerAccountId : null;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=request-actor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-actor.js","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AACA,sEAAsE;AACtE,gFAAgF;AAChF,4EAA4E;AAC5E,wEAAwE;AACxE,OAAO,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAuB;IAC9D,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;AACpF,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import type { CustomerAccountReadPort, CustomerGroupReadPort, OrganizationDetailsPort } from '@endora-commerce/contracts';
|
|
5
|
+
import type { PwaConfigResolver } from './services/pwa-config-resolver.js';
|
|
6
|
+
import { type PwaIconService } from './services/pwa-icon-service.js';
|
|
7
|
+
import type { PushSubscriptionService } from './services/push-subscription-service.js';
|
|
8
|
+
import type { PushMessageService } from './services/push-message-service.js';
|
|
9
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
10
|
+
export interface AdminAuditContext {
|
|
11
|
+
actorAdminUserId: string | null;
|
|
12
|
+
}
|
|
13
|
+
/** Write port over SettingsAdminService (global vs per-channel value writes). */
|
|
14
|
+
export interface SettingsWritePort {
|
|
15
|
+
setValueForAllChannels(code: string, rawValue: unknown, expectedVersion: string | null, actor: AdminAuditContext): Promise<unknown>;
|
|
16
|
+
setValueForSubset(code: string, channelCodes: string[], rawValue: unknown, expectedVersion: string | null, actor: AdminAuditContext): Promise<unknown>;
|
|
17
|
+
/** Remove per-channel override rows so the channel inherits the global value. */
|
|
18
|
+
resetValues(code: string, channelCodes: string[] | undefined, actor: AdminAuditContext): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface PwaAdminRoutesDeps {
|
|
21
|
+
requireAdmin: RequireAdminFactory;
|
|
22
|
+
emFactory: () => EntityManager;
|
|
23
|
+
configResolver: PwaConfigResolver;
|
|
24
|
+
iconService: PwaIconService;
|
|
25
|
+
subscriptionService: PushSubscriptionService;
|
|
26
|
+
messageService: PushMessageService;
|
|
27
|
+
settingsWrite: SettingsWritePort;
|
|
28
|
+
/**
|
|
29
|
+
* The three Rule Builder pickers' rows, asked of the modules that own them
|
|
30
|
+
* (feature 075, Phase C). Each is gated at its owner's registration seam, so
|
|
31
|
+
* a picker over a switched-off module answers 503 `MODULE_DISABLED` instead
|
|
32
|
+
* of listing targets the operator can no longer reach.
|
|
33
|
+
*/
|
|
34
|
+
customerAccounts: CustomerAccountReadPort;
|
|
35
|
+
organizationDetails: OrganizationDetailsPort;
|
|
36
|
+
customerGroups: CustomerGroupReadPort;
|
|
37
|
+
settingsRead: {
|
|
38
|
+
get<T>(code: string, channelId: string | null, schema: z.ZodType<T>): Promise<T>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a salesChannelId query value (or null) to the channel to read/write.
|
|
42
|
+
* Answers `null` when the deployment has no channel at all, which is a
|
|
43
|
+
* platform-wide read rather than the `'default'` sentinel it used to be
|
|
44
|
+
* (feature 072, D-41).
|
|
45
|
+
*/
|
|
46
|
+
resolveScopeChannelId: (salesChannelId: string | null) => Promise<string | null>;
|
|
47
|
+
/** Map a channel id to its code (subset writes are keyed by code). */
|
|
48
|
+
channelCodeForId: (channelId: string) => Promise<string | null>;
|
|
49
|
+
resolveAuditContext: (request: FastifyRequest) => AdminAuditContext;
|
|
50
|
+
}
|
|
51
|
+
export declare function registerPwaAdminRoutes(app: FastifyInstance, deps: PwaAdminRoutesDeps): Promise<void>;
|
|
52
|
+
//# 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":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IAChC,sBAAsB,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iBAAiB,CACf,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iFAAiF;IACjF,WAAW,CACT,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,EAClC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAKD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,mBAAmB,CAAC;IAClC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,cAAc,EAAE,iBAAiB,CAAC;IAClC,WAAW,EAAE,cAAc,CAAC;IAC5B,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,cAAc,EAAE,kBAAkB,CAAC;IACnC,aAAa,EAAE,iBAAiB,CAAC;IACjC;;;;;OAKG;IACH,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,cAAc,EAAE,qBAAqB,CAAC;IACtC,YAAY,EAAE;QACZ,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KAClF,CAAC;IACF;;;;;OAKG;IACH,qBAAqB,EAAE,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjF,sEAAsE;IACtE,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,mBAAmB,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,iBAAiB,CAAC;CACrE;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CA8Of"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import webpush from 'web-push';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { PWA_PERMISSIONS, PWA_SETTING_CODES, CreatePushMessageRequestSchema, UpdatePwaConfigRequestSchema, } from '@endora-commerce/contracts';
|
|
4
|
+
import { SalesChannel } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import { PwaIconInvalid } from './services/pwa-icon-service.js';
|
|
6
|
+
const StringSchema = z.string();
|
|
7
|
+
const BoolSchema = z.boolean();
|
|
8
|
+
export async function registerPwaAdminRoutes(app, deps) {
|
|
9
|
+
const readGate = deps.requireAdmin(PWA_PERMISSIONS.READ);
|
|
10
|
+
const writeGate = deps.requireAdmin(PWA_PERMISSIONS.WRITE);
|
|
11
|
+
const sendGate = deps.requireAdmin(PWA_PERMISSIONS.SEND_PUSH);
|
|
12
|
+
const safeGet = async (code, channelId, fallback = '') => {
|
|
13
|
+
try {
|
|
14
|
+
return await deps.settingsRead.get(code, channelId, StringSchema);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const safeBool = async (code, channelId) => {
|
|
21
|
+
try {
|
|
22
|
+
return await deps.settingsRead.get(code, channelId, BoolSchema);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
// GET /admin/pwa/config — effective config + secret isSet flags.
|
|
29
|
+
app.get('/api/v1/admin/pwa/config', { preHandler: readGate }, async (request, reply) => {
|
|
30
|
+
const salesChannelId = request.query.salesChannelId ?? null;
|
|
31
|
+
const channelId = await deps.resolveScopeChannelId(salesChannelId);
|
|
32
|
+
const [appName, shortName, themeColor, backgroundColor, displayMode, iconAssetId, vapidPublicKey, vapidPrivate, fcm] = await Promise.all([
|
|
33
|
+
safeGet(PWA_SETTING_CODES.APP_NAME, channelId, 'B2B Platform'),
|
|
34
|
+
safeGet(PWA_SETTING_CODES.SHORT_NAME, channelId, 'B2B'),
|
|
35
|
+
safeGet(PWA_SETTING_CODES.THEME_COLOR, channelId, '#1d4ed8'),
|
|
36
|
+
safeGet(PWA_SETTING_CODES.BACKGROUND_COLOR, channelId, '#fafafa'),
|
|
37
|
+
safeGet(PWA_SETTING_CODES.DISPLAY_MODE, channelId, 'standalone'),
|
|
38
|
+
safeGet(PWA_SETTING_CODES.ICON_ASSET_ID, channelId, ''),
|
|
39
|
+
safeGet(PWA_SETTING_CODES.VAPID_PUBLIC_KEY, channelId, ''),
|
|
40
|
+
safeGet(PWA_SETTING_CODES.VAPID_PRIVATE_KEY, channelId, ''),
|
|
41
|
+
safeGet(PWA_SETTING_CODES.FCM_SERVICE_ACCOUNT, channelId, ''),
|
|
42
|
+
]);
|
|
43
|
+
const [cachingEnabled, pushEnabled] = await Promise.all([
|
|
44
|
+
safeBool(PWA_SETTING_CODES.CACHING_ENABLED, channelId),
|
|
45
|
+
safeBool(PWA_SETTING_CODES.PUSH_ENABLED, channelId),
|
|
46
|
+
]);
|
|
47
|
+
return reply.send({
|
|
48
|
+
salesChannelId,
|
|
49
|
+
appName,
|
|
50
|
+
shortName,
|
|
51
|
+
themeColor,
|
|
52
|
+
backgroundColor,
|
|
53
|
+
displayMode,
|
|
54
|
+
iconAssetId,
|
|
55
|
+
cachingEnabled,
|
|
56
|
+
pushEnabled,
|
|
57
|
+
vapidPublicKey,
|
|
58
|
+
vapidPrivateKeyIsSet: vapidPrivate.length > 0,
|
|
59
|
+
fcmServiceAccountIsSet: fcm.length > 0,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
// PUT /admin/pwa/config — write a subset of settings for global or a channel.
|
|
63
|
+
app.put('/api/v1/admin/pwa/config', { preHandler: writeGate }, async (request, reply) => {
|
|
64
|
+
const body = UpdatePwaConfigRequestSchema.parse(request.body);
|
|
65
|
+
const actor = deps.resolveAuditContext(request);
|
|
66
|
+
const salesChannelId = body.salesChannelId ?? null;
|
|
67
|
+
const writes = [];
|
|
68
|
+
if (body.appName !== undefined)
|
|
69
|
+
writes.push({ code: PWA_SETTING_CODES.APP_NAME, value: body.appName });
|
|
70
|
+
if (body.shortName !== undefined)
|
|
71
|
+
writes.push({ code: PWA_SETTING_CODES.SHORT_NAME, value: body.shortName });
|
|
72
|
+
if (body.themeColor !== undefined)
|
|
73
|
+
writes.push({ code: PWA_SETTING_CODES.THEME_COLOR, value: body.themeColor });
|
|
74
|
+
if (body.backgroundColor !== undefined)
|
|
75
|
+
writes.push({ code: PWA_SETTING_CODES.BACKGROUND_COLOR, value: body.backgroundColor });
|
|
76
|
+
if (body.displayMode !== undefined)
|
|
77
|
+
writes.push({ code: PWA_SETTING_CODES.DISPLAY_MODE, value: body.displayMode });
|
|
78
|
+
if (body.cachingEnabled !== undefined)
|
|
79
|
+
writes.push({ code: PWA_SETTING_CODES.CACHING_ENABLED, value: body.cachingEnabled });
|
|
80
|
+
if (body.pushEnabled !== undefined)
|
|
81
|
+
writes.push({ code: PWA_SETTING_CODES.PUSH_ENABLED, value: body.pushEnabled });
|
|
82
|
+
if (body.vapidPublicKey !== undefined)
|
|
83
|
+
writes.push({ code: PWA_SETTING_CODES.VAPID_PUBLIC_KEY, value: body.vapidPublicKey });
|
|
84
|
+
if (body.vapidPrivateKey !== undefined)
|
|
85
|
+
writes.push({ code: PWA_SETTING_CODES.VAPID_PRIVATE_KEY, value: body.vapidPrivateKey });
|
|
86
|
+
if (body.fcmServiceAccount !== undefined)
|
|
87
|
+
writes.push({ code: PWA_SETTING_CODES.FCM_SERVICE_ACCOUNT, value: body.fcmServiceAccount });
|
|
88
|
+
const channelCode = salesChannelId ? await deps.channelCodeForId(salesChannelId) : null;
|
|
89
|
+
for (const w of writes) {
|
|
90
|
+
if (channelCode) {
|
|
91
|
+
await deps.settingsWrite.setValueForSubset(w.code, [channelCode], w.value, null, actor);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
await deps.settingsWrite.setValueForAllChannels(w.code, w.value, null, actor);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return reply.send({ updated: writes.length });
|
|
98
|
+
});
|
|
99
|
+
// POST /admin/pwa/config/reset — drop a channel's per-channel overrides so it
|
|
100
|
+
// inherits the global PWA config again. Only meaningful for a concrete channel.
|
|
101
|
+
app.post('/api/v1/admin/pwa/config/reset', { preHandler: writeGate }, async (request, reply) => {
|
|
102
|
+
const body = z.object({ salesChannelId: z.string().uuid() }).parse(request.body);
|
|
103
|
+
const channelCode = await deps.channelCodeForId(body.salesChannelId);
|
|
104
|
+
if (!channelCode) {
|
|
105
|
+
return reply.code(400).send({
|
|
106
|
+
error: { code: 'PWA_CHANNEL_UNKNOWN', message: 'Unknown sales channel.' },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const actor = deps.resolveAuditContext(request);
|
|
110
|
+
const codes = Object.values(PWA_SETTING_CODES);
|
|
111
|
+
for (const code of codes) {
|
|
112
|
+
await deps.settingsWrite.resetValues(code, [channelCode], actor);
|
|
113
|
+
}
|
|
114
|
+
return reply.send({ reset: codes.length });
|
|
115
|
+
});
|
|
116
|
+
// POST /admin/pwa/vapid/generate — generate + persist a VAPID key pair.
|
|
117
|
+
app.post('/api/v1/admin/pwa/vapid/generate', { preHandler: writeGate }, async (request, reply) => {
|
|
118
|
+
const actor = deps.resolveAuditContext(request);
|
|
119
|
+
const keys = webpush.generateVAPIDKeys();
|
|
120
|
+
await deps.settingsWrite.setValueForAllChannels(PWA_SETTING_CODES.VAPID_PUBLIC_KEY, keys.publicKey, null, actor);
|
|
121
|
+
await deps.settingsWrite.setValueForAllChannels(PWA_SETTING_CODES.VAPID_PRIVATE_KEY, keys.privateKey, null, actor);
|
|
122
|
+
return reply.send({ publicKey: keys.publicKey });
|
|
123
|
+
});
|
|
124
|
+
// POST /admin/pwa/icon — multipart source upload + sharp rendition derivation.
|
|
125
|
+
app.post('/api/v1/admin/pwa/icon', { preHandler: writeGate }, async (request, reply) => {
|
|
126
|
+
if (!request.isMultipart()) {
|
|
127
|
+
return reply.code(400).send({ error: { code: 'PWA_ICON_INVALID', message: 'Upload requires multipart/form-data with a `file` part.' } });
|
|
128
|
+
}
|
|
129
|
+
const file = await request.file();
|
|
130
|
+
if (!file) {
|
|
131
|
+
return reply.code(400).send({ error: { code: 'PWA_ICON_INVALID', message: 'Missing `file` part.' } });
|
|
132
|
+
}
|
|
133
|
+
const buffer = await file.toBuffer();
|
|
134
|
+
const salesChannelId = request.query.salesChannelId ?? null;
|
|
135
|
+
try {
|
|
136
|
+
const result = await deps.iconService.ingest({
|
|
137
|
+
salesChannelId,
|
|
138
|
+
declaredMime: file.mimetype,
|
|
139
|
+
buffer,
|
|
140
|
+
});
|
|
141
|
+
// Record the source asset id on the channel/global config.
|
|
142
|
+
const actor = deps.resolveAuditContext(request);
|
|
143
|
+
const channelCode = salesChannelId ? await deps.channelCodeForId(salesChannelId) : null;
|
|
144
|
+
if (channelCode) {
|
|
145
|
+
await deps.settingsWrite.setValueForSubset(PWA_SETTING_CODES.ICON_ASSET_ID, [channelCode], result.sourceAssetId, null, actor);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
await deps.settingsWrite.setValueForAllChannels(PWA_SETTING_CODES.ICON_ASSET_ID, result.sourceAssetId, null, actor);
|
|
149
|
+
}
|
|
150
|
+
return reply.code(201).send(result);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err instanceof PwaIconInvalid) {
|
|
154
|
+
return reply.code(400).send({ error: { code: err.code, message: err.message } });
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// GET /admin/pwa/subscriptions — subscriber counts for a channel.
|
|
160
|
+
app.get('/api/v1/admin/pwa/subscriptions', { preHandler: readGate }, async (request, reply) => {
|
|
161
|
+
const channelId = await deps.resolveScopeChannelId(request.query.salesChannelId ?? null);
|
|
162
|
+
const stats = await deps.subscriptionService.statsForChannel(channelId);
|
|
163
|
+
return reply.send(stats);
|
|
164
|
+
});
|
|
165
|
+
// POST /admin/pwa/messages — admin-initiated send (FR-024).
|
|
166
|
+
app.post('/api/v1/admin/pwa/messages', { preHandler: sendGate }, async (request, reply) => {
|
|
167
|
+
const body = CreatePushMessageRequestSchema.parse(request.body);
|
|
168
|
+
const config = await deps.configResolver.getPublicConfig(body.salesChannelId);
|
|
169
|
+
if (!config.pushEnabled || !config.vapidPublicKey) {
|
|
170
|
+
return reply.code(503).send({ error: { code: 'PWA_PUSH_UNCONFIGURED', message: 'Push is not configured for this channel.' } });
|
|
171
|
+
}
|
|
172
|
+
const actor = deps.resolveAuditContext(request);
|
|
173
|
+
const result = await deps.messageService.createAndEnqueue({
|
|
174
|
+
salesChannelId: body.salesChannelId,
|
|
175
|
+
title: body.title,
|
|
176
|
+
body: body.body,
|
|
177
|
+
url: body.url ?? null,
|
|
178
|
+
iconUrl: body.iconUrl ?? null,
|
|
179
|
+
audience: body.audience,
|
|
180
|
+
trigger: 'admin',
|
|
181
|
+
createdByAdminUserId: actor.actorAdminUserId,
|
|
182
|
+
});
|
|
183
|
+
return reply.code(202).send(result);
|
|
184
|
+
});
|
|
185
|
+
// ---- Audience Rule Builder pickers (targeting criteria) -----------------
|
|
186
|
+
// Lists feeding the admin Rule Builder when composing a targeted send.
|
|
187
|
+
// Gated by the page's base read permission so a read-only viewer can still
|
|
188
|
+
// see the available targets.
|
|
189
|
+
app.get('/api/v1/admin/pwa/rule-targets/sales-channels', { preHandler: readGate }, async () => {
|
|
190
|
+
const em = deps.emFactory();
|
|
191
|
+
const rows = await em.find(SalesChannel, {}, { orderBy: { code: 'asc' } });
|
|
192
|
+
return { data: { items: rows.map((r) => ({ id: r.id, code: r.code, name: r.name })) } };
|
|
193
|
+
});
|
|
194
|
+
app.get('/api/v1/admin/pwa/rule-targets/customer-groups', { preHandler: readGate }, async () => {
|
|
195
|
+
const rows = await deps.customerGroups.listAll();
|
|
196
|
+
return { data: { items: rows.map((r) => ({ id: r.id, code: r.code, name: r.name })) } };
|
|
197
|
+
});
|
|
198
|
+
app.get('/api/v1/admin/pwa/rule-targets/organizations', { preHandler: readGate }, async (request) => {
|
|
199
|
+
const limit = Math.min(200, Math.max(1, Number(request.query.limit ?? '100')));
|
|
200
|
+
const search = (request.query.search ?? '').trim();
|
|
201
|
+
// `searchByName` matches the diacritic-folded `name_search` column the
|
|
202
|
+
// owner maintains, so "lodz" now finds "Łódź" — the local `$ilike` on
|
|
203
|
+
// `name` never did.
|
|
204
|
+
const rows = await deps.organizationDetails.searchByName(search, limit);
|
|
205
|
+
return { data: { items: rows.map((r) => ({ id: r.id, name: r.name, taxId: r.taxId })) } };
|
|
206
|
+
});
|
|
207
|
+
app.get('/api/v1/admin/pwa/rule-targets/customers', { preHandler: readGate }, async (request) => {
|
|
208
|
+
const limit = Math.min(200, Math.max(1, Number(request.query.limit ?? '100')));
|
|
209
|
+
const search = (request.query.search ?? '').trim();
|
|
210
|
+
const rows = await deps.customerAccounts.searchByEmail(search, limit);
|
|
211
|
+
return {
|
|
212
|
+
data: {
|
|
213
|
+
items: rows.map((r) => ({
|
|
214
|
+
id: r.id,
|
|
215
|
+
email: r.email,
|
|
216
|
+
organizationId: r.organizationId ?? null,
|
|
217
|
+
})),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=routes.admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { PwaConfigResolver } from './services/pwa-config-resolver.js';
|
|
3
|
+
import type { PwaIconService } from './services/pwa-icon-service.js';
|
|
4
|
+
import type { PushSubscriptionService } from './services/push-subscription-service.js';
|
|
5
|
+
export interface PwaStorefrontRoutesDeps {
|
|
6
|
+
configResolver: PwaConfigResolver;
|
|
7
|
+
iconService: PwaIconService;
|
|
8
|
+
subscriptionService: PushSubscriptionService;
|
|
9
|
+
/**
|
|
10
|
+
* A stored asset id to a servable URL, over `assets_library`' published
|
|
11
|
+
* `assetsLibraryPort` (`specs/110-instance-repository/` T118c). Absolute:
|
|
12
|
+
* `assets_library` resolves the origin itself (D-223).
|
|
13
|
+
*/
|
|
14
|
+
resolveAssetUrl: (assetId: string) => Promise<string | null>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* **`resolveChannelId` is gone, and it was never called.**
|
|
18
|
+
*
|
|
19
|
+
* Both composition roots built a `resolveChannelIdByCode` closure — a
|
|
20
|
+
* `getByCode` with a system-default fallback — `plugin.ts` threaded it here as
|
|
21
|
+
* `resolveChannelId`, and the three routes below read `getResolvedChannel`
|
|
22
|
+
* instead, which is the platform's resolved request channel and the sanctioned
|
|
23
|
+
* accessor (Constitution XII). So the option was dead the whole way down: two
|
|
24
|
+
* closures, one option, one dependency field, and no call site. It was one of
|
|
25
|
+
* the eight members of `PwaBridge`, and the only one T118c deleted rather than
|
|
26
|
+
* drained.
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerPwaStorefrontRoutes(app: FastifyInstance, deps: PwaStorefrontRoutesDeps): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=routes.storefront.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.storefront.d.ts","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAIvF,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,iBAAiB,CAAC;IAClC,WAAW,EAAE,cAAc,CAAC;IAC5B,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C;;;;OAIG;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED;;;;;;;;;;;GAWG;AAEH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAoDf"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PushSubscriptionInputSchema, PushSubscriptionDeleteSchema, } from '@endora-commerce/contracts';
|
|
2
|
+
import { getResolvedChannel } from '@endora-commerce/platform/kernel';
|
|
3
|
+
import { callingCustomerAccountId } from './request-actor.js';
|
|
4
|
+
/**
|
|
5
|
+
* **`resolveChannelId` is gone, and it was never called.**
|
|
6
|
+
*
|
|
7
|
+
* Both composition roots built a `resolveChannelIdByCode` closure — a
|
|
8
|
+
* `getByCode` with a system-default fallback — `plugin.ts` threaded it here as
|
|
9
|
+
* `resolveChannelId`, and the three routes below read `getResolvedChannel`
|
|
10
|
+
* instead, which is the platform's resolved request channel and the sanctioned
|
|
11
|
+
* accessor (Constitution XII). So the option was dead the whole way down: two
|
|
12
|
+
* closures, one option, one dependency field, and no call site. It was one of
|
|
13
|
+
* the eight members of `PwaBridge`, and the only one T118c deleted rather than
|
|
14
|
+
* drained.
|
|
15
|
+
*/
|
|
16
|
+
export async function registerPwaStorefrontRoutes(app, deps) {
|
|
17
|
+
// GET config — manifest identity + toggles + public VAPID key (FR-001/011).
|
|
18
|
+
app.get('/api/v1/storefront/pwa/config', async (request, reply) => {
|
|
19
|
+
const channelId = getResolvedChannel(request).id;
|
|
20
|
+
const config = await deps.configResolver.getPublicConfig(channelId);
|
|
21
|
+
reply.header('Cache-Control', 'public, max-age=60');
|
|
22
|
+
return reply.send(config);
|
|
23
|
+
});
|
|
24
|
+
// GET icon rendition — redirect to the stored asset URL (FR-005).
|
|
25
|
+
app.get('/api/v1/storefront/pwa/icons/:spec', async (request, reply) => {
|
|
26
|
+
const match = /^(\d+)-(any|maskable)\.png$/.exec(request.params.spec);
|
|
27
|
+
if (!match)
|
|
28
|
+
return reply.code(404).send();
|
|
29
|
+
const size = Number(match[1]);
|
|
30
|
+
const purpose = match[2];
|
|
31
|
+
const channelId = getResolvedChannel(request).id;
|
|
32
|
+
const assetId = await deps.iconService.resolveRenditionAssetId(channelId, size, purpose);
|
|
33
|
+
if (!assetId)
|
|
34
|
+
return reply.code(404).send();
|
|
35
|
+
const url = await deps.resolveAssetUrl(assetId);
|
|
36
|
+
if (!url)
|
|
37
|
+
return reply.code(404).send();
|
|
38
|
+
return reply.redirect(url, 302);
|
|
39
|
+
});
|
|
40
|
+
// POST subscribe — register (upsert) a Web-Push subscription (FR-017).
|
|
41
|
+
app.post('/api/v1/storefront/pwa/subscriptions', async (request, reply) => {
|
|
42
|
+
const channelId = getResolvedChannel(request).id;
|
|
43
|
+
const config = await deps.configResolver.getPublicConfig(channelId);
|
|
44
|
+
if (!config.pushEnabled) {
|
|
45
|
+
return reply.code(403).send({ error: { code: 'PWA_PUSH_DISABLED', message: 'Push is disabled for this channel.' } });
|
|
46
|
+
}
|
|
47
|
+
if (!config.vapidPublicKey) {
|
|
48
|
+
return reply.code(503).send({ error: { code: 'PWA_PUSH_UNCONFIGURED', message: 'Push is not configured.' } });
|
|
49
|
+
}
|
|
50
|
+
const body = PushSubscriptionInputSchema.parse(request.body);
|
|
51
|
+
const customerAccountId = callingCustomerAccountId(request);
|
|
52
|
+
const result = await deps.subscriptionService.register({
|
|
53
|
+
...body,
|
|
54
|
+
salesChannelId: channelId,
|
|
55
|
+
customerAccountId,
|
|
56
|
+
});
|
|
57
|
+
return reply.code(result.created ? 201 : 200).send({ id: result.id, status: result.status });
|
|
58
|
+
});
|
|
59
|
+
// DELETE subscribe — revoke (FR-018). Idempotent.
|
|
60
|
+
app.delete('/api/v1/storefront/pwa/subscriptions', async (request, reply) => {
|
|
61
|
+
const body = PushSubscriptionDeleteSchema.parse(request.body);
|
|
62
|
+
await deps.subscriptionService.revoke(body.endpoint);
|
|
63
|
+
return reply.code(204).send();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=routes.storefront.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.storefront.js","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"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"}
|