Skip to content

Commit 15cdd41

Browse files
committed
refactor: migrate statistics endpoints to new chained API pattern
Migrates statistics (GET), statistics.list (GET), and statistics.telemetry (POST) from the legacy addRoute() pattern to the new chained .get()/.post() API pattern with typed AJV response schemas and query parameter validation using existing isStatisticsProps and isStatisticsListProps validators. Part of #38876
1 parent 3145c41 commit 15cdd41

File tree

2 files changed

+139
-38
lines changed

2 files changed

+139
-38
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': patch
3+
---
4+
5+
Migrated `statistics`, `statistics.list`, and `statistics.telemetry` REST API endpoints from legacy `addRoute` pattern to the new chained `.get()`/`.post()` API pattern with typed response schemas and AJV query parameter validation.
Lines changed: 134 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,158 @@
1+
import type { TelemetryEvents, TelemetryMap } from '@rocket.chat/core-services';
2+
import type { IStats } from '@rocket.chat/core-typings';
3+
import { ajv, validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings';
4+
15
import { getStatistics, getLastStatistics } from '../../../statistics/server';
26
import telemetryEvent from '../../../statistics/server/lib/telemetryEvents';
37
import { API } from '../api';
48
import { getPaginationItems } from '../helpers/getPaginationItems';
59

6-
API.v1.addRoute(
10+
API.v1.get(
711
'statistics',
8-
{ authRequired: true },
912
{
10-
async get() {
11-
const { refresh = 'false' } = this.queryParams;
12-
13-
return API.v1.success(
14-
await getLastStatistics({
15-
userId: this.userId,
16-
refresh: refresh === 'true',
17-
}),
18-
);
13+
authRequired: true,
14+
query: ajv.compile<{ refresh?: 'true' | 'false' }>({
15+
type: 'object',
16+
properties: {
17+
refresh: {
18+
type: 'string',
19+
nullable: true,
20+
},
21+
},
22+
required: [],
23+
additionalProperties: false,
24+
}),
25+
response: {
26+
200: ajv.compile<IStats>({
27+
type: 'object',
28+
properties: {
29+
success: {
30+
type: 'boolean',
31+
enum: [true],
32+
},
33+
},
34+
required: ['success'],
35+
}),
36+
401: validateUnauthorizedErrorResponse,
1937
},
2038
},
39+
async function action() {
40+
const { refresh = 'false' } = this.queryParams;
41+
42+
const stats = await getLastStatistics({
43+
userId: this.userId,
44+
refresh: refresh === 'true',
45+
});
46+
47+
if (!stats) {
48+
throw new Error('No statistics found');
49+
}
50+
51+
return API.v1.success(stats);
52+
},
2153
);
2254

23-
API.v1.addRoute(
55+
API.v1.get(
2456
'statistics.list',
25-
{ authRequired: true },
2657
{
27-
async get() {
28-
const { offset, count } = await getPaginationItems(this.queryParams);
29-
const { sort, fields, query } = await this.parseJsonQuery();
30-
31-
return API.v1.success(
32-
await getStatistics({
33-
userId: this.userId,
34-
query,
35-
pagination: {
36-
offset,
37-
count,
38-
sort,
39-
fields,
58+
authRequired: true,
59+
query: ajv.compile<{ fields?: string; count?: number; offset?: number; sort?: string; query?: string }>({
60+
type: 'object',
61+
properties: {
62+
fields: { type: 'string', nullable: true },
63+
count: { type: 'number', nullable: true },
64+
offset: { type: 'number', nullable: true },
65+
sort: { type: 'string', nullable: true },
66+
query: { type: 'string', nullable: true },
67+
},
68+
required: [],
69+
additionalProperties: false,
70+
}),
71+
response: {
72+
200: ajv.compile<{
73+
statistics: unknown[];
74+
count: number;
75+
offset: number;
76+
total: number;
77+
}>({
78+
type: 'object',
79+
properties: {
80+
statistics: { type: 'array' },
81+
count: { type: 'number' },
82+
offset: { type: 'number' },
83+
total: { type: 'number' },
84+
success: {
85+
type: 'boolean',
86+
enum: [true],
4087
},
41-
}),
42-
);
88+
},
89+
required: ['statistics', 'count', 'offset', 'total', 'success'],
90+
}),
91+
401: validateUnauthorizedErrorResponse,
4392
},
4493
},
94+
async function action() {
95+
const { offset, count } = await getPaginationItems(this.queryParams);
96+
const { sort, fields, query } = await this.parseJsonQuery();
97+
98+
return API.v1.success(
99+
await getStatistics({
100+
userId: this.userId,
101+
query,
102+
pagination: {
103+
offset,
104+
count,
105+
sort,
106+
fields,
107+
},
108+
}),
109+
);
110+
},
45111
);
46112

47-
API.v1.addRoute(
113+
API.v1.post(
48114
'statistics.telemetry',
49-
{ authRequired: true },
50115
{
51-
post() {
52-
const events = this.bodyParams;
116+
authRequired: true,
117+
body: ajv.compile<{ params: { eventName: string; [key: string]: unknown }[] }>({
118+
type: 'object',
119+
properties: {
120+
params: {
121+
type: 'array',
122+
items: {
123+
type: 'object',
124+
properties: {
125+
eventName: { type: 'string' },
126+
},
127+
required: ['eventName'],
128+
},
129+
},
130+
},
131+
required: ['params'],
132+
}),
133+
response: {
134+
200: ajv.compile<void>({
135+
type: 'object',
136+
properties: {
137+
success: {
138+
type: 'boolean',
139+
enum: [true],
140+
},
141+
},
142+
required: ['success'],
143+
additionalProperties: false,
144+
}),
145+
401: validateUnauthorizedErrorResponse,
146+
},
147+
},
148+
function action() {
149+
const events = this.bodyParams;
53150

54-
events?.params?.forEach((event) => {
55-
const { eventName, ...params } = event;
56-
void telemetryEvent.call(eventName, params);
57-
});
151+
events.params.forEach((event) => {
152+
const { eventName, ...params } = event;
153+
void telemetryEvent.call(eventName as TelemetryEvents, params as TelemetryMap[TelemetryEvents]);
154+
});
58155

59-
return API.v1.success();
60-
},
156+
return API.v1.success();
61157
},
62158
);

0 commit comments

Comments
 (0)