Parse csv file from S3 using Lambda and Node Stream - node.js

I'm trying to code a lambda that triggers an s3 bucket and gets a CSV file when it is uploaded, and parse this file.
I'm using: Node 14x
This is the code:
import { S3Event } from 'aws-lambda';
import { S3 } from 'aws-sdk';
import * as csv from 'fast-csv';
const s3 = new S3({ apiVersion: 'latest' });
export async function hello(event: S3Event, context, cb) {
event.Records.forEach(async (record) => {
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
const params: S3.GetObjectRequest = {
Bucket: bucket,
Key: key,
};
const stream = s3.getObject(params).createReadStream();
console.log({ stream });
csv.parseStream(stream, {
headers: true
}).on('data', data => { console.log(data); })
.on('error', error => console.error(error))
.on('end', (rowCount: number) => console.log(`Parsed ${rowCount} rows`));
console.log('processo 01 acabou!');
});
}
When I execute this lambda I'm not receiving anything. In console.log(stream) I'm receiving a PassTrought object...
stream: PassThrough {
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: null,
ended: false,
endEmitted: false,
reading: false,
sync: false,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
dataEmitted: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: null
},
_events: [Object: null prototype] { prefinish: [Function: prefinish] },
_eventsCount: 1,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
allBuffers: true,
allNoop: true,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
errored: null,
closed: false
},
allowHalfOpen: true,
[Symbol(kCapture)]: false,
[Symbol(kTransformState)]: {
afterTransform: [Function: bound afterTransform],
needTransform: false,
transforming: false,
writecb: null,
writechunk: null,
writeencoding: null
}
}
}
I have a picture from my CloudWatch
Can anyone help me, and tell me what I'm doing wrong?

The issue with your code is that it's not correctly dealing with the asynchronous nature of JavaScript. Specifically, your code is exiting before any asynchronous activity has completed.
Your Lambda function is async so it should return a promise that is ultimately settled (fulfilled or rejected) when your processing of the S3 object(s) has completed. This allows the AWS Lambda runtime environment to await completion.
For example:
exports.handler = async function(event, context) {
const promises = event.Records.map((record) => {
const Bucket = record.s3.bucket.name;
const Key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
const params = { Bucket, Key };
const stream = s3.getObject(params).createReadStream();
return new Promise(function(resolve, reject) {
csv.parseStream(stream, {
headers: true
}).on('data', (data) => {
console.log(data);
}).on('error', (error) => {
console.error(error);
reject(error);
}).on('end', (rows) => {
console.log(`Parsed ${rows} rows`);
resolve(rows);
});
});
});
return Promise.all(promises);
}

Related

How to access the file path of uploaded files in fastify

When using a form to upload some files I can see in dev tools in the network inspector and specifically in the payload tab under form data, in view source.
Note the below includes the file name including the path twoItems/Screenshot... its this path twoItems I need to access in the API but can't.
Security? Err why do I want this?
It's for a document management app, users cant be creating folders in the browser and then add the files. They need to drag and drop nested directories of files.
------WebKitFormBoundarydJ6knkAHgNW7SIF7
Content-Disposition: form-data; name="file"; filename="twoItems/Screenshot 2022-03-11 at 08.58.24.png"
Content-Type: image/png
------WebKitFormBoundarydJ6knkAHgNW7SIF7
Content-Disposition: form-data; name="file"; filename="twoItems/Screenshot 2022-03-11 at 08.58.08.png"
Content-Type: image/png
so in the API I have a standard fastify API running
mport Fastify, { FastifyInstance, RouteShorthandOptions } from "fastify";
import { Server, IncomingMessage, ServerResponse } from "http";
const fs = require("fs");
const util = require("util");
const { pipeline } = require("stream");
const pump = util.promisify(pipeline);
const fastify: FastifyInstance = Fastify({});
fastify.register(require("fastify-multipart"));
fastify.register(require("fastify-cors"), {
methods: ["GET", "PUT", "POST"],
});
const dir = "./files";
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
fastify.post("/upload", async (req: any, reply) => {
console.log(req);
const parts = await req.files();
for await (const part of parts) {
console.log(part); //---------------- LOG BELOW
await pump(part.file, fs.createWriteStream(`./files/${part.filename}`));
}
reply.send();
});
const start = async () => {
try {
await fastify.listen(3001);
const address = fastify.server.address();
const port = typeof address === "string" ? address : address?.port;
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
I can't find how to access the path of each item
when I log out part I get...
<ref *1> {
fieldname: 'file',
filename: 'Screenshot 2022-03-11 at 17.52.11.png',
encoding: '7bit',
mimetype: 'image/png',
file: FileStream {
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: [Object], tail: [Object], length: 4 },
length: 208151,
pipes: [],
flowing: null,
ended: false,
endEmitted: false,
reading: false,
sync: false,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
dataEmitted: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: null
},
_events: [Object: null prototype] {
end: [Function (anonymous)],
limit: [Function (anonymous)]
},
_eventsCount: 2,
_maxListeners: undefined,
bytesRead: 208151,
truncated: false,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
fields: { file: [ [Object], [Object], [Object], [Circular *1] ] },
_buf: null,
toBuffer: [AsyncFunction: toBuffer]
}
this is undefined...
console.log(part.path);
You need to set the busboy's option:
fastify.register(require("fastify-multipart"), {
preservePath: true
});
You can find all the options here: https://github.com/fastify/busboy#busboy-methods

SailsJS action2 multiple file upload

I am trying to upload multiple files from sailsjs 1.2.4
Here is my action :
module.exports = {
friendlyName: 'Post',
description: 'Post something.',
files: ['mediaFiles'],
inputs: {
text : {
required: true,
type: 'string',
},
mediaFiles : {
description : "Media files",
example: '===',
required : false
}
},
exits: {
},
fn: async function (inputs) {
inputs.mediaFiles._files.forEach(file=>{
console.log(file)
})
})
}
}
I am getting below result in as file object :
{ stream:
PassThrough {
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: [Object], tail: [Object], length: 2 },
length: 43093,
pipes: null,
pipesCount: 0,
flowing: null,
ended: true,
endEmitted: false,
reading: false,
sync: false,
needReadable: false,
emittedReadable: true,
readableListening: false,
resumeScheduled: false,
emitClose: true,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: true,
domain: null,
_events:
{ prefinish: [Function: prefinish],
drain: [Function],
end: [Function],
error: [Array] },
_eventsCount: 4,
_maxListeners: undefined,
_writableState:
WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: true,
ended: true,
finished: true,
destroyed: false,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: false,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: true,
errorEmitted: false,
emitClose: true,
bufferedRequestCount: 0,
corkedRequestsFree: [Object] },
writable: false,
allowHalfOpen: true,
_transformState:
{ afterTransform: [Function: bound afterTransform],
needTransform: false,
transforming: false,
writecb: null,
writechunk: null,
writeencoding: 'buffer' },
headers:
{ 'content-disposition':
'form-data; name="mediaFiles"; filename="bwDzrPkA_400x400.jpg"',
'content-type': 'image/jpeg' },
name: 'mediaFiles',
filename: 'bwDzrPkA_400x400.jpg',
byteOffset: 378,
byteCount: 43093,
field: 'mediaFiles' },
status: 'bufferingOrWriting' }
My question is how can I write this file stream to some path like /public/media/xyz.png . I used to with sails normal file upload where I can use this.req.file("name").upload() .. but not in action 2 . I checked other answers but they are uploading to s3 not writing on same server .
You could use a dependency sails-hook-uploads, for that you are able to define the dirpath in your settings:
https://github.com/sailshq/sails-hook-uploads/blob/master/index.js#L31
module.exports.upload = {
dirpath: '.tmp/public', // recommended .tmp/uploads
adapter: require('skipper-disk'), // Default
}
I highly recommend not using the public folder because it's the compiled assets destination, so you'll probably lose these files.
As an alternative, you might use a controller to upload the file as showing below:
https://github.com/mikermcneil/ration/blob/master/api/controllers/things/upload-thing.js#L54
After that, send it to the user:
https://github.com/mikermcneil/ration/blob/master/api/controllers/things/download-photo.js#L50

MongoDB: Log a random record from collection to the console

I am using mongodb atlas and node.js. I am trying to retrieve a random record from my collection "personal_information". it seems as though I should used use :
db.collection("personal_information").aggregate([{ $sample: { size: 1 } }]);
but I am unsure as to how to log the result onto the console.
Any ideas?
UPDATE
this is what the entire section looks like
const findCustomer = function(db, callback) {
db.collection("personal_information").aggregate([{ $sample: { size: 1 }
}], function(err, data) { console.log(err, data) })
}
however the this is a snippet of what is logged to the console:
AggregationCursor {
_readableState: ReadableState {
objectMode: true,
highWaterMark: 16,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: null,
ended: false,
endEmitted: false,
reading: false,
sync: true,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: null
},

How to transform a LOB (Binary image) to send to Firebase Storage

I need to return some Oracle images that are recorded in LOB field to send them to Firebase Storage.
I am using oracledb with typescript library to call a procedure that returns certain records. One of the fields is LOB (images). I need to return this data and send this image to Firebase storage. I can't code it.
import { IConnection } from "oracledb";
import oracledb = require("oracledb");
oracledb.fetchAsString = [ oracledb.CLOB ];
export async function uploadImages(db: IConnection) {
const query = `
BEGIN
mgglo.pck_wglo_binario.p_obter_binarios_filtro
(
retorno => :retorno,
pfiltro => :pfiltro,
pmod_in_codigo => :pmod_in_codigo,
pcodigoempreendimento => :pcodigoempreendimento,
pcodigobloco => :pcodigobloco,
pcodigounidade => :pcodigounidade
);
END;`;
const bindvars = {
retorno : { dir: oracledb.BIND_OUT, type: oracledb.CURSOR },
pfiltro : 0,
pmod_in_codigo : 1,
pcodigoempreendimento : 5689,
pcodigobloco : 9645,
pcodigounidade : 8966
}
const exec = await db.execute(query, bindvars);
const row = await exec.outBinds["retorno"].getRow();
console.log(row);
}
Return:
{ BIN_IN_CODIGO: 469,
CAT_IN_CODIGO: 63,
BIN_BO_ATIVO: 'S',
BIN_ST_MIME: 'image/png',
BIN_ST_NOME: 'Image 1.png',
BIN_LO_BINARIO:
Lob {
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: [Object],
length: 0,
pipes: null,
pipesCount: 0,
flowing: null,
ended: false,
endEmitted: false,
reading: false,
sync: true,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: true,
domain: null,
_events: { end: [Object], finish: [Object] },
_eventsCount: 2,
_maxListeners: undefined,
_writableState:
WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
bufferedRequestCount: 0,
corkedRequestsFree: [Object] },
writable: true,
allowHalfOpen: true,
iLob:
ILob {
valid: true,
autoCloseLob: true,
type: 2007,
offset: 1,
pieceSize: 8060,
length: 814115,
chunkSize: 8060 },
close: [Function] },
BIN_ST_DESCRICAO: 'Teste Valmir',
BIN_DT_CRIACAO: 2019-05-28T13:32:37.000Z,
BIN_BO_LINK: 'N' }
FIELD: BIN_LO_BINARIO
The LOB is coming out as a Lob instance. That can be used for streaming large objects, but if the LOB is relatively small (compared to the amount of memory the Node.js process has access to), then you can override the default to get and String or Buffer depending on whether the LOB is a BLOB or CLOB.
Here's an example that fetches a BLOB out as a Buffer from this post:
const getSql =
`select file_name "file_name",
dbms_lob.getlength(blob_data) "file_length",
content_type "content_type",
blob_data "blob_data"
from jsao_files
where id = :id`;
async function get(id) {
const binds = {
id: id
};
const opts = {
fetchInfo: {
blob_data: {
type: oracledb.BUFFER
}
}
};
const result = await database.simpleExecute(getSql, binds, opts);
return result.rows;
}

How to correctly save images to the filesystem with fs.writeFile?

I can't figure out how i can correctly save a file i got from formidable to the file system my server is running on.
I am able to console.log the files, however i do not know what to do with the information provided there.
app.post("/sendImages", (req, res) => {
const files = req.files;
Object.keys(files).forEach((key) => {
console.log(files[key]);
fs.writeFile('images/' + files[key].name, files[key], 'binary', (error) => {
if (error) console.log(error);
else console.log('image created');
});
})
});
This request handler right here creates files with the correct names, but when i try to open them in VS Code the only thing i see is [object Object].
An example of a console logged file:
File {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
size: 3835864,
path:
'C:\\Users\\MY_USER_DIR\\AppData\\Local\\Temp\\upload_b099c61751b3b25772344e20df06a4d9',
name: '20190602_134136.jpg',
type: 'image/jpeg',
hash: null,
lastModifiedDate: 2019-06-30T15:03:22.060Z,
_writeStream:
WriteStream {
_writableState:
WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: true,
needDrain: true,
ending: true,
ended: true,
finished: true,
destroyed: true,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: false,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: true,
errorEmitted: false,
emitClose: false,
autoDestroy: false,
bufferedRequestCount: 0,
corkedRequestsFree: [Object] },
writable: false,
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
path:
'C:\\Users\\MY_USER_DIR\\AppData\\Local\\Temp\\upload_b099c61751b3b25772344e20df06a4d9',
fd: null,
flags: 'w',
mode: 438,
start: undefined,
autoClose: true,
pos: undefined,
bytesWritten: 3835864,
closed: false } }
I hope someone of you can tell me what i did wrong here, i am new to node in general and still have some problems here and there :)
You should copy files from tmp folder to images folder, like this (Node.js >= 8.5.0):
const fs = require('fs');
const util = require('util');
const path = require('path');
const copyFile = util.promisify(fs.copyFile);
app.post('/sendImages', async (req, res) => {
const files = req.files;
const results = Object.keys(files).map((key) => {
const file = files[key];
const dest = path.join('images/', file.name);
return copyFile(file.path, dest);
});
await Promise.all(results);
// ...
});
And if you don't want to save files to tmp folder, you should check the api document for change the uploadDir. Like express-formidable:
app.use(formidableMiddleware({
encoding: 'utf-8',
uploadDir: 'images/',
multiples: true
});

Resources