Сохраните несколько изображений в ответ, используя multer и s3 - PullRequest
0 голосов
/ 11 ноября 2019

У меня есть следующий компонент

import React, { Component } from "react";
import axios from "axios";
import swal from "sweetalert";

import AuthService from "../../Auth/AuthService";
import withAuth from "../../Auth/withAuth";
const Auth = new AuthService();

class Create extends Component {
  constructor() {
    super();
    this.state = {
      title: "",
      description: "",
      reference: "",
      images: [],
      price: "",
      year: "",
      categoryName: "",
      categorys: [],
      sold: false,
      titleErr: "",
      descriptionErr: "",
      referenceErr: "",
      imagesErr: "",
      priceErr: "",
      yearErr: "",
      categoryErr: ""
    };
  }

  validate = () => {
    let titleErr = "";
    let descriptionErr = "";
    let referenceErr = "";
    let imagesErr = "";
    let priceErr = "";
    let yearErr = "";
    let categoryNameErr = "";

    // Title validation
    if (!this.state.title) {
      titleErr = "Please enter a title";
    }

    // Description validation
    if (!this.state.description) {
      descriptionErr = "Please enter a description";
    }

    // Reference validation
    if (!this.state.reference) {
      referenceErr = "Please enter a reference";
    }

    // Images validation
    if (!this.state.images.length) {
      imagesErr = "You must have at least one image";
    }

    // Price validation
    if (!this.state.price) {
      priceErr = "Please enter a price";
    }

    // Year validation
    if (!this.state.year) {
      yearErr = "Please enter a year";
    }

    // Category validation
    if (!this.state.categoryName) {
      categoryNameErr = "Please enter a category";
    }

    // Render validations
    if (
      titleErr ||
      descriptionErr ||
      referenceErr ||
      imagesErr ||
      priceErr ||
      yearErr ||
      categoryNameErr
    ) {
      this.setState({
        titleErr,
        descriptionErr,
        referenceErr,
        imagesErr,
        priceErr,
        yearErr,
        categoryNameErr
      });
      return false;
    }

    return true;
  };

  componentDidMount() {
    axios
      .get("/api/category")
      .then(res => this.setState({ categorys: res.data }))
      .catch(error => {
        console.log(error);
      });
  }

  onTitleChange(event) {
    this.setState({ title: event.target.value });
  }

  onDescriptionChange(event) {
    this.setState({ description: event.target.value });
  }

  onReferenceChange(event) {
    this.setState({ reference: event.target.value });
  }

  onImagesChange(event) {
    this.setState({ images: event.target.value });
  }

  onPriceChange(event) {
    this.setState({ price: event.target.value });
  }

  onYearChange(event) {
    this.setState({ year: event.target.value });
  }

  onCategoryNameChange(event) {
    this.setState({ categoryName: event.target.value });
  }

  onSoldChange(event) {
    this.setState({ sold: event.target.value });
  }

  onSubmit = e => {
    e.preventDefault();
    const isValid = this.validate();
    const {
      title,
      description,
      reference,
      images,
      price,
      year,
      categoryName,
      sold
    } = this.state;

    let config = {
      headers: {
        "Content-Type": "multipart/form-data",
        Authorization: "bearer " + Auth.getToken()
      }
    };

    var formData = new FormData();
    formData.append("title", title);
    formData.append("description", description);
    formData.append("reference", reference);
    formData.append("price", price);
    formData.append("images", images);
    formData.append("year", year);
    formData.append("categoryName", categoryName);
    formData.append("sold", sold);

    for (let i = 0; i < images.length; i++) {
      formData.append("images", images[i]);
    }

    if (isValid) {
      axios
        .post("/api/collections/create", formData, config)
        .then(function(response) {
          swal({
            title: "Success",
            text: "You have created a collection",
            icon: "success",
            button: "OK"
          });
          this.props.history.push("/dashboard");
          console.log(response);
        })
        .catch(function(response) {
          swal({
            title: "Error",
            text: `${response}`,
            icon: "error",
            button: "Try again"
          });
        });
    }
  };

  render() {
    const {
      title,
      description,
      reference,
      images,
      price,
      year,
      categoryName,
      sold
    } = this.state;
    return (
      <>
        <div className='block md:flex md:flex-column h-full'>
          <div className='p-12 w-full text-center text-gray-800'>
            <h1 className='title mb-10'>Create a collection</h1>

            <form
              className='w-full m-auto max-w-lg'
              encType='multipart/form-data'
              onSubmit={this.onSubmit}
            >
              <div className='flex flex-wrap mb-4'>
                <label htmlFor='title'>Title:</label>
                <input
                  type='text'
                  name='title'
                  value={title}
                  onChange={this.onTitleChange.bind(this)}
                  placeholder='Title'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.titleErr}
                </p>
              </div>

              <div className='flex flex-wrap'>
                <label htmlFor='description'>Description:</label>
                <textarea
                  type='text'
                  name='description'
                  className='h-64'
                  value={description}
                  onChange={this.onDescriptionChange.bind(this)}
                  placeholder='Content'
                ></textarea>

                <p className='mb-4 text-red-500 text-xs'>
                  {this.state.descriptionErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='reference'>Reference:</label>
                <input
                  type='text'
                  name='reference'
                  value={reference}
                  onChange={this.onReferenceChange.bind(this)}
                  placeholder='reference'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.referenceErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='images'>images:</label>
                <input
                  type='file'
                  multiple
                  name='images'
                  value={images}
                  accept='image/*'
                  onChange={this.onImagesChange.bind(this)}
                ></input>

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.imagesErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='price'>Price:</label>
                <input
                  type='number'
                  name='price'
                  value={price}
                  onChange={this.onPriceChange.bind(this)}
                  placeholder='price'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.priceErr}
                </p>
              </div>

              <div className='flex flex-wrap mb-4'>
                <label htmlFor='year'>Year:</label>
                <input
                  type='number'
                  name='year'
                  value={year}
                  onChange={this.onYearChange.bind(this)}
                  placeholder='Year'
                />

                <p className='mt-2 text-red-500 text-xs'>
                  {this.state.yearErr}
                </p>
              </div>

              <div className='flex flex-col mb-4'>
                <label htmlFor='categoryName'>Category</label>

                <div className='relative'>
                  <select
                    name='categoryName'
                    value={categoryName}
                    onChange={this.onCategoryNameChange.bind(this)}
                  >
                    <option>N/A</option>
                    {this.state.categorys.map(category => (
                      <option key={category._id} value={category.name}>
                        {category.name}
                      </option>
                    ))}
                  </select>

                  <div className='pointer-events-none absolute inset-y-0 right-0 flex items-center px-2'>
                    <svg
                      className='fill-current h-4 w-4'
                      xmlns='http://www.w3.org/2000/svg'
                      viewBox='0 0 20 20'
                    >
                      <path d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z' />
                    </svg>
                  </div>
                </div>
              </div>

              <div className='flex flex-col mb-2'>
                <label htmlFor='sold'>Sold?</label>

                <div className='relative'>
                  <select
                    name='sold'
                    value={sold}
                    onChange={this.onSoldChange.bind(this)}
                  >
                    <option value='false'>No</option>
                    <option value='true'>Yes</option>
                  </select>

                  <div className='pointer-events-none absolute inset-y-0 right-0 flex items-center px-2'>
                    <svg
                      className='fill-current h-4 w-4'
                      xmlns='http://www.w3.org/2000/svg'
                      viewBox='0 0 20 20'
                    >
                      <path d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z' />
                    </svg>
                  </div>
                </div>
              </div>

              <div className='flex'>
                <button type='submit' className='btn w-full'>
                  Submit
                </button>
              </div>
            </form>
          </div>
        </div>
      </>
    );
  }
}

export default withAuth(Create);

Я пытаюсь сохранить все изображения в виде массива, чтобы я мог сохранить имя файла и местоположение s3.

С точки зрения экспресс-я имеюследующее

/* POST create a collection */
module.exports.createCollection = [
  ...validations,
  (req, res) => {
    // Get validation errors from the request
    const errors = validationResult(req);
    // Return the errors
    if (!errors.isEmpty()) {
      return res.status(422).json({ error: errors.array() });
    }

    Collections.findOne({ _id: req.body.id })
      .then(function(collection) {
        return Collections.create({
          title: req.body.title,
          description: req.body.description,
          reference: req.body.reference,
          images: req.files.map(({ originalname, location }) => ({
            originalname,
            location
          })),
          price: req.body.price,
          year: req.body.year,
          categoryName: req.body.categoryName,
          sold: req.body.sold
        });
      })
      .then(
        function(collection) {
          res.status(201).json(collection);
          console.log("Successfully uploaded " + req.files.length + " files!");
        },
        function(error) {
          console.error(error);
          res.status(500).send(error);
        }
      );
  }
];

Когда я проверяю это в почтальоне, запрос работает и я получаю желаемый результат, однако реагирую не так сильно.

Ошибка, которую я получаю Error: Request failed with status code 422.

Это ожидаемый результат в почтальоне.

Изображения загружаются при создании записей в почтальоне, поэтому проблема здесь, похоже, связана с React.

Как мне поступить в реакции?

...