Skip to content
This repository was archived by the owner on Apr 6, 2026. It is now read-only.

Commit 24f57d1

Browse files
committed
feat: add pagination to errors
1 parent 7097f13 commit 24f57d1

11 files changed

Lines changed: 210 additions & 123 deletions

File tree

src/classes/db/Query.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ import { QueryInput, QuerySchema } from '../../schemas/db/QuerySchema.js';
33
export class Query {
44
public readonly offset: number;
55
public readonly limit: number;
6-
public readonly sort?: {
7-
field: string;
8-
order: 'asc' | 'desc';
9-
};
10-
public readonly filter?: {
11-
field: string;
12-
comparator: 'equal' | 'not_equal' | 'greater_than' | 'less_than' | 'contains' | 'starts_with' | 'ends_with';
13-
value: any;
14-
};
6+
public readonly sort?: string;
7+
public readonly orderBy?: 'ASC' | 'DESC';
8+
public readonly filterField?: string;
9+
public readonly filterComparator?:
10+
| 'equal'
11+
| 'not_equal'
12+
| 'greater_than'
13+
| 'less_than'
14+
| 'contains'
15+
| 'starts_with'
16+
| 'ends_with';
17+
public readonly filterValue?: unknown;
1518

1619
private constructor(data: QueryInput) {
1720
const parsed = QuerySchema.parse(data);
1821
this.offset = parsed.offset;
1922
this.limit = parsed.limit;
20-
this.sort = parsed.sort ? { field: parsed.sort.field, order: parsed.sort.order } : undefined;
21-
this.filter = parsed.filter
22-
? {
23-
field: parsed.filter.field,
24-
comparator: parsed.filter.comparator,
25-
value: parsed.filter.value,
26-
}
27-
: undefined;
23+
this.sort = parsed.sort;
24+
this.orderBy = parsed.orderBy;
25+
this.filterField = parsed.filterField;
26+
this.filterComparator = parsed.filterComparator;
27+
this.filterValue = parsed.filterValue;
2828
}
2929

3030
public static from(data: unknown): Query {

src/classes/gmod/GmodErrors.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { GmodErrorsInput, GmodErrorsSchema } from '../../schemas/gmod/GmodErrorsSchema.js';
2+
import { mongoClient } from '../../services/mongo/index.js';
3+
import { Query } from '../db/Query.js';
4+
5+
const db = mongoClient.db('gmod_integration');
6+
const collection = db.collection('errors');
7+
8+
export class GmodErrors {
9+
public readonly serverID: string;
10+
public readonly count: number;
11+
public readonly realm: string;
12+
public readonly error: string;
13+
public readonly stack: string;
14+
public readonly name: string;
15+
public readonly steamID64: string;
16+
public readonly workshopID: string;
17+
public readonly uptime: number;
18+
19+
private constructor(data: GmodErrorsInput) {
20+
const parsed = GmodErrorsSchema.parse(data);
21+
this.serverID = parsed.serverID;
22+
this.count = parsed.count;
23+
this.realm = parsed.realm;
24+
this.error = parsed.error;
25+
this.stack = parsed.stack;
26+
this.name = parsed.name || '';
27+
this.steamID64 = parsed.steamID64 || '';
28+
this.workshopID = parsed.workshopID || '';
29+
this.uptime = parsed.uptime;
30+
}
31+
32+
public static from(data: unknown): GmodErrors {
33+
return new GmodErrors(data as GmodErrorsInput);
34+
}
35+
36+
public async save() {
37+
return await collection.insertOne({
38+
error: this.error,
39+
stack: this.stack,
40+
workshopID: this.workshopID || '',
41+
serverID: this.serverID,
42+
name: this.name,
43+
realm: this.realm,
44+
steamID64: this.steamID64 || '',
45+
uptime: this.uptime,
46+
count: this.count,
47+
createdAt: new Date(),
48+
updatedAt: new Date(),
49+
});
50+
}
51+
}
52+
53+
export async function getErrorsCountByServer(serverID: string): Promise<number> {
54+
return await collection.countDocuments({
55+
serverID,
56+
});
57+
}
58+
59+
export async function getErrorsByServer(query: Query, serverID: string) {
60+
return {
61+
errors: await collection
62+
.find({
63+
serverID,
64+
})
65+
.limit(query.limit)
66+
.skip(query.offset)
67+
.toArray(),
68+
query: {
69+
...query,
70+
total: await collection.countDocuments({
71+
serverID,
72+
}),
73+
},
74+
};
75+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Request, Response } from 'express';
2+
import { GmodErrors } from '../../classes/gmod/GmodErrors.js';
3+
4+
export async function reportError(req: Request, res: Response) {
5+
const { error, stack, id, name, realm, uptime, count } = req.body;
6+
const { serverID, steamID64 } = req.params;
7+
8+
let plyError: GmodErrors;
9+
try {
10+
plyError = GmodErrors.from({
11+
error,
12+
stack: JSON.stringify(stack),
13+
workshopID: id,
14+
name,
15+
realm,
16+
uptime,
17+
count,
18+
serverID,
19+
steamID64,
20+
});
21+
} catch (err) {
22+
return res.status(400).json({ error: 'Invalid error data' });
23+
}
24+
25+
const rtnError = await plyError.save();
26+
return res.status(200).json(rtnError);
27+
}

src/controllers/v3/errorsControllers.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/controllers/v3/usersControllers.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,31 +1186,6 @@ export async function putServerSetting(req: Request, res: Response) {
11861186
}
11871187
}
11881188

1189-
export async function getServerErrors(req: Request, res: Response) {
1190-
const { serverID } = req.params;
1191-
const { offset, limit: number } = req.query;
1192-
const limit = Number(number);
1193-
1194-
if (limit && limit > 100) {
1195-
return res.status(400).send({
1196-
error: 'limit too high',
1197-
});
1198-
}
1199-
1200-
const errors = await prisma.gm_server_errors.findMany({
1201-
where: {
1202-
serverID,
1203-
},
1204-
orderBy: {
1205-
createdAt: 'desc',
1206-
},
1207-
skip: Number(offset) || 0,
1208-
take: limit || 500,
1209-
});
1210-
1211-
return res.send(errors || []);
1212-
}
1213-
12141189
export async function getAdminGuilds(req: Request, res: Response) {
12151190
return res.send((await prisma.gm_guild.findMany()) || []);
12161191
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Request, Response } from 'express';
2+
import { Query } from '../../classes/db/Query.js';
3+
import { getErrorsByServer } from '../../classes/gmod/GmodErrors.js';
4+
5+
export async function getServerErrors(req: Request, res: Response) {
6+
const { serverID } = req.params;
7+
8+
let query: Query;
9+
try {
10+
query = Query.from(req.query);
11+
} catch (err) {
12+
console.error('Invalid query parameters:', err);
13+
return res.status(400).json({ error: 'Invalid query parameters' });
14+
}
15+
16+
const errors = await getErrorsByServer(query, serverID);
17+
return res.send(errors || []);
18+
}

src/routes/v3/clientsRoutes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express from 'express';
22
import { reportBugs, uploadScreenshot } from '../../controllers/v3/clientsControllers.js';
3-
import { reportError } from '../../controllers/v3/errorsControllers.js';
3+
import { reportError } from '../../controllers/gmod/GmodErrorsControllers.js';
44
import asyncHandler from '../../middleware/asyncHandler.js';
55
import clientValidator from '../../middleware/v3/clientValidator.js';
66

src/routes/v3/serversRoutes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
serverStart,
1818
serverStop,
1919
} from '../../controllers/v3/serversControllers.js';
20-
import { reportError } from '../../controllers/v3/errorsControllers.js';
20+
import { reportError } from '../../controllers/gmod/GmodErrorsControllers.js';
2121
import {
2222
getPlayer,
2323
playerBan,

src/routes/v3/usersRoutes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import {
4848
getProfile,
4949
getPublicServers,
5050
getScreenshotsList,
51-
getServerErrors,
5251
getServerLogs,
5352
getServerLogsTrigger,
5453
getServerPlayers,
@@ -111,6 +110,7 @@ import {
111110
} from '../../middleware/v3/userValidator.js';
112111
import asyncHandler from '../../middleware/asyncHandler.js';
113112
import { getAllPanelUsers } from '../../controllers/v3/usersAdminControllers.js';
113+
import { getServerErrors } from '../../controllers/website/WebsiteErrorsControllers.js';
114114

115115
const router = express.Router();
116116

src/schemas/db/QuerySchema.ts

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,37 @@ extendZodWithOpenApi(z);
55

66
export const QuerySchema = z
77
.object({
8-
offset: z.number().openapi({
8+
offset: z.coerce.number().int().nonnegative().default(0).openapi({
99
example: 0,
1010
description: 'Offset for pagination',
1111
}),
12-
limit: z.number().max(500).openapi({
13-
example: 10,
12+
limit: z.coerce.number().int().nonnegative().max(500).default(25).openapi({
13+
example: 25,
1414
description: 'Limit for pagination',
1515
}),
16-
sort: z
17-
.object({
18-
field: z.string().openapi({
19-
example: 'name',
20-
description: 'Field to sort by',
21-
}),
22-
order: z.enum(['asc', 'desc']).openapi({
23-
example: 'asc',
24-
description: 'Order of sorting',
25-
}),
26-
})
27-
.optional()
28-
.openapi({
29-
description: 'Sorting options',
30-
}),
31-
filter: z
32-
.object({
33-
field: z.string().openapi({
34-
example: 'status',
35-
description: 'Field to filter by',
36-
}),
37-
comparator: z
38-
.enum(['equal', 'not_equal', 'greater_than', 'less_than', 'contains', 'starts_with', 'ends_with'])
39-
.openapi({
40-
example: 'equal',
41-
description: 'Comparator for filtering',
42-
}),
43-
value: z.any().openapi({
44-
example: 'active',
45-
description: 'Value to filter by',
46-
}),
47-
})
16+
sort: z.string().optional().openapi({
17+
example: 'createdAt',
18+
description: 'Field to sort by, e.g., "createdAt" or "name"',
19+
}),
20+
orderBy: z.enum(['ASC', 'DESC']).optional().openapi({
21+
example: 'ASC',
22+
description: 'Order of sorting, either "ASC" or "DESC"',
23+
}),
24+
filterField: z.string().optional().openapi({
25+
example: 'status',
26+
description: 'Field to filter by, e.g., "status" or "type"',
27+
}),
28+
filterComparator: z
29+
.enum(['equal', 'not_equal', 'greater_than', 'less_than', 'contains', 'starts_with', 'ends_with'])
4830
.optional()
4931
.openapi({
50-
description: 'Filtering options',
32+
example: 'equal',
33+
description: 'Comparator for filtering, e.g., "equal" or "contains"',
5134
}),
35+
filterValue: z.any().optional().openapi({
36+
example: 'active',
37+
description: 'Value to filter by, e.g., "active" or "error"',
38+
}),
5239
})
5340
.openapi({ ref: 'Query' });
5441

0 commit comments

Comments
 (0)