diff --git a/api/package-lock.json b/api/package-lock.json index 41b1c85..5a18a78 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -147,12 +147,11 @@ } }, "apollo-upload-server": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/apollo-upload-server/-/apollo-upload-server-2.0.4.tgz", - "integrity": "sha512-GwkxlrCqo/JLRlBDmXl6cyNZwnZ3j2tbWGRbhyFn1S8jNivUoZr0ROOpQPpzwFwzrmfm6kJI01HGa7QAxA3keA==", + "version": "4.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/apollo-upload-server/-/apollo-upload-server-4.0.0-alpha.1.tgz", + "integrity": "sha512-uW0TtcSXDkOoJD6NlMwD4I4deMaZO61GpnouvCLiDRm4FwpeEVLVUVpSk4/s++La+TXXgzoKNb8ixfPwUAjiGw==", "requires": { - "formidable": "1.1.1", - "mkdirp": "0.5.1", + "busboy": "0.2.14", "object-path": "0.11.4" } }, @@ -260,9 +259,9 @@ "dev": true }, "binary-extensions": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", - "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, "boxen": { @@ -307,6 +306,15 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.14" + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -480,6 +488,38 @@ "inherits": "2.0.3", "readable-stream": "2.3.3", "typedarray": "0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } } }, "configstore": { @@ -534,8 +574,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", @@ -631,6 +670,15 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.14", + "streamsearch": "0.1.2" + } + }, "doctrine": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", @@ -1097,11 +1145,6 @@ "for-in": "1.0.2" } }, - "formidable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", - "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" - }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2075,9 +2118,9 @@ } }, "global-dirs": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.0.tgz", - "integrity": "sha1-ENNAOeDfBCcuJizyQiT3IJQ0308=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { "ini": "1.3.4" @@ -2309,7 +2352,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.10.0" + "binary-extensions": "1.11.0" } }, "is-buffer": { @@ -2380,7 +2423,7 @@ "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "dev": true, "requires": { - "global-dirs": "0.1.0", + "global-dirs": "0.1.1", "is-path-inside": "1.0.0" } }, @@ -3444,26 +3487,14 @@ } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } + "isarray": "0.0.1", + "string_decoder": "0.10.31" } }, "readdirp": { @@ -3476,6 +3507,38 @@ "minimatch": "3.0.4", "readable-stream": "2.3.3", "set-immediate-shim": "1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } } }, "regex-cache": { @@ -3639,6 +3702,11 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shortid": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz", + "integrity": "sha1-AzsRfWoul1gE9vCWnb59PQs1UTE=" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -3725,6 +3793,11 @@ "duplexer": "0.1.1" } }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -3736,13 +3809,9 @@ } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "strip-ansi": { "version": "4.0.0", diff --git a/api/package.json b/api/package.json index 74d5dd3..17e7a20 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@std/esm": "^0.16.0", - "apollo-upload-server": "^2.0.4", + "apollo-upload-server": "^4.0.0-alpha.1", "dotenv": "^4.0.0", "graphql": "^0.11.7", "graphql-server-koa": "^1.2.0", @@ -17,7 +17,9 @@ "koa-bodyparser": "^4.2.0", "koa-compress": "^2.0.0", "koa-router": "^7.3.0", - "lowdb": "^1.0.0" + "lowdb": "^1.0.0", + "mkdirp": "^0.5.1", + "shortid": "^2.2.8" }, "devDependencies": { "eslint": "^4.11.0", @@ -33,6 +35,7 @@ "dev": "nodemon --ext mjs", "start": "node --require @std/esm --require dotenv/config server.mjs" }, + "@std/esm": "cjs", "eslintConfig": { "parserOptions": { "sourceType": "module", diff --git a/api/resolvers.mjs b/api/resolvers.mjs index 9247015..df0f0bc 100644 --- a/api/resolvers.mjs +++ b/api/resolvers.mjs @@ -1,22 +1,51 @@ -import low from 'lowdb' +import { createWriteStream } from 'fs' +import mkdirp from 'mkdirp' +import shortid from 'shortid' +import lowdb from 'lowdb' import FileSync from 'lowdb/adapters/FileSync' +import { GraphQLUpload } from 'apollo-upload-server' -const db = low(new FileSync('db.json')) +const uploadDir = './uploads' +const db = lowdb(new FileSync('db.json')) + +// Seed an empty DB db.defaults({ uploads: [] }).write() -const saveFile = file => +// Ensure upload directory exists +mkdirp.sync(uploadDir) + +const storeUpload = async ({ stream, filename }) => { + const id = shortid.generate() + const path = `${uploadDir}/${id}-${filename}` + + return new Promise((resolve, reject) => { + stream + .pipe(createWriteStream(path)) + .on('finish', () => resolve({ id, path })) + .on('error', reject) + }) +} + +const recordFile = file => db .get('uploads') - .push({ id: file.path, ...file }) + .push(file) .last() .write() +const processUpload = async upload => { + const { stream, filename, mimetype, encoding } = await upload + const { id, path } = await storeUpload({ stream, filename }) + return recordFile({ id, filename, mimetype, encoding, path }) +} + export default { + Upload: GraphQLUpload, Query: { uploads: () => db.get('uploads').value() }, Mutation: { - singleUpload: (_, { file }) => saveFile(file), - multipleUpload: (_, { files }) => Promise.all(files.map(saveFile)) + singleUpload: (obj, { file }) => processUpload(file), + multipleUpload: (obj, { files }) => Promise.all(files.map(processUpload)) } } diff --git a/api/schema.mjs b/api/schema.mjs index 1c54056..b42ceca 100644 --- a/api/schema.mjs +++ b/api/schema.mjs @@ -1,17 +1,12 @@ export default /* GraphQL */ ` - type File { - id: String! - name: String! - type: String! - size: Int! - path: String! - } + scalar Upload - input Upload { - name: String! - type: String! - size: Int! + type File { + id: ID! path: String! + filename: String! + mimetype: String! + encoding: String! } type Query { diff --git a/api/server.mjs b/api/server.mjs index 8c974cf..00e3992 100644 --- a/api/server.mjs +++ b/api/server.mjs @@ -3,9 +3,9 @@ import cors from 'kcors' import compress from 'koa-compress' import KoaRouter from 'koa-router' import koaBody from 'koa-bodyparser' -import graphqlTools from 'graphql-tools' -import graphqlServerKoa from 'graphql-server-koa' -import apolloUploadServer from 'apollo-upload-server' +import { makeExecutableSchema } from 'graphql-tools' +import { graphqlKoa } from 'graphql-server-koa' +import { apolloUploadKoa } from 'apollo-upload-server' import types from './schema.mjs' import resolvers from './resolvers.mjs' @@ -22,9 +22,9 @@ server router.post( '/graphql', koaBody(), - apolloUploadServer.apolloUploadKoa({ uploadDir: './uploads' }), - graphqlServerKoa.graphqlKoa({ - schema: graphqlTools.makeExecutableSchema({ typeDefs: [types], resolvers }) + apolloUploadKoa(), + graphqlKoa({ + schema: makeExecutableSchema({ typeDefs: [types], resolvers }) }) ) diff --git a/app/components/multiple-uploader.js b/app/components/multiple-uploader.js index 29d5ebd..cf086b0 100644 --- a/app/components/multiple-uploader.js +++ b/app/components/multiple-uploader.js @@ -22,9 +22,9 @@ export default graphql(gql` mutation($files: [Upload!]!) { multipleUpload(files: $files) { id - name - type - size + filename + encoding + mimetype path } } diff --git a/app/components/single-uploader.js b/app/components/single-uploader.js index 8742782..ece6fb3 100644 --- a/app/components/single-uploader.js +++ b/app/components/single-uploader.js @@ -22,9 +22,9 @@ export default graphql(gql` mutation($file: Upload!) { singleUpload(file: $file) { id - name - type - size + filename + encoding + mimetype path } } diff --git a/app/components/upload-list.js b/app/components/upload-list.js index 2f3b518..695c120 100644 --- a/app/components/upload-list.js +++ b/app/components/upload-list.js @@ -6,17 +6,17 @@ const UploadList = ({ data: { uploads = [] } }) => (