json-rest-api 1.0.14 → 1.0.16
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/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +11 -2
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +99 -99
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +11 -15
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File Handling Plugin for JSON REST API
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This plugin provides automatic file upload handling based on schema definitions.
|
|
5
5
|
* It works with any protocol connector (HTTP, Express, WebSocket, etc.) by using
|
|
6
6
|
* a detector registry pattern.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - Schema-driven: Detects file fields from type: 'file' in schemas
|
|
10
10
|
* - Protocol-agnostic: Works with any connector that registers a detector
|
|
11
11
|
* - Storage pluggable: Different fields can use different storage backends
|
|
12
12
|
* - Zero configuration: Just define file fields in your schema
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* Usage:
|
|
15
15
|
* ```javascript
|
|
16
16
|
* // 1. Define schema with file fields
|
|
17
17
|
* const imageSchema = {
|
|
18
18
|
* title: { type: 'string' },
|
|
19
|
-
* uploadedImage: {
|
|
19
|
+
* uploadedImage: {
|
|
20
20
|
* type: 'file',
|
|
21
21
|
* storage: S3Storage,
|
|
22
22
|
* accepts: ['image/jpeg', 'image/png']
|
|
23
23
|
* }
|
|
24
24
|
* };
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* // 2. Use plugins (order matters - file-handling depends on rest-api)
|
|
27
27
|
* api.use(RestApiPlugin);
|
|
28
28
|
* api.use(FileHandlingPlugin);
|
|
29
29
|
* api.use(ExpressPlugin); // Or any other connector
|
|
30
|
-
*
|
|
30
|
+
*
|
|
31
31
|
* // 3. Files are automatically handled!
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import { RestApiValidationError } from '../../lib/rest-api-errors.js'
|
|
35
|
+
import { RestApiValidationError } from '../../lib/rest-api-errors.js'
|
|
36
36
|
|
|
37
37
|
export const FileHandlingPlugin = {
|
|
38
38
|
name: 'file-handling',
|
|
39
39
|
dependencies: ['rest-api'],
|
|
40
|
-
|
|
41
|
-
install({ addHook, helpers, scopes, log, vars, on, api }) {
|
|
40
|
+
|
|
41
|
+
install ({ addHook, helpers, scopes, log, vars, on, api }) {
|
|
42
42
|
// Track which scopes have file fields
|
|
43
|
-
const fileScopes = new Map()
|
|
44
|
-
|
|
43
|
+
const fileScopes = new Map() // scopeName -> fileField[]
|
|
44
|
+
|
|
45
45
|
// Registry of file detectors from various protocols
|
|
46
|
-
const detectorRegistry = []
|
|
47
|
-
|
|
46
|
+
const detectorRegistry = []
|
|
47
|
+
|
|
48
48
|
/**
|
|
49
49
|
* Register a file detector from a protocol plugin
|
|
50
|
-
*
|
|
50
|
+
*
|
|
51
51
|
* @param {Object} detector - The detector object
|
|
52
52
|
* @param {string} detector.name - Name of the detector (e.g., 'http-multipart')
|
|
53
53
|
* @param {Function} detector.detect - Function to check if this detector applies
|
|
@@ -55,25 +55,25 @@ export const FileHandlingPlugin = {
|
|
|
55
55
|
*/
|
|
56
56
|
api.rest.registerFileDetector = (detector) => {
|
|
57
57
|
if (!detector || !detector.name || !detector.detect || !detector.parse) {
|
|
58
|
-
throw new Error('File detector must have name, detect(), and parse() properties')
|
|
58
|
+
throw new Error('File detector must have name, detect(), and parse() properties')
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
detectorRegistry.push(detector)
|
|
62
|
-
log.debug(`Registered file detector: ${detector.name}`)
|
|
63
|
-
}
|
|
64
|
-
|
|
60
|
+
|
|
61
|
+
detectorRegistry.push(detector)
|
|
62
|
+
log.debug(`Registered file detector: ${detector.name}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
65
|
// Store detectors array for inspection
|
|
66
|
-
api.rest.fileDetectors = detectorRegistry
|
|
67
|
-
|
|
66
|
+
api.rest.fileDetectors = detectorRegistry
|
|
67
|
+
|
|
68
68
|
/**
|
|
69
69
|
* Analyze a scope's schema to find file fields
|
|
70
70
|
*/
|
|
71
71
|
const analyzeScopeSchema = (scopeName, scopeOptions) => {
|
|
72
|
-
const schema = scopeOptions?.schema
|
|
73
|
-
if (!schema) return
|
|
74
|
-
|
|
75
|
-
const fileFields = []
|
|
76
|
-
|
|
72
|
+
const schema = scopeOptions?.schema
|
|
73
|
+
if (!schema) return
|
|
74
|
+
|
|
75
|
+
const fileFields = []
|
|
76
|
+
|
|
77
77
|
// Look for fields with type: 'file'
|
|
78
78
|
for (const [fieldName, fieldConfig] of Object.entries(schema)) {
|
|
79
79
|
if (fieldConfig && fieldConfig.type === 'file') {
|
|
@@ -83,55 +83,55 @@ export const FileHandlingPlugin = {
|
|
|
83
83
|
accepts: fieldConfig.accepts || ['*'],
|
|
84
84
|
maxSize: fieldConfig.maxSize,
|
|
85
85
|
required: fieldConfig.required || false
|
|
86
|
-
})
|
|
86
|
+
})
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
if (fileFields.length > 0) {
|
|
91
|
-
fileScopes.set(scopeName, fileFields)
|
|
92
|
-
log.info(`Scope '${scopeName}' has ${fileFields.length} file field(s): ${fileFields.map(f => f.field).join(', ')}`)
|
|
91
|
+
fileScopes.set(scopeName, fileFields)
|
|
92
|
+
log.info(`Scope '${scopeName}' has ${fileFields.length} file field(s): ${fileFields.map(f => f.field).join(', ')}`)
|
|
93
93
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
94
|
+
}
|
|
95
|
+
|
|
96
96
|
// Analyze existing scopes
|
|
97
97
|
for (const [scopeName, scope] of Object.entries(scopes)) {
|
|
98
98
|
if (scope._scopeOptions) {
|
|
99
|
-
analyzeScopeSchema(scopeName, scope._scopeOptions)
|
|
99
|
+
analyzeScopeSchema(scopeName, scope._scopeOptions)
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
// Listen for new scopes being added
|
|
104
104
|
addHook('scope:added', 'analyzeFileFields', {}, ({ context }) => {
|
|
105
|
-
const { scopeName, scopeOptions } = context
|
|
106
|
-
analyzeScopeSchema(scopeName, scopeOptions)
|
|
107
|
-
})
|
|
108
|
-
|
|
105
|
+
const { scopeName, scopeOptions } = context
|
|
106
|
+
analyzeScopeSchema(scopeName, scopeOptions)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
109
|
/**
|
|
110
110
|
* Process files for a scope if it has file fields
|
|
111
111
|
*/
|
|
112
112
|
const processFiles = async (scopeName, params, context) => {
|
|
113
|
-
const fileFields = fileScopes.get(scopeName)
|
|
113
|
+
const fileFields = fileScopes.get(scopeName)
|
|
114
114
|
if (!fileFields || fileFields.length === 0) {
|
|
115
|
-
return
|
|
115
|
+
return // This scope doesn't have file fields
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
// Try each detector to see if we have files
|
|
119
|
-
let parsed = null
|
|
120
|
-
let detectorUsed = null
|
|
121
|
-
|
|
119
|
+
let parsed = null
|
|
120
|
+
let detectorUsed = null
|
|
121
|
+
|
|
122
122
|
for (const detector of detectorRegistry) {
|
|
123
123
|
try {
|
|
124
124
|
if (await detector.detect(params, context)) {
|
|
125
|
-
log.debug(`Detector '${detector.name}' matched for scope '${scopeName}'`)
|
|
126
|
-
parsed = await detector.parse(params, context)
|
|
127
|
-
detectorUsed = detector.name
|
|
128
|
-
break
|
|
125
|
+
log.debug(`Detector '${detector.name}' matched for scope '${scopeName}'`)
|
|
126
|
+
parsed = await detector.parse(params, context)
|
|
127
|
+
detectorUsed = detector.name
|
|
128
|
+
break
|
|
129
129
|
}
|
|
130
130
|
} catch (error) {
|
|
131
|
-
log.warn(`Detector '${detector.name}' failed:`, error)
|
|
131
|
+
log.warn(`Detector '${detector.name}' failed:`, error)
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
if (!parsed) {
|
|
136
136
|
// No files detected - check if any were required
|
|
137
137
|
for (const fieldConfig of fileFields) {
|
|
@@ -145,19 +145,19 @@ export const FileHandlingPlugin = {
|
|
|
145
145
|
message: 'This field is required'
|
|
146
146
|
}]
|
|
147
147
|
}
|
|
148
|
-
)
|
|
148
|
+
)
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
return
|
|
151
|
+
return
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
log.debug(`Processing files with detector '${detectorUsed}'`)
|
|
155
|
-
const { fields, files } = parsed
|
|
156
|
-
|
|
153
|
+
|
|
154
|
+
log.debug(`Processing files with detector '${detectorUsed}'`)
|
|
155
|
+
const { fields, files } = parsed
|
|
156
|
+
|
|
157
157
|
// Process each file field defined in schema
|
|
158
158
|
for (const fieldConfig of fileFields) {
|
|
159
|
-
const file = files[fieldConfig.field]
|
|
160
|
-
|
|
159
|
+
const file = files[fieldConfig.field]
|
|
160
|
+
|
|
161
161
|
if (!file) {
|
|
162
162
|
if (fieldConfig.required) {
|
|
163
163
|
throw new RestApiValidationError(
|
|
@@ -169,22 +169,22 @@ export const FileHandlingPlugin = {
|
|
|
169
169
|
message: 'This field is required'
|
|
170
170
|
}]
|
|
171
171
|
}
|
|
172
|
-
)
|
|
172
|
+
)
|
|
173
173
|
}
|
|
174
|
-
continue
|
|
174
|
+
continue
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
// Validate mime type
|
|
178
178
|
if (fieldConfig.accepts[0] !== '*') {
|
|
179
179
|
const acceptable = fieldConfig.accepts.some(pattern => {
|
|
180
180
|
if (pattern.endsWith('/*')) {
|
|
181
181
|
// e.g., 'image/*'
|
|
182
|
-
const prefix = pattern.slice(0, -2)
|
|
183
|
-
return file.mimetype.startsWith(prefix + '/')
|
|
182
|
+
const prefix = pattern.slice(0, -2)
|
|
183
|
+
return file.mimetype.startsWith(prefix + '/')
|
|
184
184
|
}
|
|
185
|
-
return file.mimetype === pattern
|
|
186
|
-
})
|
|
187
|
-
|
|
185
|
+
return file.mimetype === pattern
|
|
186
|
+
})
|
|
187
|
+
|
|
188
188
|
if (!acceptable) {
|
|
189
189
|
throw new RestApiValidationError(
|
|
190
190
|
`Invalid file type for field '${fieldConfig.field}'`,
|
|
@@ -195,13 +195,13 @@ export const FileHandlingPlugin = {
|
|
|
195
195
|
message: `Expected ${fieldConfig.accepts.join(' or ')}, got ${file.mimetype}`
|
|
196
196
|
}]
|
|
197
197
|
}
|
|
198
|
-
)
|
|
198
|
+
)
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
|
|
202
202
|
// Validate file size
|
|
203
203
|
if (fieldConfig.maxSize) {
|
|
204
|
-
const maxBytes = parseSize(fieldConfig.maxSize)
|
|
204
|
+
const maxBytes = parseSize(fieldConfig.maxSize)
|
|
205
205
|
if (file.size > maxBytes) {
|
|
206
206
|
throw new RestApiValidationError(
|
|
207
207
|
`File too large for field '${fieldConfig.field}'`,
|
|
@@ -212,29 +212,29 @@ export const FileHandlingPlugin = {
|
|
|
212
212
|
message: `Maximum size is ${fieldConfig.maxSize}, got ${formatSize(file.size)}`
|
|
213
213
|
}]
|
|
214
214
|
}
|
|
215
|
-
)
|
|
215
|
+
)
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
// Upload to storage
|
|
220
220
|
if (!fieldConfig.storage) {
|
|
221
|
-
throw new Error(`No storage configured for file field '${fieldConfig.field}'`)
|
|
221
|
+
throw new Error(`No storage configured for file field '${fieldConfig.field}'`)
|
|
222
222
|
}
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
try {
|
|
225
|
-
const storedUrl = await fieldConfig.storage.upload(file)
|
|
226
|
-
fields[fieldConfig.field] = storedUrl
|
|
227
|
-
log.debug(`Uploaded file for field '${fieldConfig.field}' to: ${storedUrl}`)
|
|
225
|
+
const storedUrl = await fieldConfig.storage.upload(file)
|
|
226
|
+
fields[fieldConfig.field] = storedUrl
|
|
227
|
+
log.debug(`Uploaded file for field '${fieldConfig.field}' to: ${storedUrl}`)
|
|
228
228
|
} catch (error) {
|
|
229
229
|
// Cleanup if file has cleanup function
|
|
230
230
|
if (file.cleanup) {
|
|
231
231
|
try {
|
|
232
|
-
await file.cleanup()
|
|
232
|
+
await file.cleanup()
|
|
233
233
|
} catch (cleanupError) {
|
|
234
|
-
log.warn(
|
|
234
|
+
log.warn('Failed to cleanup file after upload error:', cleanupError)
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
-
|
|
237
|
+
|
|
238
238
|
throw new RestApiValidationError(
|
|
239
239
|
`Failed to upload file for field '${fieldConfig.field}': ${error.message}`,
|
|
240
240
|
{
|
|
@@ -244,83 +244,83 @@ export const FileHandlingPlugin = {
|
|
|
244
244
|
message: error.message
|
|
245
245
|
}]
|
|
246
246
|
}
|
|
247
|
-
)
|
|
247
|
+
)
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
|
-
|
|
250
|
+
|
|
251
251
|
// Replace inputRecord with processed data
|
|
252
252
|
if (!params.inputRecord) {
|
|
253
|
-
params.inputRecord = { data: { attributes: {} } }
|
|
253
|
+
params.inputRecord = { data: { attributes: {} } }
|
|
254
254
|
}
|
|
255
255
|
if (!params.inputRecord.data) {
|
|
256
|
-
params.inputRecord.data = { attributes: {} }
|
|
256
|
+
params.inputRecord.data = { attributes: {} }
|
|
257
257
|
}
|
|
258
258
|
if (!params.inputRecord.data.attributes) {
|
|
259
|
-
params.inputRecord.data.attributes = {}
|
|
259
|
+
params.inputRecord.data.attributes = {}
|
|
260
260
|
}
|
|
261
|
-
|
|
261
|
+
|
|
262
262
|
// Merge fields into attributes
|
|
263
|
-
Object.assign(params.inputRecord.data.attributes, fields)
|
|
264
|
-
|
|
263
|
+
Object.assign(params.inputRecord.data.attributes, fields)
|
|
264
|
+
|
|
265
265
|
// Cleanup any remaining temp files
|
|
266
266
|
for (const file of Object.values(files)) {
|
|
267
267
|
if (file.cleanup) {
|
|
268
268
|
try {
|
|
269
|
-
await file.cleanup()
|
|
269
|
+
await file.cleanup()
|
|
270
270
|
} catch (error) {
|
|
271
|
-
log.warn(
|
|
271
|
+
log.warn('Failed to cleanup temp file:', error)
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
|
-
}
|
|
276
|
-
|
|
275
|
+
}
|
|
276
|
+
|
|
277
277
|
/**
|
|
278
278
|
* Hook into REST API methods to process files
|
|
279
279
|
*/
|
|
280
280
|
addHook('beforeProcessing', 'processFiles', {}, async ({ context }) => {
|
|
281
|
-
const method = context.method
|
|
282
|
-
const scopeName = context.scopeName
|
|
283
|
-
const params = context.params
|
|
284
|
-
|
|
281
|
+
const method = context.method
|
|
282
|
+
const scopeName = context.scopeName
|
|
283
|
+
const params = context.params
|
|
284
|
+
|
|
285
285
|
// Only process for mutation methods
|
|
286
286
|
if (!['post', 'put', 'patch'].includes(method)) {
|
|
287
|
-
return
|
|
287
|
+
return
|
|
288
288
|
}
|
|
289
|
-
|
|
289
|
+
|
|
290
290
|
// Process files if this scope has file fields
|
|
291
|
-
await processFiles(scopeName, params, context)
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
log.info('File handling plugin initialized successfully')
|
|
291
|
+
await processFiles(scopeName, params, context)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
log.info('File handling plugin initialized successfully')
|
|
295
295
|
}
|
|
296
|
-
}
|
|
296
|
+
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
299
|
* Parse size string to bytes
|
|
300
300
|
* @param {string} size - Size string like '10mb', '1.5GB'
|
|
301
301
|
* @returns {number} Size in bytes
|
|
302
302
|
*/
|
|
303
|
-
function parseSize(size) {
|
|
303
|
+
function parseSize (size) {
|
|
304
304
|
const units = {
|
|
305
305
|
b: 1,
|
|
306
306
|
kb: 1024,
|
|
307
307
|
mb: 1024 * 1024,
|
|
308
308
|
gb: 1024 * 1024 * 1024
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)$/)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)$/)
|
|
312
312
|
if (!match) {
|
|
313
|
-
throw new Error(`Invalid size format: ${size}`)
|
|
313
|
+
throw new Error(`Invalid size format: ${size}`)
|
|
314
314
|
}
|
|
315
|
-
|
|
316
|
-
const [, num, unit] = match
|
|
317
|
-
const multiplier = units[unit]
|
|
318
|
-
|
|
315
|
+
|
|
316
|
+
const [, num, unit] = match
|
|
317
|
+
const multiplier = units[unit]
|
|
318
|
+
|
|
319
319
|
if (!multiplier) {
|
|
320
|
-
throw new Error(`Unknown size unit: ${unit}`)
|
|
320
|
+
throw new Error(`Unknown size unit: ${unit}`)
|
|
321
321
|
}
|
|
322
|
-
|
|
323
|
-
return parseFloat(num) * multiplier
|
|
322
|
+
|
|
323
|
+
return parseFloat(num) * multiplier
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
/**
|
|
@@ -328,15 +328,15 @@ function parseSize(size) {
|
|
|
328
328
|
* @param {number} bytes - Size in bytes
|
|
329
329
|
* @returns {string} Human readable size
|
|
330
330
|
*/
|
|
331
|
-
function formatSize(bytes) {
|
|
332
|
-
const units = ['B', 'KB', 'MB', 'GB']
|
|
333
|
-
let size = bytes
|
|
334
|
-
let unitIndex = 0
|
|
335
|
-
|
|
331
|
+
function formatSize (bytes) {
|
|
332
|
+
const units = ['B', 'KB', 'MB', 'GB']
|
|
333
|
+
let size = bytes
|
|
334
|
+
let unitIndex = 0
|
|
335
|
+
|
|
336
336
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
337
|
-
size /= 1024
|
|
338
|
-
unitIndex
|
|
337
|
+
size /= 1024
|
|
338
|
+
unitIndex++
|
|
339
339
|
}
|
|
340
|
-
|
|
341
|
-
return `${size.toFixed(1)}${units[unitIndex]}
|
|
342
|
-
}
|
|
340
|
+
|
|
341
|
+
return `${size.toFixed(1)}${units[unitIndex]}`
|
|
342
|
+
}
|