codecombat/server/routes/file.coffee

210 lines
7 KiB
CoffeeScript
Raw Normal View History

2014-01-03 13:32:13 -05:00
Grid = require 'gridfs-stream'
fs = require 'fs'
request = require 'request'
2014-06-30 22:16:26 -04:00
mongoose = require 'mongoose'
errors = require '../commons/errors'
config = require '../../server_config'
2014-01-03 13:32:13 -05:00
module.exports.setup = (app) ->
2014-01-03 13:32:13 -05:00
app.all '/file*', (req, res) ->
return fileGet(req, res) if req.route.method is 'get'
return filePost(req, res) if req.route.method is 'post'
return fileDelete(req, res) if req.route.method is 'delete'
return errors.badMethod(res, ['GET', 'POST', 'DELETE'])
2014-01-03 13:32:13 -05:00
fileDelete = (req, res) ->
return errors.forbidden(res) unless req.user
query = parsePathIntoQuery(req.path)
return errors.badInput(res) if not query.filename
Grid.gfs.collection('media').findOne query, (err, filedata) =>
return errors.notFound(res) if not filedata
return errors.forbidden(res) unless userCanEditFile(req.user, filedata)
Grid.gfs.remove {_id: filedata._id, root: 'media'}, (err) ->
return errors.serverError(res) if err
return res.end()
2014-01-03 13:32:13 -05:00
fileGet = (req, res) ->
query = parsePathIntoQuery(req.path)
2014-01-03 13:32:13 -05:00
if not query.filename # it's a folder, return folder contents
2014-01-03 13:32:13 -05:00
Grid.gfs.collection('media').find query, (err, cursor) ->
return errors.serverError(res) if err
2014-01-03 13:32:13 -05:00
results = cursor.toArray (err, results) ->
return errors.serverError(res) if err
2014-01-03 13:32:13 -05:00
res.setHeader('Content-Type', 'text/json')
res.send(results)
res.end()
else # it's a single file
2014-01-03 13:32:13 -05:00
Grid.gfs.collection('media').findOne query, (err, filedata) =>
return errors.notFound(res) if not filedata
2014-06-30 22:16:26 -04:00
readstream = Grid.gfs.createReadStream({_id: filedata._id, root: 'media'})
2014-01-03 13:32:13 -05:00
if req.headers['if-modified-since'] is filedata.uploadDate
res.status(304)
return res.end()
2014-01-03 13:32:13 -05:00
res.setHeader('Content-Type', filedata.contentType)
res.setHeader('Last-Modified', filedata.uploadDate)
res.setHeader('Cache-Control', 'public')
readstream.pipe(res)
handleStreamEnd(res, res)
parsePathIntoQuery = (path) ->
path = path[6..]
path = decodeURI path
try
objectId = mongoose.Types.ObjectId(path)
query = objectId
catch e
path = path.split('/')
filename = path[path.length-1]
path = path[...path.length-1].join('/')
query =
'metadata.path': path
query.filename = filename if filename
query
2014-01-03 13:32:13 -05:00
postFileSchema =
type: 'object'
properties:
# source
url: { type: 'string', description: 'The url to download the file from.' }
postName: { type: 'string', description: 'The input field this file was sent on.' }
2014-01-06 15:37:35 -05:00
b64png: { type: 'string', description: 'Raw png data to upload.' }
2014-01-03 13:32:13 -05:00
# options
force: { type: 'string', 'default': '', description: 'Whether to overwrite existing files (as opposed to throwing an error).' }
# metadata
filename: { type: 'string', description: 'What the file will be named in the system.' }
mimetype: { type: 'string' }
name: { type: 'string', description: 'Human readable and searchable string.' }
description: { type: 'string' }
path: { type: 'string', description: 'What "folder" this file goes into.' }
required: ['filename', 'mimetype', 'path']
filePost = (req, res) ->
return errors.forbidden(res) unless req.user
2014-01-03 13:32:13 -05:00
options = req.body
tv4 = require('tv4').tv4
valid = tv4.validate(options, postFileSchema)
2014-01-06 15:37:35 -05:00
hasSource = options.url or options.postName or options.b64png
# TODO : give tv4.error to badInput
return errors.badInput(res) if (not valid) or (not hasSource)
2014-01-03 13:32:13 -05:00
return saveURL(req, res) if options.url
return saveFile(req, res) if options.postName
2014-01-06 15:37:35 -05:00
return savePNG(req, res) if options.b64png
2014-01-03 13:32:13 -05:00
saveURL = (req, res) ->
options = createPostOptions(req)
2014-04-24 13:45:52 -04:00
checkExistence options, req, res, req.body.force, (err) ->
return errors.serverError(res) if err
2014-01-03 13:32:13 -05:00
writestream = Grid.gfs.createWriteStream(options)
request(req.body.url).pipe(writestream)
handleStreamEnd(res, writestream)
saveFile = (req, res) ->
options = createPostOptions(req)
2014-04-24 13:45:52 -04:00
checkExistence options, req, res, req.body.force, (err) ->
2014-01-03 13:32:13 -05:00
return if err
writestream = Grid.gfs.createWriteStream(options)
f = req.files[req.body.postName]
fileStream = fs.createReadStream(f.path)
fileStream.pipe(writestream)
handleStreamEnd(res, writestream)
2014-01-06 15:37:35 -05:00
savePNG = (req, res) ->
options = createPostOptions(req)
2014-04-24 13:45:52 -04:00
checkExistence options, req, res, req.body.force, (err) ->
return if err
2014-01-06 15:37:35 -05:00
writestream = Grid.gfs.createWriteStream(options)
img = new Buffer(req.body.b64png, 'base64')
streamBuffers = require 'stream-buffers'
2014-06-30 22:16:26 -04:00
myReadableStreamBuffer = new streamBuffers.ReadableStreamBuffer({frequency: 10, chunkSize: 2048})
2014-01-06 15:37:35 -05:00
myReadableStreamBuffer.put(img)
myReadableStreamBuffer.pipe(writestream)
handleStreamEnd(res, writestream)
2014-06-30 22:16:26 -04:00
2014-04-24 13:45:52 -04:00
userCanEditFile = (user=null, file=null) ->
# no user means 'anyone'. No file means 'any file'
return false unless user
return true if user.isAdmin()
return false unless file
return true if file.metadata.creator is user.id
return false
checkExistence = (options, req, res, force, done) ->
2014-01-03 13:32:13 -05:00
q = {
filename: options.filename
'metadata.path': options.metadata.path
}
Grid.gfs.collection('media').find(q).toArray (err, files) ->
2014-04-24 13:45:52 -04:00
file = files[0]
if file and ((not userCanEditFile(req.user, file) or (not force)))
2014-06-30 22:16:26 -04:00
errors.conflict(res, {canForce: userCanEditFile(req.user, file)})
2014-01-03 13:32:13 -05:00
done(true)
2014-04-24 13:45:52 -04:00
else if file
fullPath = "/file/#{options.metadata.path}/#{options.filename}"
clearCloudFlareCacheForFile(fullPath)
2014-04-24 13:45:52 -04:00
q = { _id: file._id }
2014-01-03 13:32:13 -05:00
q.root = 'media'
Grid.gfs.remove q, (err) ->
2014-04-24 13:45:52 -04:00
if err
errors.serverError(res)
return done(true)
2014-01-03 13:32:13 -05:00
done()
else
done()
handleStreamEnd = (res, stream) ->
stream.on 'close', (f) ->
res.send(f)
res.end()
stream.on 'error', ->
return errors.serverError(res)
2014-01-03 13:32:13 -05:00
CHUNK_SIZE = 1024*256
createPostOptions = (req) ->
unless req.body.name
name = req.body.filename.split('.')[0]
req.body.name = _.str.humanize(name)
2014-01-03 13:32:13 -05:00
path = req.body.path or ''
path = path[1...] if path and path[0] is '/'
path = path[...path.length-2] if path and path[path.length-1] is '/'
2014-01-03 13:32:13 -05:00
options =
mode: 'w'
filename: req.body.filename
chunk_size: CHUNK_SIZE
root: 'media'
content_type: req.body.mimetype
metadata:
name: req.body.name
path: path
creator: ''+req.user._id
options.metadata.description = req.body.description if req.body.description?
2014-01-03 13:32:13 -05:00
options
clearCloudFlareCacheForFile = (path='/file') ->
unless config.cloudflare.token
console.log 'skipping clearing cloud cache, not configured'
return
request = require 'request'
r = request.post 'https://www.cloudflare.com/api_json.html', (err, httpResponse, body) ->
if (err)
console.error('CloudFlare file cache clear failed:', body)
2014-06-30 22:16:26 -04:00
form = r.form()
form.append 'tkn', config.cloudflare.token
form.append 'email', 'scott@codecombat.com'
form.append 'z', 'codecombat.com'
form.append 'a', 'zone_file_purge'
form.append 'url', "http://codecombat.com#{path}"