Загрузка изображений на S3 в GraphQL с использованием Rails и Paperclip

Ссылка на оригинал - http://graphqlme.com/2017/09/16/upload-images-to-s3-in-graphql-using-rails-and-p…, автор публикации - Matt Engledowl

Когда я впервые начал использовать GraphQL, одна из наиболее неприятных вещей заключалась в загрузке изображений в GraphQL. Это довольно просто реализовать в REST, и если вы не знаете, как это сделать, то можете быстро найти информацию. Существует множество учебных пособий по настройке загрузки изображений в облако S3 на Amazon в rails, но, черт возьми, было сложно найти что-то на эту тему в GraphQL! Это одна из тех вещей, которые, как я понял, казались болезненно очевидными, но переход от REST к GraphQL может занять некоторое время. Имея это в виду, я хотел поделиться тем, как я загружаю изображения на S3 с помощью API GraphQL.

Предположим, у вас есть модель User, в которую вы хотите добавить изображения профиля. У вас также есть UserType диаграмма в GraphQL, а также поле QueryType для поиска пользователя по идентификатору findUser. Все это может выглядеть примерно так:

# graphql/types/user_type.rb

Types::UserType = GraphQL::ObjectType.define do
  name 'UserType'
  description 'A user for the application'

  field :id, types.ID
  field :firstName, types.String, property: :first_name
  field :lastName, types.String, property: :last_name
  field :email, types.String
end

# graphql/types/query_type.rb

Types::QueryType = GraphQL::ObjectType.define do
  name 'Query'

  field :findUser, Types::UserType, 'Find a user by id' do
    argument :id, !types.ID, 'The id of the user to find'

    resolve ->(_obj, args, _ctx) {
      User.find(args[:id])
    }
  end
end

# graphql/types/mutation_type.rb

Types::MutationType = GraphQL::ObjectType.define do
  name 'Mutation'

  field :updateUser, Types::UserType, 'Update a user profile by id' do
    argument :id, !types.ID, 'The id for the user to update'

    resolve ->(_obj, args, _ctx) {
      u = User.find(args[:id])
      u.update(args.to_h)
      u
    }
  end
end

Итак, у нас есть начальное место - очевидно, наша мутация сейчас не очень полезна, но мы вернемся к этому. Следуем далее и добавим в наш Gemfile paperclip и aws-sdk и установим пакет. Нужно убедиться, что у вас установлен ImageMagick (инструкции можно найти в документации gem, ссылка выше).

Следующий шаг - запуск paperclip. В документации мы добавим наше приложение к модели User и создадим переход для добавления столбцов, которые нам понадобятся:

# user.rb

class User < ApplicationRecord
  has_attached_file :profile_image
  validates_attachment_content_type :profile_image, content_type: /\Aimage\/.*\z/
end

# migration

class AddProfileImageToUsers < ActiveRecord::Migration[5.1]
  def up
    add_attachment :users, :profile_image
  end

  def down
    remove_attachment :users, :profile_image
  end
end

Не забудьте запустить миграции. Это добавит некоторые функции нашей модели User, что позволит нам загружать изображение, взаимодействовать с ним и проверять его формат. Теперь мы можем обновить нашу схему GraphQL, чтобы вернуть это изображение, добавив поле к нашему UserType:

field :profileImageUrl, types.String do
  resolve ->(user, _args, _ctx) {
    user.profile_image.url
  }
end

И вот мы добрались до мутации, где можно загрузить изображение! Кто-то рекомендует делать отдельную конечную точку, которую вы используете для этого, и просто пропускать GraphQL. Но я хочу делать все, что есть в GraphQL, так вот что мы собираемся делать. Теперь, одна из вещей, которая на самом деле не задокументирована (или, по крайней мере, не очень хорошо), заключается в том, что она может принимать изображение с кодировкой base64 для загрузки. Поскольку у нас на самом деле нет возможности обрабатывать загрузки изображений точно так же в GraphQL, как и в REST, мы просто собираемся принять строковый аргумент, содержащий кодированную версию base64 изображения, которое мы пытаемся загрузить. Итак, давайте вернемся к нашей мутации  updateUser и добавим новые аргументы, чтобы она выглядела так:

field :updateUser, Types::UserType, 'Update a user profile by id' do
  argument :id, !types.ID, 'The id for the user to update'

  argument :profileImageBase64, as: :profile_image do
    type types.String
    description 'The base64 encoded version of the profile image to upload.'
  end

  argument :profileImageName, types.String, as: :profile_image_file_name, default_value: 'profile-image.jpg'

  resolve ->(_obj, args, _ctx) {
    u = User.find(args[:id])
    u.update(args.to_h)
    u
  }
end

У нас есть два новых аргумента, которые мы можем пройти сейчас, и profileImageBase64 - это будет наш образ, закодированный в base64, и укажет на profile_image свойство на нашем User, и profileImageName - это имя  для нашего изображения, которое по умолчанию соответствует «profile-image». JPG». Этот важно, поскольку по умолчанию paperclip будет просто называть его «data» без расширения, что затрудняет фактическое использование. В рабочей среде вы, вероятно, захотите сделать некоторую проверку имени изображения или даже установить это автоматически, а не подвергать ее клиенту, но я оставлю любой из них как упражнение, если вы хотите это сделать.

Посмотрим, что у нас есть! Убедитесь, что у вас есть пользователь в базе данных,  затем сгенерируйте строку с кодировкой base64. Я использую https://www.base64-image.de/, потому что это так просто - вы просто загружаете файл, щелкаете кнопкой «копировать изображение» - и готово! У вас есть кодированное изображение, скопированное в буфер обмена. Теперь мы можем запустить нашу мутацию:

mutation {
  updateUser(id: 1, profileImageBase64: "[paste your base64 encoded image here]") {
    id
    profileImageUrl
  }
}

Мы должны получить что-то вроде этого:

{
  "data": {
    "updateUser": {
    "id": "1",
    "profileImageUrl": "/system/users/profile_images/000/000/001/original/profile-image.jpg?1504992676"
  }
}

Так что загрузка работает! Если нужно, мы можем перейти к нашей папке проекта и развернуться, начиная с /public, до местоположения, указанного в нашем URL выше, и мы должны увидеть изображение. Это все еще не совсем то, что мы хотим - нам нужны эти образы, чтобы их подтолкнули к S3. Прежде чем мы начнем с этого, убедитесь, что у вас есть следующее уже есть:

  • Аккаунт AWS
  • Бакет S3 для проекта
  • Название вашего бакета
  • Идентификатор ключа доступа
  • Ваш секретный ключ доступа
  • Область, в которой находится ваш бакет

ПРИМЕЧАНИЕ. Для целей этого поста в блоге мы будем делать все локально. Если вы делаете это для производственного приложения, убедитесь, что вы используете production.rb для этих настроек, а не development.rb.

Чтобы сделать загрузку paperclip на S3, нам нужно будет установить некоторые детали конфигурации. Откройте config/environments/development.rb и добавьте следующее:

config.paperclip_defaults = {
    storage: :s3,
    path: '/:class/:attachment/:id_partition/:style/:filename',
    s3_protocol: 'https',
    s3_credentials: {
        bucket: ENV['S3_BUCKET_NAME'],
        access_key_id: ENV['AWS_ACCESS_KEY_ID'],
        secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
        s3_region: ENV['AWS_REGION']
    }
}

Установите соответствующие переменные среды. Существует несколько способов сделать это, но я предпочитаю dotenv для управления переменными среды, позволяя размещать их в .env файле в корневом каталоге. Если вы идете по этому маршруту, убедитесь, что добавили .env в .gitignore. Если вы этого не сделаете, ваши учетные данные могут быть скомпрометированы.

Перезагрузите сервер и повторите попытку повторной работы. Она должна быть успешной, и вы должны увидеть, что он возвращает URL-адрес вашего изображения, который указывает на него в вашем бакете S3. Теперь вы можете загружать изображения через свой API GraphQL, и все, что ваш клиент должен знать, как это сделать, - это кодирование изображения base64 (для этого есть много библиотек). На этом этапе все готово! Поздравляем, теперь вы можете загружать файлы на S3 через GraphQL в rails! Если вы застряли или хотите просмотреть код, он доступен на моем GitHub здесь.

Хотите больше полезных советов? Смотрите и подписывайтесь на наш канал! Здесь я публикую лучшие советы для пользователей Андроид, Windows, iOS и Mac OS. Также вы можете задать мне любой вопрос, подписавшись на канал.

Наш канал в Telegram