diff --git a/packages/medusa-file-spaces/.babelrc b/packages/medusa-file-spaces/.babelrc new file mode 100644 index 0000000000..4d2dfe8f09 --- /dev/null +++ b/packages/medusa-file-spaces/.babelrc @@ -0,0 +1,13 @@ +{ + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-transform-instanceof", + "@babel/plugin-transform-classes" + ], + "presets": ["@babel/preset-env"], + "env": { + "test": { + "plugins": ["@babel/plugin-transform-runtime"] + } + } +} diff --git a/packages/medusa-file-spaces/.eslintrc b/packages/medusa-file-spaces/.eslintrc new file mode 100644 index 0000000000..2a889697f0 --- /dev/null +++ b/packages/medusa-file-spaces/.eslintrc @@ -0,0 +1,9 @@ +{ + "plugins": ["prettier"], + "extends": ["prettier"], + "rules": { + "prettier/prettier": "error", + "semi": "error", + "no-unused-expressions": "true" + } +} diff --git a/packages/medusa-file-spaces/.gitignore b/packages/medusa-file-spaces/.gitignore new file mode 100644 index 0000000000..2ca7f03256 --- /dev/null +++ b/packages/medusa-file-spaces/.gitignore @@ -0,0 +1,16 @@ +/lib +node_modules +.DS_store +.env* +/*.js +!index.js +yarn.lock + +/dist + +/api +/services +/models +/subscribers +/__mocks__ + diff --git a/packages/medusa-file-spaces/.npmignore b/packages/medusa-file-spaces/.npmignore new file mode 100644 index 0000000000..486581be18 --- /dev/null +++ b/packages/medusa-file-spaces/.npmignore @@ -0,0 +1,9 @@ +/lib +node_modules +.DS_store +.env* +/*.js +!index.js +yarn.lock + + diff --git a/packages/medusa-file-spaces/.prettierrc b/packages/medusa-file-spaces/.prettierrc new file mode 100644 index 0000000000..70175ce150 --- /dev/null +++ b/packages/medusa-file-spaces/.prettierrc @@ -0,0 +1,7 @@ +{ + "endOfLine": "lf", + "semi": false, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5" +} \ No newline at end of file diff --git a/packages/medusa-file-spaces/index.js b/packages/medusa-file-spaces/index.js new file mode 100644 index 0000000000..172f1ae6a4 --- /dev/null +++ b/packages/medusa-file-spaces/index.js @@ -0,0 +1 @@ +// noop diff --git a/packages/medusa-file-spaces/package.json b/packages/medusa-file-spaces/package.json new file mode 100644 index 0000000000..a0cd9d54f4 --- /dev/null +++ b/packages/medusa-file-spaces/package.json @@ -0,0 +1,46 @@ +{ + "name": "medusa-file-spaces", + "version": "0.3.0", + "description": "Digital Ocean Spaces file connector for Medusa", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/medusa-file-spaces" + }, + "author": "Sebastian Rindom", + "license": "AGPL-3.0-or-later", + "devDependencies": { + "@babel/cli": "^7.7.5", + "@babel/core": "^7.7.5", + "@babel/node": "^7.7.4", + "@babel/plugin-proposal-class-properties": "^7.7.4", + "@babel/plugin-transform-instanceof": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/preset-env": "^7.7.5", + "@babel/register": "^7.7.4", + "@babel/runtime": "^7.9.6", + "client-sessions": "^0.8.0", + "cross-env": "^5.2.1", + "eslint": "^6.8.0", + "jest": "^25.5.2", + "medusa-test-utils": "^0.3.0" + }, + "scripts": { + "build": "babel src -d .", + "prepare": "cross-env NODE_ENV=production npm run build", + "watch": "babel -w src --out-dir . --ignore **/__tests__", + "test": "jest" + }, + "dependencies": { + "@babel/plugin-transform-classes": "^7.9.5", + "aws-sdk": "^2.710.0", + "body-parser": "^1.19.0", + "express": "^4.17.1", + "medusa-core-utils": "^0.3.0", + "medusa-interfaces": "^0.3.0", + "medusa-test-utils": "^0.3.0", + "stripe": "^8.50.0" + }, + "gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc" +} diff --git a/packages/medusa-file-spaces/src/services/digital-ocean.js b/packages/medusa-file-spaces/src/services/digital-ocean.js new file mode 100644 index 0000000000..efbc7864d8 --- /dev/null +++ b/packages/medusa-file-spaces/src/services/digital-ocean.js @@ -0,0 +1,51 @@ +import fs from "fs" +import aws from "aws-sdk" +import { FileService } from "medusa-interfaces" + +class DigitalOceanService extends FileService { + constructor({}, options) { + super() + + this.bucket_ = options.bucket + this.spacesUrl_ = options.spaces_url + this.accessKeyId_ = options.access_key_id + this.secretAccessKey_ = options.secret_access_key + this.region_ = options.region + this.endpoint_ = options.endpoint + } + + upload(file) { + aws.config.setPromisesDependency(); + aws.config.update({ + accessKeyId: this.accessKeyId_, + secretAccessKey: this.secretAccessKey_, + region: this.region_, + endpoint: this.endpoint_, + }); + + const s3 = new aws.S3(); + var params = { + ACL: 'public-read', + Bucket: this.bucket_, + Body: fs.createReadStream(file.path), + Key: `${file.originalname}` + } + + return new Promise((resolve, reject) => { + s3.upload(params, (err, data) => { + if (err) { + reject(err) + return + } + + resolve({ url: data.Location }) + }) + }) + } + + delete(file) { + console.log(file) + } +} + +export default DigitalOceanService diff --git a/packages/medusa-interfaces/src/file-service.js b/packages/medusa-interfaces/src/file-service.js new file mode 100644 index 0000000000..103e8bb49e --- /dev/null +++ b/packages/medusa-interfaces/src/file-service.js @@ -0,0 +1,21 @@ +import BaseService from "./base-service" + +/** + * Interface for file connectors + * @interface + */ +class BaseFileService extends BaseService { + constructor() { + super() + } + + upload() { + throw Error("upload must be overridden by the child class") + } + + delete() { + throw Error("delete must be overridden by the child class") + } +} + +export default BaseFileService diff --git a/packages/medusa-interfaces/src/index.js b/packages/medusa-interfaces/src/index.js index a8ea7b443e..a5711e18ed 100644 --- a/packages/medusa-interfaces/src/index.js +++ b/packages/medusa-interfaces/src/index.js @@ -2,3 +2,4 @@ export { default as BaseService } from "./base-service" export { default as BaseModel } from "./base-model" export { default as PaymentService } from "./payment-service" export { default as FulfillmentService } from "./fulfillment-service" +export { default as FileService } from "./file-service" diff --git a/packages/medusa/package.json b/packages/medusa/package.json index 041582e8d8..e48378589d 100644 --- a/packages/medusa/package.json +++ b/packages/medusa/package.json @@ -38,9 +38,6 @@ "serve": "node dist/app.js", "test": "jest" }, - "peerDependencies": { - "medusa-interfaces": "^0.3.0" - }, "peerDependencies": { "mongoose": "5.x" }, @@ -65,6 +62,7 @@ "medusa-interfaces": "^0.1.27", "medusa-test-utils": "^0.3.0", "morgan": "^1.9.1", + "multer": "^1.4.2", "passport": "^0.4.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.0", @@ -72,4 +70,4 @@ "winston": "^3.2.1" }, "gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc" -} \ No newline at end of file +} diff --git a/packages/medusa/src/api/routes/admin/index.js b/packages/medusa/src/api/routes/admin/index.js index 5bcf6320b2..f28d0da7d3 100644 --- a/packages/medusa/src/api/routes/admin/index.js +++ b/packages/medusa/src/api/routes/admin/index.js @@ -11,6 +11,7 @@ import shippingProfileRoutes from "./shipping-profiles" import discountRoutes from "./discounts" import orderRoutes from "./orders" import storeRoutes from "./store" +import uploadRoutes from "./uploads" const route = Router() @@ -46,6 +47,7 @@ export default (app, container, config) => { discountRoutes(route) orderRoutes(route) storeRoutes(route) + uploadRoutes(route) return app } diff --git a/packages/medusa/src/api/routes/admin/uploads/create-upload.js b/packages/medusa/src/api/routes/admin/uploads/create-upload.js new file mode 100644 index 0000000000..0125f0a4c5 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/uploads/create-upload.js @@ -0,0 +1,24 @@ +import fs from "fs" + +export default async (req, res) => { + try { + const fileService = req.scope.resolve("fileService") + + console.log(fileService) + + const result = await Promise.all( + req.files.map(async f => { + console.log(f) + return fileService.upload(f).then(result => { + fs.unlinkSync(f.path) + return result + }) + }) + ) + + res.status(200).json({ uploads: result }) + } catch (err) { + console.log(err) + throw err + } +} diff --git a/packages/medusa/src/api/routes/admin/uploads/index.js b/packages/medusa/src/api/routes/admin/uploads/index.js new file mode 100644 index 0000000000..27cc742961 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/uploads/index.js @@ -0,0 +1,19 @@ +import { Router } from "express" +import multer from "multer" + +import middlewares from "../../../middlewares" + +const route = Router() +const upload = multer({ dest: "uploads/" }) + +export default app => { + app.use("/uploads", route) + + route.post( + "/", + upload.array("files"), + middlewares.wrap(require("./create-upload").default) + ) + + return app +} diff --git a/packages/medusa/src/loaders/plugins.js b/packages/medusa/src/loaders/plugins.js index ecd8fba5da..d327332170 100644 --- a/packages/medusa/src/loaders/plugins.js +++ b/packages/medusa/src/loaders/plugins.js @@ -4,6 +4,7 @@ import { BaseService, PaymentService, FulfillmentService, + FileService, } from "medusa-interfaces" import { getConfigFile, createRequireFromPath } from "medusa-core-utils" import _ from "lodash" @@ -147,6 +148,13 @@ function registerServices(pluginDetails, container) { [name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)), [`fp_${loaded.identifier}`]: aliasTo(name), }) + } else if (loaded.prototype instanceof FileService) { + // Add the service directly to the container in order to make simple + // resolution if we already know which payment provider we need to use + container.register({ + [name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)), + [`fileService`]: aliasTo(name), + }) } else { container.register({ [name]: asFunction(cradle => new loaded(cradle, pluginDetails.options)), diff --git a/packages/medusa/yarn.lock b/packages/medusa/yarn.lock index db34658fff..20f2789ac0 100644 --- a/packages/medusa/yarn.lock +++ b/packages/medusa/yarn.lock @@ -1423,6 +1423,11 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= + aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1774,6 +1779,14 @@ bull@^3.12.1: util.promisify "^1.0.0" uuid "^3.3.3" +busboy@^0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" + integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= + dependencies: + dicer "0.2.5" + readable-stream "1.1.x" + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2071,6 +2084,16 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + configstore@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" @@ -2399,6 +2422,14 @@ diagnostics@^1.1.1: enabled "1.0.x" kuler "1.0.x" +dicer@0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" + integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= + dependencies: + readable-stream "1.1.x" + streamsearch "0.1.2" + diff-sequences@^25.2.6: version "25.2.6" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" @@ -3389,7 +3420,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3712,6 +3743,11 @@ is-wsl@^2.1.1: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -4747,6 +4783,20 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +multer@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" + integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== + dependencies: + append-field "^1.0.0" + busboy "^0.2.11" + concat-stream "^1.5.2" + mkdirp "^0.5.1" + object-assign "^4.1.1" + on-finished "^2.3.0" + type-is "^1.6.4" + xtend "^4.0.0" + mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -4978,7 +5028,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.1.0: +object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -5034,7 +5084,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -on-finished@~2.3.0: +on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= @@ -5500,6 +5550,16 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -5513,7 +5573,7 @@ readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^2.3.5: +readable-stream@^2.2.2, readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6187,6 +6247,11 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +streamsearch@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + string-length@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" @@ -6253,6 +6318,11 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -6570,7 +6640,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -6585,6 +6655,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + uid-safe@~2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" @@ -6943,6 +7018,11 @@ xmlchars@^2.1.1: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"