From aa63c4c5009cd3cbc73d2659b31ffa1ae158be7c Mon Sep 17 00:00:00 2001 From: Jayden Seric Date: Sun, 2 Apr 2017 23:14:11 +1000 Subject: [PATCH] Version 1.0.0. Initial release. --- .editorconfig | 11 ++++ .gitignore | 5 ++ api/.env.example | 2 + api/.gitignore | 1 + api/config.js | 4 ++ api/package.json | 58 +++++++++++++++++++++ api/readme.md | 17 +++++++ api/resolvers.js | 13 +++++ api/schema.graphql | 22 ++++++++ api/scripts/dev.js | 49 ++++++++++++++++++ api/server.js | 45 ++++++++++++++++ api/webpack.config.babel.js | 43 ++++++++++++++++ apollo-upload-logo.svg | 5 ++ client/.env.example | 3 ++ client/.gitignore | 1 + client/components/single-uploader.js | 31 ++++++++++++ client/helpers/with-data.js | 76 ++++++++++++++++++++++++++++ client/package.json | 37 ++++++++++++++ client/pages/index.js | 22 ++++++++ client/readme.md | 17 +++++++ readme.md | 11 ++++ 21 files changed, 473 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 api/.env.example create mode 100644 api/.gitignore create mode 100644 api/config.js create mode 100644 api/package.json create mode 100644 api/readme.md create mode 100644 api/resolvers.js create mode 100644 api/schema.graphql create mode 100644 api/scripts/dev.js create mode 100644 api/server.js create mode 100644 api/webpack.config.babel.js create mode 100644 apollo-upload-logo.svg create mode 100644 client/.env.example create mode 100644 client/.gitignore create mode 100644 client/components/single-uploader.js create mode 100644 client/helpers/with-data.js create mode 100644 client/package.json create mode 100644 client/pages/index.js create mode 100644 client/readme.md create mode 100644 readme.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9ca3cf1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# http://EditorConfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba8e7fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +*.log +yarn.lock +.env +.DS_Store diff --git a/api/.env.example b/api/.env.example new file mode 100644 index 0000000..9776490 --- /dev/null +++ b/api/.env.example @@ -0,0 +1,2 @@ +NODE_ENV=development +PORT=3001 diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..9b1c8b1 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1 @@ +/dist diff --git a/api/config.js b/api/config.js new file mode 100644 index 0000000..8d45014 --- /dev/null +++ b/api/config.js @@ -0,0 +1,4 @@ +import path from 'path' + +export const distPath = path.resolve(__dirname, 'dist') +export const apiEndpoint = '/graphql' diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..211041e --- /dev/null +++ b/api/package.json @@ -0,0 +1,58 @@ +{ + "name": "apollo-upload-examples-api", + "private": true, + "dependencies": { + "apollo-upload-server": "^2.0.0", + "babel-core": "^6.24.0", + "babel-loader": "^7.0.0-beta.1", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-0": "^6.22.0", + "graphql": "^0.9.2", + "graphql-server-koa": "^0.6.0", + "graphql-tag": "^2.0.0", + "graphql-tools": "^0.11.0", + "kcors": "^2.2.1", + "koa": "^2.2.0", + "koa-bodyparser": "^4.2.0", + "koa-compress": "^2.0.0", + "koa-router": "^7.1.1", + "source-map-support": "^0.4.14", + "webpack": "^2.3.2", + "zoo": "^0.1.9" + }, + "devDependencies": { + "babel-cli": "^6.24.0", + "babel-eslint": "^7.2.1", + "chalk": "^1.1.3", + "indent-string": "^3.1.0", + "standard": "^9.0.2" + }, + "scripts": { + "lint": "standard", + "dev": "zoo babel-node scripts/dev", + "build": "zoo webpack", + "start": "zoo node dist" + }, + "engines": { + "node": ">=7.6" + }, + "babel": { + "presets": [ + [ + "env", + { + "targets": { + "node": 7.6 + } + } + ], + "stage-0" + ] + }, + "standard": { + "parser": "babel-eslint", + "ignore": [ + "dist/**" + ] + } +} diff --git a/api/readme.md b/api/readme.md new file mode 100644 index 0000000..f6ded8e --- /dev/null +++ b/api/readme.md @@ -0,0 +1,17 @@ +# Apollo upload examples API + +An example GraphQL API using [Apollo upload server](https://github.com/jaydenseric/apollo-upload-server). + +## Setup + +1. Install the latest [Node.js](https://nodejs.org). +2. Run `npm install` within the `api` directory in Terminal. +3. Copy `.env.example`, rename it `.env` and customize. + +For development run `npm run dev`. + +For production run `npm run build && npm run start`. + +## Support + +- Node.js versions >= 7.6. diff --git a/api/resolvers.js b/api/resolvers.js new file mode 100644 index 0000000..4de7971 --- /dev/null +++ b/api/resolvers.js @@ -0,0 +1,13 @@ +export default { + Query: { + ignore () { + return null + } + }, + Mutation: { + singleUpload (root, {file}) { + console.log('Uploaded file:', file) + return file + } + } +} diff --git a/api/schema.graphql b/api/schema.graphql new file mode 100644 index 0000000..2a2a100 --- /dev/null +++ b/api/schema.graphql @@ -0,0 +1,22 @@ +type File { + name: String! + type: String! + size: Int! + path: String! +} + +input Upload { + name: String! + type: String! + size: Int! + path: String! +} + +type Query { + # GraphQL will not work without defining a query. + ignore: Boolean +} + +type Mutation { + singleUpload (file: Upload!): File! +} diff --git a/api/scripts/dev.js b/api/scripts/dev.js new file mode 100644 index 0000000..80b2761 --- /dev/null +++ b/api/scripts/dev.js @@ -0,0 +1,49 @@ +import 'source-map-support/register' +import {spawn} from 'child_process' +import chalk from 'chalk' +import indentString from 'indent-string' +import webpack from 'webpack' +import webpackConfig from '../webpack.config.babel' + +let serverProcess +let wasServerMessage + +function startServer () { + serverProcess = spawn('node', [webpackConfig.output.path]) + serverProcess.stdout.on('data', data => { + console.log((wasServerMessage ? '' : '\n') + indentString(chalk.white(data), 4)) + wasServerMessage = true + }) + serverProcess.stderr.on('data', data => { + console.error((wasServerMessage ? '' : '\n') + indentString(chalk.red(data), 4)) + wasServerMessage = true + }) +} + +function stopServer () { + if (serverProcess) serverProcess.kill() +} + +const compiler = webpack(webpackConfig) +const watcher = compiler.watch({}, (errors, stats) => { + const hasErrors = errors || stats.hasErrors() + console[hasErrors ? 'error' : 'log']((stats.toString('minimal'))) + wasServerMessage = false + + stopServer() + if (!hasErrors) startServer() +}) + +function exit () { + watcher.close() + stopServer() +} + +;[ + 'SIGINT', + 'SIGTERM', + 'SIGHUP', + 'SIGQUIT', + 'exit', + 'uncaughtException' +].forEach(event => process.on(event, exit)) diff --git a/api/server.js b/api/server.js new file mode 100644 index 0000000..ad8c06b --- /dev/null +++ b/api/server.js @@ -0,0 +1,45 @@ +import 'source-map-support/register' +import Koa from 'koa' +import cors from 'kcors' +import compress from 'koa-compress' +import KoaRouter from 'koa-router' +import koaBody from 'koa-bodyparser' +import {makeExecutableSchema} from 'graphql-tools' +import {graphqlKoa} from 'graphql-server-koa' +import {apolloUploadKoa} from 'apollo-upload-server' +import {apiEndpoint} from './config' +import typeDefs from './schema.graphql' +import resolvers from './resolvers' + +const app = new Koa() +const router = new KoaRouter() +const schema = makeExecutableSchema({ + typeDefs, + resolvers +}) + +// Enable Cross-Origin Resource Sharing (CORS) +app.use(cors()) + +// Enable gzip +app.use(compress()) + +// Parse body +app.use(koaBody()) + +// GraphQL API +router.post( + apiEndpoint, + apolloUploadKoa({ + uploadDir: '/tmp/uploads' + }), + graphqlKoa({ + schema + }) +) + +app.use(router.routes()) +app.use(router.allowedMethods()) + +app.listen(process.env.PORT) +console.log(`Serving at http://localhost:${process.env.PORT} in ${process.env.NODE_ENV} mode.`) diff --git a/api/webpack.config.babel.js b/api/webpack.config.babel.js new file mode 100644 index 0000000..a61e40a --- /dev/null +++ b/api/webpack.config.babel.js @@ -0,0 +1,43 @@ +import {NoEmitOnErrorsPlugin} from 'webpack' +import {distPath} from './config' + +const config = { + devtool: 'source-map', + entry: { + index: './server.js' + }, + output: { + path: distPath, + filename: '[name].js', + libraryTarget: 'commonjs2' + }, + externals: /^(?!\.|\/).+/i, + target: 'node', + node: { + __dirname: true + }, + module: { + rules: [{ + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + cacheDirectory: process.env.NODE_ENV === 'development' + } + } + }, { + test: /\.(graphql|gql)$/, + exclude: /node_modules/, + loader: 'graphql-tag/loader' + }] + } +} + +if (process.env.NODE_ENV === 'development') { + config.plugins = [ + new NoEmitOnErrorsPlugin() + ] +} + +export default config diff --git a/apollo-upload-logo.svg b/apollo-upload-logo.svg new file mode 100644 index 0000000..13d33a5 --- /dev/null +++ b/apollo-upload-logo.svg @@ -0,0 +1,5 @@ + + Apollo upload logo + + + diff --git a/client/.env.example b/client/.env.example new file mode 100644 index 0000000..de6fe9c --- /dev/null +++ b/client/.env.example @@ -0,0 +1,3 @@ +NODE_ENV=development +PORT=3000 +API_URI_URI=http://localhost:3001/graphql diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..a680367 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +.next diff --git a/client/components/single-uploader.js b/client/components/single-uploader.js new file mode 100644 index 0000000..0611ad3 --- /dev/null +++ b/client/components/single-uploader.js @@ -0,0 +1,31 @@ +import {Component} from 'react' +import {graphql, gql} from 'react-apollo' + +class SingleUploader extends Component { + handleChange = ({target}) => { + if (target.validity.valid) { + this.props + .mutate({ + variables: { + file: target.files[0] + } + }) + .then(({data}) => console.log('Mutation response:', data)) + } + } + + render () { + return + } +} + +export default graphql(gql` + mutation singleUpload ($file: Upload!) { + singleUpload (file: $file) { + name + type + size + path + } + } +`)(SingleUploader) diff --git a/client/helpers/with-data.js b/client/helpers/with-data.js new file mode 100644 index 0000000..e06a3bd --- /dev/null +++ b/client/helpers/with-data.js @@ -0,0 +1,76 @@ +import 'isomorphic-fetch' +import React from 'react' +import { + ApolloClient, + ApolloProvider, + getDataFromTree +} from 'react-apollo' +import {createNetworkInterface} from 'apollo-upload-client' + +const ssrMode = !process.browser +let apolloClient = null + +function initClient (headers, initialState) { + return new ApolloClient({ + initialState, + ssrMode, + networkInterface: createNetworkInterface({ + uri: process.env.API_URI + }) + }) +} + +function getClient (headers, initialState = {}) { + if (ssrMode) return initClient(headers, initialState) + if (!apolloClient) apolloClient = initClient(headers, initialState) + return apolloClient +} + +export default Component => ( + class extends React.Component { + static async getInitialProps (ctx) { + const headers = ctx.req ? ctx.req.headers : {} + const client = getClient(headers) + + const props = { + url: { + query: ctx.query, + pathname: ctx.pathname + }, + ...await (Component.getInitialProps ? Component.getInitialProps(ctx) : {}) + } + + if (ssrMode) { + const app = ( + + + + ) + await getDataFromTree(app) + } + + return { + initialState: { + apollo: { + data: client.getInitialState().data + } + }, + headers, + ...props + } + } + + constructor (props) { + super(props) + this.client = getClient(this.props.headers, this.props.initialState) + } + + render () { + return ( + + + + ) + } + } +) diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..91c555c --- /dev/null +++ b/client/package.json @@ -0,0 +1,37 @@ +{ + "name": "apollo-upload-examples-client", + "private": true, + "dependencies": { + "apollo-upload-client": "^3.0.1", + "babel-plugin-transform-inline-environment-variables": "^0.0.2", + "next": "^2.0.1", + "react": "^15.4.2", + "react-apollo": "^1.0.0", + "react-dom": "^15.4.2", + "zoo": "^0.1.9" + }, + "devDependencies": { + "babel-eslint": "^7.2.1", + "standard": "^9.0.2" + }, + "scripts": { + "lint": "standard", + "dev": "zoo next", + "build": "zoo next build", + "start": "zoo next start" + }, + "babel": { + "presets": [ + "next/babel" + ], + "plugins": [ + "transform-inline-environment-variables" + ] + }, + "standard": { + "parser": "babel-eslint", + "ignore": [ + ".next/**" + ] + } +} diff --git a/client/pages/index.js b/client/pages/index.js new file mode 100644 index 0000000..26ad181 --- /dev/null +++ b/client/pages/index.js @@ -0,0 +1,22 @@ +import Head from 'next/head' +import withData from '../helpers/with-data' +import SingleUploader from '../components/single-uploader' + +export default withData(props => ( +
+ + Apollo upload example + + +

Apollo upload example

+

Select an image to upload and view the response in the console.

+ +
+)) diff --git a/client/readme.md b/client/readme.md new file mode 100644 index 0000000..db2cf61 --- /dev/null +++ b/client/readme.md @@ -0,0 +1,17 @@ +# Apollo upload examples client + +An example [Next.js](https://github.com/zeit/next.js) [React Apollo client](http://dev.apollodata.com/react) using [Apollo upload client](https://github.com/jaydenseric/apollo-upload-client). + +## Setup + +1. Install the latest [Node.js](https://nodejs.org). +2. Run `npm install` within the `api` directory in Terminal. +3. Copy `.env.example`, rename it `.env` and customize. + +For development run `npm run dev`. + +For production run `npm run build && npm run start`. + +## Support + +- Node.js versions >= 7.6. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c669ef4 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +# ![Apollo upload examples](https://cdn.rawgit.com/jaydenseric/apollo-upload-example/v1.0.0/apollo-upload-logo.svg) + +![Github release](https://img.shields.io/github/release/jaydenseric/apollo-upload-example.svg?style=flat-square) ![Github issues](https://img.shields.io/github/issues/jaydenseric/apollo-upload-example.svg?style=flat-square) ![Github stars](https://img.shields.io/github/stars/jaydenseric/apollo-upload-example.svg?style=flat-square) + +An example GraphQL API using [Apollo upload server](https://github.com/jaydenseric/apollo-upload-server) and an example [Next.js](https://github.com/zeit/next.js) [React Apollo client](http://dev.apollodata.com/react) using [Apollo upload client](https://github.com/jaydenseric/apollo-upload-client). + +- [MIT license](https://en.wikipedia.org/wiki/MIT_License). + +## Setup + +See readmes in `/client` and `/api`.