@endora-commerce/mod-analytics 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 +57 -0
- package/dist/admin/index.d.ts +28 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +46 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/AnalyticsPage.d.ts +3 -0
- package/dist/admin/pages/AnalyticsPage.d.ts.map +1 -0
- package/dist/admin/pages/AnalyticsPage.js +58 -0
- package/dist/admin/pages/AnalyticsPage.js.map +1 -0
- package/dist/backend/entities/analytics-event.entity.d.ts +30 -0
- package/dist/backend/entities/analytics-event.entity.d.ts.map +1 -0
- package/dist/backend/entities/analytics-event.entity.js +89 -0
- package/dist/backend/entities/analytics-event.entity.js.map +1 -0
- package/dist/backend/index.d.ts +42 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +39 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +24 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +20 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.d.ts +11 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +22 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/analytics-ingest.service.d.ts +27 -0
- package/dist/backend/services/analytics-ingest.service.d.ts.map +1 -0
- package/dist/backend/services/analytics-ingest.service.js +56 -0
- package/dist/backend/services/analytics-ingest.service.js.map +1 -0
- package/dist/backend/services/analytics-query.service.d.ts +19 -0
- package/dist/backend/services/analytics-query.service.d.ts.map +1 -0
- package/dist/backend/services/analytics-query.service.js +51 -0
- package/dist/backend/services/analytics-query.service.js.map +1 -0
- package/dist/backend/services/ga4-forwarder.d.ts +49 -0
- package/dist/backend/services/ga4-forwarder.d.ts.map +1 -0
- package/dist/backend/services/ga4-forwarder.js +86 -0
- package/dist/backend/services/ga4-forwarder.js.map +1 -0
- package/dist/manifest.d.ts +169 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +64 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260425T143139_analytics_init.d.ts +13 -0
- package/dist/migrations/20260425T143139_analytics_init.d.ts.map +1 -0
- package/dist/migrations/20260425T143139_analytics_init.js +34 -0
- package/dist/migrations/20260425T143139_analytics_init.js.map +1 -0
- package/dist/migrations/20260912T125655_analytics_events_tenant_scope_indexes.d.ts +31 -0
- package/dist/migrations/20260912T125655_analytics_events_tenant_scope_indexes.d.ts.map +1 -0
- package/dist/migrations/20260912T125655_analytics_events_tenant_scope_indexes.js +37 -0
- package/dist/migrations/20260912T125655_analytics_events_tenant_scope_indexes.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 +30 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/analytics.md +74 -0
- package/i18n/en.json +19 -0
- package/i18n/pl.json +19 -0
- package/package.json +92 -0
- package/tailwind.css +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
|
|
2
|
+
|
|
3
|
+
# @endora-commerce/mod-analytics
|
|
4
|
+
|
|
5
|
+
Server-side analytics aggregation and dashboard data feeds.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
An **Endora Commerce module package**. Its module id is `analytics` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
|
|
10
|
+
|
|
11
|
+
A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
| Import | Contents |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@endora-commerce/mod-analytics` | the module manifest — its id, version, dependencies, settings and activation |
|
|
18
|
+
| `@endora-commerce/mod-analytics/admin` | the Admin UI contribution: the screens, navigation and palette actions it adds |
|
|
19
|
+
| `@endora-commerce/mod-analytics/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
|
|
20
|
+
| `@endora-commerce/mod-analytics/migrations` | the module’s own schema migrations, in the order the platform runs them |
|
|
21
|
+
| `@endora-commerce/mod-analytics/tailwind.css` | the Tailwind source declaration for the admin code above |
|
|
22
|
+
|
|
23
|
+
## Depends on
|
|
24
|
+
|
|
25
|
+
Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
|
|
26
|
+
|
|
27
|
+
**Endora packages**
|
|
28
|
+
|
|
29
|
+
- `@endora-commerce/admin-kit` — *optional*
|
|
30
|
+
- `@endora-commerce/contracts`
|
|
31
|
+
- `@endora-commerce/platform`
|
|
32
|
+
|
|
33
|
+
**Third-party**
|
|
34
|
+
|
|
35
|
+
- `@mikro-orm/core` ^6
|
|
36
|
+
- `@mikro-orm/migrations` ^6
|
|
37
|
+
- `@mikro-orm/postgresql` ^6
|
|
38
|
+
- `fastify` ^5
|
|
39
|
+
- `lucide-react` ^1 — *optional*
|
|
40
|
+
- `react` ^19 — *optional*
|
|
41
|
+
|
|
42
|
+
## What the tarball carries
|
|
43
|
+
|
|
44
|
+
- `dist/` — the compiled JavaScript and its type declarations
|
|
45
|
+
- `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
|
|
46
|
+
- `docs/` — the package’s own pages in the Endora Commerce documentation site
|
|
47
|
+
- `tailwind.css` — the Tailwind source declaration
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add @endora-commerce/mod-analytics
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Licence
|
|
56
|
+
|
|
57
|
+
MIT — the text is in `LICENSE`, beside this file.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `analytics`' admin surface — one route and one sidebar entry, declared by the
|
|
3
|
+
* module that owns them (feature 091, Phase 4;
|
|
4
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
|
|
5
|
+
*
|
|
6
|
+
* This is the **second batch** of the drain, and it was selected by the rule
|
|
7
|
+
* `research.md` §6 sets — ascending incoming cross-module reach — with the
|
|
8
|
+
* feasibility test batch one applied on top of it. `analytics` has **zero**
|
|
9
|
+
* admin cross-module reach in either direction, and it is the only remaining
|
|
10
|
+
* zero-incoming directory every one of whose host symbols
|
|
11
|
+
* `@endora-commerce/admin-kit` publishes: the other candidates at that rung
|
|
12
|
+
* reach either a Group A picker (`api_keys`, `comparisons`, `megamenu`, `blog`,
|
|
13
|
+
* `webhooks`) or the Group B session cluster (`payment_methods`, `promotions`),
|
|
14
|
+
* both of which `backend/scripts/ledgers/admin-surface.ts` records as
|
|
15
|
+
* unpublished with a retiring condition that is somebody else's merge request.
|
|
16
|
+
*
|
|
17
|
+
* **This entry exports data and nothing else** (R2), like every `./admin`
|
|
18
|
+
* layer: `check:module-boundary`'s D-171 rule designates a subpath as contract
|
|
19
|
+
* surface when it emits no runtime binding, and `./admin` deliberately does not
|
|
20
|
+
* qualify, so a consumer reaching into another module's `./admin` stays a
|
|
21
|
+
* counted boundary reach.
|
|
22
|
+
*
|
|
23
|
+
* **The component is a dynamic-import factory** (R6), so Vite has a split point
|
|
24
|
+
* whether or not anybody remembers to ask for one.
|
|
25
|
+
*/
|
|
26
|
+
import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
27
|
+
export declare const contributions: AdminContributions;
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAKnF,eAAO,MAAM,aAAa,EAAE,kBA0C3B,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** The module's only route: the analytics dashboard. */
|
|
2
|
+
const ROUTE_PATH = '/analytics';
|
|
3
|
+
export const contributions = {
|
|
4
|
+
routes: [
|
|
5
|
+
{
|
|
6
|
+
path: ROUTE_PATH,
|
|
7
|
+
component: () => import('./pages/AnalyticsPage.js'),
|
|
8
|
+
// Read from the route this screen calls —
|
|
9
|
+
// `requireAdmin('analytics:read')` on
|
|
10
|
+
// `GET /api/v1/admin/analytics/summary` — and the same code the
|
|
11
|
+
// module's `open-analytics` palette action declares, which
|
|
12
|
+
// `check:action-route-permissions` holds to that route.
|
|
13
|
+
requiredPermission: 'analytics:read',
|
|
14
|
+
index: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
nav: [
|
|
18
|
+
{
|
|
19
|
+
to: ROUTE_PATH,
|
|
20
|
+
// Module-relative (R8), resolved in this module's own namespace out of
|
|
21
|
+
// `packages/modules/analytics/i18n/`. It was `appShell.nav.analytics` in
|
|
22
|
+
// the shared `_i18n` bundle, one of the four shared files a module author
|
|
23
|
+
// had to edit.
|
|
24
|
+
labelKey: 'nav.analytics.label',
|
|
25
|
+
// The icon the hand-written NAV entry carried. `LineChart` joins
|
|
26
|
+
// `KnownIconNameSchema` and `icon-map.ts` in this merge request so the
|
|
27
|
+
// sidebar renders the same glyph it did before the move; swapping to a
|
|
28
|
+
// name already on the allowlist would have been a visible change bought
|
|
29
|
+
// for nothing.
|
|
30
|
+
icon: 'LineChart',
|
|
31
|
+
section: 'analyticsAds',
|
|
32
|
+
// Below `google_analytics`' 200, which is the order the two had in the
|
|
33
|
+
// hand-written NAV: `/analytics` first, `/google-analytics` second. A
|
|
34
|
+
// module's entries still append to their section as a block until the
|
|
35
|
+
// host's own entries carry weights, so the pair now sits after
|
|
36
|
+
// `/linkedin-ads` and `/meta-ads` rather than before them — recorded in
|
|
37
|
+
// `admin/test/components/AppShell.analytics-nav.test.tsx` rather than
|
|
38
|
+
// left to be discovered.
|
|
39
|
+
weight: 100,
|
|
40
|
+
// The code that opens the screen, which is the only code this module's
|
|
41
|
+
// admin surface enforces — the dashboard is read-only.
|
|
42
|
+
requiredPermission: 'analytics:read',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AA2BA,wDAAwD;AACxD,MAAM,UAAU,GAAG,YAAY,CAAC;AAEhC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC;YACnD,0CAA0C;YAC1C,sCAAsC;YACtC,gEAAgE;YAChE,2DAA2D;YAC3D,wDAAwD;YACxD,kBAAkB,EAAE,gBAAgB;YACpC,KAAK,EAAE,IAAI;SACZ;KACF;IACD,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,yEAAyE;YACzE,0EAA0E;YAC1E,eAAe;YACf,QAAQ,EAAE,qBAAqB;YAC/B,iEAAiE;YACjE,uEAAuE;YACvE,uEAAuE;YACvE,wEAAwE;YACxE,eAAe;YACf,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc;YACvB,uEAAuE;YACvE,sEAAsE;YACtE,sEAAsE;YACtE,+DAA+D;YAC/D,wEAAwE;YACxE,sEAAsE;YACtE,yBAAyB;YACzB,MAAM,EAAE,GAAG;YACX,uEAAuE;YACvE,uDAAuD;YACvD,kBAAkB,EAAE,gBAAgB;SACrC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/AnalyticsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA6BlF,MAAM,CAAC,OAAO,UAAU,aAAa,IAAI,SAAS,CA2JjD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { RefreshCw } from 'lucide-react';
|
|
4
|
+
import { ApiError, apiClient, formatDateTime } from '@endora-commerce/admin-kit/lib';
|
|
5
|
+
import { Alert, AlertDescription, Button, Card, CardContent, CardHeader, CardTitle, PageHeader, Select, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@endora-commerce/admin-kit/ui';
|
|
6
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
7
|
+
const RANGE_OPTION_VALUES = [7, 30, 90];
|
|
8
|
+
export default function AnalyticsPage() {
|
|
9
|
+
const t = useTranslation('analytics');
|
|
10
|
+
const RANGE_OPTIONS = [
|
|
11
|
+
{ value: 7, label: t('range.7days') },
|
|
12
|
+
{ value: 30, label: t('range.30days') },
|
|
13
|
+
{ value: 90, label: t('range.90days') },
|
|
14
|
+
];
|
|
15
|
+
void RANGE_OPTION_VALUES;
|
|
16
|
+
const [rangeDays, setRangeDays] = useState(30);
|
|
17
|
+
const [summary, setSummary] = useState(null);
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const window = useMemo(() => {
|
|
21
|
+
const to = new Date();
|
|
22
|
+
const from = new Date(to.getTime() - rangeDays * 24 * 60 * 60 * 1000);
|
|
23
|
+
return { fromIso: from.toISOString(), toIso: to.toISOString() };
|
|
24
|
+
}, [rangeDays]);
|
|
25
|
+
const refresh = useCallback(async () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
try {
|
|
29
|
+
const qs = `?from=${encodeURIComponent(window.fromIso)}&to=${encodeURIComponent(window.toIso)}`;
|
|
30
|
+
const res = await apiClient.get(`/api/v1/admin/analytics/summary${qs}`);
|
|
31
|
+
setSummary(res.data);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
setError(err instanceof ApiError ? err.envelope.error.message : t('error.load'));
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
setLoading(false);
|
|
38
|
+
}
|
|
39
|
+
}, [window.fromIso, window.toIso, t]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
void refresh();
|
|
42
|
+
}, [refresh]);
|
|
43
|
+
const dailyByDay = useMemo(() => groupDailyByDay(summary?.daily ?? []), [summary]);
|
|
44
|
+
const allTypes = useMemo(() => Array.from(new Set((summary?.daily ?? []).map((d) => d.type))).sort(), [summary]);
|
|
45
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('page.title'), description: _jsxs(_Fragment, { children: [t('page.descriptionPrefix'), " ", formatDateTime(window.fromIso), " \u2013", ' ', formatDateTime(window.toIso), "."] }), actions: _jsxs(_Fragment, { children: [_jsx(Select, { className: "w-auto", value: rangeDays, onChange: (e) => setRangeDays(Number(e.target.value)), children: RANGE_OPTIONS.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
46
|
+
void refresh();
|
|
47
|
+
}, children: [_jsx(RefreshCw, {}), t('refresh')] })] }) }), error ? (_jsx(Alert, { variant: "destructive", className: "mb-4", children: _jsx(AlertDescription, { children: error }) })) : null, _jsxs(Card, { className: "mb-4", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t('totals.title') }) }), _jsx(CardContent, { children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('loading') })) : !summary || summary.totalsByType.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('empty') })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('column.eventType') }), _jsx(TableHead, { className: "text-right", children: t('column.count') })] }) }), _jsx(TableBody, { children: summary.totalsByType.map((row) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: row.type }), _jsx(TableCell, { className: "text-right", children: row.count.toLocaleString() })] }, row.type))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t('daily.title') }) }), _jsx(CardContent, { children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('loading') })) : dailyByDay.size === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('empty') })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('column.day') }), allTypes.map((type) => (_jsx(TableHead, { className: "text-right font-mono text-xs", children: type }, type)))] }) }), _jsx(TableBody, { children: Array.from(dailyByDay.entries()).map(([day, counts]) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: day }), allTypes.map((type) => (_jsx(TableCell, { className: "text-right", children: (counts[type] ?? 0).toLocaleString() }, type)))] }, day))) })] })) })] })] }));
|
|
48
|
+
}
|
|
49
|
+
function groupDailyByDay(rows) {
|
|
50
|
+
const out = new Map();
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
const bucket = out.get(row.day) ?? {};
|
|
53
|
+
bucket[row.type] = row.count;
|
|
54
|
+
out.set(row.day, bucket);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=AnalyticsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsPage.js","sourceRoot":"","sources":["../../../src/admin/pages/AnalyticsPage.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* AnalyticsEvent (T237 / FR-110).
|
|
4
|
+
*
|
|
5
|
+
* Append-only event log. Storefront and admin POST batches into
|
|
6
|
+
* `/api/v1/analytics/events`; the admin dashboard reads aggregated
|
|
7
|
+
* counts from this table. The optional GA4 forwarder consumes the same
|
|
8
|
+
* stream when the feature flag is on.
|
|
9
|
+
*
|
|
10
|
+
* Indexes:
|
|
11
|
+
* - `(occurred_at)` — time-range queries dominate the dashboard
|
|
12
|
+
* - `(type, occurred_at)` — per-type time series
|
|
13
|
+
* - `(sales_channel_id)` — per-channel breakdowns
|
|
14
|
+
*/
|
|
15
|
+
export declare class AnalyticsEvent {
|
|
16
|
+
[OptionalProps]?: 'id' | 'occurredAt' | 'recordedAt' | 'salesChannelId' | 'customerAccountId' | 'organizationId' | 'sessionId' | 'properties' | 'requestId';
|
|
17
|
+
id: string;
|
|
18
|
+
type: string;
|
|
19
|
+
/** Storefront-supplied event timestamp; falls back to server `recordedAt`. */
|
|
20
|
+
occurredAt: Date;
|
|
21
|
+
recordedAt: Date;
|
|
22
|
+
salesChannelId?: string | null;
|
|
23
|
+
customerAccountId?: string | null;
|
|
24
|
+
organizationId?: string | null;
|
|
25
|
+
sessionId?: string | null;
|
|
26
|
+
properties?: Record<string, unknown> | null;
|
|
27
|
+
/** Mirror of the X-Request-Id header for cross-log correlation. */
|
|
28
|
+
requestId?: string | null;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=analytics-event.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-event.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/analytics-event.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;;;;;;;;;;;GAYG;AACH,qBAGa,cAAc;IACzB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,mBAAmB,GACnB,gBAAgB,GAChB,WAAW,GACX,YAAY,GACZ,WAAW,CAAC;IAGhB,EAAE,EAAE,MAAM,CAAgB;IAG1B,IAAI,EAAG,MAAM,CAAC;IAEd,8EAA8E;IAG9E,UAAU,EAAE,IAAI,CAAc;IAG9B,UAAU,EAAE,IAAI,CAAc;IAI9B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGlC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG/B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAE5C,mEAAmE;IAEnE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B"}
|
|
@@ -0,0 +1,89 @@
|
|
|
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 } from '@mikro-orm/core';
|
|
11
|
+
import { OrgScoped } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
/**
|
|
14
|
+
* AnalyticsEvent (T237 / FR-110).
|
|
15
|
+
*
|
|
16
|
+
* Append-only event log. Storefront and admin POST batches into
|
|
17
|
+
* `/api/v1/analytics/events`; the admin dashboard reads aggregated
|
|
18
|
+
* counts from this table. The optional GA4 forwarder consumes the same
|
|
19
|
+
* stream when the feature flag is on.
|
|
20
|
+
*
|
|
21
|
+
* Indexes:
|
|
22
|
+
* - `(occurred_at)` — time-range queries dominate the dashboard
|
|
23
|
+
* - `(type, occurred_at)` — per-type time series
|
|
24
|
+
* - `(sales_channel_id)` — per-channel breakdowns
|
|
25
|
+
*/
|
|
26
|
+
let AnalyticsEvent = class AnalyticsEvent {
|
|
27
|
+
[OptionalProps];
|
|
28
|
+
id = randomUUID();
|
|
29
|
+
type;
|
|
30
|
+
/** Storefront-supplied event timestamp; falls back to server `recordedAt`. */
|
|
31
|
+
occurredAt = new Date();
|
|
32
|
+
recordedAt = new Date();
|
|
33
|
+
salesChannelId;
|
|
34
|
+
customerAccountId;
|
|
35
|
+
organizationId;
|
|
36
|
+
sessionId;
|
|
37
|
+
properties;
|
|
38
|
+
/** Mirror of the X-Request-Id header for cross-log correlation. */
|
|
39
|
+
requestId;
|
|
40
|
+
};
|
|
41
|
+
__decorate([
|
|
42
|
+
PrimaryKey({ type: 'uuid' }),
|
|
43
|
+
__metadata("design:type", String)
|
|
44
|
+
], AnalyticsEvent.prototype, "id", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
Property({ type: 'string', length: 64 }),
|
|
47
|
+
__metadata("design:type", String)
|
|
48
|
+
], AnalyticsEvent.prototype, "type", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
Property({ type: 'datetime' }),
|
|
51
|
+
Index(),
|
|
52
|
+
__metadata("design:type", Date)
|
|
53
|
+
], AnalyticsEvent.prototype, "occurredAt", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
56
|
+
__metadata("design:type", Date)
|
|
57
|
+
], AnalyticsEvent.prototype, "recordedAt", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
Property({ type: 'uuid', nullable: true }),
|
|
60
|
+
Index(),
|
|
61
|
+
__metadata("design:type", Object)
|
|
62
|
+
], AnalyticsEvent.prototype, "salesChannelId", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
Property({ type: 'uuid', nullable: true }),
|
|
65
|
+
__metadata("design:type", Object)
|
|
66
|
+
], AnalyticsEvent.prototype, "customerAccountId", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
Property({ type: 'uuid', nullable: true }),
|
|
69
|
+
__metadata("design:type", Object)
|
|
70
|
+
], AnalyticsEvent.prototype, "organizationId", void 0);
|
|
71
|
+
__decorate([
|
|
72
|
+
Property({ type: 'string', length: 64, nullable: true }),
|
|
73
|
+
__metadata("design:type", Object)
|
|
74
|
+
], AnalyticsEvent.prototype, "sessionId", void 0);
|
|
75
|
+
__decorate([
|
|
76
|
+
Property({ type: 'json', nullable: true }),
|
|
77
|
+
__metadata("design:type", Object)
|
|
78
|
+
], AnalyticsEvent.prototype, "properties", void 0);
|
|
79
|
+
__decorate([
|
|
80
|
+
Property({ type: 'string', length: 64, nullable: true }),
|
|
81
|
+
__metadata("design:type", Object)
|
|
82
|
+
], AnalyticsEvent.prototype, "requestId", void 0);
|
|
83
|
+
AnalyticsEvent = __decorate([
|
|
84
|
+
OrgScoped(),
|
|
85
|
+
Entity({ tableName: 'analytics_events' }),
|
|
86
|
+
Index({ name: 'idx_analytics_events_type_occurred_at', properties: ['type', 'occurredAt'] })
|
|
87
|
+
], AnalyticsEvent);
|
|
88
|
+
export { AnalyticsEvent };
|
|
89
|
+
//# sourceMappingURL=analytics-event.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-event.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/analytics-event.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;;;;GAYG;AAII,IAAM,cAAc,GAApB,MAAM,cAAc;IACzB,CAAC,aAAa,CAAC,CASC;IAGhB,EAAE,GAAW,UAAU,EAAE,CAAC;IAG1B,IAAI,CAAU;IAEd,8EAA8E;IAG9E,UAAU,GAAS,IAAI,IAAI,EAAE,CAAC;IAG9B,UAAU,GAAS,IAAI,IAAI,EAAE,CAAC;IAI9B,cAAc,CAAiB;IAG/B,iBAAiB,CAAiB;IAGlC,cAAc,CAAiB;IAG/B,SAAS,CAAiB;IAG1B,UAAU,CAAkC;IAE5C,mEAAmE;IAEnE,SAAS,CAAiB;CAC3B,CAAA;AAhCC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0CACH;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;4CAC3B;AAKd;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC9B,KAAK,EAAE;8BACI,IAAI;kDAAc;AAG9B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAC/C,IAAI;kDAAc;AAI9B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1C,KAAK,EAAE;;sDACuB;AAG/B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yDACT;AAGlC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;sDACZ;AAG/B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDAC/B;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACC;AAI5C;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDAC/B;AA5Cf,cAAc;IAH1B,SAAS,EAAE;IACX,MAAM,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC;IACzC,KAAK,CAAC,EAAE,IAAI,EAAE,uCAAuC,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;GAChF,cAAc,CA6C1B"}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 { AnalyticsIngestService } from './services/analytics-ingest.service.js';
|
|
5
|
+
import { AnalyticsQueryService } from './services/analytics-query.service.js';
|
|
6
|
+
import { type AnalyticsForwarder } from './services/ga4-forwarder.js';
|
|
7
|
+
import { AnalyticsEvent } from './entities/analytics-event.entity.js';
|
|
8
|
+
/**
|
|
9
|
+
* `analytics` — the first wave-1 module whose conversion is only a conversion
|
|
10
|
+
* (feature 072, wave 1).
|
|
11
|
+
*
|
|
12
|
+
* No defect fell out of it and no other module had to move, which is worth
|
|
13
|
+
* noting because the two before it both spilled: `auth` pulled `admin_roles`
|
|
14
|
+
* in, and `currencies` pulled `dictionaries` and `languages`. This one owns its
|
|
15
|
+
* routes, owns its services, and nothing outside reads its handle — the shape
|
|
16
|
+
* the remaining sweep is hoping for.
|
|
17
|
+
*
|
|
18
|
+
* The GA4 forwarder is registered rather than defaulted inside the factory, so
|
|
19
|
+
* a test overrides it by re-registering the name instead of by passing an
|
|
20
|
+
* option through a factory it does not otherwise care about.
|
|
21
|
+
*/
|
|
22
|
+
export interface AnalyticsCradle {
|
|
23
|
+
readonly emFactory: () => EntityManager;
|
|
24
|
+
readonly requireAdmin: RequireAdminFactory;
|
|
25
|
+
readonly analyticsForwarder: AnalyticsForwarder;
|
|
26
|
+
readonly analyticsIngestService: AnalyticsIngestService;
|
|
27
|
+
readonly analyticsQueryService: AnalyticsQueryService;
|
|
28
|
+
}
|
|
29
|
+
export declare function registerModule(ctx: ModuleContext): void;
|
|
30
|
+
/**
|
|
31
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
32
|
+
* array and **no named class export** (D-168).
|
|
33
|
+
*
|
|
34
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
35
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
36
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
37
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
38
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
39
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
40
|
+
*/
|
|
41
|
+
export declare const entities: (typeof AnalyticsEvent)[];
|
|
42
|
+
//# 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,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAChD,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;IACxD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;CACvD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyBvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,2BAEpB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { registerAnalyticsRoutes } from './routes.js';
|
|
2
|
+
import { AnalyticsIngestService } from './services/analytics-ingest.service.js';
|
|
3
|
+
import { AnalyticsQueryService } from './services/analytics-query.service.js';
|
|
4
|
+
import { buildForwarderFromEnv } from './services/ga4-forwarder.js';
|
|
5
|
+
import { AnalyticsEvent } from './entities/analytics-event.entity.js';
|
|
6
|
+
export function registerModule(ctx) {
|
|
7
|
+
ctx.di.register({
|
|
8
|
+
analyticsForwarder: ctx.asFunction(() => buildForwarderFromEnv(process.env)).singleton(),
|
|
9
|
+
analyticsIngestService: ctx
|
|
10
|
+
.asFunction(({ emFactory, analyticsForwarder }) => new AnalyticsIngestService(emFactory, analyticsForwarder))
|
|
11
|
+
.singleton(),
|
|
12
|
+
analyticsQueryService: ctx
|
|
13
|
+
.asFunction(({ emFactory }) => new AnalyticsQueryService(emFactory))
|
|
14
|
+
.singleton(),
|
|
15
|
+
});
|
|
16
|
+
ctx.routes(async (app) => {
|
|
17
|
+
const { analyticsIngestService, analyticsQueryService, requireAdmin } = ctx.cradle();
|
|
18
|
+
await registerAnalyticsRoutes(app, {
|
|
19
|
+
ingestService: analyticsIngestService,
|
|
20
|
+
queryService: analyticsQueryService,
|
|
21
|
+
requireAdmin,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
27
|
+
* array and **no named class export** (D-168).
|
|
28
|
+
*
|
|
29
|
+
* This is the shape the platform reads when the package is *installed*: the
|
|
30
|
+
* boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
|
|
31
|
+
* and the static declaration reader (`scripts/lib/package-declarations.ts`),
|
|
32
|
+
* which is the third source of `check:module-boundary`'s `table→owner` map and
|
|
33
|
+
* the package pass of `check-entity-tenant-classification`. A missing array is
|
|
34
|
+
* answered with `[]` — zero entities registered, no error anywhere.
|
|
35
|
+
*/
|
|
36
|
+
export const entities = [
|
|
37
|
+
AnalyticsEvent,
|
|
38
|
+
];
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAA2B,MAAM,6BAA6B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAyBtE,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;QACd,kBAAkB,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE;QAExF,sBAAsB,EAAE,GAAG;aACxB,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAmB,EAAE,EAAE,CACrD,IAAI,sBAAsB,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAC5D;aACA,SAAS,EAAE;QAEd,qBAAqB,EAAE,GAAG;aACvB,UAAU,CAAC,CAAC,EAAE,SAAS,EAAmB,EAAE,EAAE,CAAC,IAAI,qBAAqB,CAAC,SAAS,CAAC,CAAC;aACpF,SAAS,EAAE;KACf,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,YAAY,EAAE,GACnE,GAAG,CAAC,MAAM,EAAmB,CAAC;QAChC,MAAM,uBAAuB,CAAC,GAAG,EAAE;YACjC,aAAa,EAAE,sBAAsB;YACrC,YAAY,EAAE,qBAAqB;YACnC,YAAY;SACb,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,cAAc;CACf,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
3
|
+
import { AnalyticsIngestService } from './services/analytics-ingest.service.js';
|
|
4
|
+
import { AnalyticsQueryService } from './services/analytics-query.service.js';
|
|
5
|
+
import { type AnalyticsForwarder } from './services/ga4-forwarder.js';
|
|
6
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
7
|
+
export interface AnalyticsModuleOptions {
|
|
8
|
+
emFactory: () => EntityManager;
|
|
9
|
+
requireAdmin: RequireAdminFactory;
|
|
10
|
+
/** Override for tests; defaults to the env-driven build. */
|
|
11
|
+
forwarder?: AnalyticsForwarder;
|
|
12
|
+
/** Override for tests; defaults to `process.env`. */
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
}
|
|
15
|
+
export interface AnalyticsModuleHandle {
|
|
16
|
+
ingestService: AnalyticsIngestService;
|
|
17
|
+
queryService: AnalyticsQueryService;
|
|
18
|
+
forwarder: AnalyticsForwarder;
|
|
19
|
+
}
|
|
20
|
+
export declare function analyticsModule(options: AnalyticsModuleOptions): {
|
|
21
|
+
plugin: (app: FastifyInstance) => Promise<void>;
|
|
22
|
+
handle: AnalyticsModuleHandle;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,4DAA4D;IAC5D,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,sBAAsB,CAAC;IACtC,YAAY,EAAE,qBAAqB,CAAC;IACpC,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG;IAChE,MAAM,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,EAAE,qBAAqB,CAAC;CAC/B,CAgBA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AnalyticsIngestService } from './services/analytics-ingest.service.js';
|
|
2
|
+
import { AnalyticsQueryService } from './services/analytics-query.service.js';
|
|
3
|
+
import { buildForwarderFromEnv, } from './services/ga4-forwarder.js';
|
|
4
|
+
import { registerAnalyticsRoutes } from './routes.js';
|
|
5
|
+
export function analyticsModule(options) {
|
|
6
|
+
const forwarder = options.forwarder ?? buildForwarderFromEnv(options.env ?? process.env);
|
|
7
|
+
const ingestService = new AnalyticsIngestService(options.emFactory, forwarder);
|
|
8
|
+
const queryService = new AnalyticsQueryService(options.emFactory);
|
|
9
|
+
return {
|
|
10
|
+
handle: { ingestService, queryService, forwarder },
|
|
11
|
+
plugin: async (app) => {
|
|
12
|
+
await registerAnalyticsRoutes(app, {
|
|
13
|
+
ingestService,
|
|
14
|
+
queryService,
|
|
15
|
+
requireAdmin: options.requireAdmin,
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EACL,qBAAqB,GAEtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAkBtD,MAAM,UAAU,eAAe,CAAC,OAA+B;IAI7D,MAAM,SAAS,GACb,OAAO,CAAC,SAAS,IAAI,qBAAqB,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IACzE,MAAM,aAAa,GAAG,IAAI,sBAAsB,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC/E,MAAM,YAAY,GAAG,IAAI,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAElE,OAAO;QACL,MAAM,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE;QAClD,MAAM,EAAE,KAAK,EAAE,GAAoB,EAAE,EAAE;YACrC,MAAM,uBAAuB,CAAC,GAAG,EAAE;gBACjC,aAAa;gBACb,YAAY;gBACZ,YAAY,EAAE,OAAO,CAAC,YAAY;aACnC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { AnalyticsIngestService } from './services/analytics-ingest.service.js';
|
|
3
|
+
import type { AnalyticsQueryService } from './services/analytics-query.service.js';
|
|
4
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
5
|
+
export interface AnalyticsRoutesDeps {
|
|
6
|
+
ingestService: AnalyticsIngestService;
|
|
7
|
+
queryService: AnalyticsQueryService;
|
|
8
|
+
requireAdmin: RequireAdminFactory;
|
|
9
|
+
}
|
|
10
|
+
export declare function registerAnalyticsRoutes(app: FastifyInstance, deps: AnalyticsRoutesDeps): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,sBAAsB,CAAC;IACtC,YAAY,EAAE,qBAAqB,CAAC;IACpC,YAAY,EAAE,mBAAmB,CAAC;CACnC;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { analyticsSummaryQuerySchema, ingestAnalyticsBatchRequestSchema, } from '@endora-commerce/contracts';
|
|
2
|
+
export async function registerAnalyticsRoutes(app, deps) {
|
|
3
|
+
const { ingestService, queryService, requireAdmin } = deps;
|
|
4
|
+
// Public ingest — accepts batches from storefront + admin. No auth gate by
|
|
5
|
+
// design (FR-110): we want anonymous browsing events, and the rate limiter
|
|
6
|
+
// already protects against floods.
|
|
7
|
+
app.post('/api/v1/analytics/events', { schema: { body: ingestAnalyticsBatchRequestSchema } }, async (request, reply) => {
|
|
8
|
+
const body = ingestAnalyticsBatchRequestSchema.parse(request.body);
|
|
9
|
+
const result = await ingestService.ingest({
|
|
10
|
+
events: body.events,
|
|
11
|
+
requestId: request.id,
|
|
12
|
+
});
|
|
13
|
+
reply.status(202);
|
|
14
|
+
return { data: result };
|
|
15
|
+
});
|
|
16
|
+
app.get('/api/v1/admin/analytics/summary', { preHandler: requireAdmin('analytics:read') }, async (request) => {
|
|
17
|
+
const query = analyticsSummaryQuerySchema.parse(request.query);
|
|
18
|
+
const summary = await queryService.summary(query);
|
|
19
|
+
return { data: summary };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AACA,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,GAClC,MAAM,4BAA4B,CAAC;AAWpC,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,GAAoB,EACpB,IAAyB;IAEzB,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE3D,2EAA2E;IAC3E,2EAA2E;IAC3E,mCAAmC;IACnC,GAAG,CAAC,IAAI,CACN,0BAA0B,EAC1B,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,iCAAiC,EAAE,EAAE,EACvD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC;YACxC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,OAAO,CAAC,EAAE;SACtB,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,GAAG,CACL,iCAAiC,EACjC,EAAE,UAAU,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAAE,EAC9C,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,2BAA2B,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/D,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import { type IngestAnalyticsEvent } from '@endora-commerce/contracts';
|
|
3
|
+
import type { AnalyticsForwarder } from './ga4-forwarder.js';
|
|
4
|
+
/**
|
|
5
|
+
* AnalyticsIngestService (T237 / FR-110).
|
|
6
|
+
*
|
|
7
|
+
* Validates each event in the batch independently — a single bad row should
|
|
8
|
+
* not reject the whole batch. Persisted rows are also handed to the optional
|
|
9
|
+
* GA4 forwarder; the forwarder is best-effort and never blocks ingest.
|
|
10
|
+
*/
|
|
11
|
+
export interface IngestResult {
|
|
12
|
+
accepted: number;
|
|
13
|
+
rejected: Array<{
|
|
14
|
+
index: number;
|
|
15
|
+
reason: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare class AnalyticsIngestService {
|
|
19
|
+
private readonly emFactory;
|
|
20
|
+
private readonly forwarder?;
|
|
21
|
+
constructor(emFactory: () => EntityManager, forwarder?: AnalyticsForwarder | undefined);
|
|
22
|
+
ingest(input: {
|
|
23
|
+
events: IngestAnalyticsEvent[];
|
|
24
|
+
requestId?: string;
|
|
25
|
+
}): Promise<IngestResult>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=analytics-ingest.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-ingest.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/analytics-ingest.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAA8B,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEnG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED,qBAAa,sBAAsB;IAE/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;gBADV,SAAS,EAAE,MAAM,aAAa,EAC9B,SAAS,CAAC,EAAE,kBAAkB,YAAA;IAG3C,MAAM,CAAC,KAAK,EAAE;QAClB,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,YAAY,CAAC;CAqC1B"}
|