@lenne.tech/nest-server 11.1.14 → 11.3.0

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 (77) hide show
  1. package/dist/core/common/decorators/unified-field.decorator.d.ts +2 -0
  2. package/dist/core/common/decorators/unified-field.decorator.js +26 -9
  3. package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
  4. package/dist/core/common/helpers/gridfs.helper.d.ts +42 -0
  5. package/dist/core/common/helpers/gridfs.helper.js +107 -0
  6. package/dist/core/common/helpers/gridfs.helper.js.map +1 -0
  7. package/dist/core/common/models/core-persistence.model.js +2 -2
  8. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  9. package/dist/core/common/services/brevo.service.d.ts +1 -0
  10. package/dist/core/common/services/brevo.service.js +19 -18
  11. package/dist/core/common/services/brevo.service.js.map +1 -1
  12. package/dist/core/common/services/crud.service.js +1 -1
  13. package/dist/core/common/services/crud.service.js.map +1 -1
  14. package/dist/core/modules/file/core-file-info.model.js +41 -23
  15. package/dist/core/modules/file/core-file-info.model.js.map +1 -1
  16. package/dist/core/modules/file/core-file.controller.d.ts +2 -1
  17. package/dist/core/modules/file/core-file.controller.js +1 -1
  18. package/dist/core/modules/file/core-file.controller.js.map +1 -1
  19. package/dist/core/modules/file/core-file.service.d.ts +7 -7
  20. package/dist/core/modules/file/core-file.service.js +28 -51
  21. package/dist/core/modules/file/core-file.service.js.map +1 -1
  22. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +1 -1
  23. package/dist/core/modules/user/core-user.model.js +95 -54
  24. package/dist/core/modules/user/core-user.model.js.map +1 -1
  25. package/dist/core/modules/user/core-user.service.js +2 -3
  26. package/dist/core/modules/user/core-user.service.js.map +1 -1
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/main.js +22 -0
  31. package/dist/main.js.map +1 -1
  32. package/dist/server/common/models/persistence.model.js +13 -11
  33. package/dist/server/common/models/persistence.model.js.map +1 -1
  34. package/dist/server/modules/auth/auth.model.js +6 -2
  35. package/dist/server/modules/auth/auth.model.js.map +1 -1
  36. package/dist/server/modules/file/file-info.model.d.ts +7 -3
  37. package/dist/server/modules/file/file.controller.d.ts +6 -4
  38. package/dist/server/modules/file/file.controller.js +15 -4
  39. package/dist/server/modules/file/file.controller.js.map +1 -1
  40. package/dist/server/modules/file/file.resolver.js +1 -1
  41. package/dist/server/modules/file/file.resolver.js.map +1 -1
  42. package/dist/server/modules/file/multer-config.service.d.ts +0 -2
  43. package/dist/server/modules/file/multer-config.service.js +3 -22
  44. package/dist/server/modules/file/multer-config.service.js.map +1 -1
  45. package/dist/server/modules/user/user.controller.d.ts +19 -0
  46. package/dist/server/modules/user/user.controller.js +256 -0
  47. package/dist/server/modules/user/user.controller.js.map +1 -0
  48. package/dist/server/modules/user/user.model.d.ts +7 -3
  49. package/dist/server/modules/user/user.model.js +37 -24
  50. package/dist/server/modules/user/user.model.js.map +1 -1
  51. package/dist/server/modules/user/user.module.js +2 -1
  52. package/dist/server/modules/user/user.module.js.map +1 -1
  53. package/dist/tsconfig.build.tsbuildinfo +1 -1
  54. package/dist/types/graphql-upload.d.ts +25 -0
  55. package/package.json +41 -44
  56. package/src/core/common/decorators/unified-field.decorator.ts +49 -10
  57. package/src/core/common/helpers/gridfs.helper.ts +227 -0
  58. package/src/core/common/models/core-persistence.model.ts +3 -3
  59. package/src/core/common/services/brevo.service.ts +20 -18
  60. package/src/core/common/services/crud.service.ts +3 -3
  61. package/src/core/modules/file/core-file-info.model.ts +40 -22
  62. package/src/core/modules/file/core-file.controller.ts +3 -2
  63. package/src/core/modules/file/core-file.service.ts +49 -60
  64. package/src/core/modules/file/interfaces/file-upload.interface.ts +2 -1
  65. package/src/core/modules/user/core-user.model.ts +120 -78
  66. package/src/core/modules/user/core-user.service.ts +3 -3
  67. package/src/index.ts +1 -0
  68. package/src/main.ts +25 -0
  69. package/src/server/common/models/persistence.model.ts +15 -13
  70. package/src/server/modules/auth/auth.model.ts +7 -3
  71. package/src/server/modules/file/file.controller.ts +25 -7
  72. package/src/server/modules/file/file.resolver.ts +1 -2
  73. package/src/server/modules/file/multer-config.service.ts +6 -21
  74. package/src/server/modules/user/user.controller.ts +242 -0
  75. package/src/server/modules/user/user.model.ts +39 -26
  76. package/src/server/modules/user/user.module.ts +2 -1
  77. package/src/types/graphql-upload.d.ts +25 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Type definitions for graphql-upload
3
+ * graphql-upload uses deep imports, so we declare types for the specific modules
4
+ */
5
+
6
+ declare module 'graphql-upload/graphqlUploadExpress.js' {
7
+ import { RequestHandler } from 'express';
8
+
9
+ interface GraphQLUploadOptions {
10
+ maxFiles?: number;
11
+ maxFileSize?: number;
12
+ }
13
+
14
+ function graphqlUploadExpress(options?: GraphQLUploadOptions): RequestHandler;
15
+
16
+ export = graphqlUploadExpress;
17
+ }
18
+
19
+ declare module 'graphql-upload/GraphQLUpload.js' {
20
+ import { GraphQLScalarType } from 'graphql';
21
+
22
+ const GraphQLUpload: GraphQLScalarType;
23
+
24
+ export = GraphQLUpload;
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.1.14",
3
+ "version": "11.3.0",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -14,7 +14,9 @@
14
14
  "homepage": "https://github.com/lenneTech/nest-server",
15
15
  "license": "MIT",
16
16
  "scripts": {
17
- "build": "rimraf dist && nest build",
17
+ "build": "rimraf dist && nest build && npm run build:copy-types && npm run build:add-type-references",
18
+ "build:copy-types": "mkdir -p dist/types && cp src/types/*.d.ts dist/types/",
19
+ "build:add-type-references": "node scripts/add-type-references.js",
18
20
  "build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
19
21
  "build:dev": "npm run build && yalc push --private",
20
22
  "docs": "npm run docs:ci && open http://127.0.0.1:8080/ && open ./public/index.html && compodoc -p tsconfig.json -s ",
@@ -65,79 +67,74 @@
65
67
  "node": ">= 20"
66
68
  },
67
69
  "dependencies": {
68
- "@apollo/gateway": "2.11.2",
69
- "@getbrevo/brevo": "1.0.1",
70
- "@lenne.tech/mongoose-gridfs": "1.4.2",
71
- "@lenne.tech/multer-gridfs-storage": "5.0.8",
70
+ "@apollo/gateway": "2.12.0",
71
+ "@getbrevo/brevo": "3.0.1",
72
72
  "@nestjs/apollo": "13.1.0",
73
- "@nestjs/common": "11.1.6",
74
- "@nestjs/core": "11.1.6",
73
+ "@nestjs/common": "11.1.8",
74
+ "@nestjs/core": "11.1.8",
75
75
  "@nestjs/graphql": "13.1.0",
76
- "@nestjs/jwt": "11.0.0",
76
+ "@nestjs/jwt": "11.0.1",
77
77
  "@nestjs/mongoose": "11.0.3",
78
78
  "@nestjs/passport": "11.0.5",
79
- "@nestjs/platform-express": "11.1.6",
79
+ "@nestjs/platform-express": "11.1.8",
80
80
  "@nestjs/schedule": "6.0.1",
81
- "@nestjs/swagger": "11.2.0",
81
+ "@nestjs/swagger": "11.2.1",
82
82
  "@nestjs/terminus": "11.0.0",
83
83
  "apollo-server-core": "3.13.0",
84
84
  "apollo-server-express": "3.13.0",
85
- "bcrypt": "5.1.1",
85
+ "bcrypt": "6.0.0",
86
86
  "class-transformer": "0.5.1",
87
87
  "class-validator": "0.14.2",
88
88
  "compression": "1.8.1",
89
89
  "cookie-parser": "1.4.7",
90
90
  "dotenv": "17.2.3",
91
91
  "ejs": "3.1.10",
92
- "graphql": "16.11.0",
92
+ "graphql": "16.12.0",
93
93
  "graphql-query-complexity": "1.1.0",
94
94
  "graphql-subscriptions": "3.0.0",
95
95
  "graphql-upload": "15.0.2",
96
96
  "js-sha256": "0.11.1",
97
97
  "json-to-graphql-query": "2.3.0",
98
- "light-my-request": "6.6.0",
99
98
  "lodash": "4.17.21",
100
- "mongodb": "6.20.0",
101
- "mongoose": "7.8.7",
102
- "multer": "1.4.5-lts.2",
103
- "node-mailjet": "6.0.9",
104
- "nodemailer": "7.0.9",
105
- "nodemon": "3.1.10",
99
+ "mongodb": "7.0.0",
100
+ "mongoose": "8.19.3",
101
+ "multer": "2.0.2",
102
+ "node-mailjet": "6.0.11",
103
+ "nodemailer": "7.0.10",
106
104
  "passport": "0.7.0",
107
105
  "passport-jwt": "4.0.1",
108
106
  "reflect-metadata": "0.2.2",
109
107
  "rfdc": "1.4.1",
110
- "rimraf": "6.0.1",
111
108
  "rxjs": "7.8.2",
112
109
  "yuml-diagram": "1.2.0"
113
110
  },
114
111
  "devDependencies": {
115
112
  "@babel/plugin-proposal-private-methods": "7.18.6",
116
- "@compodoc/compodoc": "1.1.31",
117
- "@lenne.tech/eslint-config-ts": "2.1.3",
113
+ "@compodoc/compodoc": "1.1.32",
114
+ "@lenne.tech/eslint-config-ts": "2.1.4",
118
115
  "@nestjs/cli": "11.0.10",
119
- "@nestjs/schematics": "11.0.8",
120
- "@nestjs/testing": "11.1.6",
121
- "@swc/cli": "0.7.8",
122
- "@swc/core": "1.13.5",
116
+ "@nestjs/schematics": "11.0.9",
117
+ "@nestjs/testing": "11.1.8",
118
+ "@swc/cli": "0.7.9",
119
+ "@swc/core": "1.15.0",
123
120
  "@swc/jest": "0.2.39",
124
121
  "@types/compression": "1.8.1",
125
- "@types/cookie-parser": "1.4.9",
122
+ "@types/cookie-parser": "1.4.10",
126
123
  "@types/ejs": "3.1.5",
127
124
  "@types/express": "4.17.21",
128
- "@types/jest": "29.5.14",
125
+ "@types/jest": "30.0.0",
129
126
  "@types/lodash": "4.17.20",
130
- "@types/multer": "1.4.12",
131
- "@types/node": "22.15.17",
132
- "@types/nodemailer": "6.4.17",
127
+ "@types/multer": "2.0.0",
128
+ "@types/node": "24.10.0",
129
+ "@types/nodemailer": "7.0.3",
133
130
  "@types/passport": "1.0.17",
134
131
  "@types/supertest": "6.0.3",
135
- "@typescript-eslint/eslint-plugin": "8.46.0",
136
- "@typescript-eslint/parser": "8.46.0",
132
+ "@typescript-eslint/eslint-plugin": "8.46.3",
133
+ "@typescript-eslint/parser": "8.46.3",
137
134
  "coffeescript": "2.7.0",
138
- "eslint": "9.37.0",
135
+ "eslint": "9.39.1",
139
136
  "eslint-config-prettier": "10.1.8",
140
- "eslint-plugin-unused-imports": "4.2.0",
137
+ "eslint-plugin-unused-imports": "4.3.0",
141
138
  "find-file-up": "2.0.1",
142
139
  "grunt": "1.6.1",
143
140
  "grunt-bg-shell": "2.3.3",
@@ -145,28 +142,28 @@
145
142
  "grunt-contrib-watch": "1.1.0",
146
143
  "grunt-sync": "0.8.2",
147
144
  "husky": "9.1.7",
148
- "jest": "29.7.0",
145
+ "jest": "30.2.0",
146
+ "nodemon": "3.1.10",
149
147
  "npm-watch": "0.13.0",
150
148
  "pm2": "6.0.13",
151
149
  "prettier": "3.6.2",
152
150
  "pretty-quick": "4.2.2",
151
+ "rimraf": "6.1.0",
153
152
  "supertest": "7.1.4",
154
- "ts-jest": "29.4.4",
153
+ "ts-jest": "29.4.5",
155
154
  "ts-loader": "9.5.4",
156
- "ts-morph": "25.0.1",
155
+ "ts-morph": "27.0.2",
157
156
  "ts-node": "10.9.2",
158
157
  "tsconfig-paths": "4.2.0",
159
- "typescript": "5.8.3",
158
+ "typescript": "5.9.3",
160
159
  "yalc": "1.0.0-pre.53"
161
160
  },
162
161
  "overrides": {
163
- "multer-gridfs-storage": {
164
- "multer": "$multer"
165
- },
166
162
  "@lykmapipo/common": {
167
163
  "flat": "5.0.2",
168
164
  "mime": "2.6.0"
169
- }
165
+ },
166
+ "ts-morph": "27.0.2"
170
167
  },
171
168
  "jest": {
172
169
  "collectCoverage": true,
@@ -1,4 +1,5 @@
1
1
  import { Field, FieldOptions } from '@nestjs/graphql';
2
+ import { Prop, PropOptions } from '@nestjs/mongoose';
2
3
  import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
3
4
  import { EnumAllowedTypes } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface';
4
5
  import { Type } from 'class-transformer';
@@ -38,6 +39,8 @@ export interface UnifiedFieldOptions {
38
39
  isArray?: boolean;
39
40
  /** Default: false */
40
41
  isOptional?: boolean;
42
+ /** Whether to apply Mongoose @Prop decorator. Optional, used for database models. Default: false */
43
+ mongoose?: boolean | PropOptions;
41
44
  /** Restricted roles */
42
45
  roles?: RestrictedType | RoleEnum | RoleEnum[];
43
46
  /** Options for swagger api documentation */
@@ -48,8 +51,12 @@ export interface UnifiedFieldOptions {
48
51
  *
49
52
  * Enums should be defined via the enum option.
50
53
  *
54
+ * For array fields, you can use either:
55
+ * - `type: () => ItemType` (recommended, decorator adds array wrapping automatically)
56
+ * - `type: () => [ItemType]` (also supported, decorator extracts ItemType to avoid double-nesting)
57
+ *
51
58
  * Supports:
52
- * - A factory function that returns the type: `() => MyType`
59
+ * - A factory function that returns the type: `() => MyType` or `() => [MyType]`
53
60
  * - A GraphQL scalar: `GraphQLScalarType`
54
61
  * - A class constructor: `MyClass`
55
62
  * */
@@ -96,7 +103,16 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
96
103
  return metadataType;
97
104
  };
98
105
 
99
- const baseType = resolvedTypeFn();
106
+ // Resolve the type and extract item type if user provided array syntax
107
+ const resolvedType = resolvedTypeFn();
108
+
109
+ // If this is an array field and the user provided type: () => [ItemType],
110
+ // extract the ItemType to avoid double-nesting (e.g., [[ItemType]])
111
+ // We check: isArrayField (should be array) && Array.isArray (is actually an array) && length === 1 (GraphQL array syntax)
112
+ const baseType =
113
+ isArrayField && Array.isArray(resolvedType) && resolvedType.length === 1
114
+ ? resolvedType[0] // Extract item type from [ItemType]
115
+ : resolvedType; // Use as-is
100
116
 
101
117
  // Prepare merged options
102
118
  const gqlOpts: FieldOptions = { ...opts.gqlOptions };
@@ -161,8 +177,14 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
161
177
  }
162
178
 
163
179
  // Type function for gql
180
+ // We need to keep the factory pattern (calling resolvedTypeFn inside the arrow function)
181
+ // to support circular references. But we also need to extract array item types to avoid double-nesting.
164
182
  const gqlTypeFn = isArrayField
165
- ? () => [opts.enum?.enum || opts.gqlType || resolvedTypeFn()]
183
+ ? () => {
184
+ const resolved = opts.enum?.enum || opts.gqlType || resolvedTypeFn();
185
+ // Extract item type if user provided [ItemType] syntax to avoid [[ItemType]]
186
+ return [Array.isArray(resolved) && resolved.length === 1 ? resolved[0] : resolved];
187
+ }
166
188
  : () => opts.enum?.enum || opts.gqlType || resolvedTypeFn();
167
189
 
168
190
  // Gql decorator
@@ -178,7 +200,7 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
178
200
 
179
201
  // Completely skip validation if its any
180
202
  if (opts.validator) {
181
- opts.validator(valOpts).forEach(d => d(target, propertyKey));
203
+ opts.validator(valOpts).forEach((d) => d(target, propertyKey));
182
204
  } else if (!opts.isAny) {
183
205
  const validator = getBuiltInValidator(baseType, valOpts, isArrayField, target);
184
206
  if (validator) {
@@ -199,6 +221,23 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
199
221
  const rolesArr = Array.isArray(opts.roles) ? opts.roles : [opts.roles];
200
222
  Restricted(...rolesArr)(target, propertyKey);
201
223
  }
224
+
225
+ // Mongoose @Prop decorator (optional)
226
+ if (opts.mongoose) {
227
+ const propOptions: any = typeof opts.mongoose === 'object' ? opts.mongoose : {};
228
+
229
+ // Set type for Prop if not already defined in propOptions
230
+ if (typeof propOptions === 'object' && !Array.isArray(propOptions) && !propOptions.type && baseType) {
231
+ propOptions.type = baseType;
232
+ }
233
+
234
+ // Apply array type if needed
235
+ if (typeof propOptions === 'object' && !Array.isArray(propOptions) && isArrayField && !propOptions.type) {
236
+ propOptions.type = [baseType];
237
+ }
238
+
239
+ Prop(propOptions)(target, propertyKey);
240
+ }
202
241
  };
203
242
  }
204
243
 
@@ -228,12 +267,12 @@ function getBuiltInValidator(
228
267
  function isGraphQLScalar(type: any): boolean {
229
268
  // CustomScalar check (The CustomScalar interface implements these functions below)
230
269
  return (
231
- (type
232
- && typeof type === 'function'
233
- && typeof type.prototype?.serialize === 'function'
234
- && typeof type.prototype?.parseValue === 'function'
235
- && typeof type.prototype?.parseLiteral === 'function')
236
- || type instanceof GraphQLScalarType
270
+ (type &&
271
+ typeof type === 'function' &&
272
+ typeof type.prototype?.serialize === 'function' &&
273
+ typeof type.prototype?.parseValue === 'function' &&
274
+ typeof type.prototype?.parseLiteral === 'function') ||
275
+ type instanceof GraphQLScalarType
237
276
  );
238
277
  }
239
278
 
@@ -0,0 +1,227 @@
1
+ import { mongo, Types } from 'mongoose';
2
+ import { Readable } from 'stream';
3
+
4
+ // Use Mongoose's MongoDB types to avoid BSON version conflicts
5
+ const ObjectId = Types.ObjectId;
6
+
7
+ /**
8
+ * GridFS File Info interface matching the structure from GridFS
9
+ * This is the normalized structure with contentType at root level
10
+ */
11
+ export interface GridFSFileInfo {
12
+ _id: Types.ObjectId;
13
+ chunkSize: number;
14
+ /**
15
+ * Content type of the file
16
+ * Note: Stored in metadata.contentType in MongoDB, normalized to root level by helper
17
+ */
18
+ contentType?: string;
19
+ filename: string;
20
+ length: number;
21
+ metadata?: Record<string, any> & { contentType?: string };
22
+ uploadDate: Date;
23
+ }
24
+
25
+ /**
26
+ * Options for reading files from GridFS
27
+ */
28
+ export interface GridFSReadOptions {
29
+ _id?: string | Types.ObjectId;
30
+ filename?: string;
31
+ }
32
+ /**
33
+ * Options for writing files to GridFS
34
+ */
35
+ export interface GridFSWriteOptions {
36
+ contentType?: string;
37
+ filename: string;
38
+ metadata?: Record<string, any>;
39
+ }
40
+
41
+ type GridFSBucket = mongo.GridFSBucket;
42
+
43
+ type GridFSBucketReadStream = mongo.GridFSBucketReadStream;
44
+
45
+ /**
46
+ * Raw GridFS file structure as returned by MongoDB
47
+ * This is the internal structure before normalization
48
+ */
49
+ interface RawGridFSFile {
50
+ _id: Types.ObjectId;
51
+ chunkSize: number;
52
+ filename: string;
53
+ length: number;
54
+ metadata?: Record<string, any> & { contentType?: string };
55
+ uploadDate: Date;
56
+ }
57
+
58
+ /**
59
+ * Helper class for GridFS operations using native MongoDB driver
60
+ * Provides Promise-based API for all GridFS operations
61
+ */
62
+ export class GridFSHelper {
63
+ /**
64
+ * Normalize file info to ensure contentType is accessible at root level
65
+ * MongoDB stores contentType in metadata, but our API expects it at root
66
+ */
67
+ private static normalizeFileInfo(fileInfo: RawGridFSFile): GridFSFileInfo {
68
+ return {
69
+ ...fileInfo,
70
+ contentType: fileInfo.metadata?.contentType,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Write a file to GridFS from a stream
76
+ */
77
+ static writeFileFromStream(
78
+ bucket: GridFSBucket,
79
+ stream: Readable,
80
+ options: GridFSWriteOptions,
81
+ ): Promise<GridFSFileInfo> {
82
+ return new Promise((resolve, reject) => {
83
+ // Store contentType in metadata to avoid deprecation warning
84
+ const metadata = { ...options.metadata };
85
+ if (options.contentType) {
86
+ metadata.contentType = options.contentType;
87
+ }
88
+
89
+ const uploadStream = bucket.openUploadStream(options.filename, {
90
+ metadata,
91
+ });
92
+
93
+ uploadStream.on('error', (error) => {
94
+ reject(error);
95
+ });
96
+
97
+ uploadStream.on('finish', () => {
98
+ // Fetch the file info after upload completes
99
+ bucket
100
+ .find({ _id: uploadStream.id })
101
+ .toArray()
102
+ .then((files) => {
103
+ if (files && files.length > 0) {
104
+ resolve(GridFSHelper.normalizeFileInfo(files[0]));
105
+ } else {
106
+ reject(new Error('File uploaded but metadata not found'));
107
+ }
108
+ })
109
+ .catch(reject);
110
+ });
111
+
112
+ stream.pipe(uploadStream);
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Read a file from GridFS to a buffer
118
+ */
119
+ static readFileToBuffer(bucket: GridFSBucket, options: GridFSReadOptions): Promise<Buffer> {
120
+ return new Promise((resolve, reject) => {
121
+ const chunks: Buffer[] = [];
122
+ let downloadStream: GridFSBucketReadStream;
123
+
124
+ if (options._id) {
125
+ const objectId = typeof options._id === 'string' ? new ObjectId(options._id) : options._id;
126
+ downloadStream = bucket.openDownloadStream(objectId);
127
+ } else if (options.filename) {
128
+ downloadStream = bucket.openDownloadStreamByName(options.filename);
129
+ } else {
130
+ return reject(new Error('Either _id or filename must be provided'));
131
+ }
132
+
133
+ downloadStream.on('data', (chunk) => {
134
+ chunks.push(chunk);
135
+ });
136
+
137
+ downloadStream.on('error', (error) => {
138
+ reject(error);
139
+ });
140
+
141
+ downloadStream.on('end', () => {
142
+ resolve(Buffer.concat(chunks));
143
+ });
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Find file metadata by ID
149
+ */
150
+ static async findFileById(bucket: GridFSBucket, id: string | Types.ObjectId): Promise<GridFSFileInfo | null> {
151
+ const objectId = typeof id === 'string' ? new ObjectId(id) : id;
152
+ const files = await bucket.find({ _id: objectId }).toArray();
153
+ return files.length > 0 ? GridFSHelper.normalizeFileInfo(files[0]) : null;
154
+ }
155
+
156
+ /**
157
+ * Find file metadata by filename
158
+ */
159
+ static async findFileByName(bucket: GridFSBucket, filename: string): Promise<GridFSFileInfo | null> {
160
+ const files = await bucket.find({ filename }).toArray();
161
+ return files.length > 0 ? GridFSHelper.normalizeFileInfo(files[0]) : null;
162
+ }
163
+
164
+ /**
165
+ * Find files with filter and options
166
+ */
167
+ static async findFiles(bucket: GridFSBucket, filter: any = {}, options: any = {}): Promise<GridFSFileInfo[]> {
168
+ const files = await bucket.find(filter, options).toArray();
169
+ return files.map(file => GridFSHelper.normalizeFileInfo(file));
170
+ }
171
+
172
+ /**
173
+ * Delete a file from GridFS
174
+ */
175
+ static async deleteFile(bucket: GridFSBucket, id: string | Types.ObjectId): Promise<void> {
176
+ const objectId = typeof id === 'string' ? new ObjectId(id) : id;
177
+ await bucket.delete(objectId);
178
+ }
179
+
180
+ /**
181
+ * Open download stream by ID
182
+ */
183
+ static openDownloadStream(bucket: GridFSBucket, id: string | Types.ObjectId): GridFSBucketReadStream {
184
+ const objectId = typeof id === 'string' ? new ObjectId(id) : id;
185
+ return bucket.openDownloadStream(objectId);
186
+ }
187
+
188
+ /**
189
+ * Open download stream by name
190
+ */
191
+ static openDownloadStreamByName(bucket: GridFSBucket, filename: string): GridFSBucketReadStream {
192
+ return bucket.openDownloadStreamByName(filename);
193
+ }
194
+
195
+ /**
196
+ * Open upload stream
197
+ */
198
+ static openUploadStream(
199
+ bucket: GridFSBucket,
200
+ filename: string,
201
+ options?: { contentType?: string },
202
+ ): mongo.GridFSBucketWriteStream {
203
+ // Store contentType in metadata to avoid deprecation warning
204
+ if (options?.contentType) {
205
+ const metadata = { contentType: options.contentType };
206
+ return bucket.openUploadStream(filename, { metadata });
207
+ }
208
+ return bucket.openUploadStream(filename, options);
209
+ }
210
+
211
+ /**
212
+ * Open upload stream with specific ID
213
+ */
214
+ static openUploadStreamWithId(
215
+ bucket: GridFSBucket,
216
+ id: Types.ObjectId,
217
+ filename: string,
218
+ options?: { contentType?: string },
219
+ ): mongo.GridFSBucketWriteStream {
220
+ // Store contentType in metadata to avoid deprecation warning
221
+ if (options?.contentType) {
222
+ const metadata = { contentType: options.contentType };
223
+ return bucket.openUploadStreamWithId(id, filename, { metadata });
224
+ }
225
+ return bucket.openUploadStreamWithId(id, filename, options);
226
+ }
227
+ }
@@ -1,5 +1,5 @@
1
1
  import { ObjectType } from '@nestjs/graphql';
2
- import { Prop, Schema } from '@nestjs/mongoose';
2
+ import { Schema } from '@nestjs/mongoose';
3
3
  import { Types } from 'mongoose';
4
4
 
5
5
  import { Restricted } from '../decorators/restricted.decorator';
@@ -85,10 +85,10 @@ export abstract class CorePersistenceModel extends CoreModel {
85
85
  /**
86
86
  * Created date, is set automatically by mongoose
87
87
  */
88
- @Prop()
89
88
  @UnifiedField({
90
89
  description: 'Created date',
91
90
  isOptional: true,
91
+ mongoose: true,
92
92
  roles: RoleEnum.S_EVERYONE,
93
93
  swaggerApiOptions: { example: 1740037703939, format: 'int64', type: Date },
94
94
  type: Date,
@@ -98,10 +98,10 @@ export abstract class CorePersistenceModel extends CoreModel {
98
98
  /**
99
99
  * Updated date is set automatically by mongoose
100
100
  */
101
- @Prop()
102
101
  @UnifiedField({
103
102
  description: 'Updated date',
104
103
  isOptional: true,
104
+ mongoose: true,
105
105
  roles: RoleEnum.S_EVERYONE,
106
106
  swaggerApiOptions: { example: 1740037703939, format: 'int64', type: Date },
107
107
  type: Date,
@@ -1,4 +1,4 @@
1
- import brevo = require('@getbrevo/brevo');
1
+ import { SendSmtpEmail, TransactionalEmailsApi, TransactionalEmailsApiApiKeys } from '@getbrevo/brevo';
2
2
  import { Injectable } from '@nestjs/common';
3
3
 
4
4
  import { ConfigService } from './config.service';
@@ -9,15 +9,15 @@ import { ConfigService } from './config.service';
9
9
  @Injectable()
10
10
  export class BrevoService {
11
11
  brevoConfig: ConfigService['configFastButReadOnly']['brevo'];
12
+ private apiInstance: TransactionalEmailsApi;
12
13
 
13
14
  constructor(protected configService: ConfigService) {
14
- const defaultClient = brevo.ApiClient.instance;
15
- const apiKey = defaultClient.authentications['api-key'];
16
15
  this.brevoConfig = configService.configFastButReadOnly.brevo;
17
16
  if (!this.brevoConfig) {
18
17
  throw new Error('Brevo configuration not set!');
19
18
  }
20
- apiKey.apiKey = this.brevoConfig.apiKey;
19
+ this.apiInstance = new TransactionalEmailsApi();
20
+ this.apiInstance.setApiKey(TransactionalEmailsApiApiKeys.apiKey, this.brevoConfig.apiKey);
21
21
  }
22
22
 
23
23
  /**
@@ -37,14 +37,15 @@ export class BrevoService {
37
37
  }
38
38
 
39
39
  // Prepare data
40
- const apiInstance = new brevo.TransactionalEmailsApi();
41
- const sendSmtpEmail = new brevo.SendSmtpEmail();
42
- sendSmtpEmail.templateId = templateId;
43
- sendSmtpEmail.to = [{ email: to }];
44
- sendSmtpEmail.params = params;
40
+ const sendSmtpEmail: SendSmtpEmail = {
41
+ params,
42
+ templateId,
43
+ to: [{ email: to }],
44
+ };
45
45
 
46
46
  // Send email
47
- return await apiInstance.sendTransacEmail(sendSmtpEmail);
47
+ const result = await this.apiInstance.sendTransacEmail(sendSmtpEmail);
48
+ return result.body;
48
49
  } catch (error) {
49
50
  console.error(error);
50
51
  }
@@ -75,16 +76,17 @@ export class BrevoService {
75
76
  }
76
77
 
77
78
  // Prepare data
78
- const apiInstance = new brevo.TransactionalEmailsApi();
79
- const sendSmtpEmail = new brevo.SendSmtpEmail();
80
- sendSmtpEmail.htmlContent = html;
81
- sendSmtpEmail.params = options?.params;
82
- sendSmtpEmail.sender = this.brevoConfig.sender;
83
- sendSmtpEmail.subject = subject;
84
- sendSmtpEmail.to = [{ email: to }];
79
+ const sendSmtpEmail: SendSmtpEmail = {
80
+ htmlContent: html,
81
+ params: options?.params,
82
+ sender: this.brevoConfig.sender,
83
+ subject,
84
+ to: [{ email: to }],
85
+ };
85
86
 
86
87
  // Send email
87
- return await apiInstance.sendTransacEmail(sendSmtpEmail);
88
+ const result = await this.apiInstance.sendTransacEmail(sendSmtpEmail);
89
+ return result.body;
88
90
  } catch (error) {
89
91
  console.error(error);
90
92
  }
@@ -105,7 +105,7 @@ export abstract class CrudService<
105
105
  * Get distinct values of a property
106
106
  */
107
107
  distinct(property: string): Promise<string[]> {
108
- return this.mainDbModel.distinct(property);
108
+ return this.mainDbModel.distinct(property).exec() as Promise<string[]>;
109
109
  }
110
110
 
111
111
  /**
@@ -178,7 +178,7 @@ export abstract class CrudService<
178
178
  find = find.collation(collation);
179
179
  }
180
180
  if (serviceOptions?.select) {
181
- find = find.select(serviceOptions.select);
181
+ find = find.select(serviceOptions.select) as any;
182
182
  }
183
183
  return find.exec();
184
184
  },
@@ -401,7 +401,7 @@ export abstract class CrudService<
401
401
  find = find.collation(collation);
402
402
  }
403
403
  if (serviceOptions?.select) {
404
- find = find.select(serviceOptions.select);
404
+ find = find.select(serviceOptions.select) as any;
405
405
  }
406
406
  return find.exec();
407
407
  },