From 4231b8172738442957bfbb61f0e01f6a6fbeafd4 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 17 Sep 2019 18:11:34 +0200 Subject: [PATCH] [GraphQL] Add create direct upload mutation --- app/graphql/mutations/base_mutation.rb | 2 +- app/graphql/mutations/create_direct_upload.rb | 41 ++++++++++ app/graphql/schema.graphql | 75 +++++++++++++++++++ app/graphql/types/mutation_type.rb | 1 + .../api/v2/graphql_controller_spec.rb | 33 ++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 app/graphql/mutations/create_direct_upload.rb diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 84d59a361..c543a75f6 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -1,4 +1,4 @@ module Mutations - class BaseMutation < GraphQL::Schema::Mutation + class BaseMutation < GraphQL::Schema::RelayClassicMutation end end diff --git a/app/graphql/mutations/create_direct_upload.rb b/app/graphql/mutations/create_direct_upload.rb new file mode 100644 index 000000000..d4c3a90e0 --- /dev/null +++ b/app/graphql/mutations/create_direct_upload.rb @@ -0,0 +1,41 @@ +module Mutations + class CreateDirectUpload < Mutations::BaseMutation + description "File information required to prepare a direct upload" + + argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType + argument :filename, String, "Original file name", required: true + argument :byte_size, Int, "File size (bytes)", required: true + argument :checksum, String, "MD5 file checksum as base64", required: true + argument :content_type, String, "File content type", required: true + + class DirectUpload < Types::BaseObject + description "Represents direct upload credentials" + + field :url, String, "Upload URL", null: false + field :headers, String, "HTTP request headers (JSON-encoded)", null: false + field :blob_id, ID, "Created blob record ID", null: false + field :signed_blob_id, ID, "Created blob record signed ID", null: false + end + + field :direct_upload, DirectUpload, null: false + + def resolve(filename:, byte_size:, checksum:, content_type:, dossier:) + blob = ActiveStorage::Blob.create_before_direct_upload!( + filename: filename, + byte_size: byte_size, + checksum: checksum, + content_type: content_type + ) + + { + direct_upload: { + url: blob.service_url_for_direct_upload, + # NOTE: we pass headers as JSON since they have no schema + headers: blob.service_headers_for_direct_upload.to_json, + blob_id: blob.id, + signed_blob_id: blob.signed_id + } + } + end + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 6b6c5e355..a79948d38 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -40,6 +40,52 @@ GeoJSON coordinates """ scalar Coordinates +""" +Autogenerated input type of CreateDirectUpload +""" +input CreateDirectUploadInput { + """ + File size (bytes) + """ + byteSize: Int! + + """ + MD5 file checksum as base64 + """ + checksum: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + File content type + """ + contentType: String! + + """ + Dossier ID + """ + dossierId: ID! + + """ + Original file name + """ + filename: String! +} + +""" +Autogenerated return type of CreateDirectUpload +""" +type CreateDirectUploadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + directUpload: DirectUpload! +} + type DateChamp implements Champ { id: ID! label: String! @@ -131,6 +177,31 @@ enum DemarcheState { publiee } +""" +Represents direct upload credentials +""" +type DirectUpload { + """ + Created blob record ID + """ + blobId: ID! + + """ + HTTP request headers (JSON-encoded) + """ + headers: String! + + """ + Created blob record signed ID + """ + signedBlobId: ID! + + """ + Upload URL + """ + url: String! +} + """ Un dossier """ @@ -324,6 +395,10 @@ type MultipleDropDownListChamp implements Champ { } type Mutation { + """ + File information required to prepare a direct upload + """ + createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload } """ diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 113861978..bbdb5021e 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,4 +1,5 @@ module Types class MutationType < Types::BaseObject + field :create_direct_upload, mutation: Mutations::CreateDirectUpload end end diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 384d85808..8aa7c0e7b 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -166,6 +166,39 @@ describe API::V2::GraphqlController do expect(gql_data[:dossier][:champs][0][:id]).to eq(dossier.champs[0].type_de_champ.to_typed_id) end end + + context "mutations" do + context 'createDirectUpload' do + let(:query) do + "mutation { + createDirectUpload(input: { + dossierId: \"#{dossier.to_typed_id}\", + filename: \"hello.png\", + byteSize: 1234, + checksum: \"qwerty1234\", + contentType: \"image/png\" + }) { + directUpload { + url + headers + blobId + signedBlobId + } + } + }" + end + + it "should initiate a direct upload" do + expect(gql_errors).to eq(nil) + + data = gql_data[:createDirectUpload][:directUpload] + expect(data[:url]).not_to be_nil + expect(data[:headers]).not_to be_nil + expect(data[:blobId]).not_to be_nil + expect(data[:signedBlobId]).not_to be_nil + end + end + end end context "when not authenticated" do