Permalink
Browse files

feat: move media upload to new controller

  • Loading branch information...
jimlambie committed Dec 19, 2016
1 parent 3eda9fe commit 12cd39cf34973581bd3385162a73ae4688776521
@@ -12,3 +12,5 @@ config/config.development.json
config/config.test.json
config/config.qa.json
config/config.production.json

workspace/media/*
@@ -1,7 +1,7 @@
<img src="http://52.209.207.148/assets/products/dadi-api-full.png" alt="DADI API" height="65"/>

[![npm (scoped)](https://img.shields.io/npm/v/@dadi/api.svg?maxAge=10800&style=flat-square)](https://www.npmjs.com/package/@dadi/api)
[![coverage](https://img.shields.io/badge/coverage-85%25-yellow.svg?style=flat-square)](https://github.com/dadi/api)
[![coverage](https://img.shields.io/badge/coverage-84%25-yellow.svg?style=flat-square)](https://github.com/dadi/api)
[![Build Status](https://travis-ci.org/dadi/api.svg?branch=master)](https://travis-ci.org/dadi/api)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/)

@@ -285,6 +285,54 @@ var conf = convict({
default: false
}
},
media: {
enabled: {
doc: 'If true, files can be uploaded to API with a POST request',
format: Boolean,
default: false
},
storage: {
doc: 'Determines the storage type for uploads',
format: ['disk', 's3'],
default: 'disk'
},
basePath: {
doc: 'Sets the root directory for uploads',
format: String,
default: 'workspace/media'
},
pathFormat: {
doc: 'Determines the format for subdirectories that are created to store uploads',
format: ['none', 'date', 'datetime', 'sha1/4', 'sha1/5', 'sha1/8'],
default: 'date'
},
s3: {
accessKey: {
doc: 'The AWS access key used to connect to S3',
format: String,
default: '',
env: 'AWS_S3_ACCESS_KEY'
},
secretKey: {
doc: 'The AWS secret key used to connect to S3',
format: String,
default: '',
env: 'AWS_S3_SECRET_KEY'
},
bucketName: {
doc: 'The name of the S3 bucket in which to store uploads',
format: String,
default: '',
env: 'AWS_S3_BUCKET_NAME'
},
region: {
doc: 'The AWS region',
format: String,
default: '',
env: 'AWS_S3_REGION'
}
}
},
env: {
doc: "The applicaton environment.",
format: ["production", "development", "test", "qa"],
@@ -13,7 +13,6 @@ implements methods corresponding to the HTTP methods it needs to support
*/
var _ = require('underscore')
var Busboy = require('busboy')
var path = require('path')
var url = require('url')

@@ -218,43 +217,6 @@ Controller.prototype.post = function (req, res, next) {
// for clearing the cache
pathname = pathname.replace('/' + req.params.id, '')

var busboy = new Busboy({ headers: req.headers })
this.data = []
this.fileName = ''

// Listen for event when Busboy finds a file to stream
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
this.fileName = filename
this.mimetype = mimetype

file.on('data', (chunk) => {
this.data.push(chunk)
})

file.on('end', () => {
// console.log('Finished with ' + fieldname)
})
})

// Listen for event when Busboy finds a non-file field
busboy.on('field', (fieldname, val) => {
// Do something with non-file field.
})

// Listen for event when Busboy is finished parsing the form
busboy.on('finish', () => {
var data = Buffer.concat(this.data)
console.log(data)

console.log(this)
// return writeFile(req, this.fileName, this.mimetype, data). then((result) => {
// help.sendBackJSON(201, result, res)
// })
})

// Pipe the HTTP Request into Busboy
req.pipe(busboy)

// flush cache for POST requests
help.clearCache(pathname, function (err) {
if (err) return next(err)
@@ -0,0 +1,183 @@
var _ = require('underscore')
var Busboy = require('busboy')
var imagesize = require('imagesize')
var PassThrough = require('stream').PassThrough
var path = require('path')
var sha1 = require('sha1')

var config = require(path.join(__dirname, '/../../../config'))
var help = require(path.join(__dirname, '/../help'))
var streamifier = require('streamifier')

var Model = require(path.join(__dirname, '/../model'))
var StorageFactory = require(path.join(__dirname, '/../storage/factory'))

var schema = {
'fields': {
'fileName': {
'type': 'String',
'required': true
},
'mimetype': {
'type': 'String',
'required': true
},
'path': {
'type': 'String',
'required': true
},
'awsUrl': {
'type': 'String',
'required': false
},
'width': {
'type': 'Number',
'required': true
},
'height': {
'type': 'Number',
'required': true
},
'contentLength': {
'type': 'Number',
'required': false
}
},
'settings': {
'cache': true,
'authenticate': true,
'count': 40,
'sort': 'filename',
'sortOrder': 1,
'storeRevisions': false
}
}

var MediaController = function () {
this.model = Model('_media', schema.fields, null, schema.settings, null)
}

MediaController.prototype.post = function (req, res, next) {
var busboy = new Busboy({ headers: req.headers })
this.data = []
this.fileName = ''

// Listen for event when Busboy finds a file to stream
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
this.fileName = filename
this.mimetype = mimetype

file.on('data', (chunk) => {
this.data.push(chunk)
})

file.on('end', () => {
// console.log('Finished with ' + fieldname)
})
})

// Listen for event when Busboy finds a non-file field
busboy.on('field', (fieldname, val) => {
// Do something with non-file field.
})

// Listen for event when Busboy is finished parsing the form
busboy.on('finish', () => {
var data = Buffer.concat(this.data)
var stream = streamifier.createReadStream(data)

var imageSizeStream = new PassThrough()
var dataStream = new PassThrough()

// duplicate the stream so we can use it for the imagesize() request and the
// response. this saves requesting the same data a second time.
stream.pipe(imageSizeStream)
stream.pipe(dataStream)

// get the image size and format
imagesize(imageSizeStream, (err, imageInfo) => {
if (err && err !== 'invalid') {
console.log(err)
}

var obj = {
fileName: this.fileName,
mimetype: this.mimetype,
width: imageInfo.width,
height: imageInfo.height
}

var internals = {
createdAt: Date.now(),
createdBy: req.client && req.client.clientId
}

return writeFile(req, this.fileName, this.mimetype, dataStream).then((result) => {
obj = _.extend(obj, result)

this.model.create(obj, internals, help.sendBackJSON(201, res, next), req)
})
})
})

// Pipe the HTTP Request into Busboy
req.pipe(busboy)
}

module.exports = function () {
return new MediaController()
}

module.exports.MediaController = MediaController

function writeFile (req, fileName, mimetype, stream) {
return new Promise((resolve, reject) => {
var folderPath = getPath(fileName)
var storageHandler = StorageFactory.create(fileName)

storageHandler.put(stream, folderPath).then((result) => {
return resolve(result)
}).catch((err) => {
return reject(err)
})
})
}

function getPath (fileName) {
var reSplitter

switch (config.get('media.pathFormat')) {
case 'sha1/4':
reSplitter = new RegExp('.{1,4}', 'g')
return sha1(fileName).match(reSplitter).join('/')
case 'sha1/5':
reSplitter = new RegExp('.{1,5}', 'g')
return sha1(fileName).match(reSplitter).join('/')
case 'sha1/8':
reSplitter = new RegExp('.{1,8}', 'g')
return sha1(fileName).match(reSplitter).join('/')
case 'date':
return formatDate()
case 'datetime':
return formatDate(true)
default:
return ''
}
}

function formatDate (includeTime) {
var d = new Date()
var dateParts = [
d.getFullYear(),
('0' + (d.getMonth() + 1)).slice(-2),
('0' + d.getDate()).slice(-2)
]

if (includeTime) {
dateParts.push(d.getHours())
dateParts.push(d.getMinutes())
dateParts.push(d.getSeconds())
}

return dateParts.join('/')
}
@@ -113,7 +113,7 @@ module.exports.transformQuery = function (obj, type) {

case 'String':
transformFunction = function (obj) {
var regexParts = obj.match(/\/([^\/]*)\/([i]{0,1})$/)
var regexParts = obj.match(/\/([^/]*)\/([i]{0,1})$/)

if (regexParts) {
try {
@@ -19,6 +19,7 @@ var api = require(path.join(__dirname, '/api'))
var auth = require(path.join(__dirname, '/auth'))
var cache = require(path.join(__dirname, '/cache'))
var controller = require(path.join(__dirname, '/controller'))
var MediaController = require(path.join(__dirname, '/controller/media'))
var dadiStatus = require('@dadi/status')
var help = require(path.join(__dirname, '/help'))
var log = require('@dadi/logger')
@@ -77,7 +78,7 @@ Server.prototype.run = function (done) {
// Watch the current directory for a "restart.api" file
var watcher = chokidar.watch(process.cwd(), {
depth: 1,
ignored: /[\/\\]\./,
ignored: /(^|[/\\])\../, // ignores dotfiles, see https://regex101.com/r/7VuO4e/1
ignoreInitial: true
})

@@ -275,6 +276,18 @@ Server.prototype.loadApi = function (options) {
self.updateVersions(endpointPath)
})

this.app.use('/api/media', function (req, res, next) {
var method = req.method && req.method.toLowerCase()
if (method !== 'post') return next()

if (!config.get('media.enabled')) {
return next()
}

var controller = new MediaController()
return controller.post(req, res, next)
})

this.app.use('/api/flush', function (req, res, next) {
var method = req.method && req.method.toLowerCase()
if (method !== 'post') return next()
Oops, something went wrong.

0 comments on commit 12cd39c

Please sign in to comment.