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,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Busboy File Detector for HTTP Multipart Uploads
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This detector handles multipart/form-data uploads using the busboy library.
|
|
5
5
|
* It's suitable for both HTTP and Express connectors.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Streaming parser (memory efficient)
|
|
9
9
|
* - Configurable file size limits
|
|
10
10
|
* - Automatic field parsing
|
|
11
11
|
* - No temporary files by default (keeps in memory)
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* Usage:
|
|
14
14
|
* ```javascript
|
|
15
15
|
* import { createBusboyDetector } from 'jsonrestapi/plugins/core/lib/busboy-detector.js';
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
17
|
* api.use(HttpPlugin, {
|
|
18
18
|
* fileParser: 'busboy',
|
|
19
19
|
* fileParserOptions: {
|
|
@@ -23,20 +23,20 @@
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { requirePackage } from 'hooked-api'
|
|
27
|
-
import { Readable } from 'stream'
|
|
26
|
+
import { requirePackage } from 'hooked-api'
|
|
27
|
+
import { Readable } from 'stream'
|
|
28
28
|
|
|
29
|
-
let Busboy
|
|
29
|
+
let Busboy
|
|
30
30
|
try {
|
|
31
|
-
Busboy = (await import('busboy')).default
|
|
31
|
+
Busboy = (await import('busboy')).default
|
|
32
32
|
} catch (e) {
|
|
33
|
-
requirePackage('busboy', 'express/http-connector',
|
|
34
|
-
'Busboy is required for multipart/form-data file uploads. This is a peer dependency.')
|
|
33
|
+
requirePackage('busboy', 'express/http-connector',
|
|
34
|
+
'Busboy is required for multipart/form-data file uploads. This is a peer dependency.')
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Creates a busboy-based file detector
|
|
39
|
-
*
|
|
39
|
+
*
|
|
40
40
|
* @param {Object} options - Busboy configuration options
|
|
41
41
|
* @param {Object} options.limits - Size limits
|
|
42
42
|
* @param {number} options.limits.fileSize - Max file size in bytes
|
|
@@ -44,132 +44,132 @@ try {
|
|
|
44
44
|
* @param {number} options.limits.fields - Max number of fields
|
|
45
45
|
* @returns {Object} Detector object with detect() and parse() methods
|
|
46
46
|
*/
|
|
47
|
-
export function createBusboyDetector(options = {}) {
|
|
47
|
+
export function createBusboyDetector (options = {}) {
|
|
48
48
|
return {
|
|
49
49
|
name: 'busboy-multipart',
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
/**
|
|
52
52
|
* Check if this detector can handle the request
|
|
53
53
|
* @param {Object} params - Request parameters
|
|
54
54
|
* @returns {boolean} True if this is a multipart request
|
|
55
55
|
*/
|
|
56
56
|
detect: (params) => {
|
|
57
|
-
const req = params._httpReq || params._expressReq
|
|
58
|
-
if (!req || !req.headers) return false
|
|
59
|
-
|
|
60
|
-
const contentType = req.headers['content-type'] || ''
|
|
61
|
-
return contentType.includes('multipart/form-data')
|
|
57
|
+
const req = params._httpReq || params._expressReq
|
|
58
|
+
if (!req || !req.headers) return false
|
|
59
|
+
|
|
60
|
+
const contentType = req.headers['content-type'] || ''
|
|
61
|
+
return contentType.includes('multipart/form-data')
|
|
62
62
|
},
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
/**
|
|
65
65
|
* Parse multipart data from the request
|
|
66
66
|
* @param {Object} params - Request parameters
|
|
67
67
|
* @returns {Promise<{fields: Object, files: Object}>} Parsed data
|
|
68
68
|
*/
|
|
69
69
|
parse: async (params) => {
|
|
70
|
-
const req = params._httpReq || params._expressReq
|
|
71
|
-
|
|
70
|
+
const req = params._httpReq || params._expressReq
|
|
71
|
+
|
|
72
72
|
return new Promise((resolve, reject) => {
|
|
73
73
|
const busboy = new Busboy({
|
|
74
74
|
headers: req.headers,
|
|
75
75
|
...options
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
const fields = {}
|
|
79
|
-
const files = {}
|
|
80
|
-
const filePromises = []
|
|
81
|
-
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const fields = {}
|
|
79
|
+
const files = {}
|
|
80
|
+
const filePromises = []
|
|
81
|
+
|
|
82
82
|
// Handle fields
|
|
83
83
|
busboy.on('field', (fieldname, val) => {
|
|
84
84
|
// Handle array notation (field[] or field[0])
|
|
85
|
-
const arrayMatch = fieldname.match(/^(.+)\[\d*\]$/)
|
|
85
|
+
const arrayMatch = fieldname.match(/^(.+)\[\d*\]$/)
|
|
86
86
|
if (arrayMatch) {
|
|
87
|
-
const baseName = arrayMatch[1]
|
|
87
|
+
const baseName = arrayMatch[1]
|
|
88
88
|
if (!fields[baseName]) {
|
|
89
|
-
fields[baseName] = []
|
|
89
|
+
fields[baseName] = []
|
|
90
90
|
}
|
|
91
91
|
if (Array.isArray(fields[baseName])) {
|
|
92
|
-
fields[baseName].push(val)
|
|
92
|
+
fields[baseName].push(val)
|
|
93
93
|
}
|
|
94
94
|
} else {
|
|
95
|
-
fields[fieldname] = val
|
|
95
|
+
fields[fieldname] = val
|
|
96
96
|
}
|
|
97
|
-
})
|
|
98
|
-
|
|
97
|
+
})
|
|
98
|
+
|
|
99
99
|
// Handle files
|
|
100
100
|
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
|
|
101
|
-
const chunks = []
|
|
102
|
-
let size = 0
|
|
103
|
-
|
|
101
|
+
const chunks = []
|
|
102
|
+
let size = 0
|
|
103
|
+
|
|
104
104
|
const filePromise = new Promise((fileResolve, fileReject) => {
|
|
105
105
|
file.on('data', (chunk) => {
|
|
106
|
-
chunks.push(chunk)
|
|
107
|
-
size += chunk.length
|
|
108
|
-
})
|
|
109
|
-
|
|
106
|
+
chunks.push(chunk)
|
|
107
|
+
size += chunk.length
|
|
108
|
+
})
|
|
109
|
+
|
|
110
110
|
file.on('limit', () => {
|
|
111
|
-
fileReject(new Error(`File size limit exceeded for field '${fieldname}'`))
|
|
112
|
-
})
|
|
113
|
-
|
|
111
|
+
fileReject(new Error(`File size limit exceeded for field '${fieldname}'`))
|
|
112
|
+
})
|
|
113
|
+
|
|
114
114
|
file.on('end', () => {
|
|
115
115
|
files[fieldname] = {
|
|
116
|
-
filename
|
|
117
|
-
mimetype
|
|
118
|
-
encoding
|
|
119
|
-
size
|
|
116
|
+
filename,
|
|
117
|
+
mimetype,
|
|
118
|
+
encoding,
|
|
119
|
+
size,
|
|
120
120
|
data: Buffer.concat(chunks)
|
|
121
|
-
}
|
|
122
|
-
fileResolve()
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
file.on('error', fileReject)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
filePromises.push(filePromise)
|
|
129
|
-
})
|
|
130
|
-
|
|
121
|
+
}
|
|
122
|
+
fileResolve()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
file.on('error', fileReject)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
filePromises.push(filePromise)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
131
|
// Handle completion
|
|
132
132
|
busboy.on('finish', async () => {
|
|
133
133
|
try {
|
|
134
134
|
// Wait for all files to be fully read
|
|
135
|
-
await Promise.all(filePromises)
|
|
136
|
-
resolve({ fields, files })
|
|
135
|
+
await Promise.all(filePromises)
|
|
136
|
+
resolve({ fields, files })
|
|
137
137
|
} catch (error) {
|
|
138
|
-
reject(error)
|
|
138
|
+
reject(error)
|
|
139
139
|
}
|
|
140
|
-
})
|
|
141
|
-
|
|
140
|
+
})
|
|
141
|
+
|
|
142
142
|
// Handle errors
|
|
143
143
|
busboy.on('error', (error) => {
|
|
144
|
-
reject(error)
|
|
145
|
-
})
|
|
146
|
-
|
|
144
|
+
reject(error)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
147
|
// Handle limit errors
|
|
148
148
|
busboy.on('partsLimit', () => {
|
|
149
|
-
reject(new Error('Parts limit exceeded'))
|
|
150
|
-
})
|
|
151
|
-
|
|
149
|
+
reject(new Error('Parts limit exceeded'))
|
|
150
|
+
})
|
|
151
|
+
|
|
152
152
|
busboy.on('filesLimit', () => {
|
|
153
|
-
reject(new Error('Files limit exceeded'))
|
|
154
|
-
})
|
|
155
|
-
|
|
153
|
+
reject(new Error('Files limit exceeded'))
|
|
154
|
+
})
|
|
155
|
+
|
|
156
156
|
busboy.on('fieldsLimit', () => {
|
|
157
|
-
reject(new Error('Fields limit exceeded'))
|
|
158
|
-
})
|
|
159
|
-
|
|
157
|
+
reject(new Error('Fields limit exceeded'))
|
|
158
|
+
})
|
|
159
|
+
|
|
160
160
|
// Pipe request to busboy
|
|
161
161
|
if (req.pipe) {
|
|
162
|
-
req.pipe(busboy)
|
|
162
|
+
req.pipe(busboy)
|
|
163
163
|
} else if (req.on) {
|
|
164
164
|
// For already buffered requests
|
|
165
|
-
const stream = new Readable()
|
|
166
|
-
stream.push(req.body || req)
|
|
167
|
-
stream.push(null)
|
|
168
|
-
stream.pipe(busboy)
|
|
165
|
+
const stream = new Readable()
|
|
166
|
+
stream.push(req.body || req)
|
|
167
|
+
stream.push(null)
|
|
168
|
+
stream.pipe(busboy)
|
|
169
169
|
} else {
|
|
170
|
-
reject(new Error('Request object is not a stream'))
|
|
170
|
+
reject(new Error('Request object is not a stream'))
|
|
171
171
|
}
|
|
172
|
-
})
|
|
172
|
+
})
|
|
173
173
|
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Formidable File Detector for HTTP Multipart Uploads
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This detector handles multipart/form-data uploads using the formidable library.
|
|
5
5
|
* Unlike busboy, formidable saves files to disk temporarily, which is better
|
|
6
6
|
* for large files.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - Disk-based storage (better for large files)
|
|
10
10
|
* - Automatic temp file cleanup
|
|
11
11
|
* - Progress tracking support
|
|
12
12
|
* - Built-in file type detection
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* Usage:
|
|
15
15
|
* ```javascript
|
|
16
16
|
* import { createFormidableDetector } from 'jsonrestapi/plugins/core/lib/formidable-detector.js';
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
18
|
* api.use(ExpressPlugin, {
|
|
19
19
|
* fileParser: 'formidable',
|
|
20
20
|
* fileParserOptions: {
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import { requirePackage } from 'hooked-api'
|
|
30
|
-
import { promises as fs } from 'fs'
|
|
31
|
-
import path from 'path'
|
|
29
|
+
import { requirePackage } from 'hooked-api'
|
|
30
|
+
import { promises as fs } from 'fs'
|
|
31
|
+
import path from 'path'
|
|
32
32
|
|
|
33
|
-
let formidable
|
|
33
|
+
let formidable
|
|
34
34
|
try {
|
|
35
|
-
formidable = (await import('formidable')).default
|
|
35
|
+
formidable = (await import('formidable')).default
|
|
36
36
|
} catch (e) {
|
|
37
|
-
requirePackage('formidable', 'express/http-connector',
|
|
38
|
-
'Formidable is required for multipart/form-data file uploads with disk storage. This is a peer dependency.')
|
|
37
|
+
requirePackage('formidable', 'express/http-connector',
|
|
38
|
+
'Formidable is required for multipart/form-data file uploads with disk storage. This is a peer dependency.')
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Creates a formidable-based file detector
|
|
43
|
-
*
|
|
43
|
+
*
|
|
44
44
|
* @param {Object} options - Formidable configuration options
|
|
45
45
|
* @param {string} options.uploadDir - Directory for temporary files
|
|
46
46
|
* @param {boolean} options.keepExtensions - Keep file extensions
|
|
@@ -48,91 +48,91 @@ try {
|
|
|
48
48
|
* @param {boolean} options.multiples - Allow multiple files per field
|
|
49
49
|
* @returns {Object} Detector object with detect() and parse() methods
|
|
50
50
|
*/
|
|
51
|
-
export function createFormidableDetector(options = {}) {
|
|
51
|
+
export function createFormidableDetector (options = {}) {
|
|
52
52
|
// Set default upload directory
|
|
53
|
-
const uploadDir = options.uploadDir || path.join(process.cwd(), 'uploads', 'temp')
|
|
54
|
-
|
|
53
|
+
const uploadDir = options.uploadDir || path.join(process.cwd(), 'uploads', 'temp')
|
|
54
|
+
|
|
55
55
|
return {
|
|
56
56
|
name: 'formidable-multipart',
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
/**
|
|
59
59
|
* Check if this detector can handle the request
|
|
60
60
|
* @param {Object} params - Request parameters
|
|
61
61
|
* @returns {boolean} True if this is a multipart request
|
|
62
62
|
*/
|
|
63
63
|
detect: (params) => {
|
|
64
|
-
const req = params._httpReq || params._expressReq
|
|
65
|
-
if (!req || !req.headers) return false
|
|
66
|
-
|
|
67
|
-
const contentType = req.headers['content-type'] || ''
|
|
68
|
-
return contentType.includes('multipart/form-data')
|
|
64
|
+
const req = params._httpReq || params._expressReq
|
|
65
|
+
if (!req || !req.headers) return false
|
|
66
|
+
|
|
67
|
+
const contentType = req.headers['content-type'] || ''
|
|
68
|
+
return contentType.includes('multipart/form-data')
|
|
69
69
|
},
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
/**
|
|
72
72
|
* Parse multipart data from the request
|
|
73
73
|
* @param {Object} params - Request parameters
|
|
74
74
|
* @returns {Promise<{fields: Object, files: Object}>} Parsed data
|
|
75
75
|
*/
|
|
76
76
|
parse: async (params) => {
|
|
77
|
-
const req = params._httpReq || params._expressReq
|
|
78
|
-
|
|
77
|
+
const req = params._httpReq || params._expressReq
|
|
78
|
+
|
|
79
79
|
// Ensure upload directory exists
|
|
80
|
-
await fs.mkdir(uploadDir, { recursive: true })
|
|
81
|
-
|
|
80
|
+
await fs.mkdir(uploadDir, { recursive: true })
|
|
81
|
+
|
|
82
82
|
// Create form parser
|
|
83
83
|
const form = formidable({
|
|
84
84
|
uploadDir,
|
|
85
85
|
keepExtensions: true,
|
|
86
86
|
...options
|
|
87
|
-
})
|
|
88
|
-
|
|
87
|
+
})
|
|
88
|
+
|
|
89
89
|
// Parse the request
|
|
90
|
-
const [fields, fileUploads] = await form.parse(req)
|
|
91
|
-
|
|
90
|
+
const [fields, fileUploads] = await form.parse(req)
|
|
91
|
+
|
|
92
92
|
// Normalize formidable's file format to our standard format
|
|
93
|
-
const files = {}
|
|
94
|
-
|
|
93
|
+
const files = {}
|
|
94
|
+
|
|
95
95
|
for (const [fieldname, uploadedFiles] of Object.entries(fileUploads)) {
|
|
96
96
|
// Formidable returns arrays for multiple files
|
|
97
|
-
const fileArray = Array.isArray(uploadedFiles) ? uploadedFiles : [uploadedFiles]
|
|
98
|
-
|
|
97
|
+
const fileArray = Array.isArray(uploadedFiles) ? uploadedFiles : [uploadedFiles]
|
|
98
|
+
|
|
99
99
|
// For now, just take the first file (TODO: handle multiple files per field)
|
|
100
|
-
const file = fileArray[0]
|
|
101
|
-
|
|
100
|
+
const file = fileArray[0]
|
|
101
|
+
|
|
102
102
|
if (file) {
|
|
103
103
|
// Read file data into memory (for small files)
|
|
104
104
|
// For large files, you might want to keep them on disk
|
|
105
|
-
const data = await fs.readFile(file.filepath)
|
|
106
|
-
|
|
105
|
+
const data = await fs.readFile(file.filepath)
|
|
106
|
+
|
|
107
107
|
files[fieldname] = {
|
|
108
108
|
filename: file.originalFilename || 'unknown',
|
|
109
109
|
mimetype: file.mimetype || 'application/octet-stream',
|
|
110
110
|
size: file.size,
|
|
111
|
-
data
|
|
111
|
+
data,
|
|
112
112
|
filepath: file.filepath, // Keep for reference
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
// Cleanup function to remove temp file
|
|
115
115
|
cleanup: async () => {
|
|
116
116
|
try {
|
|
117
|
-
await fs.unlink(file.filepath)
|
|
117
|
+
await fs.unlink(file.filepath)
|
|
118
118
|
} catch (error) {
|
|
119
119
|
// File might already be deleted, ignore
|
|
120
120
|
if (error.code !== 'ENOENT') {
|
|
121
|
-
throw error
|
|
121
|
+
throw error
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
}
|
|
125
|
+
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
// Normalize fields (formidable returns arrays for repeated fields)
|
|
130
|
-
const normalizedFields = {}
|
|
130
|
+
const normalizedFields = {}
|
|
131
131
|
for (const [key, value] of Object.entries(fields)) {
|
|
132
|
-
normalizedFields[key] = Array.isArray(value) && value.length === 1 ? value[0] : value
|
|
132
|
+
normalizedFields[key] = Array.isArray(value) && value.length === 1 ? value[0] : value
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
return { fields: normalizedFields, files }
|
|
134
|
+
|
|
135
|
+
return { fields: normalizedFields, files }
|
|
136
136
|
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
137
|
+
}
|
|
138
|
+
}
|