just-another-http-api 1.0.5 → 1.2.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/.eslintrc +148 -0
- package/LICENSE +1 -1
- package/README.md +401 -151
- package/api.js +220 -126
- package/package.json +15 -9
- package/src/auth.js +140 -0
- package/src/cache.js +69 -0
- package/src/cors.js +9 -0
- package/src/upload.js +146 -0
- package/utils/response.js +48 -0
package/src/upload.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const multer = require ( 'fastify-multer' );
|
|
2
|
+
const os = require ( 'os' );
|
|
3
|
+
const crypto = require ( 'crypto' );
|
|
4
|
+
const fs = require ( 'fs' );
|
|
5
|
+
const path = require ( 'path' );
|
|
6
|
+
const { Upload } = require ( '@aws-sdk/lib-storage' );
|
|
7
|
+
const { PassThrough } = require ( 'stream' );
|
|
8
|
+
|
|
9
|
+
let upload;
|
|
10
|
+
|
|
11
|
+
exports.initialiseUploads = async ( app, config ) => {
|
|
12
|
+
if ( config?.uploads?.enabled ) {
|
|
13
|
+
try {
|
|
14
|
+
app.register ( multer.contentParser );
|
|
15
|
+
}
|
|
16
|
+
catch ( error ) {
|
|
17
|
+
console.error ( 'Error registering multer content parser:', error );
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.handleUpload = ( handlerConfig, globalConfig ) => {
|
|
24
|
+
let storage;
|
|
25
|
+
try {
|
|
26
|
+
switch ( handlerConfig?.upload?.storageType || globalConfig.uploads.storageType ) {
|
|
27
|
+
case 's3':
|
|
28
|
+
storage = s3Storage ( handlerConfig, globalConfig );
|
|
29
|
+
break;
|
|
30
|
+
case 'filesystem':
|
|
31
|
+
const uploadDir = globalConfig.uploads.localUploadDirectory || os.tmpdir ();
|
|
32
|
+
ensureDirectoryExists ( uploadDir );
|
|
33
|
+
storage = multer.diskStorage ( {
|
|
34
|
+
destination: ( req, file, cb ) => cb ( null, uploadDir ),
|
|
35
|
+
filename: ( req, file, cb ) => cb ( null, crypto.randomUUID () + path.extname ( file.originalname ) )
|
|
36
|
+
} );
|
|
37
|
+
break;
|
|
38
|
+
case 'memory':
|
|
39
|
+
default:
|
|
40
|
+
storage = multer.memoryStorage ();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch ( error ) {
|
|
45
|
+
console.error ( 'Error setting up storage:', error );
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
upload = multer ( {
|
|
50
|
+
storage,
|
|
51
|
+
limits: {
|
|
52
|
+
fileSize: handlerConfig.upload.maxFileSize,
|
|
53
|
+
files: handlerConfig.upload.maxFiles
|
|
54
|
+
}
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
return async ( req, reply ) => {
|
|
58
|
+
const fieldName = handlerConfig?.upload?.requestFileKey || 'file';
|
|
59
|
+
|
|
60
|
+
const multerHandler = handlerConfig.upload.maxFiles > 1 ? upload.array ( fieldName, handlerConfig.upload.maxFiles ) : upload.single ( fieldName );
|
|
61
|
+
|
|
62
|
+
return new Promise ( ( resolve, reject ) => {
|
|
63
|
+
multerHandler ( req, reply.raw, ( err ) => {
|
|
64
|
+
if ( err ) {
|
|
65
|
+
console.error ( 'Multer error:', err );
|
|
66
|
+
reply.code ( 500 ).send ( err );
|
|
67
|
+
reject ( err );
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
resolve ();
|
|
71
|
+
}
|
|
72
|
+
} );
|
|
73
|
+
} );
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function ensureDirectoryExists ( dir ) {
|
|
78
|
+
try {
|
|
79
|
+
if ( !fs.existsSync ( dir ) ) {
|
|
80
|
+
fs.mkdirSync ( dir, { recursive: true } );
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch ( error ) {
|
|
84
|
+
console.error ( `Error ensuring directory ${dir} exists:`, error );
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const s3Storage = ( handlerConfig, globalConfig ) => {
|
|
90
|
+
const _handleFile = function ( req, file, cb ) {
|
|
91
|
+
const passThroughStream = new PassThrough ();
|
|
92
|
+
const filename = crypto.randomUUID () + path.extname ( file.originalname );
|
|
93
|
+
const Key = [ globalConfig.uploads.s3UploadDirectory, handlerConfig?.upload?.subDirectory, filename ].filter ( Boolean ).join ( '/' );
|
|
94
|
+
|
|
95
|
+
const params = {
|
|
96
|
+
Bucket: globalConfig.uploads.s3UploadBucket,
|
|
97
|
+
Key,
|
|
98
|
+
Body: passThroughStream,
|
|
99
|
+
ContentType: file.mimetype,
|
|
100
|
+
ACL: handlerConfig?.upload?.s3ACL || 'public-read'
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const upload = new Upload ( {
|
|
104
|
+
client: globalConfig.uploads.s3Client,
|
|
105
|
+
params
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
upload.done ()
|
|
109
|
+
.then ( () => cb ( null, {
|
|
110
|
+
path: params.Key,
|
|
111
|
+
size: file.size
|
|
112
|
+
} ) )
|
|
113
|
+
.catch ( error => {
|
|
114
|
+
console.log ( 'ERROR UPLOADING FILE', error );
|
|
115
|
+
_removeFile ( req, { Key, Bucket: params.Bucket }, () => cb ( error ) );
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
file.stream.pipe ( passThroughStream );
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const _removeFile = function ( req, file, cb ) {
|
|
122
|
+
if ( !file || !file.Key || !file.Bucket ) {
|
|
123
|
+
console.log ( 'No file information available for deletion' );
|
|
124
|
+
cb ();
|
|
125
|
+
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const deleteParams = {
|
|
130
|
+
Bucket: file.Bucket,
|
|
131
|
+
Key: file.Key
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
globalConfig.uploads.s3Client.send ( new DeleteObjectCommand ( deleteParams ) )
|
|
135
|
+
.then ( () => {
|
|
136
|
+
console.log ( `File deleted: ${file.Key}` );
|
|
137
|
+
cb ();
|
|
138
|
+
} )
|
|
139
|
+
.catch ( error => {
|
|
140
|
+
console.log ( `Error deleting file: ${file.Key}`, error );
|
|
141
|
+
cb ( error );
|
|
142
|
+
} );
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return { _handleFile, _removeFile };
|
|
146
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a standardized response object.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} options - The options for the response.
|
|
5
|
+
* @param {string} [options.json] - Sends JSON as the response body.
|
|
6
|
+
* @param {string} [options.html] - Sends HTML as the response body.
|
|
7
|
+
* @param {string} [options.text] - Sends plain text as the response body.
|
|
8
|
+
* @param {object} [options.file] - Sends a file as the response.
|
|
9
|
+
* @param {object} [options.redirect] - Redirects to a specified URL.
|
|
10
|
+
* @param {object} [options.headers={}] - Sets the headers for the response.
|
|
11
|
+
* @param {number} [options.code] - The HTTP status code for the response.
|
|
12
|
+
* @param {object} [options.error={message, code=500}] - Error response with message and code.
|
|
13
|
+
* @returns {object} An object containing the response data.
|
|
14
|
+
*/
|
|
15
|
+
function createResponse ( options = {} ) {
|
|
16
|
+
const response = {
|
|
17
|
+
headers: options.headers || {},
|
|
18
|
+
code: options.code || 200,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if ( 'json' in options ) {
|
|
22
|
+
response.json = options.json;
|
|
23
|
+
response.headers[ 'Content-Type' ] = 'application/json';
|
|
24
|
+
}
|
|
25
|
+
else if ( 'html' in options ) {
|
|
26
|
+
response.html = options.html;
|
|
27
|
+
response.headers[ 'Content-Type' ] = 'text/html';
|
|
28
|
+
}
|
|
29
|
+
else if ( 'text' in options ) {
|
|
30
|
+
response.text = options.text;
|
|
31
|
+
response.headers[ 'Content-Type' ] = 'text/plain';
|
|
32
|
+
}
|
|
33
|
+
else if ( 'file' in options ) {
|
|
34
|
+
response.file = options.file; // Assuming this is a buffer or stream
|
|
35
|
+
}
|
|
36
|
+
else if ( 'redirect' in options ) {
|
|
37
|
+
response.redirect = options.redirect;
|
|
38
|
+
}
|
|
39
|
+
else if ( options.error ) {
|
|
40
|
+
response.error = options.error;
|
|
41
|
+
response.code = options.error.code || 500;
|
|
42
|
+
response.headers[ 'Content-Type' ] = 'application/json';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return response;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = createResponse;
|