s3.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import aws from '@aws-sdk/client-s3';
  2. import multer from 'multer';
  3. import multerS3 from 'multer-s3';
  4. import mime from 'mime-types';
  5. import s3RequestPresigner from '@aws-sdk/s3-request-presigner';
  6. import { uid } from '../../uid.js';
  7. const {
  8. S3Client,
  9. ListObjectsCommand,
  10. GetObjectCommand,
  11. DeleteObjectCommand,
  12. HeadObjectCommand,
  13. } = aws;
  14. const { getSignedUrl } = s3RequestPresigner;
  15. // Help here https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html
  16. const S3FileBackend = ({
  17. bucket,
  18. secretKey,
  19. accessKey,
  20. endpoint,
  21. region,
  22. proxy = false,
  23. cdn = '',
  24. signedUrl = true,
  25. }) => {
  26. const s3 = new S3Client({
  27. secretAccessKey: secretKey,
  28. accessKeyId: accessKey,
  29. endpoint,
  30. region,
  31. });
  32. const upload = multer({
  33. storage: multerS3({
  34. s3: s3,
  35. acl: 'public-read',
  36. bucket: bucket,
  37. //contentType: multerS3.AUTO_CONTENT_TYPE,
  38. contentType: (req, file, cb) => {
  39. cb(null, file.mimetype);
  40. },
  41. key: (req, file, cb) => {
  42. const keyPath = `${req.siteId}/${req.boxId}/${req.resourceId}`;
  43. const ext = mime.extension(file.mimetype);
  44. const filename = `${uid()}.${ext}`;
  45. // Add filename to file
  46. file.filename = filename;
  47. cb(null, `${keyPath}/${filename}`);
  48. },
  49. }),
  50. limits: { fileSize: 1024 * 1024 * 5 }, // 5MB
  51. });
  52. return {
  53. uploadManager: upload.single('file'),
  54. async list(siteId, boxId, resourceId) {
  55. const params = {
  56. Bucket: bucket,
  57. Delimiter: '/',
  58. Prefix: `${siteId}/${boxId}/${resourceId}/`,
  59. };
  60. const data = await s3.send(new ListObjectsCommand(params));
  61. if (data.Contents === undefined) {
  62. return [];
  63. }
  64. const toRemove = new RegExp(`^${siteId}/${boxId}/${resourceId}/`);
  65. return data.Contents.map(({ Key }) => Key.replace(toRemove, ''));
  66. },
  67. async store(siteId, boxId, resourceId, file) {
  68. return file.filename;
  69. },
  70. async exists(siteId, boxId, resourceId, filename) {
  71. const headParams = {
  72. Bucket: bucket,
  73. Key: `${siteId}/${boxId}/${resourceId}/${filename}`,
  74. };
  75. try {
  76. await s3.send(new HeadObjectCommand(headParams));
  77. return true;
  78. } catch (headErr) {
  79. if (headErr.name === 'NotFound') {
  80. return false;
  81. }
  82. throw headErr;
  83. }
  84. },
  85. async get(
  86. siteId,
  87. boxId,
  88. resourceId,
  89. filename,
  90. {
  91. 'if-none-match': IfNoneMatch,
  92. 'if-match': IfMatch,
  93. 'if-modified-since': IfModifiedSince,
  94. 'if-unmodified-since': IfUnmodifiedSince,
  95. range: Range,
  96. }
  97. ) {
  98. // Here we proxy the file if needed
  99. if (proxy) {
  100. const params = {
  101. Bucket: bucket,
  102. Key: `${siteId}/${boxId}/${resourceId}/${filename}`,
  103. IfNoneMatch,
  104. IfUnmodifiedSince,
  105. IfModifiedSince,
  106. IfMatch,
  107. Range,
  108. };
  109. const { Body } = await s3.send(new GetObjectCommand(params));
  110. return {
  111. length: Body.headers['content-length'],
  112. mimetype: Body.headers['content-type'],
  113. eTag: Body.headers['etag'],
  114. lastModified: Body.headers['last-modified'],
  115. statusCode: Body.statusCode,
  116. stream: Body.statusCode === 304 ? null : Body,
  117. };
  118. }
  119. // Here we have a cdn in front
  120. if (cdn) {
  121. return {
  122. redirectTo: `${cdn}/${siteId}/${boxId}/${resourceId}/${filename}`,
  123. };
  124. }
  125. // We generate a signed url and we return it
  126. if (signedUrl) {
  127. const params = {
  128. Bucket: bucket,
  129. Key: `${siteId}/${boxId}/${resourceId}/${filename}`,
  130. };
  131. const command = new GetObjectCommand(params);
  132. const url = await getSignedUrl(s3, command, { expiresIn: 60 * 5 });
  133. return { redirectTo: url };
  134. }
  135. // Finally we just use public URL
  136. return {
  137. redirectTo: `${endpoint}/${siteId}/${boxId}/${resourceId}/${filename}`,
  138. };
  139. },
  140. async delete(siteId, boxId, resourceId, filename) {
  141. const key = `${siteId}/${boxId}/${resourceId}/${filename}`;
  142. const headParams = {
  143. Bucket: bucket,
  144. Key: key,
  145. };
  146. await s3.send(new DeleteObjectCommand(headParams));
  147. },
  148. };
  149. };
  150. export default S3FileBackend;