Skip to content

Commit

Permalink
Accepte un webhook datapass pour créér les resources HubEE (#176)
Browse files Browse the repository at this point in the history
* Ajoute une route pour setup une collectivité depuis un webhook datapass

L'organizer SetupCollectivity va créer les resources nécessaires sur
HubEE (organisation et abonnement) puis créer l'entrée de la
collectivité sur FQF.

Une doc à été ajoutée dans docs/scripts.md pour créer des communes de
test en local ou en sandbox.

* Update spec/organizers/datapass_webhook/setup_collectivity_spec.rb

Co-authored-by: Delmaire Loïc <[email protected]>

---------

Co-authored-by: Delmaire Loïc <[email protected]>
  • Loading branch information
jbfeldis and skelz0r authored Jan 23, 2025
1 parent 960c698 commit f00a2fb
Show file tree
Hide file tree
Showing 29 changed files with 742 additions and 6 deletions.
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ gem "factory_bot_rails"
gem "active_model_serializers", github: "rails-api/active_model_serializers", branch: "0-10-stable"
gem "activerecord-session_store"

gem "faraday"
gem "faraday-gzip"
gem "faraday-net_http"
gem "faraday-retry"
gem "faraday-encoding"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[mri windows]
Expand Down
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,17 @@ GEM
faraday (2.10.0)
faraday-net_http (>= 2.0, < 3.2)
logger
faraday-encoding (0.0.6)
faraday
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-gzip (3.0.2)
faraday (>= 2.0, < 3)
zlib (~> 3.0)
faraday-net_http (3.1.0)
net-http
faraday-retry (2.2.1)
faraday (~> 2.0)
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
formatador (1.1.0)
Expand Down Expand Up @@ -542,6 +549,7 @@ GEM
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.1)
zlib (3.2.0)

PLATFORMS
arm64-darwin-23
Expand All @@ -559,6 +567,11 @@ DEPENDENCIES
database_cleaner-active_record
debug
factory_bot_rails
faraday
faraday-encoding
faraday-gzip
faraday-net_http
faraday-retry
good_job (~> 3.99)
guard
guard-cucumber
Expand Down
57 changes: 57 additions & 0 deletions app/controllers/api/datapass_webhooks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
class Api::DatapassWebhooksController < ActionController::API
before_action :verify_hub_signature!, only: :create

def create
if event == "approve"
SetupCollectivityJob.perform_later(datapass_id:, collectivity_siret:, collectivity_email:, service_provider:)
end
end

private

def collectivity_email
webhook_params.dig("data", "applicant", "email")
end

def collectivity_siret
webhook_params.dig("data", "organization", "siret")
end

def datapass_id
webhook_params["model_id"]
end

def event
webhook_params["event"]
end

def hub_signature
request.headers["X-Hub-Signature-256"]
end

def hub_signature_valid?
HubSignature.new(hub_signature, request.raw_post).valid?
end

def service_provider
webhook_params.dig("data", "service_provider")
end

def unauthorized
render json: {error: "Unauthorized"}, status: :unauthorized
end

def webhook_params
@webhook_params ||= params.permit(
:event,
:model_type,
:model_id,
:fired_at,
data: {}
).to_h
end

def verify_hub_signature!
unauthorized unless hub_signature_valid?
end
end
25 changes: 25 additions & 0 deletions app/interactors/datapass_webhook/create_collectivity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class DatapassWebhook::CreateCollectivity < BaseInteractor
delegate :organization, to: :context

def call
context.collectivity = find_or_create_collectivity
end

private

def editor_id
Hash(context.service_provider).fetch("id", nil)
end

def find_or_create_collectivity
collectivity = Collectivity.find_or_initialize_by(siret: organization.siret)

collectivity.update!(
name: organization.denomination,
code_cog: organization.code_commune_etablissement,
departement: organization.code_postal_etablissement[0..1],
status: :active,
editor: editor_id
)
end
end
15 changes: 15 additions & 0 deletions app/interactors/datapass_webhook/create_hubee_organization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class DatapassWebhook::CreateHubEEOrganization < BaseInteractor
def call
context.hubee_organization_payload = find_or_create_organization_on_hubee
end

private

def find_or_create_organization_on_hubee
hubee_api_client.find_or_create_organization(context.organization, context.collectivity_email)
end

def hubee_api_client
@hubee_api_client ||= HubEE::AdminApi.new
end
end
42 changes: 42 additions & 0 deletions app/interactors/datapass_webhook/create_hubee_subscription.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class DatapassWebhook::CreateHubEESubscription < BaseInteractor
delegate :collectivity_email, :datapass_id, :hubee_organization_payload, :service_provider, to: :context

def call
context.hubee_subscription_payload = create_subscription_on_hubee
end

private

def create_subscription_on_hubee
hubee_api_client.create_subscription(datapass_id:, collectivity_email:, organization_payload: hubee_organization_payload, process_code:, editor_payload:)
end

def editor_organization
@editor_organization ||= Organization.new(service_provider["siret"])
end

def editor_payload
return {} unless editor_subscription?

{
delegationActor: {
branchCode: editor_organization.code_commune_etablissement,
companyRegister: editor_organization.siret,
type: "EDT",
},
accessMode: "API",
}
end

def editor_subscription?
service_provider["type"] == "editor"
end

def hubee_api_client
@hubee_api_client ||= HubEE::AdminApi.new
end

def process_code
"FormulaireQF"
end
end
6 changes: 6 additions & 0 deletions app/jobs/setup_collectivity_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class SetupCollectivityJob < ApplicationJob
def perform(datapass_id:, collectivity_siret:, collectivity_email:, service_provider:)
organization = Organization.new(collectivity_siret)
DatapassWebhook::SetupCollectivity.call(datapass_id:, organization:, collectivity_email:, service_provider:)
end
end
37 changes: 37 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Organization
attr_reader :siret

def initialize(siret)
@siret = siret
end

def denomination
unite_legale_insee_payload["denominationUniteLegale"]
end

def code_commune_etablissement
adresse_etablissement_insee_payload["codeCommuneEtablissement"]
end

def code_postal_etablissement
adresse_etablissement_insee_payload["codePostalEtablissement"]
end

private

def adresse_etablissement_insee_payload
etablissement_insee_payload["adresseEtablissement"]
end

def etablissement_insee_payload
insee_payload["etablissement"]
end

def unite_legale_insee_payload
etablissement_insee_payload["uniteLegale"]
end

def insee_payload
@insee_payload ||= INSEESirene::Api.new.etablissement(siret:)
end
end
24 changes: 24 additions & 0 deletions app/organizers/datapass_webhook/setup_collectivity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class DatapassWebhook::SetupCollectivity < BaseOrganizer
organize DatapassWebhook::CreateHubEEOrganization,
DatapassWebhook::CreateHubEESubscription,
DatapassWebhook::CreateCollectivity

around do |interactor|
interactor.call
rescue => e
if Rails.env.local?
raise e
else
Sentry.set_context(
"DataPass webhook: create HubEE resources",
payload: {
organization: context.organization.siret,
collectivity_email: context.collectivity_email,
service_provider: context.service_provider,
}
)

Sentry.capture_exception(e)
end
end
end
2 changes: 1 addition & 1 deletion config/credentials.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2mAuNgte6oJSy+gYfmqaPXiNLTenG/E+boDez2wGBuUaT5hDKSkZ56GcfQ+Ox2pG9xO5CEre/ZdDN0ygb/p9l/spcnM38Ewm3qeBCXYgs8r0Gor4E2tfsJBRo+1R5OsbNjPhHm6uKy1Qc/SYGb2oZIDKjYqsE1iMgqUtL8+RzohzGfb5eWQSJxh1zRwfnFhJXAH0lpwk+Yf/Z4EK0cPma6KTqEwZP29tc/T2NkAA8B5GzPx/xozRd7Jwp+/IYAkMstJbepZifa/F2cqZHXa41P+LaFx/UL/RSvDiBywFpac0/t4hC7TGaY1Cidw9LXXdQ4MT74Un/wEdPjCFIsLrHkTcfI+OSpywgBQSYJXb1arHJAui4SVu/j6dvir8/pPbP4jSPW8YymqcZoqSKFA4kYEni12KSgW3z23lbY7fHvnr7l9IfTqLsGoyBrhQBZhenXrlX7nG/pjJioJAcjNBQACB7b48h3UQCdKuXrensvdyFfjsSBkJwoUInR0CvKBOgLSb1cEW1HG2maIpzoTMftwpsVxdCXuPCRj2Irt2+eRpwfwWkqLO2l1o0/hijWNp7uJgcH7fdF5TYxalB5KJqDo7WmVNyE/R5xzS6OMK40kBnfsyZ9h5vwfVsdi14zTyv4oPdGAIrKxCnbOjdZOghLz6CqltBHecQ1DVp6ZeMgAn09wNIRFpO3zhPe84vk092eodH/Lrsb5Sc8L2pm4JdJLRTVtVUS2bdfX3gJvXww8ncrP5LYWkGeRi3XXVrColdIXoqW1JUFbU1F7Y4NGabMD9QEPCw3I+2mGYWNzbPKu+p01thxEEnD+QwfxR/z208EBxCrytYAftOkMANHyKZ+qLqS3ktyyk3i8/svMECvz4i9NkFNYCuwshxs3k4JQ6gX0AE5rQWtaAJt/5/RnubXyLUsJ71ruFx99YSU8P/EXz2v1m9orfem1RptHCq+5xlGCAFmiE13DiA4i8hzUH/oZ90BtKVwEubRlIAIGrCqt+h7UeJuOQ7hJj53Ey7zQso4fXJ+tTYeo3Y8TeguIxp1QqWVJgi/9swz7pdQ93Q+5ekO/0kk2OEdrm31hE5Z4SzM08pUCCq3C93QlA8AQf33oVSEykQHdYxqSvFQB8FWH6LveiCZEdRPbk+hTYiOqUm5Q6CfjiMPzKYXrWXykuTQDemdVFjbp4YVpt5rl+ICy9UjNUUG+vNkuYKkBUOplStcF4b78y+w47zCWNVi5EQ1jizMKvdDza69gYm4ksbGpnOSSJIAGAZH8OIbKMJqyrbobetRhbSZY2+wBwj8jc2MjQSCj3XzE6eFZbPqKDwz8lcpw84PAy3fYMByonH4AFHkTAVjeO5Cs00OY5Sco6RddCiY5jTyhlP4hCkvziwFwNaRtE/vJTCn9KCuyIjHYic++mEO89owMpB/02xkjifRwM4hhf3QpfDS6FYcKwJKB/LfL0Mvj4x1fZA9fNAIMvLCwhNY2hYYVjIXu9sQTjdlBZDW8DU9QauRnMeDJqctbbzIfZgSSD27VBJ/FSSQb6lzSv744/1IF6yGqu2/09wPeSdMgNAz36tkeHwbM3NZKDZA58hyrJcCX+WsOa--9hUvhkDG/m62mhN/--ESmze2CGI5lwomznqY6wwQ==
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--+O1lTkK2C88CovZ2--MyWfiwCGlQr9Qpr1BioxWQ==
2 changes: 1 addition & 1 deletion config/credentials/development.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pDTtrAtVyuEe74DSOoHr1YMXva20+JPSVLtIo3TSTu8jxS4+8QD58bd6YjOWbSwQVz46/Esj+hW4VXBpeXAUH/SDjLMDPrlk+GScXnp3LDklW0tl+400hy7CNhhzcfe4o9z/hVn7fAK6h4ihlkQ8EYGX5dhckmaBhq2/Y/KHa5bLicnaw3B3mPjanhwgaxv4raJrh3XKUbz61QDORjw/745Q+lvbxauzaua47A2Ep3nw2AMD1BUbbKdmHmjjyiJO1/cocUNwRi8I+d0FJWsFl2IoQ3xElEpnkxq7+qjblYb8lR+j5wcS60DbR9WJHbUd6+1uh7+8uZRrZ1nX6AWT8Mx0WTuaqMXoUSMLzBghjvfrFU/I5FXLBZ2joMXOKe8llJlFk1eS/XFKAK51S/NzoRkOpNFTszwpE9TeBc8DRDRcCpH22EXDf5Ic9q/UB9ig2VnzLuq1ZZ/6XlDpEekLYqHanfMAHv6cIVh5VsgxN+5Rw1lCxLI7BqiVQwr0vJftgalRBcU5DyjnqOeQplgceTPf3Z6MZ3UjDI+cTda6R17tc6woPBWrHgUIy97/JiOAGl0YtS746q8je8G5h62qm7JHvmD4i82d1x89NFqRD3heZlQ+4fRck2VUE8u8zRyP96mu7ImvEDkwGL95ZaZFk/sGwZgViW8IE5Cg/Q+APxnjClJcvzZF2Oe/ChQZJo14KXUwsPnS/yYPVlXK8yPs+MIjGJmmoEzUhJKbmtXwtxzgycuxd0g/HCLlDwD7APZzh9bixZeCT/PiiXEKrBxtInxIPPGnq3EG+AZwVYHyf0Td103BZ+KpSmoxTzzSSgzwcdPkspWPJTFfEBCD3FdBJeJeehmqfNS85tcQjyXNzYHmDuQppD/ofB5rgUe2BABofMP6FdfYo/fIBsyN39TXr+aLhjaPj87CgEqt10w4rAdjKq7FLnRQqkVsS4T5cTv+bIpigaj+qcnhVWO9bQeiFk+/dtK86R9gvW80cYV6Jt68uWIJGJSlTEdvh/Zim1DkwnEccVeTbK8lkxSI/W/aBols9T6/fPKeNbL97gQMkbODrcA6XzB1hHTP+0SuKBkoau8FdL9zUwmWjMJhdObsuzfEaY2nEMWZ6qRYOnjV9WhlecJLQfAQwXrLNN+3DUZRh0ML1MN+0ySnn+88TG9jW/mjHX5FevJCfqiGIMRPNXGP0/JVtkv6vHrbncIf/G/MQAVfScgby9CvSp8GbNjImsJFjFS72p9SdFQf+hsJv1umF6MD16rQvX5ma+gpJTQ2KDo7OO9plVF772+Uj2UYbKMzhHkx+fur/8WdHxsihMAkPbpZfP17RB7EUBfbfBJQmhVp/Jk/48GYBZvb71XdccafsHapvUQkzNeaR9DYvfeUTcFINyLJnRIxzVEzazD9jdebn5OkIsYMek4HbbEKaMg5rcJlS1CZfPYM/B3ZkU7oPWmA3yjYdzM4Lw1amQ/Y5BqymuHKhO4FzqekLpQ68RJopcAxTHP0d6hDUQye3yWybBZZF739LnIUmMyMXi8pBV1St7Z4CMJt1MkKA3kdTptmv+5qvPKwpPQWswbB+MzZ5UMDRo/rnfIFn6rniIkiHTK3PkzBvZSWt6ypAg4KQvdcYASXUcwyywiOtjoBM3ZliyhgrB3pccPbx9+PNWPvT1ZZSpLL5al768QGcTGx8Sy9wQ5LZOJZbID2gNsAO8HJRlLR+RAyNqNVi0b+vhNAs2LCoczLeJTx/mxC+veDdeKzEhVZf3pf0zF1dsf6G248MmdXL7ft9xVLJqWpczUgUpspgo8K5K/doBOC38mzPHiUAPc4yqqBZTqejAJ445nnlWKRYfkwoKRg1TAqIq54XLBfqLP+lROaiVGWNKOzOGwoyDlBUpAEppcsndBiY2ddnMJK0tTWQA+i8m9sehrJqAuPla9YXgVgG0V5sQeVZTWhy7MwUpQOYBVEu9SytVVz2hl7IjJK9r744umUt78boKjsu8xx7OHSWZadbwO1XTfWjvktCNApE9JzJCaFnUoHdSovciogy/MFpx6p2p45fQXQXw5nY8g3rlbPZfYhnC5kAhgCWaJUvTTMUIMGJeNt8vFduPVANSJH3BcOtybf/T0kuDx6N/WdstBOgmKJj5lJER/IFBTVT6AfNdE2EcOMHOzkrv0vewo+a+/3Abkvjgb0X4JmCw3Y6bGIQ5LtcRU7H/5NwCgFB+HCrxsv1bhHYwM8dw+zagQt4BYtOSqJmpi/fowV1JlwxCkulrUhLa+F3cj52FDNJJUeWq8uI26RY8JxLNRpsFSPJrQ+nMRh8anJRPLUGsoQY8MfEt81ZrGyTyIdduwUlwuY5djqpPXIkNJCja5E5y8GtaPW/1Zd3PEuWqZPsssneX9ebAwL7r9qK5klYz1MYGSQKe5UJOfm/6jRtDiXBx6jQmDWA17kSBL/2X3Y7N6TqPGIa5N69RryqgFR0Nwsy+x8Sl99Pc79WY8YtVbemkJZWHGSUqQ4/m2miVRjsBCbm7vyB6pv+aHVYHRd2KsVqvdJGXO2/MEZDIgGPZ8OgxmM1DXG+WHLxL+TKYpeza8IzwzW5Uj5ARHjy74Yw6T0mkyEK2P6iAiAAAe4tZoAxlBHVQt/poHVpYZQGgKr0Sj8hg435tEKsKsymN454eX8dGiZLWoHfbz8LOBQykL8At63/CA1rglEjXIuKC+0BFK3V4MUV8+Ct31MbfQQgKtJdoDF/vm/mGBAWSunoprDDQn2n1qPsIn5mBjtQ+ziO5M/OOWoMklLTetP1BBBj3+Cn4SA2NAYHyRRiEN+BpgwiIeE8a/7cF4GT+LdzMW75EBDqhDrgAWrk/7lefXwTaNlUVPVU+l7xAovPPw7vU9utRDShs6Zjc7yXm9zi1H5J/oHBOd0tmkIvYx+C41hhY1i+AGtetH+ZAn4jZQg+WZGEZdp6kqDWQyJohuk89xF9Iq8iV8oLejsRhnpjyKUPTSSOm5T3H4Nxs36YSltClymnoEI8dVIK9varTqgIHvDgwpS0vWjMvYsARSRBcRRQZOqgX1su8OaDs/GQEtY8wUGtiBzASKdpuqkuZHp+DOM9CLdg4BTID+2oFY0PNucNgmcZcMfkFWFPVdnrbUxens5dzJm4TINQdWHs01kVmhUVx9fNAkyBifZ2X/Jwik0En9ztyczBTiF9I3Vp6szHMO6Cs+7S1Z1Up/zIkhGINQsifx3u09f1c15SYh3TyodKme1QbdxR9FQ1+N5N151Q7Ux05U436BDACZb7W4aacGbwK/XZIqYYfcUco1/N6j2VI08ojBQpHohxNEkhJ6ZqG0SXT4Bvu+R2Tf1afLBVRWO/SYC2W7Hl39A122BiBomJsr7Vb7da0Oi9wleAFwcQ/A6oLajForWVT9lK06ck2TfeTuWG9tV9+4wJhFq9oSK+svTvPPAxGepPAWwlKoD9lAAYmWtR1c7Fk4e8TxANNES/Km+/r4/gAlOUuUk/bKWcaceeDNh1xaM+gdL3Me78+vCAgc4jRLMTrA589F5ypp3fjb+WuEbaExoksF5RUR3INyIN9wTUBC0XCn0zXrKbRCbrNZV1Lb2LNYJJ3XkfsxeN7NpyYC6rmoI7AzMSUbsJ41iIBBGg4wfvsAk5vIFje/FgZPe8LyK826bVsqBUS/oxrcKGt8hcvmnZ+wLsx+RSbs0jLXQ27horWd9qISkpxfnnLeFtphUmeoAeRgTIK+yfFL6OTXI2Hfv7zhZDzOoXzoTioQ6PY2N/9TikBe6Et9fcavSW+PxHceB8xwPthmxHiqyfbCACsEGlNMPME+Z8UduRoZ+9LepUblfwXTaP2xC7t4PqAXx6eRlsc58vW4AarbKIATpX5ls47Sg/OyVHRTZQNK3ZC11ymHOze0fEmq7BktgYccMCf3WIqjRk3r++PswktKqZ7lu--8qCW2ehjT6VTrADj--8N4dNDFGbxOVuiCcl0JV4Q==
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--P4xvAGgHv0SOc+DT--HQoFM/B4kciQecbG2AWshA==
Loading

0 comments on commit f00a2fb

Please sign in to comment.