@tmlmobilidade/interfaces 20260411.1248.30 → 20260418.1237.24

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.
@@ -120,7 +120,7 @@ export declare abstract class MongoCollectionClass<T extends Document, TCreate,
120
120
  * @returns A promise that resolves to the result of the insert operation
121
121
  */
122
122
  insertMany(docs: (TCreate & {
123
- _id?: string;
123
+ _id?: T['_id'];
124
124
  created_at?: UnixTimestamp;
125
125
  created_by?: string;
126
126
  updated_at?: UnixTimestamp;
@@ -136,7 +136,7 @@ export declare abstract class MongoCollectionClass<T extends Document, TCreate,
136
136
  * @returns A promise that resolves to the result of the insert operation.
137
137
  */
138
138
  insertOne<TReturnDocument extends boolean = true>(doc: TCreate & {
139
- _id?: string;
139
+ _id?: T['_id'];
140
140
  created_at?: UnixTimestamp;
141
141
  created_by?: string;
142
142
  updated_at?: UnixTimestamp;
@@ -24,6 +24,7 @@ declare class EventsClass extends MongoCollectionClass<Event, CreateEventDto, Up
24
24
  updated_at: number & {
25
25
  __brand: "UnixTimestamp";
26
26
  };
27
+ code: string;
27
28
  description: string;
28
29
  title: string;
29
30
  agency_ids: string[];
@@ -27,6 +27,7 @@ declare class YearPeriodsClass extends MongoCollectionClass<YearPeriod, CreateYe
27
27
  name: string;
28
28
  agency_ids: string[];
29
29
  updated_by?: string | undefined;
30
+ code?: string | undefined;
30
31
  color?: string | undefined;
31
32
  dates?: import("@tmlmobilidade/types").OperationalDate[] | undefined;
32
33
  }>[]>;
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable perfectionist/sort-classes */
3
- import { HttpException, HTTP_STATUS } from '@tmlmobilidade/consts';
3
+ import { HTTP_STATUS, HttpException } from '@tmlmobilidade/consts';
4
4
  import { MongoConnector } from '@tmlmobilidade/mongo';
5
5
  import { asyncSingletonProxy } from '@tmlmobilidade/utils';
6
6
  /* * */
@@ -2,8 +2,7 @@
2
2
  import { MongoCollectionClass } from '../../common/mongo-collection.js';
3
3
  import { roles } from '../auth/roles.js';
4
4
  import { users } from '../auth/users.js';
5
- import { getAppConfig } from '@tmlmobilidade/consts';
6
- import { sendGenericNotificationEmail } from '@tmlmobilidade/emails';
5
+ import { getModuleConfig } from '@tmlmobilidade/consts';
7
6
  import { CreateNotificationSchema, UpdateNotificationSchema } from '@tmlmobilidade/types';
8
7
  import { asyncSingletonProxy, mergeObjects } from '@tmlmobilidade/utils';
9
8
  /* * */
@@ -40,7 +39,7 @@ class NotificationsClass extends MongoCollectionClass {
40
39
  is_read: false,
41
40
  payload: {
42
41
  body: description,
43
- href: `${getAppConfig(scope, 'frontend_url')}/${scope}/${id}`,
42
+ href: `${getModuleConfig(scope, 'frontend_url')}/${scope}/${id}`,
44
43
  icon: scope,
45
44
  title,
46
45
  },
@@ -55,18 +54,18 @@ class NotificationsClass extends MongoCollectionClass {
55
54
  const canReceiveEmail = this.getNotificationPermission(permissions, topic);
56
55
  const newNotification = { ...baseNotification, user_id: recipient._id };
57
56
  const result = await notifications.insertOne(newNotification);
58
- // Send email if permission allows
59
- if (canReceiveEmail) {
60
- await sendGenericNotificationEmail({
61
- data: {
62
- body: result.payload.body,
63
- notificationId: result._id,
64
- notificationUrl: result.payload.href ?? '',
65
- title: result.payload.title,
66
- },
67
- to: recipient.email,
68
- });
69
- }
57
+ // // Send email if permission allows
58
+ // if (canReceiveEmail) {
59
+ // await sendGenericNotificationEmail({
60
+ // data: {
61
+ // body: result.payload.body,
62
+ // notificationId: result._id,
63
+ // notificationUrl: result.payload.href ?? '',
64
+ // title: result.payload.title,
65
+ // },
66
+ // to: recipient.email,
67
+ // });
68
+ // }
70
69
  }
71
70
  }
72
71
  getCollectionIndexes() {
@@ -123,14 +123,14 @@ declare class PatternsClass extends MongoCollectionClass<Pattern, CreatePatternD
123
123
  updated_by?: string | undefined;
124
124
  path?: {
125
125
  _id: string;
126
- stop_id: string;
126
+ stop_id: number;
127
127
  timepoint: boolean;
128
128
  allow_drop_off: boolean;
129
129
  allow_pickup: boolean;
130
130
  distance_delta: number | null;
131
131
  zones?: string[] | undefined;
132
132
  stop?: {
133
- _id: string;
133
+ _id: number;
134
134
  created_at: number & {
135
135
  __brand: "UnixTimestamp";
136
136
  };
@@ -200,11 +200,19 @@ declare class PatternsClass extends MongoCollectionClass<Pattern, CreatePatternD
200
200
  parish_id: string | null;
201
201
  shelter_code: string | null;
202
202
  shelter_maintainer: string | null;
203
+ flags: {
204
+ short_name: string;
205
+ stop_id: string;
206
+ agency_ids: string[];
207
+ is_harmonized: boolean;
208
+ }[];
203
209
  is_deleted: boolean;
204
210
  jurisdiction: "unknown" | "municipality" | "ip" | "other";
205
211
  legacy_id: string | null;
212
+ legacy_ids: string[];
206
213
  lifecycle_status: "draft" | "active" | "inactive" | "provisional" | "seasonal" | "voided";
207
214
  new_name: string | null;
215
+ previous_go_id: string | null;
208
216
  tts_name: string;
209
217
  district_id: string;
210
218
  locality_id: string | null;
@@ -242,7 +250,7 @@ declare class PatternsClass extends MongoCollectionClass<Pattern, CreatePatternD
242
250
  } | undefined;
243
251
  parameters?: ({
244
252
  path: {
245
- stop_id: string;
253
+ stop_id: number;
246
254
  avg_speed: number;
247
255
  dwell_time: number;
248
256
  }[];
@@ -252,7 +260,7 @@ declare class PatternsClass extends MongoCollectionClass<Pattern, CreatePatternD
252
260
  vehicle_type?: string | undefined;
253
261
  } | {
254
262
  path: {
255
- stop_id: string;
263
+ stop_id: number;
256
264
  avg_speed: number;
257
265
  dwell_time: number;
258
266
  }[];
@@ -262,6 +262,107 @@ export function ridesPipelineSeenStatus({ filter } = {}) {
262
262
  }
263
263
  return pipeline;
264
264
  }
265
+ /**
266
+ * Attempts to map search term to specific indexed field based on structural
267
+ * heuristics. Returns null when term does not match known pattern.
268
+ */
269
+ function routeTermToField(term) {
270
+ // Exact pattern_id match: "1001_0_2"
271
+ if (/^\d+_\d+_\d+$/.test(term)) {
272
+ return { pattern_id: term };
273
+ }
274
+ // Exact trip_id match: "1001_0_2_0800_0829_0_26"
275
+ if (/^\d+_\d+_\d+_\d+_\d+_\d+_\d+$/.test(term)) {
276
+ return { trip_id: term };
277
+ }
278
+ // route_id: "1001_0"
279
+ if (/^\d+_\d+$/.test(term)) {
280
+ return { route_id: term };
281
+ }
282
+ // operational_date: "20240101"
283
+ if (/^\d{8}$/.test(term)) {
284
+ return { operational_date: term };
285
+ }
286
+ // line_id: pure integer string like "1001"
287
+ // agency_id also numeric, so use length heuristic
288
+ if (/^\d+$/.test(term)) {
289
+ const n = Number(term);
290
+ if (term.length >= 3)
291
+ return { line_id: n };
292
+ if (term.length <= 2)
293
+ return { agency_id: term };
294
+ }
295
+ // plan_id: uppercase alpha-numeric prefix like "KACZ2"
296
+ if (/^[A-Z]+\d*$/.test(term)) {
297
+ return { plan_id: term };
298
+ }
299
+ return null;
300
+ }
301
+ function buildSearchPipeline(filter) {
302
+ const pipeline = [];
303
+ if (!filter.search)
304
+ return pipeline;
305
+ const vehicleMatch = filter.search.match(/v:([\d,]+)/);
306
+ const driverMatch = filter.search.match(/d:([\d,]+)/);
307
+ const rawSearch = filter.search
308
+ .replace(/v:[\d,]+/g, '')
309
+ .replace(/d:[\d,]+/g, '')
310
+ .trim();
311
+ if (rawSearch.length > 0) {
312
+ const terms = rawSearch
313
+ .split(/\s+/)
314
+ .filter(k => k.length > 0);
315
+ const conditions = [];
316
+ const unroutableTerms = [];
317
+ const routedFields = new Set();
318
+ for (const term of terms) {
319
+ const routed = routeTermToField(term);
320
+ if (routed) {
321
+ const [field] = Object.keys(routed);
322
+ if (routedFields.has(field)) {
323
+ unroutableTerms.push(term);
324
+ continue;
325
+ }
326
+ routedFields.add(field);
327
+ conditions.push(routed);
328
+ }
329
+ else {
330
+ unroutableTerms.push(term);
331
+ }
332
+ }
333
+ if (unroutableTerms.length > 0) {
334
+ const pattern = unroutableTerms
335
+ .map(k => `(?=.*${k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`)
336
+ .join('') + '.*';
337
+ conditions.push({ _id: { $options: 'i', $regex: pattern } });
338
+ }
339
+ if (conditions.length === 1) {
340
+ pipeline.push({ $match: conditions[0] });
341
+ }
342
+ else if (conditions.length > 1) {
343
+ pipeline.push({ $match: { $and: conditions } });
344
+ }
345
+ }
346
+ if (vehicleMatch) {
347
+ const vehicleIDs = vehicleMatch[1]
348
+ .split(',')
349
+ .map(id => Number(id.trim()))
350
+ .filter(id => !isNaN(id));
351
+ if (vehicleIDs.length > 0) {
352
+ pipeline.push({ $match: { vehicle_ids: { $in: vehicleIDs } } });
353
+ }
354
+ }
355
+ if (driverMatch) {
356
+ const driverIDs = driverMatch[1]
357
+ .split(',')
358
+ .map(id => id.trim())
359
+ .filter(Boolean);
360
+ if (driverIDs.length > 0) {
361
+ pipeline.push({ $match: { driver_ids: { $in: driverIDs } } });
362
+ }
363
+ }
364
+ return pipeline;
365
+ }
265
366
  /**
266
367
  * Creates MongoDB aggregation pipeline stages to filter and process ride data.
267
368
  *
@@ -288,51 +389,8 @@ export function ridesBatchAggregationPipeline({ ...filter }) {
288
389
  // Stage 3: Filter by line IDs if provided
289
390
  if (filter.line_ids?.length)
290
391
  pipeline.push({ $match: { line_id: { $in: filter.line_ids.map(id => Number(id)) } } });
291
- // Stage 4: Search by ride ID if provided
292
- // Uses regex pattern matching with case-insensitive option
293
- // Also removes all vehicle IDs and driver IDs from the search string
294
- // And adds a filter for the vehicle IDs if they are present in the search string (v:1117,1118)
295
- // And adds a filter for the driver IDs if they are present in the search string (d:123,456)
296
- if (filter.search) {
297
- // Extract vehicle and driver IDs before stripping them from search
298
- const vehicleMatch = filter.search.match(/v:([\d,]+)/);
299
- const driverMatch = filter.search.match(/d:([\d,]+)/);
300
- // Remove v: and d: patterns from search string for ride ID matching
301
- const searchWithoutSpecialFilters = filter.search.replace(/v:[\d,]+/g, '').replace(/d:[\d,]+/g, '').trim();
302
- const keywords = searchWithoutSpecialFilters
303
- .split(/\s+/)
304
- .filter(k => k.length > 0)
305
- .map((v) => {
306
- // Escape regex special chars EXCEPT %
307
- let escaped = v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
308
- // Convert SQL wildcards to regex
309
- escaped = escaped.replace(/%%/g, '.*');
310
- return escaped;
311
- });
312
- if (keywords.length > 0) {
313
- const pattern = keywords.map(k => `(?=.*${k})`).join('') + '.*';
314
- pipeline.push({
315
- $match: { _id: { $options: 'i', $regex: pattern } },
316
- });
317
- }
318
- if (vehicleMatch) {
319
- const value = vehicleMatch[1];
320
- const vehicleIDs = value
321
- .split(',')
322
- .map(id => Number(id.trim()))
323
- .filter(id => !isNaN(id));
324
- if (vehicleIDs.length > 0) {
325
- pipeline.push({ $match: { vehicle_ids: { $in: vehicleIDs } } });
326
- }
327
- }
328
- if (driverMatch) {
329
- const value = driverMatch[1];
330
- const driverIDs = value.split(',').map(id => id.trim()).filter(id => id);
331
- if (driverIDs.length > 0) {
332
- pipeline.push({ $match: { driver_ids: { $in: driverIDs } } });
333
- }
334
- }
335
- }
392
+ // Stage 4: Search by term routing and selective fallback regex
393
+ pipeline.push(...buildSearchPipeline(filter));
336
394
  // Stage 5: Search by vehicle IDs from search field if provided (legacy support)
337
395
  // This allows filtering via search field with "v:1117" or "v:1117,1118" format
338
396
  // Remove literal 'v:' from the search string since it is breaking stuff
@@ -22,6 +22,13 @@ declare class StopsClass extends MongoCollectionClass<Stop, CreateStopDto, Updat
22
22
  * @returns A promise that rejects with an error indicating deletion is not allowed.
23
23
  */
24
24
  deleteOne(): Promise<DeleteResult>;
25
+ /**
26
+ * Override deleteMany to prevent actual deletion of stop documents.
27
+ * Stops cannot be deleted, only archived.
28
+ * @param filter The filter used to select the documents to "delete".
29
+ * @returns A promise that rejects with an error indicating deletion is not allowed.
30
+ */
31
+ deleteMany(): Promise<DeleteResult>;
25
32
  /**
26
33
  * Finds stop documents by municipality ID with optional pagination and sorting.
27
34
  * @param id The municipality ID to search for
@@ -31,7 +38,7 @@ declare class StopsClass extends MongoCollectionClass<Stop, CreateStopDto, Updat
31
38
  * @returns A promise that resolves to an array of matching stop documents
32
39
  */
33
40
  findByMunicipalityId(id: string, perPage?: number, page?: number, sort?: Sort): Promise<import("mongodb").WithId<{
34
- _id: string;
41
+ _id: number;
35
42
  created_at: number & {
36
43
  __brand: "UnixTimestamp";
37
44
  };
@@ -101,11 +108,19 @@ declare class StopsClass extends MongoCollectionClass<Stop, CreateStopDto, Updat
101
108
  parish_id: string | null;
102
109
  shelter_code: string | null;
103
110
  shelter_maintainer: string | null;
111
+ flags: {
112
+ short_name: string;
113
+ stop_id: string;
114
+ agency_ids: string[];
115
+ is_harmonized: boolean;
116
+ }[];
104
117
  is_deleted: boolean;
105
118
  jurisdiction: "unknown" | "municipality" | "ip" | "other";
106
119
  legacy_id: string | null;
120
+ legacy_ids: string[];
107
121
  lifecycle_status: "draft" | "active" | "inactive" | "provisional" | "seasonal" | "voided";
108
122
  new_name: string | null;
123
+ previous_go_id: string | null;
109
124
  tts_name: string;
110
125
  district_id: string;
111
126
  locality_id: string | null;
@@ -134,8 +149,8 @@ declare class StopsClass extends MongoCollectionClass<Stop, CreateStopDto, Updat
134
149
  * @param ids Array of stop IDs to search for
135
150
  * @returns A promise that resolves to an array of matching stop documents
136
151
  */
137
- findManyByIds(ids: string[]): Promise<import("mongodb").WithId<{
138
- _id: string;
152
+ findManyByIds(ids: number[]): Promise<import("mongodb").WithId<{
153
+ _id: number;
139
154
  created_at: number & {
140
155
  __brand: "UnixTimestamp";
141
156
  };
@@ -205,11 +220,19 @@ declare class StopsClass extends MongoCollectionClass<Stop, CreateStopDto, Updat
205
220
  parish_id: string | null;
206
221
  shelter_code: string | null;
207
222
  shelter_maintainer: string | null;
223
+ flags: {
224
+ short_name: string;
225
+ stop_id: string;
226
+ agency_ids: string[];
227
+ is_harmonized: boolean;
228
+ }[];
208
229
  is_deleted: boolean;
209
230
  jurisdiction: "unknown" | "municipality" | "ip" | "other";
210
231
  legacy_id: string | null;
232
+ legacy_ids: string[];
211
233
  lifecycle_status: "draft" | "active" | "inactive" | "provisional" | "seasonal" | "voided";
212
234
  new_name: string | null;
235
+ previous_go_id: string | null;
213
236
  tts_name: string;
214
237
  district_id: string;
215
238
  locality_id: string | null;
@@ -241,7 +264,7 @@ declare class StopsClass extends MongoCollectionClass<Stop, CreateStopDto, Updat
241
264
  * @param forceValue Optional boolean to explicitly set the deleted status.
242
265
  * @returns A promise that resolves to the result of the delete operation.
243
266
  */
244
- toggleDeleteById(id: string, forceValue?: boolean): Promise<void>;
267
+ toggleDeleteById(id: number, forceValue?: boolean): Promise<void>;
245
268
  protected getCollectionIndexes(): IndexDescription[];
246
269
  protected getCollectionName(): string;
247
270
  protected getEnvName(): string;
@@ -36,6 +36,15 @@ class StopsClass extends MongoCollectionClass {
36
36
  async deleteOne() {
37
37
  throw new Error('Method not implemented. Stops cannot be deleted, only archived.');
38
38
  }
39
+ /**
40
+ * Override deleteMany to prevent actual deletion of stop documents.
41
+ * Stops cannot be deleted, only archived.
42
+ * @param filter The filter used to select the documents to "delete".
43
+ * @returns A promise that rejects with an error indicating deletion is not allowed.
44
+ */
45
+ async deleteMany() {
46
+ throw new Error('Method not implemented. Stops cannot be deleted, only archived.');
47
+ }
39
48
  /**
40
49
  * Finds stop documents by municipality ID with optional pagination and sorting.
41
50
  * @param id The municipality ID to search for
@@ -53,7 +53,7 @@ declare class AuthProvider {
53
53
  * @throws An HTTP error code:
54
54
  * - INTERNAL_SERVER_ERROR if user creation fails
55
55
  */
56
- register(createUserDto: CreateUserDto): Promise<void>;
56
+ register(createUserDto: CreateUserDto): Promise<string>;
57
57
  }
58
58
  export declare const authProvider: AuthProvider;
59
59
  export {};
@@ -1,8 +1,7 @@
1
1
  /* * */
2
2
  import { organizations, roles, sessions, users, verificationTokens } from '../../interfaces/index.js';
3
- import { HTTP_STATUS, HttpException, PAGE_ROUTES } from '@tmlmobilidade/consts';
3
+ import { HTTP_STATUS, HttpException } from '@tmlmobilidade/consts';
4
4
  import { Dates } from '@tmlmobilidade/dates';
5
- import { sendWelcomeEmail } from '@tmlmobilidade/emails';
6
5
  import { generateRandomString, generateRandomToken } from '@tmlmobilidade/strings';
7
6
  import { asyncSingletonProxy, mergeObjects } from '@tmlmobilidade/utils';
8
7
  import bcrypt from 'bcryptjs';
@@ -159,14 +158,7 @@ class AuthProvider {
159
158
  token: verificationToken,
160
159
  user_id: insertNewUserResult._id,
161
160
  });
162
- // Send a welcome email to the user with the verification token
163
- await sendWelcomeEmail({
164
- data: {
165
- firstName: createUserDto.first_name,
166
- resetPasswordUrl: `${PAGE_ROUTES.auth.CHANGE_PASSWORD_LIST}?token=${verificationToken}&email=${encodeURIComponent(createUserDto.email)}`,
167
- },
168
- to: createUserDto.email,
169
- });
161
+ return verificationToken;
170
162
  }
171
163
  }
172
164
  /* * */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/interfaces",
3
- "version": "20260411.1248.30",
3
+ "version": "20260418.1237.24",
4
4
  "author": {
5
5
  "email": "iso@tmlmobilidade.pt",
6
6
  "name": "TML-ISO"
@@ -39,7 +39,6 @@
39
39
  "@tmlmobilidade/consts": "*",
40
40
  "@tmlmobilidade/databases": "*",
41
41
  "@tmlmobilidade/dates": "*",
42
- "@tmlmobilidade/emails": "*",
43
42
  "@tmlmobilidade/files": "*",
44
43
  "@tmlmobilidade/logger": "*",
45
44
  "@tmlmobilidade/mongo": "*",
@@ -49,14 +48,14 @@
49
48
  "@tmlmobilidade/writers": "*",
50
49
  "bcryptjs": "3.0.3",
51
50
  "mongodb": "7.1.1",
52
- "oci-common": "2.127.0",
53
- "oci-objectstorage": "2.127.0",
51
+ "oci-common": "2.129.0",
52
+ "oci-objectstorage": "2.129.0",
54
53
  "zod": "3.25.76"
55
54
  },
56
55
  "devDependencies": {
57
56
  "@tmlmobilidade/tsconfig": "*",
58
57
  "@tmlmobilidade/types": "*",
59
- "@types/node": "25.5.2",
58
+ "@types/node": "25.6.0",
60
59
  "resolve-tspaths": "0.8.23",
61
60
  "tsc-watch": "7.2.0",
62
61
  "typescript": "5.9.3"