@the-situation/indexer 0.17.1 → 0.18.1
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/dist/aggregator/cohort.d.ts +30 -0
- package/dist/aggregator/cohort.d.ts.map +1 -0
- package/dist/aggregator/cohort.js +153 -0
- package/dist/aggregator/cohort.js.map +1 -0
- package/dist/aggregator/daily.d.ts +71 -0
- package/dist/aggregator/daily.d.ts.map +1 -0
- package/dist/aggregator/daily.js +249 -0
- package/dist/aggregator/daily.js.map +1 -0
- package/dist/aggregator/domains.d.ts +20 -0
- package/dist/aggregator/domains.d.ts.map +1 -0
- package/dist/aggregator/domains.js +38 -0
- package/dist/aggregator/domains.js.map +1 -0
- package/dist/aggregator/index.d.ts +31 -0
- package/dist/aggregator/index.d.ts.map +1 -0
- package/dist/aggregator/index.js +100 -0
- package/dist/aggregator/index.js.map +1 -0
- package/dist/aggregator/lifetime.d.ts +52 -0
- package/dist/aggregator/lifetime.d.ts.map +1 -0
- package/dist/aggregator/lifetime.js +222 -0
- package/dist/aggregator/lifetime.js.map +1 -0
- package/dist/aggregator/roi.d.ts +42 -0
- package/dist/aggregator/roi.d.ts.map +1 -0
- package/dist/aggregator/roi.js +153 -0
- package/dist/aggregator/roi.js.map +1 -0
- package/dist/aggregator/sources.d.ts +59 -0
- package/dist/aggregator/sources.d.ts.map +1 -0
- package/dist/aggregator/sources.js +53 -0
- package/dist/aggregator/sources.js.map +1 -0
- package/dist/aggregator/writers.d.ts +22 -0
- package/dist/aggregator/writers.d.ts.map +1 -0
- package/dist/aggregator/writers.js +147 -0
- package/dist/aggregator/writers.js.map +1 -0
- package/dist/api/app.d.ts +177 -13
- package/dist/api/app.d.ts.map +1 -1
- package/dist/api/app.js +4 -3
- package/dist/api/app.js.map +1 -1
- package/dist/api/routes/admin-subscriptions.d.ts +10 -10
- package/dist/api/routes/analytics.d.ts +205 -0
- package/dist/api/routes/analytics.d.ts.map +1 -0
- package/dist/api/routes/analytics.js +122 -0
- package/dist/api/routes/analytics.js.map +1 -0
- package/dist/api/routes/index.d.ts +1 -0
- package/dist/api/routes/index.d.ts.map +1 -1
- package/dist/api/routes/index.js +1 -0
- package/dist/api/routes/index.js.map +1 -1
- package/dist/api/routes/lp-history.d.ts +1 -1
- package/dist/api/routes/rankings.d.ts +17 -3
- package/dist/api/routes/rankings.d.ts.map +1 -1
- package/dist/api/routes/rankings.js +44 -5
- package/dist/api/routes/rankings.js.map +1 -1
- package/dist/api/routes/trader-stats.d.ts +11 -2
- package/dist/api/routes/trader-stats.d.ts.map +1 -1
- package/dist/api/routes/trader-stats.js +72 -2
- package/dist/api/routes/trader-stats.js.map +1 -1
- package/dist/client/IndexerClient.d.ts +30 -1
- package/dist/client/IndexerClient.d.ts.map +1 -1
- package/dist/client/IndexerClient.js.map +1 -1
- package/dist/client/IndexerClientLive.d.ts.map +1 -1
- package/dist/client/IndexerClientLive.js +50 -1
- package/dist/client/IndexerClientLive.js.map +1 -1
- package/dist/client/convenience.d.ts +9 -2
- package/dist/client/convenience.d.ts.map +1 -1
- package/dist/client/convenience.js +9 -1
- package/dist/client/convenience.js.map +1 -1
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/etl/event-indexer.d.ts +1 -1
- package/dist/etl/lp-position-refresher.d.ts +2 -1
- package/dist/etl/lp-position-refresher.d.ts.map +1 -1
- package/dist/etl/lp-position-refresher.js +24 -11
- package/dist/etl/lp-position-refresher.js.map +1 -1
- package/dist/etl/position-refresher.d.ts +1 -1
- package/dist/etl/state-refresher.d.ts +1 -1
- package/dist/index.js +54 -5
- package/dist/index.js.map +1 -1
- package/dist/layers/ChainReaderLive.d.ts.map +1 -1
- package/dist/layers/ChainReaderLive.js +15 -0
- package/dist/layers/ChainReaderLive.js.map +1 -1
- package/dist/services/ChainReader.d.ts +10 -0
- package/dist/services/ChainReader.d.ts.map +1 -1
- package/dist/services/ChainReader.js.map +1 -1
- package/dist/types/analytics.d.ts +194 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +10 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/warehouse/analytics-helpers.d.ts +36 -0
- package/dist/warehouse/analytics-helpers.d.ts.map +1 -0
- package/dist/warehouse/analytics-helpers.js +142 -0
- package/dist/warehouse/analytics-helpers.js.map +1 -0
- package/dist/warehouse/backfill.d.ts +26 -0
- package/dist/warehouse/backfill.d.ts.map +1 -0
- package/dist/warehouse/backfill.js +77 -0
- package/dist/warehouse/backfill.js.map +1 -0
- package/dist/warehouse/connection.d.ts +24 -0
- package/dist/warehouse/connection.d.ts.map +1 -0
- package/dist/warehouse/connection.js +30 -0
- package/dist/warehouse/connection.js.map +1 -0
- package/dist/warehouse/index.d.ts +14 -0
- package/dist/warehouse/index.d.ts.map +1 -0
- package/dist/warehouse/index.js +14 -0
- package/dist/warehouse/index.js.map +1 -0
- package/dist/warehouse/repositories/analytics.d.ts +61 -0
- package/dist/warehouse/repositories/analytics.d.ts.map +1 -0
- package/dist/warehouse/repositories/analytics.js +418 -0
- package/dist/warehouse/repositories/analytics.js.map +1 -0
- package/dist/warehouse/schema.d.ts +9 -0
- package/dist/warehouse/schema.d.ts.map +1 -0
- package/dist/warehouse/schema.js +219 -0
- package/dist/warehouse/schema.js.map +1 -0
- package/dist/warehouse/seed-domains.d.ts +29 -0
- package/dist/warehouse/seed-domains.d.ts.map +1 -0
- package/dist/warehouse/seed-domains.js +223 -0
- package/dist/warehouse/seed-domains.js.map +1 -0
- package/package.json +1 -1
|
@@ -28,10 +28,10 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
|
|
|
28
28
|
name: string;
|
|
29
29
|
webhook_url: string;
|
|
30
30
|
filters: {
|
|
31
|
-
|
|
32
|
-
eventTypes?: ("settlement" | "trade_move" | "market_created")[];
|
|
31
|
+
eventTypes?: ("trade_move" | "settlement" | "market_created")[];
|
|
33
32
|
marketTypes?: string[];
|
|
34
33
|
marketAddresses?: string[];
|
|
34
|
+
traders?: string[];
|
|
35
35
|
minCollateral?: number;
|
|
36
36
|
};
|
|
37
37
|
};
|
|
@@ -39,9 +39,9 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
|
|
|
39
39
|
query: unknown;
|
|
40
40
|
headers: unknown;
|
|
41
41
|
response: {
|
|
42
|
-
200: {
|
|
42
|
+
200: SubscriptionResponse | {
|
|
43
43
|
error: string;
|
|
44
|
-
}
|
|
44
|
+
};
|
|
45
45
|
422: {
|
|
46
46
|
type: "validation";
|
|
47
47
|
on: string;
|
|
@@ -83,9 +83,9 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
|
|
|
83
83
|
query: unknown;
|
|
84
84
|
headers: unknown;
|
|
85
85
|
response: {
|
|
86
|
-
200: {
|
|
86
|
+
200: SubscriptionResponse | {
|
|
87
87
|
error: string;
|
|
88
|
-
} |
|
|
88
|
+
} | {
|
|
89
89
|
error: string;
|
|
90
90
|
};
|
|
91
91
|
422: {
|
|
@@ -112,10 +112,10 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
|
|
|
112
112
|
webhook_url?: string;
|
|
113
113
|
secret?: string;
|
|
114
114
|
filters?: {
|
|
115
|
-
|
|
116
|
-
eventTypes?: ("settlement" | "trade_move" | "market_created")[];
|
|
115
|
+
eventTypes?: ("trade_move" | "settlement" | "market_created")[];
|
|
117
116
|
marketTypes?: string[];
|
|
118
117
|
marketAddresses?: string[];
|
|
118
|
+
traders?: string[];
|
|
119
119
|
minCollateral?: number;
|
|
120
120
|
};
|
|
121
121
|
is_active?: boolean;
|
|
@@ -126,9 +126,9 @@ export declare const adminSubscriptionRoutes: (db: DatabaseService, adminApiKey:
|
|
|
126
126
|
query: unknown;
|
|
127
127
|
headers: unknown;
|
|
128
128
|
response: {
|
|
129
|
-
200: {
|
|
129
|
+
200: SubscriptionResponse | {
|
|
130
130
|
error: string;
|
|
131
|
-
} |
|
|
131
|
+
} | {
|
|
132
132
|
error: string;
|
|
133
133
|
};
|
|
134
134
|
422: {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/analytics/* — analytics endpoints backed by the Postgres warehouse.
|
|
3
|
+
*
|
|
4
|
+
* Routes:
|
|
5
|
+
* GET /api/analytics/overview — platform-wide headline stats
|
|
6
|
+
* GET /api/analytics/domains — per-domain summary list
|
|
7
|
+
* GET /api/analytics/domains/:slug — domain detail (time-series + top-N)
|
|
8
|
+
* GET /api/analytics/cohorts — cohort retention curves
|
|
9
|
+
* GET /api/analytics/roi — ROI distribution + summary stats
|
|
10
|
+
* GET /api/analytics/leaderboard — leaderboard with ?domain= and time window
|
|
11
|
+
* GET /api/analytics/traders/:address — per-trader analytics (per-domain + cohort)
|
|
12
|
+
*
|
|
13
|
+
* These routes are only registered when WAREHOUSE_DATABASE_URL is set.
|
|
14
|
+
*
|
|
15
|
+
* Validation errors throw via parseAnalyticsFilters / parseEntityType — the
|
|
16
|
+
* shared error-handler maps Error → 400 (we tag it explicitly via `set.status`).
|
|
17
|
+
*/
|
|
18
|
+
import { Elysia } from 'elysia';
|
|
19
|
+
import type { AnalyticsRepository } from '../../warehouse/repositories/analytics';
|
|
20
|
+
/**
|
|
21
|
+
* Registers analytics routes. When `repo` is null (warehouse not configured),
|
|
22
|
+
* every analytics route returns 503 with a clear message so consumers can
|
|
23
|
+
* fall back gracefully.
|
|
24
|
+
*/
|
|
25
|
+
export declare const analyticsRoutes: (repo: AnalyticsRepository | null) => Elysia<"", {
|
|
26
|
+
decorator: {};
|
|
27
|
+
store: {};
|
|
28
|
+
derive: {};
|
|
29
|
+
resolve: {};
|
|
30
|
+
}, {
|
|
31
|
+
typebox: {};
|
|
32
|
+
error: {};
|
|
33
|
+
}, {
|
|
34
|
+
schema: {};
|
|
35
|
+
standaloneSchema: {};
|
|
36
|
+
macro: {};
|
|
37
|
+
macroFn: {};
|
|
38
|
+
parser: {};
|
|
39
|
+
response: {};
|
|
40
|
+
}, {
|
|
41
|
+
api: {
|
|
42
|
+
analytics: {
|
|
43
|
+
overview: {
|
|
44
|
+
get: {
|
|
45
|
+
body: unknown;
|
|
46
|
+
params: {};
|
|
47
|
+
query: unknown;
|
|
48
|
+
headers: unknown;
|
|
49
|
+
response: {
|
|
50
|
+
200: import("../../types").AnalyticsOverviewResponse | {
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
} & {
|
|
59
|
+
api: {
|
|
60
|
+
analytics: {
|
|
61
|
+
domains: {
|
|
62
|
+
get: {
|
|
63
|
+
body: unknown;
|
|
64
|
+
params: {};
|
|
65
|
+
query: unknown;
|
|
66
|
+
headers: unknown;
|
|
67
|
+
response: {
|
|
68
|
+
200: import("../../types").DomainsResponse | {
|
|
69
|
+
error: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
} & {
|
|
77
|
+
api: {
|
|
78
|
+
analytics: {
|
|
79
|
+
domains: {
|
|
80
|
+
":slug": {
|
|
81
|
+
get: {
|
|
82
|
+
body: unknown;
|
|
83
|
+
params: {
|
|
84
|
+
slug: string;
|
|
85
|
+
} & {};
|
|
86
|
+
query: unknown;
|
|
87
|
+
headers: unknown;
|
|
88
|
+
response: {
|
|
89
|
+
200: import("../../types").DomainDetailResponse | {
|
|
90
|
+
error: string;
|
|
91
|
+
};
|
|
92
|
+
422: {
|
|
93
|
+
type: "validation";
|
|
94
|
+
on: string;
|
|
95
|
+
summary?: string;
|
|
96
|
+
message?: string;
|
|
97
|
+
found?: unknown;
|
|
98
|
+
property?: string;
|
|
99
|
+
expected?: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
} & {
|
|
108
|
+
api: {
|
|
109
|
+
analytics: {
|
|
110
|
+
cohorts: {
|
|
111
|
+
get: {
|
|
112
|
+
body: unknown;
|
|
113
|
+
params: {};
|
|
114
|
+
query: unknown;
|
|
115
|
+
headers: unknown;
|
|
116
|
+
response: {
|
|
117
|
+
200: import("../../types").CohortsResponse | {
|
|
118
|
+
error: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
} & {
|
|
126
|
+
api: {
|
|
127
|
+
analytics: {
|
|
128
|
+
roi: {
|
|
129
|
+
get: {
|
|
130
|
+
body: unknown;
|
|
131
|
+
params: {};
|
|
132
|
+
query: unknown;
|
|
133
|
+
headers: unknown;
|
|
134
|
+
response: {
|
|
135
|
+
200: import("../../types").RoiResponse | {
|
|
136
|
+
error: string;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
} & {
|
|
144
|
+
api: {
|
|
145
|
+
analytics: {
|
|
146
|
+
leaderboard: {
|
|
147
|
+
get: {
|
|
148
|
+
body: unknown;
|
|
149
|
+
params: {};
|
|
150
|
+
query: unknown;
|
|
151
|
+
headers: unknown;
|
|
152
|
+
response: {
|
|
153
|
+
200: import("../../types").AnalyticsLeaderboardResponse | {
|
|
154
|
+
error: string;
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
} & {
|
|
162
|
+
api: {
|
|
163
|
+
analytics: {
|
|
164
|
+
traders: {
|
|
165
|
+
":address": {
|
|
166
|
+
get: {
|
|
167
|
+
body: unknown;
|
|
168
|
+
params: {
|
|
169
|
+
address: string;
|
|
170
|
+
} & {};
|
|
171
|
+
query: unknown;
|
|
172
|
+
headers: unknown;
|
|
173
|
+
response: {
|
|
174
|
+
200: import("../../types").TraderAnalyticsResponse | {
|
|
175
|
+
error: string;
|
|
176
|
+
};
|
|
177
|
+
422: {
|
|
178
|
+
type: "validation";
|
|
179
|
+
on: string;
|
|
180
|
+
summary?: string;
|
|
181
|
+
message?: string;
|
|
182
|
+
found?: unknown;
|
|
183
|
+
property?: string;
|
|
184
|
+
expected?: string;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
}, {
|
|
193
|
+
derive: {};
|
|
194
|
+
resolve: {};
|
|
195
|
+
schema: {};
|
|
196
|
+
standaloneSchema: {};
|
|
197
|
+
response: {};
|
|
198
|
+
}, {
|
|
199
|
+
derive: {};
|
|
200
|
+
resolve: {};
|
|
201
|
+
schema: {};
|
|
202
|
+
standaloneSchema: {};
|
|
203
|
+
response: {};
|
|
204
|
+
}>;
|
|
205
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/api/routes/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EACV,mBAAmB,EACpB,MAAM,wCAAwC,CAAC;AAahD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,mBAAmB,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuG1D,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/analytics/* — analytics endpoints backed by the Postgres warehouse.
|
|
3
|
+
*
|
|
4
|
+
* Routes:
|
|
5
|
+
* GET /api/analytics/overview — platform-wide headline stats
|
|
6
|
+
* GET /api/analytics/domains — per-domain summary list
|
|
7
|
+
* GET /api/analytics/domains/:slug — domain detail (time-series + top-N)
|
|
8
|
+
* GET /api/analytics/cohorts — cohort retention curves
|
|
9
|
+
* GET /api/analytics/roi — ROI distribution + summary stats
|
|
10
|
+
* GET /api/analytics/leaderboard — leaderboard with ?domain= and time window
|
|
11
|
+
* GET /api/analytics/traders/:address — per-trader analytics (per-domain + cohort)
|
|
12
|
+
*
|
|
13
|
+
* These routes are only registered when WAREHOUSE_DATABASE_URL is set.
|
|
14
|
+
*
|
|
15
|
+
* Validation errors throw via parseAnalyticsFilters / parseEntityType — the
|
|
16
|
+
* shared error-handler maps Error → 400 (we tag it explicitly via `set.status`).
|
|
17
|
+
*/
|
|
18
|
+
import { Elysia } from 'elysia';
|
|
19
|
+
import { parseAnalyticsFilters, parseEntityType, } from '../../warehouse/analytics-helpers';
|
|
20
|
+
const ADDRESS_RE = /^0x[0-9a-fA-F]+$/;
|
|
21
|
+
const SLUG_RE = /^[a-z][a-z0-9_-]*$/;
|
|
22
|
+
const NOT_CONFIGURED_BODY = {
|
|
23
|
+
error: 'Analytics warehouse is not configured (set WAREHOUSE_DATABASE_URL).',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Registers analytics routes. When `repo` is null (warehouse not configured),
|
|
27
|
+
* every analytics route returns 503 with a clear message so consumers can
|
|
28
|
+
* fall back gracefully.
|
|
29
|
+
*/
|
|
30
|
+
export const analyticsRoutes = (repo) => new Elysia()
|
|
31
|
+
.get('/api/analytics/overview', async ({ set }) => {
|
|
32
|
+
if (!repo) {
|
|
33
|
+
set.status = 503;
|
|
34
|
+
return NOT_CONFIGURED_BODY;
|
|
35
|
+
}
|
|
36
|
+
return repo.getOverview();
|
|
37
|
+
})
|
|
38
|
+
.get('/api/analytics/domains', async ({ set }) => {
|
|
39
|
+
if (!repo) {
|
|
40
|
+
set.status = 503;
|
|
41
|
+
return NOT_CONFIGURED_BODY;
|
|
42
|
+
}
|
|
43
|
+
return repo.getDomains();
|
|
44
|
+
})
|
|
45
|
+
.get('/api/analytics/domains/:slug', async ({ params, set }) => {
|
|
46
|
+
if (!repo) {
|
|
47
|
+
set.status = 503;
|
|
48
|
+
return NOT_CONFIGURED_BODY;
|
|
49
|
+
}
|
|
50
|
+
const slug = String(params.slug ?? '').toLowerCase();
|
|
51
|
+
if (!SLUG_RE.test(slug)) {
|
|
52
|
+
set.status = 400;
|
|
53
|
+
return { error: `Invalid domain slug: ${slug}` };
|
|
54
|
+
}
|
|
55
|
+
const detail = await repo.getDomainDetail(slug);
|
|
56
|
+
if (!detail) {
|
|
57
|
+
set.status = 404;
|
|
58
|
+
return { error: `Domain not found: ${slug}` };
|
|
59
|
+
}
|
|
60
|
+
return detail;
|
|
61
|
+
})
|
|
62
|
+
.get('/api/analytics/cohorts', async ({ query, set }) => {
|
|
63
|
+
if (!repo) {
|
|
64
|
+
set.status = 503;
|
|
65
|
+
return NOT_CONFIGURED_BODY;
|
|
66
|
+
}
|
|
67
|
+
const q = query;
|
|
68
|
+
try {
|
|
69
|
+
const filters = parseAnalyticsFilters({ domain: q.domain });
|
|
70
|
+
const entityType = parseEntityType(q.entityType ?? q.entity_type);
|
|
71
|
+
return await repo.getCohorts({ entityType, domain: filters.domain });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
set.status = 400;
|
|
75
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.get('/api/analytics/roi', async ({ query, set }) => {
|
|
79
|
+
if (!repo) {
|
|
80
|
+
set.status = 503;
|
|
81
|
+
return NOT_CONFIGURED_BODY;
|
|
82
|
+
}
|
|
83
|
+
const q = query;
|
|
84
|
+
try {
|
|
85
|
+
const filters = parseAnalyticsFilters({ domain: q.domain });
|
|
86
|
+
const entityType = parseEntityType(q.entityType ?? q.entity_type);
|
|
87
|
+
return await repo.getRoi({ entityType, domain: filters.domain });
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
set.status = 400;
|
|
91
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.get('/api/analytics/leaderboard', async ({ query, set }) => {
|
|
95
|
+
if (!repo) {
|
|
96
|
+
set.status = 503;
|
|
97
|
+
return NOT_CONFIGURED_BODY;
|
|
98
|
+
}
|
|
99
|
+
const q = query;
|
|
100
|
+
try {
|
|
101
|
+
const filters = parseAnalyticsFilters(q);
|
|
102
|
+
const limit = Math.min(Math.max(1, Number.parseInt(q.limit ?? '50', 10) || 50), 500);
|
|
103
|
+
return await repo.getLeaderboard({ ...filters, limit });
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
set.status = 400;
|
|
107
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.get('/api/analytics/traders/:address', async ({ params, set }) => {
|
|
111
|
+
if (!repo) {
|
|
112
|
+
set.status = 503;
|
|
113
|
+
return NOT_CONFIGURED_BODY;
|
|
114
|
+
}
|
|
115
|
+
const address = String(params.address ?? '');
|
|
116
|
+
if (!ADDRESS_RE.test(address)) {
|
|
117
|
+
set.status = 400;
|
|
118
|
+
return { error: `Invalid address: ${address}` };
|
|
119
|
+
}
|
|
120
|
+
return await repo.getTraderAnalytics(address);
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/api/routes/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,mCAAmC,CAAC;AAE3C,MAAM,UAAU,GAAG,kBAAkB,CAAC;AACtC,MAAM,OAAO,GAAG,oBAAoB,CAAC;AAErC,MAAM,mBAAmB,GAAG;IAC1B,KAAK,EAAE,qEAAqE;CAC7E,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAgC,EAAE,EAAE,CAClE,IAAI,MAAM,EAAE;KACT,GAAG,CAAC,yBAAyB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;IAChD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC,CAAC;KAED,GAAG,CAAC,wBAAwB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;IAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;AAC3B,CAAC,CAAC;KAED,GAAG,CAAC,8BAA8B,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE;IAC7D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACrD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,wBAAwB,IAAI,EAAE,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,qBAAqB,IAAI,EAAE,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;KAED,GAAG,CAAC,wBAAwB,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IACtD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,MAAM,CAAC,GAAG,KAAuE,CAAC;IAClF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC;QAClE,OAAO,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,CAAC;AACH,CAAC,CAAC;KAED,GAAG,CAAC,oBAAoB,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IAClD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,MAAM,CAAC,GAAG,KAAuE,CAAC;IAClF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC;QAClE,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,CAAC;AACH,CAAC,CAAC;KAED,GAAG,CAAC,4BAA4B,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IAC1D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,MAAM,CAAC,GAAG,KAKT,CAAC;IACF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EACvD,GAAG,CACJ,CAAC;QACF,OAAO,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,CAAC;AACH,CAAC,CAAC;KAED,GAAG,CAAC,iCAAiC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE;IAChE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,oBAAoB,OAAO,EAAE,EAAE,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
|
package/dist/api/routes/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC"}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GET /api/rankings —
|
|
2
|
+
* GET /api/rankings — leaderboard with optional ?domain=&from=&to= filters.
|
|
3
|
+
*
|
|
4
|
+
* Filter behaviour:
|
|
5
|
+
* - No filters → SQLite-backed lifetime leaderboard (current behaviour).
|
|
6
|
+
* - domain=<slug>, from=, to= → routed to the warehouse-backed analytics
|
|
7
|
+
* leaderboard, which knows how to slice by domain & time-window. Response
|
|
8
|
+
* shape stays compatible (RankingEntry[]) so existing webapp consumers
|
|
9
|
+
* don't break.
|
|
10
|
+
*
|
|
11
|
+
* If filters are passed but the warehouse is not configured, we 503 — the
|
|
12
|
+
* filtered view requires the warehouse to be running.
|
|
3
13
|
*/
|
|
4
14
|
import { Elysia } from 'elysia';
|
|
5
15
|
import type { DatabaseService } from '../../services/Database';
|
|
6
|
-
|
|
16
|
+
import type { RankingEntry } from '../../types/api';
|
|
17
|
+
import type { AnalyticsRepository } from '../../warehouse/repositories/analytics';
|
|
18
|
+
export declare const rankingRoutes: (db: DatabaseService, analytics: AnalyticsRepository | null) => Elysia<"", {
|
|
7
19
|
decorator: {};
|
|
8
20
|
store: {};
|
|
9
21
|
derive: {};
|
|
@@ -27,7 +39,9 @@ export declare const rankingRoutes: (db: DatabaseService) => Elysia<"", {
|
|
|
27
39
|
query: unknown;
|
|
28
40
|
headers: unknown;
|
|
29
41
|
response: {
|
|
30
|
-
200:
|
|
42
|
+
200: RankingEntry[] | {
|
|
43
|
+
error: string;
|
|
44
|
+
};
|
|
31
45
|
};
|
|
32
46
|
};
|
|
33
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rankings.d.ts","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rankings.d.ts","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAGlF,eAAO,MAAM,aAAa,GACxB,IAAI,eAAe,EACnB,WAAW,mBAAmB,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CnC,CAAC"}
|
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GET /api/rankings —
|
|
2
|
+
* GET /api/rankings — leaderboard with optional ?domain=&from=&to= filters.
|
|
3
|
+
*
|
|
4
|
+
* Filter behaviour:
|
|
5
|
+
* - No filters → SQLite-backed lifetime leaderboard (current behaviour).
|
|
6
|
+
* - domain=<slug>, from=, to= → routed to the warehouse-backed analytics
|
|
7
|
+
* leaderboard, which knows how to slice by domain & time-window. Response
|
|
8
|
+
* shape stays compatible (RankingEntry[]) so existing webapp consumers
|
|
9
|
+
* don't break.
|
|
10
|
+
*
|
|
11
|
+
* If filters are passed but the warehouse is not configured, we 503 — the
|
|
12
|
+
* filtered view requires the warehouse to be running.
|
|
3
13
|
*/
|
|
4
14
|
import { Elysia } from 'elysia';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
15
|
+
import { parseAnalyticsFilters } from '../../warehouse/analytics-helpers';
|
|
16
|
+
export const rankingRoutes = (db, analytics) => new Elysia().get('/api/rankings', async ({ query, set }) => {
|
|
17
|
+
const q = query;
|
|
18
|
+
const limit = Math.min(Math.max(1, parseInt(q.limit ?? '50', 10) || 50), 200);
|
|
19
|
+
const wantsFiltered = (q.domain != null && q.domain !== '' && q.domain !== 'all') ||
|
|
20
|
+
q.from != null ||
|
|
21
|
+
q.to != null;
|
|
22
|
+
if (!wantsFiltered) {
|
|
23
|
+
return db.rankings.getLeaderboard(limit);
|
|
24
|
+
}
|
|
25
|
+
if (!analytics) {
|
|
26
|
+
set.status = 503;
|
|
27
|
+
return {
|
|
28
|
+
error: 'Filtered rankings require the analytics warehouse, which is not configured.',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
let filters;
|
|
32
|
+
try {
|
|
33
|
+
filters = parseAnalyticsFilters(q);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
set.status = 400;
|
|
37
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
38
|
+
}
|
|
39
|
+
const result = await analytics.getLeaderboard({ ...filters, limit });
|
|
40
|
+
// Backwards-compatible shape — webapp's existing useLeaderboard consumes RankingEntry[].
|
|
41
|
+
const compat = result.entries.map((e) => ({
|
|
42
|
+
trader: e.trader,
|
|
43
|
+
totalPnl: e.totalPnl,
|
|
44
|
+
marketsTraded: e.marketsTraded,
|
|
45
|
+
totalTrades: e.totalTrades,
|
|
46
|
+
}));
|
|
47
|
+
return compat;
|
|
9
48
|
});
|
|
10
49
|
//# sourceMappingURL=rankings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rankings.js","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rankings.js","sourceRoot":"","sources":["../../../src/api/routes/rankings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,EAAmB,EACnB,SAAqC,EACrC,EAAE,CACF,IAAI,MAAM,EAAE,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;IACzD,MAAM,CAAC,GAAG,KAKT,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAE9E,MAAM,aAAa,GACjB,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC;QAC3D,CAAC,CAAC,IAAI,IAAI,IAAI;QACd,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC;IAEf,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO;YACL,KAAK,EAAE,6EAA6E;SACrF,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,yFAAyF;IACzF,MAAM,MAAM,GAAmB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,WAAW,EAAE,CAAC,CAAC,WAAW;KAC3B,CAAC,CAAC,CAAC;IACJ,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC,CAAC"}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GET /api/positions/:trader/stats — aggregated trader statistics.
|
|
3
|
+
*
|
|
4
|
+
* Optional ?domain=<slug> routes the request to the warehouse-backed analytics
|
|
5
|
+
* trader endpoint and returns a domain-filtered subset of the same fields.
|
|
6
|
+
* Time-window filters (?from=, ?to=) are accepted but currently apply only to
|
|
7
|
+
* the warehouse path — they sum daily fact rows in the window.
|
|
3
8
|
*/
|
|
4
9
|
import { Elysia } from 'elysia';
|
|
5
10
|
import type { DatabaseService } from '../../services/Database';
|
|
6
|
-
|
|
11
|
+
import type { TraderStatsResponse } from '../../types/api';
|
|
12
|
+
import type { AnalyticsRepository } from '../../warehouse/repositories/analytics';
|
|
13
|
+
export declare const traderStatsRoutes: (db: DatabaseService, analytics: AnalyticsRepository | null) => Elysia<"", {
|
|
7
14
|
decorator: {};
|
|
8
15
|
store: {};
|
|
9
16
|
derive: {};
|
|
@@ -31,7 +38,9 @@ export declare const traderStatsRoutes: (db: DatabaseService) => Elysia<"", {
|
|
|
31
38
|
query: unknown;
|
|
32
39
|
headers: unknown;
|
|
33
40
|
response: {
|
|
34
|
-
200:
|
|
41
|
+
200: TraderStatsResponse | {
|
|
42
|
+
error: string;
|
|
43
|
+
};
|
|
35
44
|
422: {
|
|
36
45
|
type: "validation";
|
|
37
46
|
on: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trader-stats.d.ts","sourceRoot":"","sources":["../../../src/api/routes/trader-stats.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"trader-stats.d.ts","sourceRoot":"","sources":["../../../src/api/routes/trader-stats.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAGlF,eAAO,MAAM,iBAAiB,GAC5B,IAAI,eAAe,EACnB,WAAW,mBAAmB,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0EnC,CAAC"}
|
|
@@ -1,8 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GET /api/positions/:trader/stats — aggregated trader statistics.
|
|
3
|
+
*
|
|
4
|
+
* Optional ?domain=<slug> routes the request to the warehouse-backed analytics
|
|
5
|
+
* trader endpoint and returns a domain-filtered subset of the same fields.
|
|
6
|
+
* Time-window filters (?from=, ?to=) are accepted but currently apply only to
|
|
7
|
+
* the warehouse path — they sum daily fact rows in the window.
|
|
3
8
|
*/
|
|
4
9
|
import { Elysia } from 'elysia';
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
import { parseAnalyticsFilters } from '../../warehouse/analytics-helpers';
|
|
11
|
+
export const traderStatsRoutes = (db, analytics) => new Elysia().get('/api/positions/:trader/stats', async ({ params, query, set }) => {
|
|
12
|
+
const q = query;
|
|
13
|
+
const wantsFiltered = (q.domain != null && q.domain !== '' && q.domain !== 'all') ||
|
|
14
|
+
q.from != null ||
|
|
15
|
+
q.to != null;
|
|
16
|
+
if (!wantsFiltered) {
|
|
17
|
+
return db.rankings.getTraderStats(params.trader);
|
|
18
|
+
}
|
|
19
|
+
if (!analytics) {
|
|
20
|
+
set.status = 503;
|
|
21
|
+
return {
|
|
22
|
+
error: 'Filtered trader stats require the analytics warehouse, which is not configured.',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
let filters;
|
|
26
|
+
try {
|
|
27
|
+
filters = parseAnalyticsFilters(q);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
set.status = 400;
|
|
31
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
32
|
+
}
|
|
33
|
+
const traderAnalytics = await analytics.getTraderAnalytics(params.trader);
|
|
34
|
+
if (!traderAnalytics.lifetime) {
|
|
35
|
+
// Trader has never traded — return zero stats consistent with TraderStatsResponse shape.
|
|
36
|
+
const empty = {
|
|
37
|
+
trader: params.trader,
|
|
38
|
+
totalPnl: 0,
|
|
39
|
+
unrealizedPnl: 0,
|
|
40
|
+
biggestWin: 0,
|
|
41
|
+
totalTrades: 0,
|
|
42
|
+
marketsTraded: 0,
|
|
43
|
+
firstTradeAt: null,
|
|
44
|
+
lastTradeAt: null,
|
|
45
|
+
};
|
|
46
|
+
return empty;
|
|
47
|
+
}
|
|
48
|
+
if (filters.domain !== 'all') {
|
|
49
|
+
// Domain-filtered slice — read from perDomain.
|
|
50
|
+
const domainSlice = traderAnalytics.perDomain.find((d) => d.slug === filters.domain);
|
|
51
|
+
const stats = {
|
|
52
|
+
trader: params.trader,
|
|
53
|
+
// P&L is not currently tracked per-domain; expose lifetime totals so the
|
|
54
|
+
// client can show "domain stats + portfolio P&L". This is honest about
|
|
55
|
+
// current capability — no fake per-domain P&L.
|
|
56
|
+
totalPnl: traderAnalytics.lifetime.totalPnl,
|
|
57
|
+
unrealizedPnl: traderAnalytics.lifetime.unrealizedPnl,
|
|
58
|
+
biggestWin: 0,
|
|
59
|
+
totalTrades: domainSlice?.tradeCount ?? 0,
|
|
60
|
+
marketsTraded: domainSlice?.marketsTraded ?? 0,
|
|
61
|
+
firstTradeAt: traderAnalytics.lifetime.firstTradeAt,
|
|
62
|
+
lastTradeAt: traderAnalytics.lifetime.lastTradeAt,
|
|
63
|
+
};
|
|
64
|
+
return stats;
|
|
65
|
+
}
|
|
66
|
+
const stats = {
|
|
67
|
+
trader: params.trader,
|
|
68
|
+
totalPnl: traderAnalytics.lifetime.totalPnl,
|
|
69
|
+
unrealizedPnl: traderAnalytics.lifetime.unrealizedPnl,
|
|
70
|
+
biggestWin: 0,
|
|
71
|
+
totalTrades: traderAnalytics.lifetime.totalTrades,
|
|
72
|
+
marketsTraded: traderAnalytics.lifetime.marketsTraded,
|
|
73
|
+
firstTradeAt: traderAnalytics.lifetime.firstTradeAt,
|
|
74
|
+
lastTradeAt: traderAnalytics.lifetime.lastTradeAt,
|
|
75
|
+
};
|
|
76
|
+
return stats;
|
|
7
77
|
});
|
|
8
78
|
//# sourceMappingURL=trader-stats.js.map
|