diff --git a/Gemfile.lock b/Gemfile.lock index 423259e6f..a96a2ea2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -443,7 +443,7 @@ GEM byebug (~> 10.0) pry (~> 0.10) public_suffix (4.0.1) - puma (3.12.0) + puma (3.12.2) pundit (2.0.1) activesupport (>= 3.0.0) rack (2.0.7) diff --git a/app/graphql/extensions/attachment.rb b/app/graphql/extensions/attachment.rb index f0ac99aed..adfed8e95 100644 --- a/app/graphql/extensions/attachment.rb +++ b/app/graphql/extensions/attachment.rb @@ -28,9 +28,9 @@ module Extensions # This method is called if the result of the `resolve` # is a lazy value (e.g., a Promise – like in our case) def after_resolve(value:, **_rest) - return if value.nil? - - Rails.application.routes.url_helpers.url_for(value) + if value&.virus_scanner&.safe? || value&.virus_scanner&.pending? + value + end end end end diff --git a/app/graphql/mutations/dossier_accepter.rb b/app/graphql/mutations/dossier_accepter.rb index 0ad5af78a..15ba05326 100644 --- a/app/graphql/mutations/dossier_accepter.rb +++ b/app/graphql/mutations/dossier_accepter.rb @@ -22,7 +22,7 @@ module Mutations end end - def authorized?(dossier:, instructeur:, motivation: nil) + def authorized?(dossier:, instructeur:, motivation: nil, justificatif: nil) instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id) end end diff --git a/app/graphql/mutations/dossier_classer_sans_suite.rb b/app/graphql/mutations/dossier_classer_sans_suite.rb index fea8a0fa8..5e4307967 100644 --- a/app/graphql/mutations/dossier_classer_sans_suite.rb +++ b/app/graphql/mutations/dossier_classer_sans_suite.rb @@ -22,7 +22,7 @@ module Mutations end end - def authorized?(dossier:, instructeur:, motivation:) + def authorized?(dossier:, instructeur:, motivation:, justificatif: nil) instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id) end end diff --git a/app/graphql/mutations/dossier_envoyer_message.rb b/app/graphql/mutations/dossier_envoyer_message.rb index 601e448de..75c7bb4ae 100644 --- a/app/graphql/mutations/dossier_envoyer_message.rb +++ b/app/graphql/mutations/dossier_envoyer_message.rb @@ -20,7 +20,7 @@ module Mutations end end - def authorized?(dossier:, instructeur:, body:) + def authorized?(dossier:, instructeur:, body:, attachment: nil) instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id) end end diff --git a/app/graphql/mutations/dossier_refuser.rb b/app/graphql/mutations/dossier_refuser.rb index 25328bee4..10ffeec36 100644 --- a/app/graphql/mutations/dossier_refuser.rb +++ b/app/graphql/mutations/dossier_refuser.rb @@ -22,7 +22,7 @@ module Mutations end end - def authorized?(dossier:, instructeur:, motivation:) + def authorized?(dossier:, instructeur:, motivation:, justificatif: nil) instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id) end end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 2604850d6..7f6364605 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -8,7 +8,7 @@ type Association { } type Avis { - attachmentUrl: URL + attachment: File dateQuestion: ISO8601DateTime! dateReponse: ISO8601DateTime expert: Profile @@ -383,7 +383,7 @@ type Dossier { instructeurs: [Profile!]! messages: [Message!]! motivation: String - motivationAttachmentUrl: URL + motivationAttachment: File """ Le numero du dossier. @@ -663,6 +663,14 @@ type Entreprise { siretSiegeSocial: String! } +type File { + byteSize: Int! + checksum: String! + contentType: String! + filename: String! + url: URL! +} + interface GeoArea { geometry: GeoJSON! id: ID! @@ -742,7 +750,7 @@ type LinkedDropDownListChamp implements Champ { } type Message { - attachmentUrl: URL + attachment: File body: String! createdAt: ISO8601DateTime! email: String! @@ -875,6 +883,7 @@ type PersonnePhysique implements Demandeur { } type PieceJustificativeChamp implements Champ { + file: File id: ID! """ @@ -886,7 +895,6 @@ type PieceJustificativeChamp implements Champ { La valeur du champ sous forme texte. """ stringValue: String - url: URL } type Profile { diff --git a/app/graphql/types/avis_type.rb b/app/graphql/types/avis_type.rb index 3901d37ad..d31217299 100644 --- a/app/graphql/types/avis_type.rb +++ b/app/graphql/types/avis_type.rb @@ -7,7 +7,7 @@ module Types field :date_question, GraphQL::Types::ISO8601DateTime, null: false, method: :created_at field :date_reponse, GraphQL::Types::ISO8601DateTime, null: true, method: :updated_at - field :attachment_url, Types::URL, null: true, extensions: [ + field :attachment, Types::File, null: true, extensions: [ { Extensions::Attachment => { attachment: :piece_justificative_file } } ] diff --git a/app/graphql/types/champs/piece_justificative_champ_type.rb b/app/graphql/types/champs/piece_justificative_champ_type.rb index 5062125e7..bf0b70571 100644 --- a/app/graphql/types/champs/piece_justificative_champ_type.rb +++ b/app/graphql/types/champs/piece_justificative_champ_type.rb @@ -3,7 +3,7 @@ module Types::Champs include Rails.application.routes.url_helpers implements Types::ChampType - field :url, Types::URL, null: true, extensions: [ + field :file, Types::File, null: true, extensions: [ { Extensions::Attachment => { attachment: :piece_justificative_file } } ] end diff --git a/app/graphql/types/dossier_type.rb b/app/graphql/types/dossier_type.rb index ac8961241..5cfe64b81 100644 --- a/app/graphql/types/dossier_type.rb +++ b/app/graphql/types/dossier_type.rb @@ -20,7 +20,7 @@ module Types field :archived, Boolean, null: false field :motivation, String, null: true - field :motivation_attachment_url, Types::URL, null: true, extensions: [ + field :motivation_attachment, Types::File, null: true, extensions: [ { Extensions::Attachment => { attachment: :justificatif_motivation } } ] diff --git a/app/graphql/types/file.rb b/app/graphql/types/file.rb new file mode 100644 index 000000000..58b79d083 --- /dev/null +++ b/app/graphql/types/file.rb @@ -0,0 +1,13 @@ +module Types + class File < Types::BaseObject + field :url, Types::URL, null: false + field :filename, String, null: false + field :byte_size, Int, null: false + field :checksum, String, null: false + field :content_type, String, null: false + + def url + Rails.application.routes.url_helpers.url_for(object) + end + end +end diff --git a/app/graphql/types/message_type.rb b/app/graphql/types/message_type.rb index 0c5ab3472..59f2332b9 100644 --- a/app/graphql/types/message_type.rb +++ b/app/graphql/types/message_type.rb @@ -4,7 +4,7 @@ module Types field :email, String, null: false field :body, String, null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false - field :attachment_url, Types::URL, null: true, extensions: [ + field :attachment, Types::File, null: true, extensions: [ { Extensions::Attachment => { attachment: :piece_jointe } } ] diff --git a/config/initializers/graphiql.rb b/config/initializers/graphiql.rb index 65a8eeb28..b67ca0c50 100644 --- a/config/initializers/graphiql.rb +++ b/config/initializers/graphiql.rb @@ -1,7 +1,7 @@ DEFAULT_QUERY = "# La documentation officielle de la spécification (Anglais) : https://graphql.org/ # Une introduction aux concepts et raisons d'être de GraphQL (Français) : https://blog.octo.com/graphql-et-pourquoi-faire/ # Le schema GraphQL de demarches-simplifiees.fr : https://demarches-simplifiees-graphql.netlify.com -# Le endpoint GraphQL de demarches-simplifiees.fr : https://demarches-simplifiees.fr/api/v2/graphql +# Le endpoint GraphQL de demarches-simplifiees.fr : https://www.demarches-simplifiees.fr/api/v2/graphql query getDemarche($demarcheNumber: Int!) { demarche(number: $demarcheNumber) { @@ -54,7 +54,9 @@ query getDemarche($demarcheNumber: Int!) { secondaryValue } ... on PieceJustificativeChamp { - url + file { + url + } } ... on CarteChamp { geoAreas { diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 0d7b6436e..dc5176980 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -10,7 +10,7 @@ describe API::V2::GraphqlController do :with_all_champs, :for_individual, procedure: procedure) - create(:commentaire, dossier: dossier, email: 'test@test.com') + create(:commentaire, :with_file, dossier: dossier, email: 'test@test.com') dossier end let(:dossier1) { create(:dossier, :en_construction, :for_individual, procedure: procedure, en_construction_at: 1.day.ago) } @@ -19,6 +19,31 @@ describe API::V2::GraphqlController do let(:dossiers) { [dossier2, dossier1, dossier] } let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) } + def compute_checksum_in_chunks(io) + Digest::MD5.new.tap do |checksum| + while (chunk = io.read(5.megabytes)) + checksum << chunk + end + + io.rewind + end.base64digest + end + + let(:file) { Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png') } + let(:blob_info) do + { + filename: file.original_filename, + byte_size: file.size, + checksum: compute_checksum_in_chunks(file), + content_type: file.content_type + } + end + let(:blob) do + blob = ActiveStorage::Blob.create_before_direct_upload!(blob_info) + blob.upload(file) + blob + end + before do instructeur.assign_to_procedure(procedure) end @@ -155,7 +180,9 @@ describe API::V2::GraphqlController do datePassageEnInstruction dateTraitement motivation - motivationAttachmentUrl + motivationAttachment { + url + } usager { id email @@ -176,7 +203,13 @@ describe API::V2::GraphqlController do messages { email body - attachmentUrl + attachment { + filename + checksum + byteSize + contentType + url + } } avis { expert { @@ -186,7 +219,10 @@ describe API::V2::GraphqlController do reponse dateQuestion dateReponse - attachmentUrl + attachment { + url + filename + } } champs { id @@ -209,7 +245,7 @@ describe API::V2::GraphqlController do datePassageEnInstruction: nil, dateTraitement: nil, motivation: nil, - motivationAttachmentUrl: nil, + motivationAttachment: nil, usager: { id: dossier.user.to_typed_id, email: dossier.user.email @@ -230,7 +266,13 @@ describe API::V2::GraphqlController do messages: dossier.commentaires.map do |commentaire| { body: commentaire.body, - attachmentUrl: nil, + attachment: { + filename: commentaire.piece_jointe.filename.to_s, + contentType: commentaire.piece_jointe.content_type, + checksum: commentaire.piece_jointe.checksum, + byteSize: commentaire.piece_jointe.byte_size, + url: Rails.application.routes.url_helpers.url_for(commentaire.piece_jointe) + }, email: commentaire.email } end, @@ -306,7 +348,8 @@ describe API::V2::GraphqlController do dossierEnvoyerMessage(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", - body: \"Bonjour\" + body: \"Bonjour\", + attachment: \"#{blob.signed_id}\" }) { message { body @@ -429,7 +472,8 @@ describe API::V2::GraphqlController do dossierClasserSansSuite(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", - motivation: \"Parce que\" + motivation: \"Parce que\", + justificatif: \"#{blob.signed_id}\" }) { dossier { id @@ -478,7 +522,8 @@ describe API::V2::GraphqlController do dossierRefuser(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", - motivation: \"Parce que\" + motivation: \"Parce que\", + justificatif: \"#{blob.signed_id}\" }) { dossier { id @@ -527,7 +572,8 @@ describe API::V2::GraphqlController do dossierAccepter(input: { dossierId: \"#{dossier.to_typed_id}\", instructeurId: \"#{instructeur.to_typed_id}\", - motivation: \"Parce que\" + motivation: \"Parce que\", + justificatif: \"#{blob.signed_id}\" }) { dossier { id @@ -607,10 +653,10 @@ describe API::V2::GraphqlController do "mutation { createDirectUpload(input: { dossierId: \"#{dossier.to_typed_id}\", - filename: \"hello.png\", - byteSize: 1234, - checksum: \"qwerty1234\", - contentType: \"image/png\" + filename: \"#{blob_info[:filename]}\", + byteSize: #{blob_info[:byte_size]}, + checksum: \"#{blob_info[:checksum]}\", + contentType: \"#{blob_info[:content_type]}\" }) { directUpload { url