@modular-rest/server 1.19.0 → 1.20.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 (35) hide show
  1. package/.nvmrc +1 -1
  2. package/dist/application.js +5 -4
  3. package/dist/class/combinator.js +7 -3
  4. package/dist/class/directory.d.ts +2 -4
  5. package/dist/class/directory.js +42 -64
  6. package/dist/helper/data_insertion.js +93 -26
  7. package/dist/services/data_provider/model_registry.d.ts +5 -0
  8. package/dist/services/data_provider/model_registry.js +25 -0
  9. package/dist/services/data_provider/service.js +8 -0
  10. package/dist/services/file/service.d.ts +47 -78
  11. package/dist/services/file/service.js +114 -155
  12. package/dist/services/functions/service.js +4 -4
  13. package/dist/services/jwt/router.js +2 -1
  14. package/dist/services/user_manager/router.js +1 -1
  15. package/dist/services/user_manager/service.js +48 -17
  16. package/jest.config.ts +18 -0
  17. package/package.json +11 -2
  18. package/src/application.ts +5 -4
  19. package/src/class/combinator.ts +10 -3
  20. package/src/class/directory.ts +40 -58
  21. package/src/helper/data_insertion.ts +101 -27
  22. package/src/services/data_provider/model_registry.ts +28 -0
  23. package/src/services/data_provider/service.ts +6 -0
  24. package/src/services/file/service.ts +136 -178
  25. package/src/services/functions/service.ts +4 -4
  26. package/src/services/jwt/router.ts +2 -1
  27. package/src/services/user_manager/router.ts +1 -1
  28. package/src/services/user_manager/service.ts +49 -20
  29. package/tests/helpers/test-app.ts +182 -0
  30. package/tests/router/data-provider.router.int.test.ts +192 -0
  31. package/tests/router/file.router.int.test.ts +104 -0
  32. package/tests/router/functions.router.int.test.ts +91 -0
  33. package/tests/router/jwt.router.int.test.ts +69 -0
  34. package/tests/router/user-manager.router.int.test.ts +85 -0
  35. package/tests/setup/jest.setup.ts +5 -0
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- v21.5.0
1
+ v22.12.0
@@ -206,16 +206,17 @@ async function createRest(options) {
206
206
  extension: '.js',
207
207
  },
208
208
  });
209
- // 5. Plug in additional defined functions
210
- if (config_1.config.functions) {
211
- combinator_1.default.addFunctionsByArray(config_1.config.functions);
212
- }
209
+ }
210
+ // 5. Plug in additional defined functions
211
+ if (config_1.config.functions) {
212
+ combinator_1.default.addFunctionsByArray(config_1.config.functions);
213
213
  }
214
214
  // 4. Setting up default services
215
215
  try {
216
216
  await require('./helper/presetup_services').setup(config_1.config);
217
217
  }
218
218
  catch (e) {
219
+ console.error(`[createRest] Error in setup:`, e);
219
220
  return Promise.reject(e);
220
221
  }
221
222
  /**
@@ -43,7 +43,7 @@ class Combinator {
43
43
  // find route paths
44
44
  const option = {
45
45
  name: 'router',
46
- filter: ['.js'],
46
+ filter: ['.js', '.ts'],
47
47
  };
48
48
  let routerPaths = [];
49
49
  try {
@@ -52,6 +52,8 @@ class Combinator {
52
52
  catch (e) {
53
53
  console.log(e);
54
54
  }
55
+ // ignore type declarations
56
+ routerPaths = routerPaths.filter(routePath => !routePath.endsWith('.d.ts'));
55
57
  // create and combine routes into the app
56
58
  for (let i = 0; i < routerPaths.length; i++) {
57
59
  const service = require(routerPaths[i]);
@@ -70,7 +72,7 @@ class Combinator {
70
72
  let rootObject_temp;
71
73
  const option = {
72
74
  name: filename.name,
73
- filter: [filename.extension],
75
+ filter: [filename.extension, '.ts'],
74
76
  };
75
77
  let modulesPath = [];
76
78
  try {
@@ -79,6 +81,7 @@ class Combinator {
79
81
  catch (e) {
80
82
  console.log(e);
81
83
  }
84
+ modulesPath = modulesPath.filter(modulePath => !modulePath.endsWith('.d.ts'));
82
85
  // create and combine routes into the app
83
86
  for (let i = 0; i < modulesPath.length; i++) {
84
87
  const moduleObject = require(modulesPath[i]);
@@ -117,7 +120,7 @@ class Combinator {
117
120
  // find route paths
118
121
  const option = {
119
122
  name: filename.name,
120
- filter: [filename.extension],
123
+ filter: [filename.extension, '.ts'],
121
124
  };
122
125
  let functionsPaths = [];
123
126
  try {
@@ -126,6 +129,7 @@ class Combinator {
126
129
  catch (e) {
127
130
  console.log(e);
128
131
  }
132
+ functionsPaths = functionsPaths.filter(functionPath => !functionPath.endsWith('.d.ts'));
129
133
  // create and combine routes into the app
130
134
  for (let i = 0; i < functionsPaths.length; i++) {
131
135
  const modularFunctions = require(functionsPaths[i]);
@@ -2,14 +2,12 @@ interface DirectorySettings {
2
2
  name?: string;
3
3
  filter?: string[];
4
4
  }
5
- type WalkCallback = (err: Error | null, results: string[]) => void;
6
5
  /**
7
- * Walk through a directory and its subdirectories
6
+ * Walk through a directory and its subdirectories efficiently
8
7
  * @param dir - Directory to walk
9
8
  * @param settings - Settings for filtering files
10
- * @param done - Callback function
11
9
  */
12
- declare function walk(dir: string, settings: DirectorySettings, done: WalkCallback): void;
10
+ declare function walk(dir: string, settings: DirectorySettings): Promise<string[]>;
13
11
  /**
14
12
  * Find files in a directory with Promise API
15
13
  * @param dir - Directory to search
@@ -8,66 +8,45 @@ exports.find = find;
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  /**
11
- * Walk through a directory and its subdirectories
11
+ * Walk through a directory and its subdirectories efficiently
12
12
  * @param dir - Directory to walk
13
13
  * @param settings - Settings for filtering files
14
- * @param done - Callback function
15
14
  */
16
- function walk(dir, settings, done) {
15
+ async function walk(dir, settings) {
17
16
  let results = [];
18
- // Read director file and folders
19
- fs_1.default.readdir(dir, function (err, list) {
20
- if (err)
21
- return done(err, results);
22
- let pending = list.length;
23
- if (!pending)
24
- return done(null, results);
25
- list.forEach(function (file) {
26
- file = path_1.default.join(dir, file);
27
- fs_1.default.stat(file, function (err, stat) {
28
- if (err) {
29
- // Handle file stat error but continue with other files
30
- console.error(`Error reading file stats for ${file}:`, err);
31
- if (!--pending)
32
- done(null, results);
33
- return;
17
+ const list = await fs_1.default.promises.readdir(dir);
18
+ for (const file of list) {
19
+ const filePath = path_1.default.join(dir, file);
20
+ const stat = await fs_1.default.promises.stat(filePath);
21
+ if (stat && stat.isDirectory()) {
22
+ // Ignore common directories that should not be scanned
23
+ if (file === 'node_modules' || file === '.git' || file === 'dist') {
24
+ continue;
25
+ }
26
+ const res = await walk(filePath, settings);
27
+ results = results.concat(res);
28
+ }
29
+ else {
30
+ const extension = path_1.default.extname(filePath);
31
+ const fileName = path_1.default.basename(filePath).split('.')[0];
32
+ let fileNameKey = true;
33
+ // name filter
34
+ if (settings.name && settings.name !== fileName) {
35
+ fileNameKey = false;
36
+ }
37
+ // extension filter
38
+ if (settings.filter && fileNameKey) {
39
+ if (settings.filter.some(ext => ext.toLowerCase() === extension.toLowerCase())) {
40
+ results.push(filePath);
34
41
  }
35
- // If directory, execute a recursive call
36
- if (stat && stat.isDirectory()) {
37
- // Add directory to array [comment if you need to remove the directories from the array]
38
- // results.push(file);
39
- walk(file, settings, function (err, res) {
40
- results = results.concat(res);
41
- if (!--pending)
42
- done(null, results);
43
- });
44
- }
45
- else {
46
- // file filter
47
- const extension = path_1.default.extname(file);
48
- const fileName = path_1.default.basename(file).split('.')[0];
49
- let fileNameKey = true;
50
- // name filter
51
- if (settings.name && settings.name === fileName)
52
- fileNameKey = true;
53
- else
54
- fileNameKey = false;
55
- // extension filter
56
- if (settings.filter && fileNameKey) {
57
- settings.filter.forEach(function (element) {
58
- if (element.toLowerCase() === extension.toLowerCase())
59
- results.push(file);
60
- });
61
- }
62
- // push any file if no option
63
- else if (fileNameKey)
64
- results.push(file);
65
- if (!--pending)
66
- done(null, results);
67
- }
68
- });
69
- });
70
- });
42
+ }
43
+ // push any file if no name filter and no extension filter
44
+ else if (fileNameKey && !settings.filter) {
45
+ results.push(filePath);
46
+ }
47
+ }
48
+ }
49
+ return results;
71
50
  }
72
51
  /**
73
52
  * Find files in a directory with Promise API
@@ -75,13 +54,12 @@ function walk(dir, settings, done) {
75
54
  * @param settings - Settings for filtering files
76
55
  * @returns Promise resolving to an array of file paths
77
56
  */
78
- function find(dir, settings) {
79
- return new Promise((resolve, reject) => {
80
- walk(dir, settings, (err, result) => {
81
- if (err)
82
- reject(err);
83
- else
84
- resolve(result);
85
- });
86
- });
57
+ async function find(dir, settings) {
58
+ try {
59
+ return await walk(dir, settings);
60
+ }
61
+ catch (err) {
62
+ console.error(`Error in directory.find for ${dir}:`, err);
63
+ return [];
64
+ }
87
65
  }
@@ -65,43 +65,110 @@ const userManager = __importStar(require("../services/user_manager/service"));
65
65
  */
66
66
  async function createAdminUser({ email, password }) {
67
67
  const authModel = DataProvider.getCollection('cms', 'auth');
68
+ // Wait for connection to be ready for queries
69
+ const connection = authModel.db;
70
+ if (connection) {
71
+ if (connection.readyState !== 1) {
72
+ await new Promise((resolve, reject) => {
73
+ const timeout = setTimeout(() => {
74
+ reject(new Error('Database connection timeout - connection not ready after 10s'));
75
+ }, 10000);
76
+ if (connection.readyState === 1) {
77
+ clearTimeout(timeout);
78
+ resolve();
79
+ }
80
+ else {
81
+ connection.once('connected', () => {
82
+ clearTimeout(timeout);
83
+ resolve();
84
+ });
85
+ connection.once('error', err => {
86
+ clearTimeout(timeout);
87
+ reject(err);
88
+ });
89
+ }
90
+ });
91
+ }
92
+ // Test if database is actually ready by doing a simple operation
93
+ // This ensures the connection is fully initialized
94
+ let retries = 5;
95
+ let lastError = null;
96
+ while (retries > 0) {
97
+ try {
98
+ await authModel.findOne({}).limit(1).maxTimeMS(3000).lean().exec();
99
+ // Additional small delay to ensure write operations will work
100
+ await new Promise(resolve => setTimeout(resolve, 100));
101
+ break; // Success, connection is ready
102
+ }
103
+ catch (err) {
104
+ lastError = err instanceof Error ? err : new Error(String(err));
105
+ retries--;
106
+ if (retries > 0) {
107
+ await new Promise(resolve => setTimeout(resolve, 500));
108
+ }
109
+ }
110
+ }
111
+ if (retries === 0 && lastError) {
112
+ throw new Error(`Database not ready for queries: ${lastError.message}`);
113
+ }
114
+ }
68
115
  try {
69
- const isAnonymousExisted = await authModel.countDocuments({ type: 'anonymous' }).exec();
116
+ // Execute queries with explicit timeout handling
117
+ const queryOptions = { maxTimeMS: 5000 }; // 5 second timeout
118
+ const isAnonymousExisted = await authModel
119
+ .countDocuments({ type: 'anonymous' })
120
+ .maxTimeMS(5000)
121
+ .exec();
70
122
  const isAdministratorExisted = await authModel
71
123
  .countDocuments({ type: 'user', email: email })
124
+ .maxTimeMS(5000)
72
125
  .exec();
73
126
  if (isAnonymousExisted === 0) {
74
- await userManager.main.registerUser({
75
- permissionGroup: (0, permissionManager_1.getDefaultAnonymousPermissionGroup)().title,
76
- email: '',
77
- phone: '',
78
- password: '',
79
- type: 'anonymous',
80
- });
81
- // await new authModel({
82
- // permission: getDefaultAnonymousPermissionGroup().title,
83
- // email: "",
84
- // phone: "",
85
- // password: "",
86
- // type: "anonymous",
87
- // }).save();
127
+ // Wrap registerUser with timeout to prevent hanging
128
+ try {
129
+ let timeoutId;
130
+ await Promise.race([
131
+ userManager.main.registerUser({
132
+ permissionGroup: (0, permissionManager_1.getDefaultAnonymousPermissionGroup)().title,
133
+ email: '',
134
+ phone: '',
135
+ password: '',
136
+ type: 'anonymous',
137
+ }),
138
+ new Promise((_, reject) => {
139
+ timeoutId = setTimeout(() => reject(new Error('registerUser timeout for anonymous user after 15s')), 15000);
140
+ }),
141
+ ]).finally(() => {
142
+ if (timeoutId)
143
+ clearTimeout(timeoutId);
144
+ });
145
+ }
146
+ catch (err) {
147
+ // If anonymous user creation fails, log but don't fail completely
148
+ // It might already exist from a previous attempt
149
+ console.warn('Failed to create anonymous user:', err instanceof Error ? err.message : String(err));
150
+ }
88
151
  }
89
152
  if (isAdministratorExisted === 0) {
90
153
  if (!email || !password) {
91
154
  return Promise.reject('Invalid email or password for admin user.');
92
155
  }
93
- await userManager.main.registerUser({
94
- permissionGroup: (0, permissionManager_1.getDefaultAdministratorPermissionGroup)().title,
95
- email: email,
96
- password: password,
97
- type: 'user',
156
+ // Wrap registerUser with timeout to prevent hanging
157
+ let timeoutId;
158
+ await Promise.race([
159
+ userManager.main.registerUser({
160
+ permissionGroup: (0, permissionManager_1.getDefaultAdministratorPermissionGroup)().title,
161
+ email: email,
162
+ password: password,
163
+ type: 'user',
164
+ }),
165
+ new Promise((_, reject) => {
166
+ timeoutId = setTimeout(() => reject(new Error('registerUser timeout for admin user after 15s')), 15000);
167
+ }),
168
+ ]).finally(() => {
169
+ if (timeoutId)
170
+ clearTimeout(timeoutId);
98
171
  });
99
- // await new authModel({
100
- // permission: getDefaultAdministratorPermissionGroup().title,
101
- // email: email,
102
- // password: password,
103
- // type: "user",
104
- // }).save();
105
172
  }
106
173
  }
107
174
  catch (e) {
@@ -62,6 +62,11 @@ declare class ModelRegistry {
62
62
  * @returns {boolean} True if model exists, false otherwise
63
63
  */
64
64
  hasModel(database: string, collection: string): boolean;
65
+ /**
66
+ * Clear all internal state, including connections and models.
67
+ * Useful for testing when different database prefixes are used.
68
+ */
69
+ clear(): Promise<void>;
65
70
  }
66
71
  export declare const modelRegistry: ModelRegistry;
67
72
  export default modelRegistry;
@@ -63,6 +63,8 @@ class ModelRegistry {
63
63
  useUnifiedTopology: true,
64
64
  useNewUrlParser: true,
65
65
  dbName: fullDbName,
66
+ serverSelectionTimeoutMS: 10000, // 10 second timeout for server selection
67
+ socketTimeoutMS: 45000, // 45 second timeout for socket operations
66
68
  });
67
69
  this.connections[database] = connection;
68
70
  }
@@ -120,6 +122,8 @@ class ModelRegistry {
120
122
  useUnifiedTopology: true,
121
123
  useNewUrlParser: true,
122
124
  dbName: fullDbName,
125
+ serverSelectionTimeoutMS: 10000, // 10 second timeout for server selection
126
+ socketTimeoutMS: 45000, // 45 second timeout for socket operations
123
127
  });
124
128
  this.connections[dbName] = newConnection;
125
129
  // Create models for this database if they don't exist
@@ -183,6 +187,27 @@ class ModelRegistry {
183
187
  hasModel(database, collection) {
184
188
  return !!(this.models[database] && this.models[database][collection]);
185
189
  }
190
+ /**
191
+ * Clear all internal state, including connections and models.
192
+ * Useful for testing when different database prefixes are used.
193
+ */
194
+ async clear() {
195
+ // Close all connections first
196
+ await Promise.all(Object.values(this.connections).map(async (connection) => {
197
+ if (connection.readyState !== 0) {
198
+ try {
199
+ await connection.close();
200
+ }
201
+ catch (err) {
202
+ // Ignore close errors
203
+ }
204
+ }
205
+ }));
206
+ this.connections = {};
207
+ this.models = {};
208
+ this.collectionDefinitions = [];
209
+ this.mongoOption = null;
210
+ }
186
211
  }
187
212
  // Export singleton instance
188
213
  exports.modelRegistry = ModelRegistry.getInstance();
@@ -51,6 +51,8 @@ function connectToDatabaseByCollectionDefinitionList(dbName, collectionDefinitio
51
51
  useUnifiedTopology: true,
52
52
  useNewUrlParser: true,
53
53
  dbName: fullDbName,
54
+ serverSelectionTimeoutMS: 10000, // 10 second timeout for server selection
55
+ socketTimeoutMS: 45000, // 45 second timeout for socket operations
54
56
  });
55
57
  }
56
58
  else {
@@ -236,6 +238,12 @@ function checkAccess(db, collection, operationType, queryOrDoc, user) {
236
238
  return true;
237
239
  if (permission.accessType === 'user_access' && user.type === 'user')
238
240
  return true;
241
+ if (typeof user.hasPermission === 'function' && user.hasPermission(permission.accessType)) {
242
+ if (operationType === security_1.AccessTypes.read)
243
+ return permission.read;
244
+ if (operationType === security_1.AccessTypes.write)
245
+ return permission.write;
246
+ }
239
247
  return false;
240
248
  });
241
249
  }
@@ -21,9 +21,12 @@ interface StoredFileDetail {
21
21
  * File upload options interface
22
22
  * @interface StoreFileOptions
23
23
  * @property {Object} file - File details
24
- * @property {string} file.path - Temporary file path
25
- * @property {string} file.type - MIME type of the file
26
- * @property {string} file.name - Original filename
24
+ * @property {string} [file.path] - Temporary file path (legacy)
25
+ * @property {string} [file.filepath] - Temporary file path (koa-body v6+)
26
+ * @property {string} [file.type] - MIME type of the file (legacy)
27
+ * @property {string} [file.mimetype] - MIME type of the file (koa-body v6+)
28
+ * @property {string} [file.name] - Original filename (legacy)
29
+ * @property {string} [file.originalFilename] - Original filename (koa-body v6+)
27
30
  * @property {number} file.size - File size in bytes
28
31
  * @property {string} ownerId - ID of the file owner
29
32
  * @property {string} tag - Tag for file organization
@@ -31,9 +34,12 @@ interface StoredFileDetail {
31
34
  */
32
35
  interface StoreFileOptions {
33
36
  file: {
34
- path: string;
35
- type: string;
36
- name: string;
37
+ path?: string;
38
+ filepath?: string;
39
+ type?: string;
40
+ mimetype?: string;
41
+ name?: string;
42
+ originalFilename?: string;
37
43
  size: number;
38
44
  };
39
45
  ownerId: string;
@@ -41,11 +47,11 @@ interface StoreFileOptions {
41
47
  removeFileAfterStore?: boolean;
42
48
  }
43
49
  /**
44
- * File service for handling file storage and retrieval.
45
- *
46
- * This service provides functionality for storing, retrieving, and managing files.
47
- * It handles file storage on disk and maintains file metadata in the database.
48
- * Files are organized by format and tag in the upload directory.
50
+ * File service class for handling file operations
51
+ * @class FileService
52
+ * @description
53
+ * This class provides methods for managing file uploads, retrieval, and deletion.
54
+ * It handles physical file storage and database metadata management.
49
55
  */
50
56
  declare class FileService {
51
57
  /**
@@ -86,29 +92,17 @@ declare class FileService {
86
92
  */
87
93
  setUploadDirectory(directoryOrConfig: string | StaticPathOptions): void;
88
94
  /**
89
- * @hidden
90
- *
91
- * Creates stored file details with unique filename
95
+ * Creates a unique filename and storage details
92
96
  * @param {string} fileType - MIME type of the file
93
- * @param {string} tag - Tag for file organization
94
- * @returns {StoredFileDetail} Storage details including filename and path
95
- * @throws {Error} If upload directory is not set
96
- *
97
- * @example
98
- * ```typescript
99
- * import { fileService } from '@modular-rest/server';
100
- *
101
- * const details = fileService.createStoredDetail('image/jpeg', 'profile');
102
- * // Returns: { fileName: '1234567890.jpeg', fullPath: '/uploads/jpeg/profile/1234567890.jpeg', fileFormat: 'jpeg' }
103
- * ```
97
+ * @param {string} tag - File tag
98
+ * @returns {StoredFileDetail} Storage details including unique filename and path
99
+ * @hidden
104
100
  */
105
101
  createStoredDetail(fileType: string, tag: string): StoredFileDetail;
106
102
  /**
107
- * @hidden
108
- *
109
- * Stores a file, removes the temporary file, and saves metadata to database
103
+ * Stores a file on disc and creates metadata in database
110
104
  * @param {StoreFileOptions} options - File storage options
111
- * @returns {Promise<IFile>} Promise resolving to stored file document
105
+ * @returns {Promise<IFile>} The created file document
112
106
  * @throws {Error} If upload directory is not set or storage fails
113
107
  * @example
114
108
  * ```typescript
@@ -129,75 +123,50 @@ declare class FileService {
129
123
  */
130
124
  storeFile({ file, ownerId, tag, removeFileAfterStore }: StoreFileOptions): Promise<IFile>;
131
125
  /**
132
- * @hidden
133
- *
134
- * Removes a file from the disk
135
- * @param {string} path - File path to remove
136
- * @returns {Promise<void>} Promise resolving when file is removed
137
- * @throws {Error} If file removal fails
126
+ * Deletes a file from disc and database
127
+ * @param {string} fileId - ID of the file to delete
128
+ * @returns {Promise<boolean>} True if deletion was successful
129
+ * @throws {Error} If file is not found or deletion fails
138
130
  * @example
139
131
  * ```typescript
140
132
  * import { fileService } from '@modular-rest/server';
141
133
  *
142
- * await fileService.removeFromDisc('/uploads/jpeg/profile/1234567890.jpeg');
134
+ * await fileService.removeFile('file123');
143
135
  * ```
144
136
  */
145
- removeFromDisc(path: string): Promise<void>;
137
+ removeFile(fileId: string): Promise<boolean>;
146
138
  /**
147
- * Removes a file from both database and disk
148
- *
149
- * @param {string} fileId - File ID to remove
150
- * @returns {Promise<void>} Promise resolving when file is removed
151
- * @throws {Error} If file is not found or removal fails
152
- * @example
153
- * ```typescript
154
- * import { fileService } from '@modular-rest/server';
155
- *
156
- * try {
157
- * await fileService.removeFile('file123');
158
- * console.log('File removed successfully');
159
- * } catch (error) {
160
- * console.error('Failed to remove file:', error);
161
- * }
162
- * ```
139
+ * Deletes a file from physical storage
140
+ * @param {string} path - Physical path to the file
141
+ * @returns {Promise<boolean>} True if deletion was successful
142
+ * @hidden
163
143
  */
164
- removeFile(fileId: string): Promise<void>;
144
+ removeFromDisc(path: string): Promise<boolean>;
165
145
  /**
166
- * Retrieves a file document from the database
167
- *
168
- * @param {string} fileId - File ID to retrieve
169
- * @returns {Promise<IFile>} Promise resolving to file document
170
- * @throws {Error} If collection model is not found or file is not found
171
- * @example
172
- * ```typescript
173
- * import { fileService } from '@modular-rest/server';
174
- *
175
- * const fileDoc = await fileService.getFile('file123');
176
- * console.log('File details:', fileDoc);
177
- * ```
146
+ * Retrieves a file document from database
147
+ * @param {string} fileId - ID of the file
148
+ * @returns {Promise<IFile>} The file document
149
+ * @throws {Error} If file is not found
150
+ * @hidden
178
151
  */
179
152
  getFile(fileId: string): Promise<IFile>;
180
153
  /**
181
- * Retrieves the public URL link for a file
182
- *
183
- * @param {string} fileId - File ID to get link for
184
- * @returns {Promise<string>} Promise resolving to file URL
185
- * @throws {Error} If URL path is not defined or file is not found
154
+ * Gets the public URL for a file
155
+ * @param {string} fileId - ID of the file
156
+ * @returns {Promise<string>} The public URL
186
157
  * @example
187
158
  * ```typescript
188
159
  * import { fileService } from '@modular-rest/server';
189
160
  *
190
- * const link = await fileService.getFileLink('file123');
191
- * // Returns: '/uploads/jpeg/profile/1234567890.jpeg'
161
+ * const url = await fileService.getFileLink('file123');
162
+ * // Returns: '/assets/jpeg/profile/1234567890.jpeg'
192
163
  * ```
193
164
  */
194
165
  getFileLink(fileId: string): Promise<string>;
195
166
  /**
196
- * Gets the full filesystem path for a file
197
- *
198
- * @param {string} fileId - File ID to get path for
199
- * @returns {Promise<string>} Promise resolving to full file path
200
- * @throws {Error} If upload directory is not set or file is not found
167
+ * Gets the physical path for a file
168
+ * @param {string} fileId - ID of the file
169
+ * @returns {Promise<string>} The physical path
201
170
  * @example
202
171
  * ```typescript
203
172
  * import { fileService } from '@modular-rest/server';