Closed artpylon closed 7 years ago
These are my routes for the files resource:
.resources('files', { only: ['index', 'show', 'destroy', 'create', 'update'] })
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.
Can you share the entire controller file?
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'] }
] })
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.
If your logs aren't running, neither is the code. Look further up. Are any of the controller actions in that controller working?
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'] }
] })
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.
$ 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
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!
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'] },
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)
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
})
}
What does the console.log('data is ', data)
show?
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
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.
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
You need to use new FormData
not getFormFields
Can you show the HTML of the form? The name attributes are going to inform FormData how to build the key value pairs.
You're right. I messed up.
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">×</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>
Should this be hidden? <input type="file" name="[file][file]" hidden>
Also, did you try this?
Inspect the FormData in more detail following this guide: https://stackoverflow.com/questions/17066875/how-to-inspect-formdata
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.
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:
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]);
}
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:
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)
}
So, we've been spinning our wheels on this for 24 hours, could we possibly meet with an instructor face-to-face?
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]
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] ?
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
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
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
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?
That isn't in the multer code, so it doesn't seem like it would be necessary...
Sounds like you should try that!
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?
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:
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
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
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'] }
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.
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
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).
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)
}
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
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...
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
It is working, thanks all.
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:
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.