@modular-rest/server 1.19.0 → 1.20.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.
- package/.nvmrc +1 -1
- package/dist/application.js +5 -4
- package/dist/class/combinator.js +7 -3
- package/dist/class/directory.d.ts +2 -4
- package/dist/class/directory.js +42 -64
- package/dist/helper/data_insertion.js +93 -26
- package/dist/services/data_provider/model_registry.d.ts +5 -0
- package/dist/services/data_provider/model_registry.js +25 -0
- package/dist/services/data_provider/service.js +8 -0
- package/dist/services/file/service.d.ts +47 -78
- package/dist/services/file/service.js +124 -155
- package/dist/services/functions/service.js +4 -4
- package/dist/services/jwt/router.js +2 -1
- package/dist/services/user_manager/router.js +1 -1
- package/dist/services/user_manager/service.js +48 -17
- package/jest.config.ts +18 -0
- package/package.json +11 -2
- package/src/application.ts +5 -4
- package/src/class/combinator.ts +10 -3
- package/src/class/directory.ts +40 -58
- package/src/helper/data_insertion.ts +101 -27
- package/src/services/data_provider/model_registry.ts +28 -0
- package/src/services/data_provider/service.ts +6 -0
- package/src/services/file/service.ts +146 -178
- package/src/services/functions/service.ts +4 -4
- package/src/services/jwt/router.ts +2 -1
- package/src/services/user_manager/router.ts +1 -1
- package/src/services/user_manager/service.ts +49 -20
- package/tests/helpers/test-app.ts +182 -0
- package/tests/router/data-provider.router.int.test.ts +192 -0
- package/tests/router/file.router.int.test.ts +104 -0
- package/tests/router/functions.router.int.test.ts +91 -0
- package/tests/router/jwt.router.int.test.ts +69 -0
- package/tests/router/user-manager.router.int.test.ts +85 -0
- package/tests/setup/jest.setup.ts +5 -0
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
v22.12.0
|
package/dist/application.js
CHANGED
|
@@ -206,16 +206,17 @@ async function createRest(options) {
|
|
|
206
206
|
extension: '.js',
|
|
207
207
|
},
|
|
208
208
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
/**
|
package/dist/class/combinator.js
CHANGED
|
@@ -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
|
|
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
|
package/dist/class/directory.js
CHANGED
|
@@ -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
|
|
15
|
+
async function walk(dir, settings) {
|
|
17
16
|
let results = [];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
80
|
-
walk(dir, settings
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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.
|
|
26
|
-
* @property {string} file.
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
*
|
|
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 -
|
|
94
|
-
* @returns {StoredFileDetail} Storage details including filename and path
|
|
95
|
-
* @
|
|
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
|
-
*
|
|
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>}
|
|
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
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* @
|
|
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.
|
|
134
|
+
* await fileService.removeFile('file123');
|
|
143
135
|
* ```
|
|
144
136
|
*/
|
|
145
|
-
|
|
137
|
+
removeFile(fileId: string): Promise<boolean>;
|
|
146
138
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* @
|
|
150
|
-
* @
|
|
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
|
-
|
|
144
|
+
removeFromDisc(path: string): Promise<boolean>;
|
|
165
145
|
/**
|
|
166
|
-
* Retrieves a file document from
|
|
167
|
-
*
|
|
168
|
-
* @
|
|
169
|
-
* @
|
|
170
|
-
* @
|
|
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
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* @
|
|
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
|
|
191
|
-
* // Returns: '/
|
|
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
|
|
197
|
-
*
|
|
198
|
-
* @
|
|
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';
|