converting from blob to binary to save it to mongodb
Asked Answered
P

1

2

I get the object like that from the front end to my node express server.

{ picture: [ { preview: 'blob:http://localhost:3000/1f413443-83d8-499e-a432-9ac51a2592b7' } ],
  name: 'fsdfs',
  description: 'fdfd',
  url: 'fdfd',
  about: 'dfdf' }

i get this error :

TypeError: path must be a string or Buffer
    at TypeError (native)

this is my function where i save to the mongodb

exports.create_a_project = function(req, res) {
  console.log(req.body);
  var new_project = new Project(req.body);

  new_project.picture.data = fs.readFileSync(req.body.picture[0]);
  new_project.picture.contentType = 'image/png';
  new_project.save(function(err, project) {
    if (err)
      res.send(err);
    res.json(project);
  });
};

some how i need to convert the image i am receiving to be binary in order to save. or i need to send it as a binary base64 from the client side it self.

my client side i use react redux Dropzone for sending my data.

here is my form and how it look like.

import React from 'react';
import {Field, reduxForm} from 'redux-form';
import Dropzone from 'react-dropzone';

const FILE_FIELD_NAME = 'picture';

const renderDropzoneInput = (field) => {
  const files = field.input.value;
  return (
      <div>
        <Dropzone
            name={field.name}
            onDrop={(filesToUpload, e) => field.input.onChange(filesToUpload)}
        >
          <div>Try dropping some files here, or click to select files to
            upload.
          </div>
        </Dropzone>
        {field.meta.touched &&
        field.meta.error &&
        <span className="error">{field.meta.error}</span>}
        {files && Array.isArray(files) && (
            <ul>
              {files.map((file, i) => <li key={i}>{file.name}</li>)}
            </ul>
        )}
      </div>
  );
};

const validate = values => {
  const errors = {};
  if (!values.name) {
    errors.name = 'Required';
  } else if (values.name.length > 15) {
    errors.name = 'Must be 15 characters or less';
  }
  if (!values.description) {
    errors.description = 'Required';
  } else if (values.description.length > 15) {
    errors.description = 'Must be 75 characters or less';
  }

  if (!values.url) {
    errors.url = 'Required';
  } else if (values.url.length > 15) {
    errors.url = 'Must be 15 characters or less';
  }
  if (!values.about) {
    errors.about = 'Required';
  } else if (values.about.length > 15) {
    errors.about = 'Must be 15 characters or less';
  }
  if (!values.picture) {
    errors.picture = 'Required';
  } else if (values.picture.length > 15) {
    errors.picture = 'Must be 15 characters or less';
  }
  // if (!values.email) {
  //   errors.email = 'Required';
  // } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
  //   errors.email = 'Invalid email address';
  // }
  // if (!values.age) {
  //   errors.age = 'Required';
  // } else if (isNaN(Number(values.age))) {
  //   errors.age = 'Must be a number';
  // } else if (Number(values.age) < 18) {
  //   errors.age = 'Sorry, you must be at least 18 years old';
  // }
  return errors;
};

const warn = values => {
  const warnings = {};
  // if (values.age < 19) {
  //   warnings.age = 'Hmm, you seem a bit young...';
  // }
  return warnings;
};

const renderField = ({input, label, type, meta: {touched, error, warning}}) => (
    <div>
      <label>{label}</label>
      <div>
        <input {...input} placeholder={label} type={type}/>
        {touched && ((error && <span>{error}</span>) ||
            (warning && <span>{warning}</span>))}
      </div>
    </div>
);

const SyncValidationForm = (props) => {
  const {handleSubmit, pristine, reset, submitting} = props;
  return (
      <form onSubmit={handleSubmit}>
        <Field name="name" type="text" component={renderField}
               label="Name"/>
        <Field name="description" type="text" component={renderField}
               label="Description"/>
        <Field name="url" type="text" component={renderField}
               label="Url"/>
        <Field name="about" type="text" component={renderField}
               label="About"/>
        {/*<Field name="picture" type="text" component={renderField}*/}
               {/*label="Picture"/>*/}
        <Field
            name={FILE_FIELD_NAME}
            component={renderDropzoneInput}
        />
        {/*<Field name="email" type="email" component={renderField} label="Email"/>*/}
        {/*<Field name="age" type="number" component={renderField} label="Age"/>*/}
        <div>
          <button type="submit" disabled={submitting}>Submit</button>
          <button type="button" disabled={pristine || submitting}
                  onClick={reset}>Clear Values
          </button>
        </div>
      </form>
  );
};

export default reduxForm({
  form: 'syncValidation',  // a unique identifier for this form
  validate,                // <--- validation function given to redux-form
  warn                     // <--- warning function given to redux-form
})(SyncValidationForm);

this is my service function which deals with the rest api.

const addProject = (newProject) => {

  let data = JSON.stringify(newProject);



  return axios.post('http://localhost:3008/projects', data, {
        headers: {
          'Content-Type': 'application/json',
        }
      }
  ).then(response => {
    // console.log(response)
  }).catch(error => {
    console.log(error)
  });
};
Pool answered 30/11, 2017 at 13:4 Comment(0)
H
0

So I think your problem is in this line:

new_project.picture.data = fs.readFileSync(req.body.picture[0]);

And it's the mongoDB table column that you are inserting data into that is giving you that error. It's expecting a string or Buffer and you gave it a File object.

You can get a base64 byte string using what I posted here, which I'll try and integrate with your code, below:

You'd need to ensure you have a variable to collect your file(s). This is how I have the top of my page set up:

import React from 'react'
import Reflux from 'reflux'
import {Form, Card, CardBlock, CardHeader, CardText, Col, Row, Button } from 'reactstrap'
import actions from '~/actions/actions'
import DropZone from 'react-dropzone'

// stores
import SomeStore from '~/stores/someStore.jsx'

Reflux.defineReact(React)
export default class myUploaderClass extends Reflux.Component {
  constructor(props) {
    super(props);
    this.state = {
        attachments: [],
    };
    this.stores = [
        SomeStore,
    ]
    ....

Then bind new functions:

    ....
    this.getData = this.getData.bind(this);
    this.processFile = this.processFile.bind(this);
    this.errorHandler = this.errorHandler.bind(this);
    this.progressHandler = this.progressHandler.bind(this);
  } // close constructor

Then we work on supplying the bytes to attachments and sending it to new_project.picture.data. For me, I use a function that runs onDrop of the DropZone using onDrop={this.uploadFile}. I can't really tell what you're doing because I have no clue what filesToUpload refers to. My uploadFile looks like this:

uploadFile(event){
  this.setState({
    files: event,
  });

  document.getElementById('docDZ').classList.remove('dragover');
  document.getElementById('progress').textContent = '000';
  document.getElementById('progressBar').style.width = '0%';

  this.state.files = event;  // just for good measure
  for (let i = 0; i < this.state.files.length; i++) {
    const a = i + 1;
    console.log('in loop, pass: ' + a);
    const f = this.state.files[i];

    this.getData(f); // this will load the file to SomeStore.state.attachments
  }
}

and this would be the getData function ran for each file dropped/added to the DropZone:

getData(f) {
    const reader = new FileReader();
    reader.onerror = this.errorHandler;
    reader.onprogress = this.progressHandler;
    reader.onload = this.processFile(f);
    reader.readAsDataURL(f);
}

Then processFile() from the onload runs:

processFile(theFile) {
  return function(e) {
    const bytes = e.target.result.split('base64,')[1];
    const fileArray = [];

    // *** Collect any existing attachments ***
    // I found I could not get it from this.state, but had to use
    // my store, instead
    if (SomeStore.state.attachments.length > 0) {
      for (let i=0; i < SomeStore.state.attachments.length; i++) {
        fileArray.push(SomeStore.state.attachments[i]);
     }
    }

    // Add the new one to this.state
    fileArray.push(bytes);

    // Update the state
    SomeStore.setState({
      attachments: fileArray,
    });
    // This seemed to automatically add it to this.state, for me.
  }
}

Once you have that, you should be able to do:

new_project.picture.data = this.state.attachments[0];

If not, for some reason, you might try to call this inside exports.create_a_project(), as the first thing you do:

getData(req.body.picture[0]);

This might even work without having to modify your onDrop routine from what you have. And if this.state.attachments doesn't have anything, your SomeStore.state.attachments definitely should, assuming you have this saved to a folder called stores as someStore.jsx.

import Reflux from 'reflux'
import Actions from '~/actions/actions`

class SomeStore extends Reflux.Store
{
    constructor()
    {
        super();
        this.state = {
            attachments: [],
        };
        this.listenables = Actions;
        this.baseState = {
            attachments: [],
        };
    }

    onUpdateFields(name, value) {
        this.setState({
            [name]: value,
        });
    }

    onResetFields() {
        this.setState({
           attachments: [],
        });
    }
}
const reqformdata = new SomeStore

export default reqformdata

Additional functions

errorHandler(e){
    switch (e.target.error.code) {
      case e.target.error.NOT_FOUND_ERR:
        alert('File not found.');
        break;
      case e.target.error.NOT_READABLE_ERR:
        alert('File is not readable.');
        break;
      case e.target.error.ABORT_ERR:
        break;    // no operation
      default:
        alert('An error occurred reading this file.');
        break;
    }
  }

progressHandler(e) {
    if (e.lengthComputable){
      const loaded = Math.round((e.loaded / e.total) * 100);
      let zeros = '';

      // Percent loaded in string
      if (loaded >= 0 && loaded < 10) {
        zeros = '00';
      }
      else if (loaded < 100) {
        zeros = '0';
      }

      // Display progress in 3-digits and increase bar length
      document.getElementById("progress").textContent = zeros + loaded.toString();
      document.getElementById("progressBar").style.width = loaded + '%';
    }
  }

And my DropZone & applicable progress indicator markup:

render(){

const dropZoneStyle = {
  height: "34px",
  width: "300px",
  border: "1px solid #ccc",
  borderRadius: "4px",
};

return (
  <Form>
    <Col xs={5}>
            <DropZone type="file" id="docDZ"
              onDrop={this.uploadFile}
              onDropRejected={this.onDropRejected}
              onClick={this.handleUploadClick}
              onChange={this.handleChange}
              onDragEnter={this.handleDragEnter}
              onDragLeave={this.handleDragLeave}
              accept=".doc, .docx, .gif, .png, .jpg, .jpeg, .pdf"
              multiple="true"
              maxSize={999000}
              style={dropZoneStyle}>
               {'Click HERE to upload or drop files here...'}
            </DropZone>
            <table id="tblProgress">
              <tbody>
                <tr>
                  <td><b><span id="progress">000</span>%</b> <span className="progressBar"><span id="progressBar" /></span></td>
                </tr>
              </tbody>
            </table>
          </Col>
      </Row>
    </Form>
    )
  } // close render
}  // close class

And CSS:

.progressBar {
  background-color: rgba(255, 255, 255, .1);
  width: 100%;
  height: 26px;
}
#progressBar {
  background-color: rgba(87, 184, 208, .5);
  content: '';
  width: 0;
  height: 26px;
}

Other functions you're missing:

handleUploadClick(){
  return this.state;
}

handleChange(){
  this.state.errors.fileError = "";
}

handleDragEnter(event){
  event.preventDefault();
  document.getElementById("docDZ").classList.add("dragover");
}

handleDragLeave(event){
  event.preventDefault();
  document.getElementById("docDZ").classList.remove("dragover");
}

onDropRejected(files){
    const errors ={}
    let isAlertVisible = false;

    for(let i=0, j = files.length; i < j; i++){
      const file = files[i];
      const ext = file.name.split('.').pop().toLowerCase();
      //console.log(ext)

     if(this.isFileTypeValid(ext)===false){
        errors.fileError = "Only image files (JPG, GIF, PNG), Word files (DOC, DOCX), and PDF files are allowed.";
        isAlertVisible = true;
     }

     if(ext === "docx" || ext ==="gif" || ext ==="png" || ext ==="jpg" || ext ==="jpeg" || ext ==="pdf" || ext ==="doc" && file.size > 999000){
      errors.fileError = "Exceeded File Size limit! The maximum file size for uploads is 999 KB.";
      isAlertVisible = true;
    }

    this.setState({
      "errors": errors,
      "isAlertVisible": isAlertVisible,
    })
  }
}
Housecarl answered 4/4, 2018 at 21:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.