itlab-internal-services 2.16.0 → 2.16.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.
Files changed (74) hide show
  1. package/dist/classes/document-merger.class.js +2 -1
  2. package/dist/classes/index.d.ts +3 -1
  3. package/dist/classes/index.js +5 -3
  4. package/dist/classes/schema-builder.class.d.ts +75 -0
  5. package/dist/classes/schema-builder.class.js +109 -0
  6. package/dist/classes/task-manager.class.d.ts +71 -0
  7. package/dist/classes/task-manager.class.js +99 -0
  8. package/dist/exceptions/bad-parameter.exception.js +2 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +3 -3
  11. package/dist/interceptors/attribute-sanitizer.interceptor.d.ts +68 -0
  12. package/dist/interceptors/attribute-sanitizer.interceptor.js +154 -0
  13. package/dist/interceptors/index.d.ts +1 -0
  14. package/dist/interceptors/index.js +17 -0
  15. package/dist/middleware/http-logging.middleware.d.ts +19 -0
  16. package/dist/middleware/http-logging.middleware.js +50 -0
  17. package/dist/middleware/index.d.ts +1 -0
  18. package/dist/middleware/index.js +17 -0
  19. package/dist/modules/authentication/guards/jwt-auth.guard.js +1 -7
  20. package/dist/modules/authentication/guards/permissions.guard.js +4 -3
  21. package/dist/modules/authentication/guards/service-auth.guard.js +1 -1
  22. package/dist/modules/comment/comment-module-options.interface.d.ts +2 -2
  23. package/dist/modules/comment/comment.controller.d.ts +5 -5
  24. package/dist/modules/comment/comment.controller.js +3 -3
  25. package/dist/modules/comment/comment.service.d.ts +3 -3
  26. package/dist/modules/comment/comment.service.js +1 -1
  27. package/dist/modules/like/like-module-options.interface.d.ts +2 -2
  28. package/dist/modules/like/like.controller.d.ts +2 -2
  29. package/dist/modules/like/like.controller.js +2 -2
  30. package/dist/modules/services/providers/accounts.service.js +2 -0
  31. package/dist/modules/services/providers/books.service.js +2 -0
  32. package/dist/modules/services/providers/changelog.service.js +2 -0
  33. package/dist/modules/services/providers/comments/comments.service.d.ts +3 -3
  34. package/dist/modules/services/providers/comments/comments.service.js +2 -1
  35. package/dist/modules/services/providers/content/{content-types.d.ts → content-return-types.d.ts} +3 -4
  36. package/dist/modules/services/providers/content/{content-types.js → content-return-types.js} +2 -2
  37. package/dist/modules/services/providers/content/content.service.d.ts +7 -5
  38. package/dist/modules/services/providers/content/content.service.js +29 -12
  39. package/dist/modules/services/providers/content/index.d.ts +1 -0
  40. package/dist/modules/services/providers/demo-hive.service.js +2 -0
  41. package/dist/modules/services/providers/events.service.js +2 -0
  42. package/dist/modules/services/providers/hackschool.service.js +2 -0
  43. package/dist/modules/services/providers/ideas.service.js +2 -0
  44. package/dist/modules/services/providers/lunch-roulette.service.js +2 -0
  45. package/dist/modules/services/providers/mail/mail.service.js +1 -1
  46. package/dist/modules/services/providers/newletter.service.js +2 -0
  47. package/dist/modules/services/providers/newsroom.service.js +2 -0
  48. package/dist/modules/services/providers/podcasts.service.js +2 -0
  49. package/dist/modules/services/providers/search/search.service.js +1 -1
  50. package/dist/modules/services/providers/team.service.js +2 -0
  51. package/dist/modules/services/providers/tech-radar.service.js +2 -0
  52. package/dist/properties/content-return-type.property.d.ts +7 -0
  53. package/dist/properties/content-return-type.property.js +22 -0
  54. package/dist/properties/index.d.ts +3 -0
  55. package/dist/properties/index.js +7 -1
  56. package/dist/properties/likeable.properties.d.ts +1 -0
  57. package/dist/properties/likeable.properties.js +66 -0
  58. package/dist/properties/viewable.properties.d.ts +1 -0
  59. package/dist/properties/viewable.properties.js +66 -0
  60. package/dist/types/index.d.ts +2 -0
  61. package/dist/types/index.js +18 -0
  62. package/dist/types/likeable.type.d.ts +15 -0
  63. package/dist/types/likeable.type.js +2 -0
  64. package/dist/types/viewable.type.d.ts +15 -0
  65. package/dist/types/viewable.type.js +2 -0
  66. package/package.json +2 -2
  67. package/dist/classes/document-updater.class.d.ts +0 -39
  68. package/dist/classes/document-updater.class.js +0 -57
  69. package/dist/http-logger.middleware.d.ts +0 -12
  70. package/dist/http-logger.middleware.js +0 -43
  71. package/dist/likeable.interface.d.ts +0 -41
  72. package/dist/likeable.interface.js +0 -48
  73. package/dist/viewable.interface.d.ts +0 -41
  74. package/dist/viewable.interface.js +0 -48
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DocumentMerger = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
+ const itlab_functions_1 = require("itlab-functions");
5
6
  /**
6
7
  * DocumentMerger
7
8
  *
@@ -75,7 +76,7 @@ class DocumentMerger {
75
76
  });
76
77
  const elapsedTime = Date.now() - startTime;
77
78
  // Log completion with fields merged and time taken
78
- this.logger.log(`Successfully merged fields: ${this.fields.join(', ')} (took ${elapsedTime} ms)`);
79
+ this.logger.log(`Successfully merged fields: ${(0, itlab_functions_1.formatList)(this.fields)} (took ${elapsedTime} ms)`);
79
80
  }
80
81
  }
81
82
  exports.DocumentMerger = DocumentMerger;
@@ -1,3 +1,5 @@
1
1
  export { DocumentMerger } from './document-merger.class';
2
- export { DocumentUpdater } from './document-updater.class';
3
2
  export { ReportCategory } from './report-category.class';
3
+ export { SchemaBuilder } from './schema-builder.class';
4
+ export type { SchemaBuilderDocument, SchemaBuilderModel, SchemaBuilderOptions } from './schema-builder.class';
5
+ export { TaskManager } from './task-manager.class';
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ReportCategory = exports.DocumentUpdater = exports.DocumentMerger = void 0;
3
+ exports.TaskManager = exports.SchemaBuilder = exports.ReportCategory = exports.DocumentMerger = void 0;
4
4
  var document_merger_class_1 = require("./document-merger.class");
5
5
  Object.defineProperty(exports, "DocumentMerger", { enumerable: true, get: function () { return document_merger_class_1.DocumentMerger; } });
6
- var document_updater_class_1 = require("./document-updater.class");
7
- Object.defineProperty(exports, "DocumentUpdater", { enumerable: true, get: function () { return document_updater_class_1.DocumentUpdater; } });
8
6
  var report_category_class_1 = require("./report-category.class");
9
7
  Object.defineProperty(exports, "ReportCategory", { enumerable: true, get: function () { return report_category_class_1.ReportCategory; } });
8
+ var schema_builder_class_1 = require("./schema-builder.class");
9
+ Object.defineProperty(exports, "SchemaBuilder", { enumerable: true, get: function () { return schema_builder_class_1.SchemaBuilder; } });
10
+ var task_manager_class_1 = require("./task-manager.class");
11
+ Object.defineProperty(exports, "TaskManager", { enumerable: true, get: function () { return task_manager_class_1.TaskManager; } });
@@ -0,0 +1,75 @@
1
+ import { Type } from '@nestjs/common';
2
+ import { Document, Model, Schema } from 'mongoose';
3
+ import { Likeable, Viewable } from '../types';
4
+ /**
5
+ * Options that determine how the schema should be extended.
6
+ *
7
+ * @template TClass - The document type for the schema.
8
+ */
9
+ export type SchemaBuilderOptions<TClass extends Document> = {
10
+ /** Whether this schema should support view tracking. */
11
+ viewable?: boolean;
12
+ /** Whether this schema should support like tracking. */
13
+ likeable?: boolean;
14
+ /**
15
+ * A set of keys that uniquely identify the document.
16
+ * These keys are used when checking for duplicates.
17
+ */
18
+ criticalKeys?: (keyof Omit<TClass, keyof Document>)[];
19
+ };
20
+ /**
21
+ * Computes the augmented document type based on SchemaOptions.
22
+ */
23
+ export type SchemaBuilderDocument<TClass extends Document, TOptions extends SchemaBuilderOptions<TClass>> = TClass & (TOptions['viewable'] extends true ? Viewable : object) & (TOptions['likeable'] extends true ? Likeable : object);
24
+ /**
25
+ * Extends a standard Mongoose model with custom static methods
26
+ * injected by the SchemaBuilder.
27
+ */
28
+ export type SchemaBuilderModel<TClass extends Document> = Model<TClass> & {
29
+ /**
30
+ * Checks if the given object is a duplicate of an existing document.
31
+ *
32
+ * @param dto - Partial object with properties to check for duplicates.
33
+ * @param updateTarget - The document being updated, if applicable.
34
+ * @returns {Promise<boolean>} - True if a duplicate exists, false otherwise.
35
+ */
36
+ isDuplicate(dto: Partial<TClass>, updateTarget?: TClass): Promise<boolean>;
37
+ };
38
+ /**
39
+ * Generic builder for creating extended Mongoose schemas.
40
+ *
41
+ * This class wraps Nest's SchemaFactory and automatically injects common
42
+ * behaviors such as:
43
+ * - View tracking (who viewed, view counts)
44
+ * - Like tracking (who liked, like counts)
45
+ * - Duplicate checking based on critical keys
46
+ *
47
+ * @template TClass - A class extending Mongoose Document.
48
+ */
49
+ export declare class SchemaBuilder<TClass extends Document> {
50
+ private schema;
51
+ readonly options: SchemaBuilderOptions<TClass>;
52
+ constructor(target: Type<TClass>, options?: SchemaBuilderOptions<TClass>);
53
+ /**
54
+ * Adds interaction tracking (likes or views) to a schema.
55
+ * This method generalizes the shared logic between "viewable" and "likeable".
56
+ *
57
+ * @param field - The internal array field storing account IDs. (e.g. _viewedBy or _likedBy)
58
+ * @param counter - The numeric counter field tracking totals. (e.g. views or likes)
59
+ */
60
+ provideInteraction(field: string, counter: string): this;
61
+ /**
62
+ * Adds a duplicate-checking static method to the schema.
63
+ * This method compares critical keys to detect conflicts.
64
+ */
65
+ private provideDuplicateChecker;
66
+ /**
67
+ * Finalizes the schema and injects any selected behaviors (viewable, likeable, duplicate checking).
68
+ *
69
+ * @returns An object containing:
70
+ * - `schema`: The Mongoose schema.
71
+ * - `modelType`: A strongly typed model with additional static methods.
72
+ * - `documentType`: The inferred document type with injected properties.
73
+ */
74
+ build(): Schema;
75
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchemaBuilder = void 0;
4
+ const mongoose_1 = require("@nestjs/mongoose");
5
+ const class_validator_1 = require("class-validator");
6
+ /**
7
+ * Generic builder for creating extended Mongoose schemas.
8
+ *
9
+ * This class wraps Nest's SchemaFactory and automatically injects common
10
+ * behaviors such as:
11
+ * - View tracking (who viewed, view counts)
12
+ * - Like tracking (who liked, like counts)
13
+ * - Duplicate checking based on critical keys
14
+ *
15
+ * @template TClass - A class extending Mongoose Document.
16
+ */
17
+ class SchemaBuilder {
18
+ constructor(target, options = {}) {
19
+ this.schema = mongoose_1.SchemaFactory.createForClass(target);
20
+ this.options = options;
21
+ /**
22
+ * Create a wildcard index required for CosmosDB to support sorting on all fields.
23
+ *
24
+ * This is a workaround for CosmosDB's partial MongoDB API compatibility.
25
+ * See: https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/indexing#wildcard-indexes
26
+ */
27
+ this.schema.index({ '$**': 1 }, { name: 'Sort Index' });
28
+ this.provideDuplicateChecker();
29
+ }
30
+ /**
31
+ * Adds interaction tracking (likes or views) to a schema.
32
+ * This method generalizes the shared logic between "viewable" and "likeable".
33
+ *
34
+ * @param field - The internal array field storing account IDs. (e.g. _viewedBy or _likedBy)
35
+ * @param counter - The numeric counter field tracking totals. (e.g. views or likes)
36
+ */
37
+ provideInteraction(field, counter) {
38
+ // Add fields to the schema
39
+ this.schema.add({ [field]: { type: [String], default: [] }, [counter]: { type: Number, default: 0 } });
40
+ // Expose a virtual property (without underscore) for convenience
41
+ this.schema.virtual(field.replace(/^_/, ''));
42
+ // Add method to increment counter
43
+ this.schema.methods[`add${counter[0].toUpperCase() + counter.slice(1)}`] = async function (accountId) {
44
+ if (!(0, class_validator_1.isMongoId)(accountId))
45
+ return Number(this[counter]);
46
+ // Ensure uniqueness by converting to a Set
47
+ this[field] = Array.from(new Set([...this[field], accountId]));
48
+ this[counter] = this[field].length;
49
+ await this.save({ timestamps: false });
50
+ return this[counter];
51
+ };
52
+ // Add method to decrement counter
53
+ this.schema.methods[`remove${counter[0].toUpperCase() + counter.slice(1)}`] = async function (accountId) {
54
+ if (!(0, class_validator_1.isMongoId)(accountId))
55
+ return Number(this[counter]);
56
+ // Remove the accountId from the list
57
+ this[field] = this[field].filter((id) => id !== accountId);
58
+ this[counter] = this[field].length;
59
+ await this.save({ timestamps: false });
60
+ return this[counter];
61
+ };
62
+ return this;
63
+ }
64
+ /**
65
+ * Adds a duplicate-checking static method to the schema.
66
+ * This method compares critical keys to detect conflicts.
67
+ */
68
+ provideDuplicateChecker() {
69
+ var _a;
70
+ const criticalKeys = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.criticalKeys) || [];
71
+ this.schema.statics.isDuplicate = async function (dto, updateTarget) {
72
+ var _a;
73
+ if (criticalKeys.length === 0)
74
+ return false;
75
+ // Build a filter using the critical keys
76
+ const filterQuery = criticalKeys.reduce((query, key) => {
77
+ if (dto && key in dto)
78
+ query[String(key)] = dto[key];
79
+ else if (updateTarget && key in updateTarget)
80
+ query[String(key)] = updateTarget[key];
81
+ return query;
82
+ }, {});
83
+ // If a matching document exists, check whether it’s the same entity
84
+ const potentialDuplicate = await this.findOne(filterQuery);
85
+ if (!potentialDuplicate)
86
+ return false;
87
+ const potentialId = potentialDuplicate._id.toString().toLowerCase();
88
+ const targetId = (_a = updateTarget === null || updateTarget === void 0 ? void 0 : updateTarget._id) === null || _a === void 0 ? void 0 : _a.toString().toLowerCase();
89
+ return potentialId !== targetId;
90
+ };
91
+ }
92
+ /**
93
+ * Finalizes the schema and injects any selected behaviors (viewable, likeable, duplicate checking).
94
+ *
95
+ * @returns An object containing:
96
+ * - `schema`: The Mongoose schema.
97
+ * - `modelType`: A strongly typed model with additional static methods.
98
+ * - `documentType`: The inferred document type with injected properties.
99
+ */
100
+ build() {
101
+ var _a, _b;
102
+ if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.viewable)
103
+ this.provideInteraction('_viewedBy', 'views');
104
+ if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.likeable)
105
+ this.provideInteraction('_likedBy', 'likes');
106
+ return this.schema;
107
+ }
108
+ }
109
+ exports.SchemaBuilder = SchemaBuilder;
@@ -0,0 +1,71 @@
1
+ import { Account, User } from '../models';
2
+ import { AccountsService } from '../modules';
3
+ /**
4
+ * TaskManager is a utility class that collects asynchronous tasks
5
+ * and executes them in parallel.
6
+ *
7
+ * This is especially useful for orchestrating optional async operations
8
+ * where not all tasks need to be executed every time.
9
+ *
10
+ * Example:
11
+ * ```ts
12
+ * const taskManager = new TaskManager();
13
+ * taskManager.add(async () => { ... });
14
+ * taskManager.add(async () => { ... });
15
+ * await taskManager.run();
16
+ * ```
17
+ */
18
+ export declare class TaskManager {
19
+ private tasks;
20
+ /**
21
+ * Fetches all users for the given ID groups and returns a mapping.
22
+ * Ensures each user is fetched only once.
23
+ * @param userIdGroups Array of arrays of user IDs (e.g., authors, likedBy, viewedBy)
24
+ * @param accountsService Service responsible for fetching users
25
+ * @returns Map of userId -> user
26
+ */
27
+ static fetchAndMapUsers(userIdGroups: string[][], accountsService: AccountsService): Promise<Map<string, User>>;
28
+ /**
29
+ * Fetches all users for the given ID groups and returns a mapping.
30
+ * Ensures each user is fetched only once.
31
+ * @param accountIdGroups Array of arrays of user IDs (e.g., authors, likedBy, viewedBy)
32
+ * @param accountsService Service responsible for fetching users
33
+ * @returns Map of userId -> user
34
+ */
35
+ static fetchAndMapAccounts(accountIdGroups: string[][], accountsService: AccountsService): Promise<Map<string, Account>>;
36
+ /**
37
+ * Maps an array of IDs to user objects using a userMap.
38
+ * Filters out any IDs that don’t have a corresponding user.
39
+ * @param userIds Array of user IDs to map
40
+ * @param userMap Map of userId -> user object
41
+ * @returns Array of user objects
42
+ */
43
+ static mapIdsToUsers(userIds: string[], userMap: Map<string, User>): any[];
44
+ /**
45
+ * Maps an array of IDs to user objects using a userMap.
46
+ * Filters out any IDs that don’t have a corresponding user.
47
+ * @param accountIds Array of user IDs to map
48
+ * @param accountMap Map of userId -> user object
49
+ * @returns Array of user objects
50
+ */
51
+ static mapIdsToAccounts(accountIds: string[], accountMap: Map<string, Account>): any[];
52
+ /**
53
+ * Adds a new asynchronous task to the queue.
54
+ *
55
+ * @param task - A function that returns a Promise.
56
+ * It should contain the actual async logic to be executed.
57
+ */
58
+ add(task: () => Promise<void>): void;
59
+ /**
60
+ * Executes all queued tasks in parallel and waits for their completion.
61
+ *
62
+ * - Tasks are executed concurrently using `Promise.all`.
63
+ * - If one task fails, the error will propagate and stop execution.
64
+ */
65
+ run(): Promise<void>;
66
+ /**
67
+ * Clears all tasks from the queue without executing them.
68
+ * Useful if you want to reset and reuse the TaskManager instance.
69
+ */
70
+ clear(): void;
71
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskManager = void 0;
4
+ /**
5
+ * TaskManager is a utility class that collects asynchronous tasks
6
+ * and executes them in parallel.
7
+ *
8
+ * This is especially useful for orchestrating optional async operations
9
+ * where not all tasks need to be executed every time.
10
+ *
11
+ * Example:
12
+ * ```ts
13
+ * const taskManager = new TaskManager();
14
+ * taskManager.add(async () => { ... });
15
+ * taskManager.add(async () => { ... });
16
+ * await taskManager.run();
17
+ * ```
18
+ */
19
+ class TaskManager {
20
+ constructor() {
21
+ this.tasks = [];
22
+ }
23
+ /**
24
+ * Fetches all users for the given ID groups and returns a mapping.
25
+ * Ensures each user is fetched only once.
26
+ * @param userIdGroups Array of arrays of user IDs (e.g., authors, likedBy, viewedBy)
27
+ * @param accountsService Service responsible for fetching users
28
+ * @returns Map of userId -> user
29
+ */
30
+ static async fetchAndMapUsers(userIdGroups, accountsService) {
31
+ // Collect all unique user IDs across all groups
32
+ const uniqueUserIds = new Set(userIdGroups.flat());
33
+ // Fetch all users once
34
+ const userCollection = await accountsService.getUserCollection(Array.from(uniqueUserIds));
35
+ // Build a map for quick lookup
36
+ return new Map(userCollection.map((user) => [user.id, user]));
37
+ }
38
+ /**
39
+ * Fetches all users for the given ID groups and returns a mapping.
40
+ * Ensures each user is fetched only once.
41
+ * @param accountIdGroups Array of arrays of user IDs (e.g., authors, likedBy, viewedBy)
42
+ * @param accountsService Service responsible for fetching users
43
+ * @returns Map of userId -> user
44
+ */
45
+ static async fetchAndMapAccounts(accountIdGroups, accountsService) {
46
+ // Collect all unique user IDs across all groups
47
+ const uniqueAccountIds = new Set(accountIdGroups.flat());
48
+ // Fetch all users once
49
+ const accountCollection = await accountsService.getAccountCollection(Array.from(uniqueAccountIds));
50
+ // Build a map for quick lookup
51
+ return new Map(accountCollection.map((account) => [account.id, account]));
52
+ }
53
+ /**
54
+ * Maps an array of IDs to user objects using a userMap.
55
+ * Filters out any IDs that don’t have a corresponding user.
56
+ * @param userIds Array of user IDs to map
57
+ * @param userMap Map of userId -> user object
58
+ * @returns Array of user objects
59
+ */
60
+ static mapIdsToUsers(userIds, userMap) {
61
+ return userIds.map((userId) => userMap.get(userId)).filter(Boolean);
62
+ }
63
+ /**
64
+ * Maps an array of IDs to user objects using a userMap.
65
+ * Filters out any IDs that don’t have a corresponding user.
66
+ * @param accountIds Array of user IDs to map
67
+ * @param accountMap Map of userId -> user object
68
+ * @returns Array of user objects
69
+ */
70
+ static mapIdsToAccounts(accountIds, accountMap) {
71
+ return accountIds.map((accountId) => accountMap.get(accountId)).filter(Boolean);
72
+ }
73
+ /**
74
+ * Adds a new asynchronous task to the queue.
75
+ *
76
+ * @param task - A function that returns a Promise.
77
+ * It should contain the actual async logic to be executed.
78
+ */
79
+ add(task) {
80
+ this.tasks.push(task);
81
+ }
82
+ /**
83
+ * Executes all queued tasks in parallel and waits for their completion.
84
+ *
85
+ * - Tasks are executed concurrently using `Promise.all`.
86
+ * - If one task fails, the error will propagate and stop execution.
87
+ */
88
+ async run() {
89
+ await Promise.all(this.tasks.map((task) => task()));
90
+ }
91
+ /**
92
+ * Clears all tasks from the queue without executing them.
93
+ * Useful if you want to reset and reuse the TaskManager instance.
94
+ */
95
+ clear() {
96
+ this.tasks = [];
97
+ }
98
+ }
99
+ exports.TaskManager = TaskManager;
@@ -4,6 +4,7 @@ exports.BadParameterException = void 0;
4
4
  exports.ApiBadParameterResponse = ApiBadParameterResponse;
5
5
  const common_1 = require("@nestjs/common");
6
6
  const swagger_1 = require("@nestjs/swagger");
7
+ const itlab_functions_1 = require("itlab-functions");
7
8
  /**
8
9
  * BadParameterException
9
10
  *
@@ -56,7 +57,7 @@ function ApiBadParameterResponse(...params) {
56
57
  // Format description with proper pluralization.
57
58
  const pluralized = params.length > 1 ? 's' : '';
58
59
  const newDescription = params.length
59
- ? `Invalid parameter${pluralized}: ${params.join(', ')}`
60
+ ? `Invalid parameter${pluralized}: ${(0, itlab_functions_1.formatList)(params, 'or')}`
60
61
  : 'One or more request parameters are invalid.';
61
62
  // Merge with existing description if needed.
62
63
  const mergedDescription = existingDescription ? `${existingDescription}<br />${newDescription}` : newDescription;
package/dist/index.d.ts CHANGED
@@ -3,13 +3,13 @@ export * from './controllers';
3
3
  export * from './decorators';
4
4
  export * from './exceptions';
5
5
  export * from './functions';
6
+ export * from './interceptors';
7
+ export * from './middleware';
6
8
  export * from './models';
7
9
  export * from './modules';
8
10
  export * from './pipes';
9
11
  export * from './properties';
10
12
  export * from './swagger';
11
13
  export * from './transform';
12
- export * from './http-logger.middleware';
14
+ export * from './types';
13
15
  export * from './hub-resource.enum';
14
- export * from './likeable.interface';
15
- export * from './viewable.interface';
package/dist/index.js CHANGED
@@ -19,13 +19,13 @@ __exportStar(require("./controllers"), exports);
19
19
  __exportStar(require("./decorators"), exports);
20
20
  __exportStar(require("./exceptions"), exports);
21
21
  __exportStar(require("./functions"), exports);
22
+ __exportStar(require("./interceptors"), exports);
23
+ __exportStar(require("./middleware"), exports);
22
24
  __exportStar(require("./models"), exports);
23
25
  __exportStar(require("./modules"), exports);
24
26
  __exportStar(require("./pipes"), exports);
25
27
  __exportStar(require("./properties"), exports);
26
28
  __exportStar(require("./swagger"), exports);
27
29
  __exportStar(require("./transform"), exports);
28
- __exportStar(require("./http-logger.middleware"), exports);
30
+ __exportStar(require("./types"), exports);
29
31
  __exportStar(require("./hub-resource.enum"), exports);
30
- __exportStar(require("./likeable.interface"), exports);
31
- __exportStar(require("./viewable.interface"), exports);
@@ -0,0 +1,68 @@
1
+ import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ /**
4
+ * AttributeSanitizerInterceptor
5
+ *
6
+ * This interceptor removes specified attributes from response objects.
7
+ * It works with plain objects, arrays, sets, maps and also with Mongoose
8
+ * documents by normalizing them to plain objects before sanitization.
9
+ *
10
+ * This is especially useful for removing sensitive fields (e.g., passwords,
11
+ * tokens, SSNs) consistently across deeply nested response structures.
12
+ */
13
+ export declare class AttributeSanitizerInterceptor implements NestInterceptor {
14
+ private readonly keys;
15
+ private readonly mongooseToObjectOptions;
16
+ /**
17
+ * @param keys - One or more property names to remove from every object.
18
+ * @param mongooseToObjectOptions - Options forwarded to Mongoose `.toObject()`
19
+ * when normalizing documents.
20
+ */
21
+ constructor(keys: string | string[], mongooseToObjectOptions?: Record<string, unknown>);
22
+ /**
23
+ * Intercepts the response and removes the targeted attributes.
24
+ *
25
+ * @param _context - Execution context (unused here).
26
+ * @param next - Next handler in the pipeline.
27
+ * @returns Sanitized response data.
28
+ */
29
+ intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown>;
30
+ /**
31
+ * Recursively removes targeted keys from objects, arrays, and nested structures.
32
+ *
33
+ * @param value - Any response value.
34
+ * @returns A sanitized value without the targeted attributes.
35
+ */
36
+ private sanitizeRecursively;
37
+ /**
38
+ * Converts a Mongoose document to a plain object if needed.
39
+ *
40
+ * @param value - Possible Mongoose document.
41
+ * @returns A plain object if conversion is possible, otherwise the value itself.
42
+ */
43
+ private normalizeIfMongooseDocument;
44
+ /**
45
+ * Checks whether a value is a plain object (and not a class instance, Date, etc.).
46
+ *
47
+ * @param value - Any value.
48
+ * @returns True if the value is a plain object.
49
+ */
50
+ private isPlainObject;
51
+ /**
52
+ * Converts the user-provided keys into a Set for O(1) lookup.
53
+ *
54
+ * @param keys - String or array of strings.
55
+ * @returns A Set of keys.
56
+ */
57
+ private ensureKeySet;
58
+ }
59
+ /**
60
+ * @UseAttributeSanitizer
61
+ *
62
+ * Convenience decorator that applies the AttributeSanitizerInterceptor
63
+ * to a route or controller.
64
+ *
65
+ * @param keys - One or more keys to remove from all response objects.
66
+ * @param mongooseToObjectOptions - Options for Mongoose `.toObject()` normalization.
67
+ */
68
+ export declare function UseAttributeSanitizer(keys: string | string[], mongooseToObjectOptions?: Record<string, unknown>): MethodDecorator & ClassDecorator;
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AttributeSanitizerInterceptor = void 0;
13
+ exports.UseAttributeSanitizer = UseAttributeSanitizer;
14
+ const common_1 = require("@nestjs/common");
15
+ const operators_1 = require("rxjs/operators");
16
+ /**
17
+ * AttributeSanitizerInterceptor
18
+ *
19
+ * This interceptor removes specified attributes from response objects.
20
+ * It works with plain objects, arrays, sets, maps and also with Mongoose
21
+ * documents by normalizing them to plain objects before sanitization.
22
+ *
23
+ * This is especially useful for removing sensitive fields (e.g., passwords,
24
+ * tokens, SSNs) consistently across deeply nested response structures.
25
+ */
26
+ let AttributeSanitizerInterceptor = class AttributeSanitizerInterceptor {
27
+ /**
28
+ * @param keys - One or more property names to remove from every object.
29
+ * @param mongooseToObjectOptions - Options forwarded to Mongoose `.toObject()`
30
+ * when normalizing documents.
31
+ */
32
+ constructor(keys, mongooseToObjectOptions = {
33
+ virtuals: true,
34
+ getters: true,
35
+ versionKey: false,
36
+ }) {
37
+ this.keys = keys;
38
+ this.mongooseToObjectOptions = mongooseToObjectOptions;
39
+ }
40
+ /**
41
+ * Intercepts the response and removes the targeted attributes.
42
+ *
43
+ * @param _context - Execution context (unused here).
44
+ * @param next - Next handler in the pipeline.
45
+ * @returns Sanitized response data.
46
+ */
47
+ intercept(_context, next) {
48
+ return next.handle().pipe((0, operators_1.map)((data) => this.sanitizeRecursively(data)));
49
+ }
50
+ /**
51
+ * Recursively removes targeted keys from objects, arrays, and nested structures.
52
+ *
53
+ * @param value - Any response value.
54
+ * @returns A sanitized value without the targeted attributes.
55
+ */
56
+ sanitizeRecursively(value) {
57
+ const normalized = this.normalizeIfMongooseDocument(value);
58
+ if (normalized === null || typeof normalized !== 'object') {
59
+ return normalized;
60
+ }
61
+ // Handle arrays
62
+ if (Array.isArray(normalized)) {
63
+ return normalized.map((item) => this.sanitizeRecursively(item));
64
+ }
65
+ // Handle Set → array
66
+ if (normalized instanceof Set) {
67
+ return Array.from(normalized).map((item) => this.sanitizeRecursively(item));
68
+ }
69
+ // Handle Map → plain object
70
+ if (normalized instanceof Map) {
71
+ const obj = {};
72
+ for (const [k, v] of normalized.entries()) {
73
+ obj[String(k)] = this.sanitizeRecursively(v);
74
+ }
75
+ return obj;
76
+ }
77
+ // Handle plain objects
78
+ if (this.isPlainObject(normalized)) {
79
+ const forbidden = this.ensureKeySet(this.keys);
80
+ const result = {};
81
+ for (const [prop, nested] of Object.entries(normalized)) {
82
+ if (forbidden.has(prop))
83
+ continue;
84
+ result[prop] = this.sanitizeRecursively(nested);
85
+ }
86
+ return result;
87
+ }
88
+ // Other complex types (Date, Buffer, etc.) → leave unchanged
89
+ return normalized;
90
+ }
91
+ /**
92
+ * Converts a Mongoose document to a plain object if needed.
93
+ *
94
+ * @param value - Possible Mongoose document.
95
+ * @returns A plain object if conversion is possible, otherwise the value itself.
96
+ */
97
+ normalizeIfMongooseDocument(value) {
98
+ // Heuristic: Mongoose documents expose `.toObject()` and an internal `$__`.
99
+ // We avoid importing `mongoose` to keep this interceptor framework-agnostic.
100
+ const maybeDoc = value;
101
+ if (maybeDoc &&
102
+ typeof maybeDoc === 'object' &&
103
+ (typeof maybeDoc.toObject === 'function' || typeof maybeDoc.toJSON === 'function') &&
104
+ '$__' in maybeDoc) {
105
+ try {
106
+ if (typeof maybeDoc.toObject === 'function')
107
+ return maybeDoc.toObject(this.mongooseToObjectOptions);
108
+ if (typeof maybeDoc.toJSON === 'function')
109
+ return maybeDoc.toJSON(this.mongooseToObjectOptions);
110
+ }
111
+ catch (_a) {
112
+ // If normalization fails, fall back to the original value—better to
113
+ // return data than to throw at the last step.
114
+ return value;
115
+ }
116
+ }
117
+ return value;
118
+ }
119
+ /**
120
+ * Checks whether a value is a plain object (and not a class instance, Date, etc.).
121
+ *
122
+ * @param value - Any value.
123
+ * @returns True if the value is a plain object.
124
+ */
125
+ isPlainObject(value) {
126
+ return Object.prototype.toString.call(value) === '[object Object]';
127
+ }
128
+ /**
129
+ * Converts the user-provided keys into a Set for O(1) lookup.
130
+ *
131
+ * @param keys - String or array of strings.
132
+ * @returns A Set of keys.
133
+ */
134
+ ensureKeySet(keys) {
135
+ return Array.isArray(keys) ? new Set(keys) : new Set([keys]);
136
+ }
137
+ };
138
+ exports.AttributeSanitizerInterceptor = AttributeSanitizerInterceptor;
139
+ exports.AttributeSanitizerInterceptor = AttributeSanitizerInterceptor = __decorate([
140
+ (0, common_1.Injectable)(),
141
+ __metadata("design:paramtypes", [Object, Object])
142
+ ], AttributeSanitizerInterceptor);
143
+ /**
144
+ * @UseAttributeSanitizer
145
+ *
146
+ * Convenience decorator that applies the AttributeSanitizerInterceptor
147
+ * to a route or controller.
148
+ *
149
+ * @param keys - One or more keys to remove from all response objects.
150
+ * @param mongooseToObjectOptions - Options for Mongoose `.toObject()` normalization.
151
+ */
152
+ function UseAttributeSanitizer(keys, mongooseToObjectOptions) {
153
+ return (0, common_1.UseInterceptors)(new AttributeSanitizerInterceptor(keys, mongooseToObjectOptions));
154
+ }