@tmlmobilidade/controllers 20260128.2248.28

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/README.md ADDED
@@ -0,0 +1,3 @@
1
+ ## @tmlmobilidade/controllers
2
+
3
+ These are shared controllers to retrieve and prepare common data across apps.
@@ -0,0 +1,20 @@
1
+ import { type FastifyReply, type FastifyRequest } from '@tmlmobilidade/fastify';
2
+ import { type Agency } from '@tmlmobilidade/types';
3
+ export declare class AgenciesSharedController {
4
+ /**
5
+ * Returns all Agencies sorted by ID.
6
+ * @param request The request object
7
+ * @param reply The reply object
8
+ */
9
+ static getAll(request: FastifyRequest, reply: FastifyReply<Agency[]>): Promise<void>;
10
+ /**
11
+ * Returns an Agency by ID.
12
+ * @param request The request object
13
+ * @param reply The reply object
14
+ */
15
+ static getById(request: FastifyRequest<{
16
+ Params: {
17
+ id: string;
18
+ };
19
+ }>, reply: FastifyReply<Agency>): Promise<void>;
20
+ }
@@ -0,0 +1,27 @@
1
+ /* * */
2
+ import { HttpException, HttpStatus } from '@tmlmobilidade/consts';
3
+ import { agencies } from '@tmlmobilidade/interfaces';
4
+ /* * */
5
+ export class AgenciesSharedController {
6
+ //
7
+ /**
8
+ * Returns all Agencies sorted by ID.
9
+ * @param request The request object
10
+ * @param reply The reply object
11
+ */
12
+ static async getAll(request, reply) {
13
+ const allAgencies = await agencies.findMany({}, { sort: { _id: 1 } });
14
+ reply.send({ data: allAgencies, error: null, statusCode: HttpStatus.OK });
15
+ }
16
+ /**
17
+ * Returns an Agency by ID.
18
+ * @param request The request object
19
+ * @param reply The reply object
20
+ */
21
+ static async getById(request, reply) {
22
+ const agencyData = await agencies.findById(request.params.id);
23
+ if (!agencyData)
24
+ throw new HttpException(HttpStatus.NOT_FOUND, 'Agency not found');
25
+ reply.send({ data: agencyData, error: null, statusCode: HttpStatus.OK });
26
+ }
27
+ }
@@ -0,0 +1 @@
1
+ export * from './agencies.js';
@@ -0,0 +1 @@
1
+ export * from './agencies.js';
@@ -0,0 +1,2 @@
1
+ export * from './agencies/index.js';
2
+ export * from './rides/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './agencies/index.js';
2
+ export * from './rides/index.js';
@@ -0,0 +1 @@
1
+ export * from './rides.js';
@@ -0,0 +1 @@
1
+ export * from './rides.js';
@@ -0,0 +1,18 @@
1
+ import { type FastifyReply, type FastifyRequest } from '@tmlmobilidade/fastify';
2
+ import { type ActionsOf, type GetRidesBatchQuery, type Permission, type RideNormalized } from '@tmlmobilidade/types';
3
+ export declare class RidesSharedController {
4
+ /**
5
+ * Gets a batch of Rides built with an aggregation pipeline.
6
+ * @param request The Fastify request object.
7
+ * @param reply The Fastify reply object.
8
+ */
9
+ static getBatch<S extends Permission['scope']>(request: FastifyRequest<{
10
+ Querystring: GetRidesBatchQuery;
11
+ }>, reply: FastifyReply<RideNormalized[]>, scope: S, action: ActionsOf<S>): Promise<never>;
12
+ /**
13
+ * Get a Ride by ID.
14
+ * @param request The Fastify request object.
15
+ * @param reply The Fastify reply object.
16
+ */
17
+ static getRideById<S extends Permission['scope']>(request: FastifyRequest, reply: FastifyReply<RideNormalized>, scope: S, action: ActionsOf<S>): Promise<never>;
18
+ }
@@ -0,0 +1,98 @@
1
+ /* * */
2
+ import { HttpStatus } from '@tmlmobilidade/consts';
3
+ import { rides, ridesBatchAggregationPipeline } from '@tmlmobilidade/interfaces';
4
+ import { normalizeRide } from '@tmlmobilidade/normalizers';
5
+ import { GetRidesBatchQuerySchema, PermissionCatalog } from '@tmlmobilidade/types';
6
+ /* * */
7
+ export class RidesSharedController {
8
+ //
9
+ /**
10
+ * Gets a batch of Rides built with an aggregation pipeline.
11
+ * @param request The Fastify request object.
12
+ * @param reply The Fastify reply object.
13
+ */
14
+ static async getBatch(request, reply, scope, action) {
15
+ //
16
+ //
17
+ // Validate the request query parameters
18
+ const parsedQuery = GetRidesBatchQuerySchema.parse(request.query);
19
+ console.log('RidesSharedController.getBatch - parsedQuery:', parsedQuery);
20
+ //
21
+ // Detect which agency_ids the user has access to,
22
+ // based on their permissions. If none, return an empty array.
23
+ const ridesPermission = PermissionCatalog.get(request.permissions, scope, action);
24
+ if (!ridesPermission['resources']?.agency_ids?.length)
25
+ return reply.send({ data: [], error: null, statusCode: HttpStatus.OK });
26
+ const allowAllAgencies = ridesPermission['resources'].agency_ids.includes(PermissionCatalog.ALLOW_ALL_FLAG);
27
+ //
28
+ // If search is provided, immediately try to find the ride by ID.
29
+ // If found, return it as the only result. This optimizes
30
+ // for the common case of searching by ride ID.
31
+ const searchQuery = parsedQuery.search?.trim() ?? '';
32
+ const foundRideById = await rides.findOne({
33
+ _id: searchQuery,
34
+ ...(allowAllAgencies ? {} : { agency_id: { $in: ridesPermission['resources'].agency_ids } }),
35
+ });
36
+ if (foundRideById) {
37
+ const normalizedRide = normalizeRide(foundRideById);
38
+ return reply.send({ data: [normalizedRide], error: null, statusCode: HttpStatus.OK });
39
+ }
40
+ //
41
+ // Get the rides batch using native MongoDB cursor
42
+ // with batchSize to prevent memory issues
43
+ const pipeline = ridesBatchAggregationPipeline({
44
+ agency_ids: parsedQuery.agency_ids?.filter(id => allowAllAgencies || ridesPermission['resources'].agency_ids.includes(id)) ?? [],
45
+ date_end: parsedQuery.date_end,
46
+ date_start: parsedQuery.date_start,
47
+ delay_statuses: parsedQuery.delay_statuses,
48
+ line_ids: parsedQuery.line_ids,
49
+ operational_statuses: parsedQuery.operational_statuses,
50
+ search: parsedQuery.search,
51
+ seen_statuses: parsedQuery.seen_statuses,
52
+ stop_ids: parsedQuery.stop_ids,
53
+ });
54
+ //
55
+ // Limit the number of rides to 2000 and sort by start_time_scheduled
56
+ pipeline.push({ $limit: 2000 }, { $sort: { start_time_scheduled: 1 } });
57
+ //
58
+ // Fetch the rides batch from the database
59
+ const ridesBatch = await rides.aggregate(pipeline);
60
+ console.log('RidesSharedController.getBatch - ridesBatch count:', ridesBatch?.length ?? 0);
61
+ //
62
+ // Send the response
63
+ reply.send({
64
+ data: ridesBatch ?? [],
65
+ error: null,
66
+ statusCode: HttpStatus.OK,
67
+ });
68
+ //
69
+ }
70
+ /**
71
+ * Get a Ride by ID.
72
+ * @param request The Fastify request object.
73
+ * @param reply The Fastify reply object.
74
+ */
75
+ static async getRideById(request, reply, scope, action) {
76
+ //
77
+ //
78
+ // Detect which agency_ids the user has access to,
79
+ // based on their permissions. If none, return an empty array.
80
+ const ridesPermission = PermissionCatalog.get(request.permissions, scope, action);
81
+ if (!ridesPermission['resources']?.agency_ids?.length)
82
+ return reply.send({ data: null, error: null, statusCode: HttpStatus.OK });
83
+ const allowAllAgencies = ridesPermission['resources'].agency_ids.includes(PermissionCatalog.ALLOW_ALL_FLAG);
84
+ //
85
+ // If search is provided, immediately try to find the ride by ID.
86
+ // If found, return it as the only result. This optimizes
87
+ // for the common case of searching by ride ID.
88
+ const foundRideById = await rides.findOne({
89
+ _id: request.params['id'],
90
+ ...(allowAllAgencies ? {} : { agency_id: { $in: ridesPermission['resources'].agency_ids } }),
91
+ });
92
+ if (foundRideById) {
93
+ const normalizedRide = normalizeRide(foundRideById);
94
+ return reply.send({ data: normalizedRide, error: null, statusCode: HttpStatus.OK });
95
+ }
96
+ //
97
+ }
98
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@tmlmobilidade/controllers",
3
+ "version": "20260128.2248.28",
4
+ "author": {
5
+ "email": "iso@tmlmobilidade.pt",
6
+ "name": "TML-ISO"
7
+ },
8
+ "license": "AGPL-3.0-or-later",
9
+ "homepage": "https://github.com/tmlmobilidade/services#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/tmlmobilidade/services/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/tmlmobilidade/services.git"
16
+ },
17
+ "keywords": [
18
+ "public transit",
19
+ "tml",
20
+ "transportes metropolitanos de lisboa",
21
+ "services"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "scripts": {
33
+ "build": "tsc && resolve-tspaths",
34
+ "lint": "eslint && tsc --noEmit",
35
+ "lint:fix": "eslint --fix",
36
+ "watch": "tsc-watch --onSuccess 'resolve-tspaths'"
37
+ },
38
+ "dependencies": {
39
+ "@tmlmobilidade/consts": "*",
40
+ "@tmlmobilidade/fastify": "*",
41
+ "@tmlmobilidade/interfaces": "*",
42
+ "@tmlmobilidade/normalizers": "*",
43
+ "@tmlmobilidade/utils": "*",
44
+ "zod": "3.25.76"
45
+ },
46
+ "devDependencies": {
47
+ "@tmlmobilidade/tsconfig": "*",
48
+ "@tmlmobilidade/types": "*",
49
+ "@types/node": "25.1.0",
50
+ "resolve-tspaths": "0.8.23",
51
+ "tsc-watch": "7.2.0",
52
+ "typescript": "5.9.3"
53
+ }
54
+ }