ga-wdi-boston / team-project

Other
2 stars 39 forks source link

Ajax/Node/AWS (filebucket) - Unable to create new file - 500 error returned #347

Closed artpylon closed 7 years ago

artpylon commented 7 years ago

I'm attempting to submit the add file form from my local client, 500 error returned.

This is the api call that I copied over from the class express-multer example:

const addFile = function (data) {
  console.log('data is ', data)
  return $.ajax({
    url: 'http://localhost:4741/files',
    method: 'POST',
    data,
    contentType: false,
    processData: false
  })
}

This is the create function in the controller, I added some console logs to try to elucidate the problem, but none of them fired in the server console. So, I guess the test didn't get that far.

const create = (req, res, next) => {
  console.log('req is ', req)
  const awsFile = {
    path: req.file.originalname,
    name: req.body.file.name
  }
  awsUpload(awsFile)
    .then((s3Response) => {
      console.log('s3Response is ', s3Response)
      return File.create({
        url: s3Response.Location,
        title: s3Response.Key
      })
    })
    .then((file) => res.status(201).json({file}))
    .catch(next)
}
artpylon commented 7 years ago

These are my routes for the files resource:

.resources('files', { only: ['index', 'show', 'destroy', 'create', 'update'] })
MicFin commented 7 years ago

None of these fired? console.log('req is ', req) console.log('s3Response is ', s3Response)

Additionally you'd want to lo req.file.originalname and req.body.file.name as well once we can get to those lines firing.

MicFin commented 7 years ago

Can you share the entire controller file?

artpylon commented 7 years ago

No, none of the console logs fired. I should be looking for these in the terminal where I have the server running, correct?

entire controller:

'use strict'

const controller = require('lib/wiring/controller')
const models = require('app/models')
const File = models.file

const authenticate = require('./concerns/authenticate')
const setUser = require('./concerns/set-current-user')
const setModel = require('./concerns/set-mongoose-model')

const multer = require('multer')
const multerUpload = multer({ dest: '/tmp/' })

const awsUpload = require('lib/aws-upload')

const index = (req, res, next) => {
  File.find()
    .then(files => res.json({
      files: files.map((e) =>
        e.toJSON({ virtuals: true }))
    }))
    .catch(next)
}

const show = (req, res) => {
  res.json({
    file: req.file.toJSON({ virtuals: true })
  })
}

const create = (req, res, next) => {
  console.log('req is ', req)
  const awsFile = {
    path: req.file.originalname,
    name: req.body.file.name
  }
  awsUpload(awsFile)
    .then((s3Response) => {
      console.log('s3Response is ', s3Response)
      return File.create({
        url: s3Response.Location,
        title: s3Response.Key
      })
    })
    .then((file) => res.status(201).json({file}))
    .catch(next)
}

const update = (req, res, next) => {
  delete req.body._owner  // disallow owner reassignment.
  req.file.update(req.body.file)
    .then(() => res.sendStatus(204))
    .catch(next)
}

const destroy = (req, res, next) => {
  req.file.remove()
    .then(() => res.sendStatus(204))
    .catch(next)
}

module.exports = controller({
  index,
  show,
  create,
  update,
  destroy
}, { before: [
  { method: setUser, only: ['destroy', 'update'] },
  { method: multerUpload.single('file[file]'), only: ['create'] },
  { method: authenticate, except: ['index', 'show'] },
  { method: setModel(File), only: ['show', 'destroy', 'update'] },
  { method: setModel(File, { forUser: true }), only: ['update', 'destroy'] }
] })
MicFin commented 7 years ago

500 error is an internal server error so that means your Request is being made to the right endpoint but then the server is hitting an error. Do you see any error messages in your Express API server log? Try to upload again and show me what the API server log says.

payne-chris-r commented 7 years ago

If your logs aren't running, neither is the code. Look further up. Are any of the controller actions in that controller working?

artpylon commented 7 years ago

Yes, the index action is working. Looking upstream it seems like the routes are correct, they include the create route for the resource files. The other thing upstream is in the before: code at the bottom of the controller.

We've been trying to match Mike's multer code from class and this looks the same as that, apart from the authenticate line. It seems like if it were an authentication issue we wouldn't be getting at 500.

{ before: [
  { method: setUser, only: ['destroy', 'update'] },
  { method: multerUpload.single('file[file]'), only: ['create'] },
  { method: authenticate, except: ['index', 'show'] },
  { method: setModel(File), only: ['show', 'destroy', 'update'] },
  { method: setModel(File, { forUser: true }), only: ['update', 'destroy'] }
] })
MicFin commented 7 years ago

Please upload a file and show us the server log. So you should go to your client application in your browser, upload a file, and then post what your server log says.

MarcyNash commented 7 years ago
$ nodemon start
[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `./bin/express server start`
(node:47622) DeprecationWarning: `open()` is deprecated in mongoose >= 4.11.0, use `openUri()` instead, or set the `useMongoClient` option if using `connect()` or `createConnection()`
Server listening on port 4741
GET /files 304 14.309 ms - -
POST /sign-in 200 139.693 ms - 286
POST /files 500 15.882 ms - 123
MarcyNash commented 7 years ago

I was able to get into the files.create method when I used an earlier version of files.js:

'use strict'

const controller = require('lib/wiring/controller')
const models = require('app/models')
const File = models.file

const authenticate = require('./concerns/authenticate')
const setUser = require('./concerns/set-current-user')
const setModel = require('./concerns/set-mongoose-model')

const index = (req, res, next) => {
  File.find()
    .then(files => res.json({
      files: files.map((e) =>
        e.toJSON({ virtuals: true }))
    }))
    .catch(next)
}

const show = (req, res) => {
  res.json({
    file: req.file.toJSON({ virtuals: true })
  })
}

const create = (req, res, next) => {
  console.log('in create')
  const file = Object.assign(req.body.file, {
    _owner: req.user._id
  })
  File.create(file)
    .then(file =>
      res.status(201)
        .json({
          file: file.toJSON({ virtuals: true, user: req.user })
        }))
    .catch(next)
}

const update = (req, res, next) => {
  delete req.body._owner  // disallow owner reassignment.
  req.file.update(req.body.file)
    .then(() => res.sendStatus(204))
    .catch(next)
}

const destroy = (req, res, next) => {
  req.file.remove()
    .then(() => res.sendStatus(204))
    .catch(next)
}

module.exports = controller({
  index,
  show,
  create,
  update,
  destroy
}, { before: [
  { method: setUser, only: ['destroy', 'update'] },
  { method: authenticate, except: ['index', 'show'] },
  { method: setModel(File), only: ['show', 'destroy', 'update'] },
  { method: setModel(File, { forUser: true }), only: ['update', 'destroy'] }
] })

This code is prior to adding the AWS and multer code. The server logs:

nodemon start
[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `./bin/express server start`
(node:47840) DeprecationWarning: `open()` is deprecated in mongoose >= 4.11.0, use `openUri()` instead, or set the `useMongoClient` option if using `connect()` or `createConnection()`
Server listening on port 4741
GET /files 304 13.361 ms - -
POST /sign-in 200 129.980 ms - 286
in create
POST /files 500 3.708 ms - 77

I am now going to add back in the AWS and multer code a bit at a time to see where it breaks. Any other suggestions would be greatly appreciated!

MarcyNash commented 7 years ago

With this update to files.js, I no longer get the 'in create' console log

'use strict'

const controller = require('lib/wiring/controller')
const models = require('app/models')
const File = models.file

const authenticate = require('./concerns/authenticate')
const setUser = require('./concerns/set-current-user')
const setModel = require('./concerns/set-mongoose-model')

const multer = require('multer')
const multerUpload = multer({ dest: '/tmp/' })

const awsUpload = require('lib/aws-upload')

const index = (req, res, next) => {
  File.find()
    .then(files => res.json({
      files: files.map((e) =>
        e.toJSON({ virtuals: true }))
    }))
    .catch(next)
}

const show = (req, res) => {
  res.json({
    file: req.file.toJSON({ virtuals: true })
  })
}

const create = (req, res, next) => {
  console.log('in create')
  const file = Object.assign(req.body.file, {
    _owner: req.user._id
  })
  File.create(file)
    .then(file =>
      res.status(201)
        .json({
          file: file.toJSON({ virtuals: true, user: req.user })
        }))
    .catch(next)
}

const update = (req, res, next) => {
  delete req.body._owner  // disallow owner reassignment.
  req.file.update(req.body.file)
    .then(() => res.sendStatus(204))
    .catch(next)
}

const destroy = (req, res, next) => {
  req.file.remove()
    .then(() => res.sendStatus(204))
    .catch(next)
}

module.exports = controller({
  index,
  show,
  create,
  update,
  destroy
}, { before: [
  { method: setUser, only: ['destroy', 'update'] },
  **{ method: multerUpload.single('file[file]'), only: ['create'] },**
  { method: authenticate, except: ['index', 'show'] },
  { method: setModel(File), only: ['show', 'destroy', 'update'] },
  { method: setModel(File, { forUser: true }), only: ['update', 'destroy'] }
] })

Server log --

[nodemon] restarting due to changes...
[nodemon] starting `./bin/express server start`
(node:48182) DeprecationWarning: `open()` is deprecated in mongoose >= 4.11.0, use `openUri()` instead, or set the `useMongoClient` option if using `connect()` or `createConnection()`
Server listening on port 4741
POST /files 500 17.021 ms - 123

Any ideas?? Adding this line stops create from being called --

{ method: multerUpload.single('file[file]'), only: ['create'] },
MicFin commented 7 years ago

The example without Multer still shows 500 error, does it not? But at least it does hit the log!

On your client application, can you show your relevant events.js function and api.js function.

(additionally, I think naming the resource "File" was potentially a poor choice but it shouldnt be a deal breaker)

MarcyNash commented 7 years ago

from events.js

const onAddFile = function (event) {
  event.preventDefault()
  const data = new FormData(event.target)
  fileapi.addFile(data)
    .then(fileui.addFileSuccess)
    .catch(fileui.addFileFailure)
}

From api.js

const addFile = function (data) {
  console.log('data is ', data)
  return $.ajax({
    url: 'http://localhost:4741/files',
    method: 'POST',
    headers: {
      Authorization: 'Token token=' + store.token
    },
    data,
    contentType: false,
    processData: false
  })
}
MicFin commented 7 years ago

What does the console.log('data is ', data) show?

MarcyNash commented 7 years ago

data is FormData

data is FormDataproto: FormDataappend: function append()delete: function delete()entries: function entries()forEach: function forEach()get: function get()getAll: function getAll()has: function has()keys: function keys()set: function set()values: function values()constructor: function FormData()Symbol(Symbol.iterator): function ()Symbol(Symbol.toStringTag): "FormData"proto: Object

MicFin commented 7 years ago

Inspect the FormData in more detail following this guide: https://stackoverflow.com/questions/17066875/how-to-inspect-formdata

Also, can you show the HTML of the form, the name attributes are going to inform FormData how to build the key value pairs.

MarcyNash commented 7 years ago

In events.js just changed new FormData(event.target) to getFormFields(event.target)

const onAddFile = function (event) { event.preventDefault() // const data = new FormData(event.target) const data = getFormFields(event.target) fileapi.addFile(data) .then(fileui.addFileSuccess) .catch(fileui.addFileFailure) }

And we're now back in create

[nodemon] restarting due to changes... [nodemon] starting ./bin/express server start (node:48395) DeprecationWarning: open() is deprecated in mongoose >= 4.11.0, use openUri() instead, or set the useMongoClient option if using connect() or createConnection() Server listening on port 4741 GET /files 304 18.925 ms - - POST /sign-in 200 135.982 ms - 286 in create POST /files 500 4.432 ms - 77

Yay!

Will start adding more aws code into files.js and see what happens. I can now also check if create is getting the stuff from the frontend

MicFin commented 7 years ago

You need to use new FormData not getFormFields

MicFin commented 7 years ago

Can you show the HTML of the form? The name attributes are going to inform FormData how to build the key value pairs.

MarcyNash commented 7 years ago

You're right. I messed up.

artpylon commented 7 years ago

HTML for add file modal:

<!-- Add File Modal-->

    <!-- Modal -->
      <div class="modal fade add-file-modal" role="dialog">
              <div class="modal-dialog">
    <!-- Modal Content-->
            <div class="col-sm-2"></div>
                    <div class="modal-content col-sm-8">

                <form id="add-file" class="modal_form">
                  <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal">&times;</button>
                    <h3 class="modal-title">Add a File</h3>
                  </div>
                    <p class="modal-folder-name">Folder:</p>

                    <div class="form-group">
                      <label>File Name</label>
                      <input type="text" name="[file][name]" placeholder="File name" required="true" class="form-control name-add">
                    </div>

                    <label class="btn btn-default">

                      <input type="file" name="[file][file]" hidden>
                    </label>

                    <div class="form-group">
                      <label for="addTagNew">Add a Tag:</label>
                      <select class="form-control" id="addTagNew">

                        <option>New</option>
                        <option>Pending Review</option>
                        <option>Complete</option>

                      </select>
                    </div>

                    <p class="errormsg"></p>
                    <input type="submit" class="btn btn-default button" name="submit" value="Add File">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

                </form>

                    </div>
            <div class="col-sm-2"></div>
                </div>
              </div>
MicFin commented 7 years ago

Should this be hidden? <input type="file" name="[file][file]" hidden>

MicFin commented 7 years ago

Also, did you try this?

Inspect the FormData in more detail following this guide: https://stackoverflow.com/questions/17066875/how-to-inspect-formdata

artpylon commented 7 years ago

I removed the hidden attribute from the html, submitted the form. I have a console log for the data before the add ajax call is made, shown in the browser console. Still getting a 500 error.

screen shot 2017-06-28 at 11 12 05 am
artpylon commented 7 years ago

Sorry, meant to show the node sever logs there instead of my repo terminal window:

Same console logs are still in the create function, but not shown in the node server:

screen shot 2017-06-28 at 11 16 43 am
MicFin commented 7 years ago

FormData objects are opaque. You will need to log this:

// Display the key/value pairs
for (let pair of formData.entries()) {
    console.log(pair[0]+ ', ' + pair[1]); 
}
artpylon commented 7 years ago

Alright, I've console logged that in the event function, I am seeing the file name correctly, the file itself shows [object file], unsure if that is expected or not:

screen shot 2017-06-28 at 11 33 34 am
artpylon commented 7 years ago

event function with console log looks like this:

const onAddFile = function (event) {
  event.preventDefault()
  const data = new FormData(event.target)
  for (let pair of data.entries()) {
    console.log(pair[0] + ', ' + pair[1])
  }
  fileApi.addFile(data)
    .then(fileUi.addFileSuccess)
    .catch(fileUi.addFileFailure)
}
artpylon commented 7 years ago

So, we've been spinning our wheels on this for 24 hours, could we possibly meet with an instructor face-to-face?

jordanallain commented 7 years ago

try just console.logging pair[i] by itself, it is an object and you are concatenating it with a string so it is just showing you [object File]

jordanallain commented 7 years ago

console.log(pair[0], pair[1])

changing the log to this should also show the actual object instead of the string.

additionally, why did you use [file][name] and [file][file] for the name attribute of your forms instead of file[name] and file[file] ?

artpylon commented 7 years ago

Thank you, OK, so now my console logs are firing inside the create function, very verbose:

req is  IncomingMessage {
  _readableState: 
   ReadableState {
     objectMode: false,
     highWaterMark: 16384,
     buffer: BufferList { head: null, tail: null, length: 0 },
     length: 0,
     pipes: null,
     pipesCount: 0,
     flowing: false,
     ended: true,
     endEmitted: true,
     reading: false,
     sync: false,
     needReadable: false,
     emittedReadable: false,
     readableListening: false,
     resumeScheduled: false,
     defaultEncoding: 'utf8',
     ranOut: false,
     awaitDrain: 0,
     readingMore: false,
     decoder: null,
     encoding: null },
  readable: false,
  domain: null,
  _events: { readable: [Function: bound ] },
  _eventsCount: 1,
  _maxListeners: undefined,
  socket: 
   Socket {
     connecting: false,
     _hadError: false,
     _handle: 
      TCP {
        bytesRead: 604593,
        _externalStream: {},
        fd: 17,
        reading: true,
        owner: [Circular],
        onread: [Function: onread],
        onconnection: null,
        writeQueueSize: 0,
        _consumed: true },
     _parent: null,
     _host: null,
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [Object],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: true,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: true,
     domain: null,
     _events: 
      { end: [Object],
        finish: [Function: onSocketFinish],
        _socketEnd: [Function: onSocketEnd],
        drain: [Object],
        timeout: [Function],
        error: [Object],
        close: [Object],
        data: [Function: socketOnData],
        resume: [Function: onSocketResume],
        pause: [Function: onSocketPause] },
     _eventsCount: 10,
     _maxListeners: undefined,
     _writableState: 
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: false,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: false,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: true,
     allowHalfOpen: true,
     destroyed: false,
     _bytesDispatched: 1550,
     _sockname: null,
     _pendingData: null,
     _pendingEncoding: '',
     server: 
      Server {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        _connections: 1,
        _handle: [Object],
        _usingSlaves: false,
        _slaves: [],
        _unref: false,
        allowHalfOpen: true,
        pauseOnConnect: false,
        httpAllowHalfOpen: false,
        timeout: 120000,
        _pendingResponseData: 0,
        _connectionKey: '6::::4741' },
     _server: 
      Server {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        _connections: 1,
        _handle: [Object],
        _usingSlaves: false,
        _slaves: [],
        _unref: false,
        allowHalfOpen: true,
        pauseOnConnect: false,
        httpAllowHalfOpen: false,
        timeout: 120000,
        _pendingResponseData: 0,
        _connectionKey: '6::::4741' },
     _idleTimeout: 120000,
     _idleNext: 
      TimersList {
        _idleNext: [Circular],
        _idlePrev: [Circular],
        _timer: [Object],
        _unrefed: true,
        msecs: 120000,
        nextTick: false },
     _idlePrev: 
      TimersList {
        _idleNext: [Circular],
        _idlePrev: [Circular],
        _timer: [Object],
        _unrefed: true,
        msecs: 120000,
        nextTick: false },
     _idleStart: 177237,
     parser: 
      HTTPParser {
        '0': [Function: parserOnHeaders],
        '1': [Function: parserOnHeadersComplete],
        '2': [Function: parserOnBody],
        '3': [Function: parserOnMessageComplete],
        '4': [Function: onParserExecute],
        _headers: [],
        _url: '',
        _consumed: true,
        socket: [Circular],
        incoming: [Circular],
        outgoing: null,
        maxHeaderPairs: 2000,
        onIncoming: [Function: parserOnIncoming] },
     on: [Function: socketOnWrap],
     _paused: false,
     read: [Function],
     _consuming: true,
     _httpMessage: 
      ServerResponse {
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: false,
        upgrading: false,
        chunkedEncoding: false,
        shouldKeepAlive: true,
        useChunkedEncodingByDefault: true,
        sendDate: true,
        _removedHeader: {},
        _contentLength: null,
        _hasBody: true,
        _trailer: '',
        finished: false,
        _headerSent: false,
        socket: [Circular],
        connection: [Circular],
        _header: null,
        _headers: [Object],
        _headerNames: [Object],
        _onPendingData: [Function: updateOutgoingData],
        req: [Circular],
        locals: {},
        _startAt: undefined,
        _startTime: undefined,
        writeHead: [Function: writeHead],
        __onFinished: [Object] },
     _peername: { address: '::1', family: 'IPv6', port: 54468 } },
  connection: 
   Socket {
     connecting: false,
     _hadError: false,
     _handle: 
      TCP {
        bytesRead: 604593,
        _externalStream: {},
        fd: 17,
        reading: true,
        owner: [Circular],
        onread: [Function: onread],
        onconnection: null,
        writeQueueSize: 0,
        _consumed: true },
     _parent: null,
     _host: null,
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [Object],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: true,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: true,
     domain: null,
     _events: 
      { end: [Object],
        finish: [Function: onSocketFinish],
        _socketEnd: [Function: onSocketEnd],
        drain: [Object],
        timeout: [Function],
        error: [Object],
        close: [Object],
        data: [Function: socketOnData],
        resume: [Function: onSocketResume],
        pause: [Function: onSocketPause] },
     _eventsCount: 10,
     _maxListeners: undefined,
     _writableState: 
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: false,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: false,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: true,
     allowHalfOpen: true,
     destroyed: false,
     _bytesDispatched: 1550,
     _sockname: null,
     _pendingData: null,
     _pendingEncoding: '',
     server: 
      Server {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        _connections: 1,
        _handle: [Object],
        _usingSlaves: false,
        _slaves: [],
        _unref: false,
        allowHalfOpen: true,
        pauseOnConnect: false,
        httpAllowHalfOpen: false,
        timeout: 120000,
        _pendingResponseData: 0,
        _connectionKey: '6::::4741' },
     _server: 
      Server {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        _connections: 1,
        _handle: [Object],
        _usingSlaves: false,
        _slaves: [],
        _unref: false,
        allowHalfOpen: true,
        pauseOnConnect: false,
        httpAllowHalfOpen: false,
        timeout: 120000,
        _pendingResponseData: 0,
        _connectionKey: '6::::4741' },
     _idleTimeout: 120000,
     _idleNext: 
      TimersList {
        _idleNext: [Circular],
        _idlePrev: [Circular],
        _timer: [Object],
        _unrefed: true,
        msecs: 120000,
        nextTick: false },
     _idlePrev: 
      TimersList {
        _idleNext: [Circular],
        _idlePrev: [Circular],
        _timer: [Object],
        _unrefed: true,
        msecs: 120000,
        nextTick: false },
     _idleStart: 177237,
     parser: 
      HTTPParser {
        '0': [Function: parserOnHeaders],
        '1': [Function: parserOnHeadersComplete],
        '2': [Function: parserOnBody],
        '3': [Function: parserOnMessageComplete],
        '4': [Function: onParserExecute],
        _headers: [],
        _url: '',
        _consumed: true,
        socket: [Circular],
        incoming: [Circular],
        outgoing: null,
        maxHeaderPairs: 2000,
        onIncoming: [Function: parserOnIncoming] },
     on: [Function: socketOnWrap],
     _paused: false,
     read: [Function],
     _consuming: true,
     _httpMessage: 
      ServerResponse {
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: false,
        upgrading: false,
        chunkedEncoding: false,
        shouldKeepAlive: true,
        useChunkedEncodingByDefault: true,
        sendDate: true,
        _removedHeader: {},
        _contentLength: null,
        _hasBody: true,
        _trailer: '',
        finished: false,
        _headerSent: false,
        socket: [Circular],
        connection: [Circular],
        _header: null,
        _headers: [Object],
        _headerNames: [Object],
        _onPendingData: [Function: updateOutgoingData],
        req: [Circular],
        locals: {},
        _startAt: undefined,
        _startTime: undefined,
        writeHead: [Function: writeHead],
        __onFinished: [Object] },
     _peername: { address: '::1', family: 'IPv6', port: 54468 } },
  httpVersionMajor: 1,
  httpVersionMinor: 1,
  httpVersion: '1.1',
  complete: true,
  headers: 
   { host: 'localhost:4741',
     connection: 'keep-alive',
     'content-length': '300900',
     pragma: 'no-cache',
     'cache-control': 'no-cache',
     authorization: 'Token token=aTbIFSV+9AKaxEsZT6KrO54one/J7WQhwFrGDcoWg0Q=--lA0Io7nIaVqXQL2h3kH8+jUHwLCkZDawOJuQkqVIZao=',
     origin: 'http://localhost:7165',
     'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
     'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryTBSuBilU98ZvfbEx',
     accept: '*/*',
     dnt: '1',
     referer: 'http://localhost:7165/',
     'accept-encoding': 'gzip, deflate, br',
     'accept-language': 'en-US,en;q=0.8,la;q=0.6' },
  rawHeaders: 
   [ 'Host',
     'localhost:4741',
     'Connection',
     'keep-alive',
     'Content-Length',
     '300900',
     'Pragma',
     'no-cache',
     'Cache-Control',
     'no-cache',
     'Authorization',
     'Token token=aTbIFSV+9AKaxEsZT6KrO54one/J7WQhwFrGDcoWg0Q=--lA0Io7nIaVqXQL2h3kH8+jUHwLCkZDawOJuQkqVIZao=',
     'Origin',
     'http://localhost:7165',
     'User-Agent',
     'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
     'Content-Type',
     'multipart/form-data; boundary=----WebKitFormBoundaryTBSuBilU98ZvfbEx',
     'Accept',
     '*/*',
     'DNT',
     '1',
     'Referer',
     'http://localhost:7165/',
     'Accept-Encoding',
     'gzip, deflate, br',
     'Accept-Language',
     'en-US,en;q=0.8,la;q=0.6' ],
  trailers: {},
  rawTrailers: [],
  upgrade: false,
  url: '/files',
  method: 'POST',
  statusCode: null,
  statusMessage: null,
  client: 
   Socket {
     connecting: false,
     _hadError: false,
     _handle: 
      TCP {
        bytesRead: 604593,
        _externalStream: {},
        fd: 17,
        reading: true,
        owner: [Circular],
        onread: [Function: onread],
        onconnection: null,
        writeQueueSize: 0,
        _consumed: true },
     _parent: null,
     _host: null,
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [Object],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: true,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: true,
     domain: null,
     _events: 
      { end: [Object],
        finish: [Function: onSocketFinish],
        _socketEnd: [Function: onSocketEnd],
        drain: [Object],
        timeout: [Function],
        error: [Object],
        close: [Object],
        data: [Function: socketOnData],
        resume: [Function: onSocketResume],
        pause: [Function: onSocketPause] },
     _eventsCount: 10,
     _maxListeners: undefined,
     _writableState: 
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: false,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: false,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: true,
     allowHalfOpen: true,
     destroyed: false,
     _bytesDispatched: 1550,
     _sockname: null,
     _pendingData: null,
     _pendingEncoding: '',
     server: 
      Server {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        _connections: 1,
        _handle: [Object],
        _usingSlaves: false,
        _slaves: [],
        _unref: false,
        allowHalfOpen: true,
        pauseOnConnect: false,
        httpAllowHalfOpen: false,
        timeout: 120000,
        _pendingResponseData: 0,
        _connectionKey: '6::::4741' },
     _server: 
      Server {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        _connections: 1,
        _handle: [Object],
        _usingSlaves: false,
        _slaves: [],
        _unref: false,
        allowHalfOpen: true,
        pauseOnConnect: false,
        httpAllowHalfOpen: false,
        timeout: 120000,
        _pendingResponseData: 0,
        _connectionKey: '6::::4741' },
     _idleTimeout: 120000,
     _idleNext: 
      TimersList {
        _idleNext: [Circular],
        _idlePrev: [Circular],
        _timer: [Object],
        _unrefed: true,
        msecs: 120000,
        nextTick: false },
     _idlePrev: 
      TimersList {
        _idleNext: [Circular],
        _idlePrev: [Circular],
        _timer: [Object],
        _unrefed: true,
        msecs: 120000,
        nextTick: false },
     _idleStart: 177237,
     parser: 
      HTTPParser {
        '0': [Function: parserOnHeaders],
        '1': [Function: parserOnHeadersComplete],
        '2': [Function: parserOnBody],
        '3': [Function: parserOnMessageComplete],
        '4': [Function: onParserExecute],
        _headers: [],
        _url: '',
        _consumed: true,
        socket: [Circular],
        incoming: [Circular],
        outgoing: null,
        maxHeaderPairs: 2000,
        onIncoming: [Function: parserOnIncoming] },
     on: [Function: socketOnWrap],
     _paused: false,
     read: [Function],
     _consuming: true,
     _httpMessage: 
      ServerResponse {
        domain: null,
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: false,
        upgrading: false,
        chunkedEncoding: false,
        shouldKeepAlive: true,
        useChunkedEncodingByDefault: true,
        sendDate: true,
        _removedHeader: {},
        _contentLength: null,
        _hasBody: true,
        _trailer: '',
        finished: false,
        _headerSent: false,
        socket: [Circular],
        connection: [Circular],
        _header: null,
        _headers: [Object],
        _headerNames: [Object],
        _onPendingData: [Function: updateOutgoingData],
        req: [Circular],
        locals: {},
        _startAt: undefined,
        _startTime: undefined,
        writeHead: [Function: writeHead],
        __onFinished: [Object] },
     _peername: { address: '::1', family: 'IPv6', port: 54468 } },
  _consuming: true,
  _dumped: false,
  next: [Function: next],
  baseUrl: '',
  originalUrl: '/files',
  _parsedUrl: 
   Url {
     protocol: null,
     slashes: null,
     auth: null,
     host: null,
     port: null,
     hostname: null,
     hash: null,
     search: null,
     query: null,
     pathname: '/files',
     path: '/files',
     href: '/files',
     _raw: '/files' },
  params: {},
  query: {},
  res: 
   ServerResponse {
     domain: null,
     _events: { finish: [Object], end: [Function: onevent] },
     _eventsCount: 2,
     _maxListeners: undefined,
     output: [],
     outputEncodings: [],
     outputCallbacks: [],
     outputSize: 0,
     writable: true,
     _last: false,
     upgrading: false,
     chunkedEncoding: false,
     shouldKeepAlive: true,
     useChunkedEncodingByDefault: true,
     sendDate: true,
     _removedHeader: {},
     _contentLength: null,
     _hasBody: true,
     _trailer: '',
     finished: false,
     _headerSent: false,
     socket: 
      Socket {
        connecting: false,
        _hadError: false,
        _handle: [Object],
        _parent: null,
        _host: null,
        _readableState: [Object],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 10,
        _maxListeners: undefined,
        _writableState: [Object],
        writable: true,
        allowHalfOpen: true,
        destroyed: false,
        _bytesDispatched: 1550,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: [Object],
        _server: [Object],
        _idleTimeout: 120000,
        _idleNext: [Object],
        _idlePrev: [Object],
        _idleStart: 177237,
        parser: [Object],
        on: [Function: socketOnWrap],
        _paused: false,
        read: [Function],
        _consuming: true,
        _httpMessage: [Circular],
        _peername: [Object] },
     connection: 
      Socket {
        connecting: false,
        _hadError: false,
        _handle: [Object],
        _parent: null,
        _host: null,
        _readableState: [Object],
        readable: true,
        domain: null,
        _events: [Object],
        _eventsCount: 10,
        _maxListeners: undefined,
        _writableState: [Object],
        writable: true,
        allowHalfOpen: true,
        destroyed: false,
        _bytesDispatched: 1550,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: [Object],
        _server: [Object],
        _idleTimeout: 120000,
        _idleNext: [Object],
        _idlePrev: [Object],
        _idleStart: 177237,
        parser: [Object],
        on: [Function: socketOnWrap],
        _paused: false,
        read: [Function],
        _consuming: true,
        _httpMessage: [Circular],
        _peername: [Object] },
     _header: null,
     _headers: 
      { 'x-powered-by': 'Express',
        'access-control-allow-origin': 'http://localhost:7165',
        vary: 'Origin' },
     _headerNames: 
      { 'x-powered-by': 'X-Powered-By',
        'access-control-allow-origin': 'Access-Control-Allow-Origin',
        vary: 'Vary' },
     _onPendingData: [Function: updateOutgoingData],
     req: [Circular],
     locals: {},
     _startAt: undefined,
     _startTime: undefined,
     writeHead: [Function: writeHead],
     __onFinished: { [Function: listener] queue: [Object] } },
  _startAt: [ 623470, 736991641 ],
  _startTime: 2017-06-28T16:58:42.262Z,
  _remoteAddress: '::1',
  body: { file: { name: 'asdasd' } },
  route: 
   Route {
     path: '/files',
     stack: [ [Object], [Object], [Object] ],
     methods: { post: true } },
  read: [Function],
  file: 
   { fieldname: 'file[file]',
     originalname: 'Screen Shot 2017-06-28 at 12.48.19 PM.png',
     encoding: '7bit',
     mimetype: 'image/png',
     destination: '/tmp/',
     filename: '6cd4a08ec0455b57475cf30f92334d60',
     path: '/tmp/6cd4a08ec0455b57475cf30f92334d60',
     size: 300577 },
  user: 
   { _id: 5952c562c8f77b0d1cfafec4,
     passwordDigest: '$2a$10$jfiRYRdh0qUgqosYCKyB8.tRBRNW3Xd/NF1y2Qma.Hez8qCUnE966',
     updatedAt: 2017-06-28T16:58:33.495Z,
     createdAt: 2017-06-27T20:51:46.388Z,
     email: 'tram@mam.com',
     token: '0f0nb7khh35OodfFoxH47A==',
     __v: 0,
     password: undefined,
     id: '5952c562c8f77b0d1cfafec4' } }
{ path: 'Screen Shot 2017-06-28 at 12.48.19 PM.png',
  name: 'asdasd' }
inside crypto success d334d6808259432b0e1e2f15600727d5
TypeError: path must be a string or Buffer
    at TypeError (native)
    at Object.fs.open (fs.js:632:11)
    at ReadStream.open (fs.js:1951:6)
    at new ReadStream (fs.js:1938:10)
    at Object.fs.createReadStream (fs.js:1885:10)
    at s3Upload (/Users/matthewgray/wdi/projects/drip-drop-backend/lib/aws-upload.js:47:21)
    at process._tickDomainCallback (internal/process/next_tick.js:135:7)
s3Response is  undefined
POST /files 500 44.285 ms - 79
artpylon commented 7 years ago

So, my guess is that the last piece of that is what's important here: TypeError: path must be a string or Buffer

In the controller, I updated the naming for file so it's consistent with the rest of the function and added console logs for the name and original name.

New controller create function:

const create = (req, res, next) => {
  // console.log('req is ', req)
  console.log('req file originalname is ', req.file.originalname)
  console.log('req body file name is ', req.body.file.name)
  const file = {
    path: req.file.originalname,
    name: req.body.file.name
  }
  // console.log(awsFile)
  awsUpload(file)
    .then((s3Response) => {
      console.log('s3Response is ', s3Response)
      return File.create({
        url: s3Response.Location,
        title: s3Response.Key
      })
    })
    .then((file) => res.status(201).json({file}))
    .catch(next)
}

Trying another add file still returns 500, the console logs for name and original name return what I would expect: POST /files 500 47.802 ms - 79 req file originalname is Screen Shot 2017-06-28 at 12.48.19 PM.png req body file name is asdasd inside crypto success e7fd84cab080c3ed1892fad4f349e37a TypeError: path must be a string or Buffer at TypeError (native) at Object.fs.open (fs.js:632:11) at ReadStream.open (fs.js:1951:6) at new ReadStream (fs.js:1938:10) at Object.fs.createReadStream (fs.js:1885:10) at s3Upload (/Users/matthewgray/wdi/projects/drip-drop-backend/lib/aws-upload.js:47:21) at process._tickDomainCallback (internal/process/next_tick.js:135:7) s3Response is undefined POST /files 500 20.317 ms - 79

artpylon commented 7 years ago

this is our aws-upload file, where awsUpload function is defined:

'use strict'

// require dotenv and run load method
// this loads my env variables to a process.env object
require('dotenv').load()

// require file system module
const fs = require('fs')
const AWS = require('aws-sdk')
const mime = require('mime')
const path = require('path')
const crypto = require('crypto')

// create an instance of AWS.S3 object
const s3 = new AWS.S3()

const randomBytesPromise = function () {
  return new Promise((resolve, reject) => {
    // Generates cryptographically strong pseudo-random data.
    // The size argument is a number indicating the number of bytes to generate.
    // If a callback function is provided, the bytes are generated asynchronously
    // and the callback function is invoked with two arguments: err and buf
    // If an error occurs, err will be an Error object;
    // otherwise it is null.
    // The buf argument is a Buffer containing the generated bytes.
    // https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback
    crypto.randomBytes(16, function (error, buffer) {
      // If an error occurs, err will be an Error object;
      if (error) {
        reject(error)
      } else {
        // More on buffer
        // https://nodejs.org/api/buffer.html#buffer_buffer
        console.log('inside crypto success', buffer.toString('hex'))
        resolve(buffer.toString('hex'))
      }
    })
  })
}

// s3Upload accepts file options as a param and returns a promise,
// that resolved or rejected base on s3.upload response
const s3Upload = function (options) {
  // use node fs module to create a read stream
  // for our image file
  // https://www.sitepoint.com/basics-node-js-streams/
  const stream = fs.createReadStream(options.originalname)

  // use node mime module to get image mime type
  // https://www.npmjs.com/package/mime
  const contentType = mime.lookup(options.originalname)

  // use node path module to get image extension (.jpg, .gif)
  // https://nodejs.org/docs/latest/api/path.html#path_path
  const ext = path.extname(options.originalname) // .png
  // const ext = mime.extension(contentType)

  // get current date, turn into ISO string, and split to access correctly formatted date
  const folder = new Date().toISOString().split('T')[0]

  // params required for `.upload` to work
  // more at documentation
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
  const params = {
    ACL: 'public-read',
    Bucket: process.env.AWS_S3_BUCKET_NAME,
    Body: stream,
    Key: `${folder}/${options.name}${ext}`,
    ContentType: contentType
  }

  // return a promise object that is resolved or rejected,
  // based on the response from s3.upload
  return new Promise((resolve, reject) => {
    // pass correct params to `.upload`
    // and anonymous allback for handling response
    s3.upload(params, function (error, data) {
      if (error) {
        reject(error)
      } else {
        resolve(data)
      }
    })
  })
}

const awsUpload = function (options) {
  return randomBytesPromise()
    .then((buffer) => {
      // set file name to buffer that is returned from randomBytesPromise
      options.name = buffer

      // return file so it is passed as argument to s3Upload
      return options
    })
    .then(s3Upload)
    .catch(console.error)
}

module.exports = awsUpload
artpylon commented 7 years ago

In that function it looks like it sets options.name to a buffer, but perhaps that is where original name should also be assigned to buffer?

artpylon commented 7 years ago

That isn't in the multer code, so it doesn't seem like it would be necessary...

MicFin commented 7 years ago

Sounds like you should try that!

MicFin commented 7 years ago

You pass in options as the argument to s3Upload. What is the options object at that point?

    .then((buffer) => {
      options.name = buffer
      console.log(options) // log it to find out
      return options
    })

Because in the s3Upload function you then do

const stream = fs.createReadStream(options.originalname)

And

 const params = {
    ACL: 'public-read',
    Bucket: process.env.AWS_S3_BUCKET_NAME,
    Body: stream,
    Key: `${folder}/${options.name}${ext}`,
    ContentType: contentType
  }

Does the options object have a name and a originalname property?

artpylon commented 7 years ago

No, it looks like options only has path and name. Then, it wouldn't make sense that s3Upload doesn't have options.originalname

So, that could mean a few things, either:

  1. I need to add originalname to the file variable that's in the controller create
  2. I need to change options.originalname to options.name in aws-upload file

After trying #2 above, this is the response from the server log. It looks like randombytes has encoded the path (which was initially the original file name) successfully. I'm unsure why it says open ddc17a4e10a7cf110b8524bdcb4ca35a', i would think that's what it is creating.

{ Error: ENOENT: no such file or directory, open 'ddc17a4e10a7cf110b8524bdcb4ca35a' at Error (native) errno: -2, code: 'ENOENT', syscall: 'open', path: 'ddc17a4e10a7cf110b8524bdcb4ca35a' } s3Response is undefined POST /files 500 45.188 ms - 79

artpylon commented 7 years ago

googling this error "Error: ENOENT: no such file or directory" yielded a post where the issue seems to be the path to the tmp folder.

https://stackoverflow.com/questions/9047094/node-js-error-enoent

artpylon commented 7 years ago

confirming that my multer references to tmp folder match up with multer class example code:

const multer = require('multer')
const multerUpload = multer({ dest: '/tmp/' })

{ method: multerUpload.single('file[file]'), only: ['create'] }
MicFin commented 7 years ago

options.originalname is used 3 times in the s3Upload function. Did you update each of them?

Add a console.log after each of your variables declared in s3Upload and see if one is not what you expect or errors.

artpylon commented 7 years ago

ok, so, I changed all three to options.path since my options only include path and name. Then, I console logged all three variables declared in s3Upload. As far as I can tell, stream seems to be a stream, contentType gives me image/png, and ext gives the extension. Not sure which of these is wrong as they match the names.

server logs with those console logs:

stream is  ReadStream {
  _readableState: 
   ReadableState {
     objectMode: false,
     highWaterMark: 65536,
     buffer: BufferList { head: null, tail: null, length: 0 },
     length: 0,
     pipes: null,
     pipesCount: 0,
     flowing: null,
     ended: false,
     endEmitted: false,
     reading: false,
     sync: true,
     needReadable: false,
     emittedReadable: false,
     readableListening: false,
     resumeScheduled: false,
     defaultEncoding: 'utf8',
     ranOut: false,
     awaitDrain: 0,
     readingMore: false,
     decoder: null,
     encoding: null },
  readable: true,
  domain: null,
  _events: { end: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined,
  path: 'Screen Shot 2017-06-28 at 12.52.54 PM.png',
  fd: null,
  flags: 'r',
  mode: 438,
  start: undefined,
  end: undefined,
  autoClose: true,
  pos: undefined,
  bytesRead: 0 }
contentType is  image/png
ext is  .png
{ Error: ENOENT: no such file or directory, open 'Screen Shot 2017-06-28 at 12.52.54 PM.png'
    at Error (native)
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'Screen Shot 2017-06-28 at 12.52.54 PM.png' }
s3Response is  undefined
POST /files 500 48.165 ms - 79
artpylon commented 7 years ago

Perhaps the issue is with the read stream, I think it can't find the file that multer loaded into tmp...

I've tried passing readStream both options.name and options.path as an argument, neither produce a new error (the thing it's trying to open changes, of course).

artpylon commented 7 years ago

OK, so i went back to the controller and changed the path in file to this and I am now able to store something on s3:

const create = (req, res, next) => {
  // console.log('req is ', req)
  console.log('req file originalname is ', req.file.originalname)
  console.log('req body file name is ', req.body.file.name)
  const file = {
    path: req.file.path,
    name: req.body.file.name
  }
  // console.log(awsFile)
  awsUpload(file)
    .then((s3Response) => {
      console.log('s3Response is ', s3Response)
      return File.create({
        url: s3Response.Location,
        title: s3Response.Key
      })
    })
    .then((file) => res.status(201).json({file}))
    .catch(next)
}
artpylon commented 7 years ago

However, I am still getting a 500 error. This is the new server log:

s3Response is  { ETag: '"d12c52ac53b8ce9f16e1c5a3de4fa6f1"',
  Location: 'https://mrg-wdi.s3.amazonaws.com/2017-06-28/d07c0e1ba49a6c94c5aa8a7a967daa6a',
  key: '2017-06-28/d07c0e1ba49a6c94c5aa8a7a967daa6a',
  Key: '2017-06-28/d07c0e1ba49a6c94c5aa8a7a967daa6a',
  Bucket: 'mrg-wdi' }
POST /files 500 513.507 ms - 1026
artpylon commented 7 years ago

In the above create controller function, in return File.create it has url and title. But, nothing in this app is called title... so that's probably wrong.

I'm wondering if I need to be storing both the name the user provided and the name that's in s3. Though, I am storing the url, so perhaps I don't care what s3 is calling it...

artpylon commented 7 years ago

It must be the File.create function that is returning the error now, I'm trying to add all the required fields into that function so it could create a valid file according to our model. No luck yet.

new create controller function:

const create = (req, res, next) => {
  // console.log('req is ', req)
  console.log('req file originalname is ', req.file.originalname)
  console.log('req body file name is ', req.body.file.name)
  const file = {
    path: req.file.path,
    name: req.body.file.name
  }
  // console.log(awsFile)
  awsUpload(file)
    .then((s3Response) => {
      console.log('s3Response is ', s3Response)
      return File.create({
        name: file.name,
        url: s3Response.Location,
        folder: req.file.folder
        // title: s3Response.Key
      })
    })
    .then((file) => res.status(201).json({file}))
    .catch(next)
}

Here's the model:

const mongoose = require('mongoose')

const fileSchema = new mongoose.Schema({
  _owner: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  url: {
    type: String,
    required: true
  },
  name: {
    type: String,
    required: true
  },
  folder: {
    type: String,
    required: true
  },
  pendingReview: {
    type: Boolean
  },
  complete: {
    type: Boolean
  }
}, {
  timestamps: true,
  toJSON: {
    virtuals: true,
    transform: function (doc, ret, options) {
      const userId = (options.user && options.user._id) || false
      ret.editable = userId && userId.equals(doc._owner)
      return ret
    }
  }
})

const File = mongoose.model('File', fileSchema)

module.exports = File
artpylon commented 7 years ago

It is working, thanks all.