feat(medusa-file-local): Local file storage plugin (#4118)

Simple local file storage to eliminate/reduce friction related to file services when developers are trying out Medusa for the first time
This commit is contained in:
Oliver Windall Juhl
2023-05-22 11:11:38 +02:00
committed by GitHub
parent 1709c0cc69
commit c4aae6b976
8 changed files with 578 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
---
"medusa-file-local": patch
---
feat(medusa-file-local): Add plugin for local file storage

16
packages/medusa-file-local/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
/dist
/api
/services
/models
/subscribers
/__mocks__

View File

@@ -0,0 +1,49 @@
# Local file storage
Store uploaded files to your Medusa backend locally.
> Not suited for production environments
[Plugin Documentation](https://docs.medusajs.com/plugins/file-service/local) | [Medusa Website](https://medusajs.com) | [Medusa Repository](https://github.com/medusajs/medusa)
## Features
- Store product images locally
---
## Prerequisites
- [Medusa backend](https://docs.medusajs.com/development/backend/install)
---
## How to Install
1\. Run the following command in the directory of the Medusa backend:
```bash
npm install medusa-file-local
```
2 \. In `medusa-config.js` add the following at the end of the `plugins` array:
```js
const plugins = [
// ...
`medusa-file-local`
]
```
---
## Test the Plugin
1\. Run the following command in the directory of the Medusa backend to run the backend:
```bash
npm run start
```
2\. Upload an image for a product using the admin dashboard or using [the Admin APIs](https://docs.medusajs.com/api/admin#tag/Upload).

View File

@@ -0,0 +1,36 @@
{
"name": "medusa-file-local",
"version": "1.0.0",
"description": "Local file plugin",
"main": "dist/index.js",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-file-local"
},
"author": "Medusa",
"license": "MIT",
"devDependencies": {
"@medusajs/medusa": "1.10.1",
"cross-env": "^5.2.1",
"jest": "^25.5.4",
"typescript": "^4.9.5"
},
"scripts": {
"prepare": "cross-env NODE_ENV=production yarn run build",
"test": "jest --passWithNoTests src",
"build": "tsc",
"watch": "tsc --watch"
},
"peerDependencies": {
"medusa-interfaces": "1.3.7"
},
"gitHead": "81a7ff73d012fda722f6e9ef0bd9ba0232d37808",
"keywords": [
"medusa-plugin",
"medusa-plugin-file"
]
}

View File

@@ -0,0 +1,11 @@
import express from "express"
export default (rootDirectory, pluginOptions) => {
const app = express.Router()
const uploadDir = pluginOptions.upload_dir ?? "uploads/images"
app.use(`/${uploadDir}`, express.static(uploadDir))
return app
}

View File

@@ -0,0 +1,69 @@
import {
AbstractFileService,
FileServiceGetUploadStreamResult,
FileServiceUploadResult,
IFileService,
} from "@medusajs/medusa"
import fs from "fs"
import { parse } from "path"
class LocalService extends AbstractFileService implements IFileService {
protected uploadDir_: string
protected backendUrl_: string
constructor({}, options) {
super({}, options)
this.uploadDir_ = options.upload_dir || "uploads/images"
this.backendUrl_ = options.backend_url || "http://localhost:9000"
}
async upload(file: Express.Multer.File): Promise<FileServiceUploadResult> {
return await this.uploadFile(file)
}
async uploadProtected(file: Express.Multer.File) {
return await this.uploadFile(file, {})
}
async uploadFile(
file: Express.Multer.File,
options = {}
): Promise<{ url: string }> {
const parsedFilename = parse(file.originalname)
const fileKey = `${parsedFilename.name}-${Date.now()}${parsedFilename.ext}`
return new Promise((resolve, reject) => {
fs.copyFile(file.path, `${this.uploadDir_}/${fileKey}`, (err) => {
if (err) {
throw err
}
const fileUrl = `${this.backendUrl_}/${this.uploadDir_}/${fileKey}`
resolve({ url: fileUrl })
})
})
}
async delete(file): Promise<void> {
throw Error("Not implemented")
}
async getUploadStreamDescriptor(
fileData
): Promise<FileServiceGetUploadStreamResult> {
throw Error("Not implemented")
}
async getDownloadStream(fileData): Promise<NodeJS.ReadableStream> {
throw Error("Not implemented")
}
async getPresignedDownloadUrl(fileData): Promise<string> {
throw Error("Not implemented")
}
}
export default LocalService

View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"lib": ["es5", "es6", "es2019"],
"target": "es5",
"outDir": "./dist",
"rootDir": "src",
"esModuleInterop": true,
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true,
"downlevelIteration": true // to use ES5 specific tooling
},
"include": ["src"],
"exclude": [
"dist",
"src/**/__tests__",
"src/**/__mocks__",
"src/**/__fixtures__",
"node_modules"
]
}

369
yarn.lock
View File

@@ -5781,6 +5781,25 @@ __metadata:
languageName: node
linkType: hard
"@mapbox/node-pre-gyp@npm:^1.0.0":
version: 1.0.10
resolution: "@mapbox/node-pre-gyp@npm:1.0.10"
dependencies:
detect-libc: ^2.0.0
https-proxy-agent: ^5.0.0
make-dir: ^3.1.0
node-fetch: ^2.6.7
nopt: ^5.0.0
npmlog: ^5.0.1
rimraf: ^3.0.2
semver: ^7.3.5
tar: ^6.1.11
bin:
node-pre-gyp: bin/node-pre-gyp
checksum: 469f3bc00778c76e0a7ffaf40742482462e05fec31b53c55ad6d6a892894046c0db7bb8543ed49b2cf1926dfcd9af1289985c367c5d20076939f8a889f686e45
languageName: node
linkType: hard
"@mdx-js/mdx@npm:^1.6.22":
version: 1.6.22
resolution: "@mdx-js/mdx@npm:1.6.22"
@@ -6048,6 +6067,44 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/medusa-cli@npm:1.3.13":
version: 1.3.13
resolution: "@medusajs/medusa-cli@npm:1.3.13"
dependencies:
"@medusajs/utils": 1.8.4
axios: ^0.21.4
chalk: ^4.0.0
configstore: 5.0.1
core-js: ^3.6.5
dotenv: ^8.2.0
execa: ^5.1.1
fs-exists-cached: ^1.0.0
fs-extra: ^10.0.0
hosted-git-info: ^4.0.2
inquirer: ^8.0.0
is-valid-path: ^0.1.1
meant: ^1.0.3
medusa-core-utils: ^1.2.0
medusa-telemetry: 0.0.16
open: ^8.0.6
ora: ^5.4.1
pg-god: ^1.0.12
prompts: ^2.4.2
regenerator-runtime: ^0.13.11
resolve-cwd: ^3.0.0
semver: ^7.3.8
sqlite3: ^5.0.2
stack-trace: ^0.0.10
ulid: ^2.3.0
url: ^0.11.0
winston: ^3.8.2
yargs: ^15.3.1
bin:
medusa: cli.js
checksum: 13161f3c0b75b2e80d030abde23401113eb6caacbdeeda53483e1a9fd86fc31202334816384a2cf5b9416c7aefb92869a12c18495868c3816bf307d6c9a77a95
languageName: node
linkType: hard
"@medusajs/medusa-js@5.0.0, @medusajs/medusa-js@workspace:packages/medusa-js":
version: 0.0.0-use.local
resolution: "@medusajs/medusa-js@workspace:packages/medusa-js"
@@ -6164,6 +6221,68 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/medusa@npm:1.10.1":
version: 1.10.1
resolution: "@medusajs/medusa@npm:1.10.1"
dependencies:
"@medusajs/medusa-cli": 1.3.13
"@medusajs/modules-sdk": 1.8.5
"@medusajs/utils": 1.8.4
"@types/ioredis": ^4.28.10
"@types/lodash": ^4.14.191
awilix: ^8.0.0
body-parser: ^1.19.0
boxen: ^5.0.1
bullmq: ^3.5.6
chokidar: ^3.4.2
class-transformer: ^0.5.1
class-validator: ^0.14.0
compression: ^1.7.4
connect-redis: ^5.0.0
cookie-parser: ^1.4.6
core-js: ^3.6.5
cors: ^2.8.5
cross-spawn: ^7.0.3
dotenv: ^16.0.3
express: ^4.18.2
express-session: ^1.17.3
fs-exists-cached: ^1.0.0
glob: ^7.1.6
ioredis: ^5.2.5
ioredis-mock: 8.4.0
iso8601-duration: ^1.3.0
jsonwebtoken: ^9.0.0
lodash: ^4.17.21
medusa-core-utils: ^1.2.0
medusa-telemetry: ^0.0.16
medusa-test-utils: ^1.1.40
morgan: ^1.9.1
multer: ^1.4.5-lts.1
node-schedule: ^2.1.1
papaparse: ^5.3.2
passport: ^0.6.0
passport-http-bearer: ^1.0.1
passport-jwt: ^4.0.1
passport-local: ^1.0.0
randomatic: ^3.1.1
redis: ^3.0.2
reflect-metadata: ^0.1.13
regenerator-runtime: ^0.13.11
request-ip: ^2.1.3
scrypt-kdf: ^2.0.1
ulid: ^2.3.0
uuid: ^9.0.0
winston: ^3.8.2
peerDependencies:
"@medusajs/types": 1.8.5
medusa-interfaces: 1.3.7
typeorm: "*"
bin:
medusa: cli.js
checksum: d6981042f25c5294806e3034a84ac1ea2907b9001142338d407fe90677677e634212b5010afd0b7914a7e492fe0831413a68b5ce6ba29f2543607c9112da0ee4
languageName: node
linkType: hard
"@medusajs/modules-sdk@1.8.6, @medusajs/modules-sdk@workspace:packages/modules-sdk":
version: 0.0.0-use.local
resolution: "@medusajs/modules-sdk@workspace:packages/modules-sdk"
@@ -6181,6 +6300,20 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/modules-sdk@npm:1.8.5":
version: 1.8.5
resolution: "@medusajs/modules-sdk@npm:1.8.5"
dependencies:
"@medusajs/types": 1.8.5
"@medusajs/utils": 1.8.4
awilix: ^8.0.0
glob: 7.1.6
medusa-telemetry: ^0.0.16
resolve-cwd: ^3.0.0
checksum: b92f65dd68c43415be7e0db2af5ef44c421443f13a2223cfd17a6e5121be76df1035ee882297e33a741816af6fdc7ed01e35b83246ebaf774d4d19f158b8b76e
languageName: node
linkType: hard
"@medusajs/oas-github-ci@workspace:packages/oas/oas-github-ci":
version: 0.0.0-use.local
resolution: "@medusajs/oas-github-ci@workspace:packages/oas/oas-github-ci"
@@ -6255,6 +6388,13 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/types@npm:1.8.5":
version: 1.8.5
resolution: "@medusajs/types@npm:1.8.5"
checksum: 857c36d81aa06eabb202dff10120a03eb4c1c9c3ea4811071b7f8e801a5a443de8db50656f017721effd542624cac0e0e6894fc98da37b0fa71e794d34c7309e
languageName: node
linkType: hard
"@medusajs/utils@1.8.5, @medusajs/utils@workspace:packages/utils":
version: 0.0.0-use.local
resolution: "@medusajs/utils@workspace:packages/utils"
@@ -6273,6 +6413,19 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/utils@npm:1.8.4":
version: 1.8.4
resolution: "@medusajs/utils@npm:1.8.4"
dependencies:
awilix: ^8.0.0
class-transformer: ^0.5.1
class-validator: ^0.14.0
typeorm: "npm:@medusajs/typeorm@0.3.16-next.0"
ulid: ^2.3.0
checksum: c20ee23dcf604d57ac7a65c67a5b7d959f4cf585d30239a506bfd1db1c22feb81ba6043b507359f116f2e1992b0e5562933b95acbf1fbdbc1dcab656a95b1af2
languageName: node
linkType: hard
"@microsoft/fetch-event-source@npm:2.0.1":
version: 2.0.1
resolution: "@microsoft/fetch-event-source@npm:2.0.1"
@@ -13037,6 +13190,17 @@ __metadata:
languageName: node
linkType: hard
"agentkeepalive@npm:^4.1.3":
version: 4.3.0
resolution: "agentkeepalive@npm:4.3.0"
dependencies:
debug: ^4.1.0
depd: ^2.0.0
humanize-ms: ^1.2.1
checksum: 61cbdab12d45e82e9ae515b0aa8d09617b66f72409e541a646dd7be4b7260d335d7f56a38079ad305bf0ffb8405592a459faf1294111289107f48352a20c2799
languageName: node
linkType: hard
"agentkeepalive@npm:^4.2.1":
version: 4.2.1
resolution: "agentkeepalive@npm:4.2.1"
@@ -15443,7 +15607,7 @@ __metadata:
languageName: node
linkType: hard
"cacache@npm:^15.0.5":
"cacache@npm:^15.0.5, cacache@npm:^15.2.0":
version: 15.3.0
resolution: "cacache@npm:15.3.0"
dependencies:
@@ -17947,7 +18111,7 @@ __metadata:
languageName: node
linkType: hard
"depd@npm:2.0.0, depd@npm:~2.0.0":
"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0":
version: 2.0.0
resolution: "depd@npm:2.0.0"
checksum: 58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
@@ -18635,7 +18799,7 @@ __metadata:
languageName: node
linkType: hard
"encoding@npm:^0.1.11, encoding@npm:^0.1.13":
"encoding@npm:^0.1.11, encoding@npm:^0.1.12, encoding@npm:^0.1.13":
version: 0.1.13
resolution: "encoding@npm:0.1.13"
dependencies:
@@ -28766,6 +28930,30 @@ __metadata:
languageName: node
linkType: hard
"make-fetch-happen@npm:^9.1.0":
version: 9.1.0
resolution: "make-fetch-happen@npm:9.1.0"
dependencies:
agentkeepalive: ^4.1.3
cacache: ^15.2.0
http-cache-semantics: ^4.1.0
http-proxy-agent: ^4.0.1
https-proxy-agent: ^5.0.0
is-lambda: ^1.0.1
lru-cache: ^6.0.0
minipass: ^3.1.3
minipass-collect: ^1.0.2
minipass-fetch: ^1.3.2
minipass-flush: ^1.0.5
minipass-pipeline: ^1.2.4
negotiator: ^0.6.2
promise-retry: ^2.0.1
socks-proxy-agent: ^6.0.0
ssri: ^8.0.0
checksum: 2c737faf6a7f67077679da548b5bfeeef890595bf8c4323a1f76eae355d27ebb33dcf9cf1a673f944cf2f2a7cbf4e2b09f0a0a62931737728f210d902c6be966
languageName: node
linkType: hard
"makeerror@npm:1.0.12":
version: 1.0.12
resolution: "makeerror@npm:1.0.12"
@@ -29056,6 +29244,19 @@ __metadata:
languageName: unknown
linkType: soft
"medusa-file-local@workspace:packages/medusa-file-local":
version: 0.0.0-use.local
resolution: "medusa-file-local@workspace:packages/medusa-file-local"
dependencies:
"@medusajs/medusa": 1.10.1
cross-env: ^5.2.1
jest: ^25.5.4
typescript: ^4.9.5
peerDependencies:
medusa-interfaces: 1.3.7
languageName: unknown
linkType: soft
"medusa-file-minio@workspace:packages/medusa-file-minio":
version: 0.0.0-use.local
resolution: "medusa-file-minio@workspace:packages/medusa-file-minio"
@@ -30245,6 +30446,21 @@ __metadata:
languageName: node
linkType: hard
"minipass-fetch@npm:^1.3.2":
version: 1.4.1
resolution: "minipass-fetch@npm:1.4.1"
dependencies:
encoding: ^0.1.12
minipass: ^3.1.0
minipass-sized: ^1.0.3
minizlib: ^2.0.0
dependenciesMeta:
encoding:
optional: true
checksum: a43da7401cd7c4f24b993887d41bd37d097356083b0bb836fd655916467463a1e6e9e553b2da4fcbe8745bf23d40c8b884eab20745562199663b3e9060cd8e7a
languageName: node
linkType: hard
"minipass-fetch@npm:^2.0.3":
version: 2.1.0
resolution: "minipass-fetch@npm:2.1.0"
@@ -30306,6 +30522,15 @@ __metadata:
languageName: node
linkType: hard
"minipass@npm:^3.1.0, minipass@npm:^3.1.3":
version: 3.3.6
resolution: "minipass@npm:3.3.6"
dependencies:
yallist: ^4.0.0
checksum: a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c
languageName: node
linkType: hard
"minizlib@npm:^1.3.3":
version: 1.3.3
resolution: "minizlib@npm:1.3.3"
@@ -30315,7 +30540,7 @@ __metadata:
languageName: node
linkType: hard
"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
"minizlib@npm:^2.0.0, minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
version: 2.1.2
resolution: "minizlib@npm:2.1.2"
dependencies:
@@ -30822,7 +31047,7 @@ __metadata:
languageName: node
linkType: hard
"negotiator@npm:0.6.3, negotiator@npm:^0.6.3, negotiator@npm:~0.6.2":
"negotiator@npm:0.6.3, negotiator@npm:^0.6.2, negotiator@npm:^0.6.3, negotiator@npm:~0.6.2":
version: 0.6.3
resolution: "negotiator@npm:0.6.3"
checksum: 3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2
@@ -30894,7 +31119,7 @@ __metadata:
languageName: node
linkType: hard
"node-addon-api@npm:^4.3.0":
"node-addon-api@npm:^4.2.0, node-addon-api@npm:^4.3.0":
version: 4.3.0
resolution: "node-addon-api@npm:4.3.0"
dependencies:
@@ -31047,6 +31272,26 @@ __metadata:
languageName: node
linkType: hard
"node-gyp@npm:8.x":
version: 8.4.1
resolution: "node-gyp@npm:8.4.1"
dependencies:
env-paths: ^2.2.0
glob: ^7.1.4
graceful-fs: ^4.2.6
make-fetch-happen: ^9.1.0
nopt: ^5.0.0
npmlog: ^6.0.0
rimraf: ^3.0.2
semver: ^7.3.5
tar: ^6.1.2
which: ^2.0.2
bin:
node-gyp: bin/node-gyp.js
checksum: 80ef333b3a882eb6a2695a8e08f31d618f4533eff192864e4a3a16b67ff0abc9d8c1d5fac0395550ec699326b9248c5e2b3be178492f7f4d1ccf97d2cf948021
languageName: node
linkType: hard
"node-gyp@npm:latest":
version: 9.0.0
resolution: "node-gyp@npm:9.0.0"
@@ -37107,6 +37352,17 @@ __metadata:
languageName: node
linkType: hard
"socks-proxy-agent@npm:^6.0.0":
version: 6.2.1
resolution: "socks-proxy-agent@npm:6.2.1"
dependencies:
agent-base: ^6.0.2
debug: ^4.3.3
socks: ^2.6.2
checksum: d75c1cf1fdd7f8309a43a77f84409b793fc0f540742ef915154e70ac09a08b0490576fe85d4f8d68bbf80e604a62957a17ab5ef50d312fe1442b0ab6f8f6e6f6
languageName: node
linkType: hard
"socks-proxy-agent@npm:^7.0.0":
version: 7.0.0
resolution: "socks-proxy-agent@npm:7.0.0"
@@ -37346,6 +37602,26 @@ __metadata:
languageName: node
linkType: hard
"sqlite3@npm:^5.0.2":
version: 5.1.6
resolution: "sqlite3@npm:5.1.6"
dependencies:
"@mapbox/node-pre-gyp": ^1.0.0
node-addon-api: ^4.2.0
node-gyp: 8.x
tar: ^6.1.11
peerDependencies:
node-gyp: 8.x
dependenciesMeta:
node-gyp:
optional: true
peerDependenciesMeta:
node-gyp:
optional: true
checksum: 85f1dd1f4b9fa906578330e7badc1116c61ef4e7c64a09897268923f5c9ff4ae1e0a447dd4594c0f8c3b20a410fcc5d8d00d1056225a5186c57ea7f7c9b18974
languageName: node
linkType: hard
"sshpk@npm:^1.7.0":
version: 1.17.0
resolution: "sshpk@npm:1.17.0"
@@ -37376,7 +37652,7 @@ __metadata:
languageName: node
linkType: hard
"ssri@npm:^8.0.1":
"ssri@npm:^8.0.0, ssri@npm:^8.0.1":
version: 8.0.1
resolution: "ssri@npm:8.0.1"
dependencies:
@@ -39685,6 +39961,85 @@ __metadata:
languageName: node
linkType: hard
"typeorm@npm:@medusajs/typeorm@0.3.16-next.0":
version: 0.3.16-next.0
resolution: "@medusajs/typeorm@npm:0.3.16-next.0"
dependencies:
"@sqltools/formatter": ^1.2.5
app-root-path: ^3.1.0
buffer: ^6.0.3
chalk: ^4.1.2
cli-highlight: ^2.1.11
debug: ^4.3.4
dotenv: ^16.0.3
glob: ^8.1.0
mkdirp: ^2.1.3
reflect-metadata: ^0.1.13
sha.js: ^2.4.11
tslib: ^2.5.0
uuid: ^9.0.0
yargs: ^17.6.2
peerDependencies:
"@google-cloud/spanner": ^5.18.0
"@sap/hana-client": ^2.12.25
better-sqlite3: ^7.1.2 || ^8.0.0
hdb-pool: ^0.1.6
ioredis: ^5.0.4
mongodb: ^5.2.0
mssql: ^9.1.1
mysql2: ^2.2.5 || ^3.0.1
oracledb: ^5.1.0
pg: ^8.5.1
pg-native: ^3.0.0
pg-query-stream: ^4.0.0
redis: ^3.1.1 || ^4.0.0
sql.js: ^1.4.0
sqlite3: ^5.0.3
ts-node: ^10.7.0
typeorm-aurora-data-api-driver: ^2.0.0
peerDependenciesMeta:
"@google-cloud/spanner":
optional: true
"@sap/hana-client":
optional: true
better-sqlite3:
optional: true
hdb-pool:
optional: true
ioredis:
optional: true
mongodb:
optional: true
mssql:
optional: true
mysql2:
optional: true
oracledb:
optional: true
pg:
optional: true
pg-native:
optional: true
pg-query-stream:
optional: true
redis:
optional: true
sql.js:
optional: true
sqlite3:
optional: true
ts-node:
optional: true
typeorm-aurora-data-api-driver:
optional: true
bin:
typeorm: cli.js
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
typeorm-ts-node-esm: cli-ts-node-esm.js
checksum: b81794a0bc5ca3195f6a57ad313bd85b2b8f6a0a8dcb00ac42c3594f908b89fcd51f9b10ea62c836504d7196e6330a4b67602b90ea6485bc956ed4dace665737
languageName: node
linkType: hard
"typeorm@npm:^0.3.16":
version: 0.3.16
resolution: "typeorm@npm:0.3.16"