diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..f7a8b05 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + "transform-es2015-modules-commonjs" + ] +} diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ec3a22d --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +version: 2 +jobs: + build: + docker: + - image: docker:latest + environment: + - di: "nfpis/storage-upload" + - dtag: "latest" + working_directory: ~/storage-upload + steps: + - run: + name: Update and install SSH & Git + command: apk update && apk upgrade && apk add --no-cache bash git openssh + - checkout + - setup_remote_docker + - run: + name: Build docker image + command: | + docker build -t test . + docker build --build-arg NODE=production -t ${di}:build_${CIRCLE_BUILD_NUM} -t ${di}:${CIRCLE_SHA1} -t ${di}:${dtag} . + - deploy: + name: Push to docker + command: | + docker login -u $DOCKER_USER -p $DOCKER_PASS + docker push ${di} + +workflows: + version: 2 + build_deploy: + jobs: + - build: + context: org-global diff --git a/.gitignore b/.gitignore index 00cbbdf..96f580b 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,9 @@ typings/ # dotenv environment variables file .env +# Local development config file +config/config.json + +# lol +package-lock.json + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e1d52c3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:slim + +ARG NODE=development + +ENV HOME=/app \ + NODE_ENV=${NODE} + +COPY package.json $HOME/ + +WORKDIR $HOME + +RUN npm install + +COPY . $HOME/ + +EXPOSE 4020 + +CMD ["npm", "start"] diff --git a/api/defaults.js b/api/defaults.js new file mode 100644 index 0000000..678754d --- /dev/null +++ b/api/defaults.js @@ -0,0 +1,14 @@ + +export default function defaults(options, defaults) { + options = options || {} + + Object.keys(defaults).forEach(function(key) { + if (typeof options[key] === 'undefined') { + // No need to do clone since we mostly deal with + // flat objects + options[key] = defaults[key] + } + }) + + return options +} diff --git a/api/router.js b/api/router.js new file mode 100644 index 0000000..9e96ca2 --- /dev/null +++ b/api/router.js @@ -0,0 +1,9 @@ +import Router from 'koa-router' + +import * as test from './test/routes' + +const router = new Router() + +router.get('/api/test', test.testStatic) + +export default router diff --git a/api/test/routes.js b/api/test/routes.js new file mode 100644 index 0000000..4d88f9c --- /dev/null +++ b/api/test/routes.js @@ -0,0 +1,9 @@ +import config from '../../config' + +export async function testStatic(ctx) { + ctx.body = { + name: config.get('name'), + version: config.get('version'), + environment: config.get('NODE_ENV'), + } +} diff --git a/config.js b/config.js new file mode 100644 index 0000000..ed1768c --- /dev/null +++ b/config.js @@ -0,0 +1,58 @@ +'use strict' + +const _ = require('lodash') +const nconf = require('nconf') + +// Helper method for global usage. +nconf.inTest = () => nconf.get('NODE_ENV') === 'test' + +// Config follow the following priority check order: +// 1. package.json +// 2. Enviroment variables +// 3. config/config.json +// 4. config/config.default.json + + +// Load package.json for name and such +let pckg = require('./package.json') + +pckg = _.pick(pckg, ['name', 'version', 'description', 'author', 'license', 'homepage']) + + +// Load overrides as first priority +nconf.overrides(pckg) + + +// Load enviroment variables as second priority +nconf.env() + + +// Load any overrides from the appropriate config file +let configFile = 'config/config.json' + +/* istanbul ignore else */ +if (nconf.get('NODE_ENV') === 'test') { + configFile = 'config/config.test.json' +} + +/* istanbul ignore if */ +if (nconf.get('NODE_ENV') === 'production') { + configFile = 'config/config.production.json' +} + +nconf.file('main', configFile) + +// Load defaults +nconf.file('default', 'config/config.default.json') + + +// Final sanity checks +/* istanbul ignore if */ +if (typeof global.it === 'function' & !nconf.inTest()) { + // eslint-disable-next-line no-console + console.log('Critical: potentially running test on production enviroment. Shutting down.') + process.exit(1) +} + + +module.exports = nconf diff --git a/config/config.default.json b/config/config.default.json new file mode 100644 index 0000000..afbec0b --- /dev/null +++ b/config/config.default.json @@ -0,0 +1,22 @@ +{ + "NODE_ENV": "development", + "server": { + "port": 4020, + "host": "0.0.0.0" + }, + "bunyan": { + "name": "storage-upload", + "streams": [{ + "stream": "process.stdout", + "level": "debug" + } + ] + }, + "jwt": { + "secret": "this-is-my-secret", + "options": { + "expiresIn": 604800 + } + }, + "fileSize": 524288000 +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..9aaf12f --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +require('babel-register') + +require('./server') diff --git a/package.json b/package.json new file mode 100644 index 0000000..f8a0e63 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "storage-upload", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "nodemon index.js", + "start": "node index.js", + "test": "env NODE_ENV=test mocha --require babel-register --recursive --reporter dot", + "docker": "docker run -it --rm --name my-running-script -v \"$PWD\":/usr/src/app -w /usr/src/app node:slim", + "docker:test": "npm run docker -- npm install && npm run test", + "docker:dev": "npm run docker -- npm install && npm run dev", + "docker:prod": "npm run docker -- npm install && npm run start" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nfp-projects/storage-upload.git" + }, + "author": "Jonatan Nilsson", + "license": "WTFPL", + "bugs": { + "url": "https://github.com/nfp-projects/storage-upload/issues" + }, + "homepage": "https://github.com/nfp-projects/storage-upload#readme", + "dependencies": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", + "babel-register": "^6.26.0", + "koa": "^2.3.0", + "koa-router": "^7.2.1", + "nconf": "^0.8.5" + }, + "devDependencies": { + "assert-extended": "^1.0.1", + "mocha": "^4.0.1", + "nodemon": "^1.12.1", + "request-json": "^0.6.2" + } +} diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server.js b/server.js new file mode 100644 index 0000000..8b0679b --- /dev/null +++ b/server.js @@ -0,0 +1,13 @@ +import Koa from 'koa' + +import config from './config' +import router from './api/router' + +const app = new Koa() + +app.use(router.routes()) +app.use(router.allowedMethods()) + +const server = app.listen(config.get('server:port')) + +export default server diff --git a/test/helper.client.js b/test/helper.client.js new file mode 100644 index 0000000..a87a945 --- /dev/null +++ b/test/helper.client.js @@ -0,0 +1,89 @@ +import request from 'request-json' + +import defaults from '../api/defaults' +import config from '../config' + +function parseBody(body, reject) { + try { + return JSON.parse(body) + } catch (error) { + // eslint-disable-next-line no-console + console.log(body) + return reject(error) + } +} + +function callback(resolve, reject) { + return (err, res, rawBody) => { + let body = rawBody + if (err) { + return reject(err) + } + if (typeof body === 'string' && body) { + body = parseBody(body, reject) + } + if (res.statusCode >= 300 || + res.statusCode < 200) { + return reject(body) + } + resolve(body) + } +} + +export default function createClient(host = config.get('server:port'), opts) { + let options = defaults(opts, {}) + + let client = request.createClient('', options) + let prefix + + prefix = `http://localhost:${host}` + client.headers['x-request-id'] = 'asdf' + + client.auth = (user) => { + // let m = helperDB.model('user', { + // id: user.id, + // level: (user.get && user.get('level')) || 1, + // institute_id: (user.get && user.get('institute_id')) || null, + // password: (user.get && user.get('password')) || null, + // }) + // let token = jwt.createUserToken(m) + // client.headers.authorization = `Bearer ${token}` + } + + // Simple wrappers to wrap into promises + client.getAsync = (path) => + new Promise((resolve, reject) => { + if (path.slice(0, 4) === 'http') { + return client.get(path, callback(resolve, reject)) + } + client.get(prefix + path, callback(resolve, reject)) + }) + + // Simple wrappers to wrap into promises + client.saveFileAsync = (path, destination) => + new Promise((resolve, reject) => { + client.saveFile(prefix + path, destination, callback(resolve, reject, true)) + }) + + client.postAsync = (path, data) => + new Promise((resolve, reject) => { + client.post(prefix + path, data, callback(resolve, reject)) + }) + + client.putAsync = (path, data) => + new Promise((resolve, reject) => { + client.put(prefix + path, data, callback(resolve, reject)) + }) + + client.deleteAsync = (path) => + new Promise((resolve, reject) => { + client.del(prefix + path, callback(resolve, reject)) + }) + + client.sendFileAsync = (path, files, data) => + new Promise((resolve, reject) => { + client.sendFile(prefix + path, files, data || {}, callback(resolve, reject)) + }) + + return client +} diff --git a/test/helper.server.js b/test/helper.server.js new file mode 100644 index 0000000..aa5e899 --- /dev/null +++ b/test/helper.server.js @@ -0,0 +1,8 @@ +// import _ from 'lodash' +// import sinon from 'sinon' +import server from '../server' +import client from './helper.client' + +after(() => server.close()) + +export const createClient = client diff --git a/test/server.test.js b/test/server.test.js new file mode 100644 index 0000000..3b5605d --- /dev/null +++ b/test/server.test.js @@ -0,0 +1,22 @@ +import assert from 'assert-extended' + +import * as server from './helper.server' + +describe('Server', () => { + let client + + beforeEach(() => { + client = server.createClient() + }) + + it('should run', () => + assert.isFulfilled( + client.getAsync('/api/test') + ) + .then(data => { + assert.ok(data) + assert.ok(data.name) + assert.ok(data.version) + }) + ) +})