diff --git a/.env.example b/.env.example index 7d94cf13..cfbaebc4 100644 --- a/.env.example +++ b/.env.example @@ -57,3 +57,9 @@ RESEED_API_KEY=changeme # Enable immediate onboarding for schools ENABLE_IMMEDIATE_SCHOOL_ONBOARDING=true + +# Salesforce Connect +SALESFORCE_ENABLED=true +SALESFORCE_CONNECT_HOST=salesforce_connect +SALESFORCE_CONNECT_PASSWORD=password +SALESFORCE_CONNECT_USER=postgres diff --git a/app/jobs/salesforce/salesforce_sync_job.rb b/app/jobs/salesforce/salesforce_sync_job.rb new file mode 100644 index 00000000..acf3d6cc --- /dev/null +++ b/app/jobs/salesforce/salesforce_sync_job.rb @@ -0,0 +1,33 @@ +# # frozen_string_literal: true + +module Salesforce + class SalesforceSyncJob < ApplicationJob + include GoodJob::ActiveJobExtensions::Concurrency + + good_job_control_concurrency_with( + perform_throttle: [2, 1.second] + ) + + SalesforceRecordNotFound = Class.new(StandardError) + SkipBecauseSalesforceIsDisabled = Class.new(StandardError) + + include ActionView::Helpers::SanitizeHelper + + queue_as :salesforce_sync + + discard_on SkipBecauseSalesforceIsDisabled + + before_perform do |_job| + unless ENV.fetch('SALESFORCE_ENABLED', 'true') == 'true' + raise SkipBecauseSalesforceIsDisabled, 'SALESFORCE_ENABLED is not true.' + end + end + + def perform(*) + raise NotImplementedError, 'Subclasses must implement perform' + end + + # TODO Consider implementing private utilities here, e.g. truncate_value + end +end + diff --git a/app/jobs/salesforce/school_sync_job.rb b/app/jobs/salesforce/school_sync_job.rb new file mode 100644 index 00000000..9326b780 --- /dev/null +++ b/app/jobs/salesforce/school_sync_job.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Salesforce + class SchoolSyncJob < SalesforceSyncJob + MODEL_CLASS = Salesforce::School + + FIELD_MAPPINGS = {}.freeze + + STATUS_MAPPINGS = {}.freeze + + + def perform(school_id:) + @school = School.find(id: school_id) + sf_school = Salesforce::School.find_or_initialize_by(school_id__c: school_id) + + # Make the sf_school match @school. + + sf_school.save! + end + end +end diff --git a/app/models/salesforce/base.rb b/app/models/salesforce/base.rb new file mode 100644 index 00000000..4229edba --- /dev/null +++ b/app/models/salesforce/base.rb @@ -0,0 +1,9 @@ +# # frozen_string_literal: true + +module Salesforce + class Base < ApplicationRecord + self.abstract_class = true + + connects_to database: { writing: :salesforce_connect } + end +end diff --git a/app/models/salesforce/school.rb b/app/models/salesforce/school.rb new file mode 100644 index 00000000..db63167f --- /dev/null +++ b/app/models/salesforce/school.rb @@ -0,0 +1,9 @@ +# # frozen_string_literal: true + +module Salesforce + class School < Salesforce::Base + self.table_name = 'salesforce.school__c' # TODO Confirm this - placeholder + self.primary_key = :school_id__c + + end +end diff --git a/app/models/school.rb b/app/models/school.rb index 24531d48..db0363fd 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -51,6 +51,9 @@ class School < ApplicationRecord # TODO: Remove the conditional once the feature flag is retired after_create :generate_code!, if: -> { FeatureFlags.immediate_school_onboarding? } + + # After creation, sync the School to Salesforce. + after_commit :do_salesforce_sync, on: [:create, :update] def self.find_for_user!(user) school = Role.find_by(user_id: user.id)&.school || find_by(creator_id: user.id) @@ -169,4 +172,8 @@ def format_uk_postal_code # ensures UK postcodes are always formatted correctly (as the inward code is always 3 chars long) self.postal_code = "#{cleaned_postal_code[0..-4]} #{cleaned_postal_code[-3..]}" end + + def do_salesforce_sync + Salesforce::SchoolSyncJob.perform_later(school_id: id) + end end diff --git a/config/database.yml b/config/database.yml index c4ddd41f..3884d4b8 100644 --- a/config/database.yml +++ b/config/database.yml @@ -7,14 +7,35 @@ default: &default password: <%= ENV.fetch('POSTGRES_PASSWORD', '') %> pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %> +salesforce_connect: &salesforce_connect + adapter: postgresql + encoding: unicode + host: <%= ENV.fetch('SALESFORCE_CONNECT_HOST', 'localhost') %> + username: <%= ENV.fetch('SALESFORCE_CONNECT_USER', '') %> + password: <%= ENV.fetch('SALESFORCE_CONNECT_PASSWORD', '') %> + pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %> + database_tasks: false + development: - <<: *default - database: <%= ENV.fetch('POSTGRES_DB', 'choco_cake_development') %> + default: + <<: *default + database: <%= ENV.fetch('POSTGRES_DB', 'choco_cake_development') %> + salesforce_connect: + <<: *salesforce_connect + database: <%= ENV.fetch('SALESFORCE_CONNECT_DB', 'salesforce_development') %> test: - <<: *default - database: <%= ENV.fetch('POSTGRES_DB', 'choco_cake_test') %> + default: + <<: *default + database: <%= ENV.fetch('POSTGRES_DB', 'choco_cake_test') %> + salesforce_connect: + <<: *salesforce_connect + database: <%= ENV.fetch('SALESFORCE_CONNECT_DB', 'salesforce_development') %> production: - <<: *default - url: <%= ENV['DATABASE_URL'] %> + default: + <<: *default + url: <%= ENV['DATABASE_URL'] %> + salesforce_connect: + <<: *salesforce_connect + url: <%= ENV.fetch('SALESFORCE_CONNECT_URL', "") %> diff --git a/config/initializers/good_job.rb b/config/initializers/good_job.rb index 600f199e..38399b54 100644 --- a/config/initializers/good_job.rb +++ b/config/initializers/good_job.rb @@ -18,5 +18,5 @@ def authenticate_admin # The create_students_job queue is a serial queue that allows only one job at a time. # DO NOT change the value of create_students_job:1 without understanding the implications # of processing more than one user creation job at once. - config.good_job.queues = 'create_students_job:1;import_schools_job:1;default:5' + config.good_job.queues = 'create_students_job:1;import_schools_job:1;salesforce_sync:1,default:5' end diff --git a/docker-compose.yml b/docker-compose.yml index 96d32661..0bc0aded 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,29 @@ services: platform: linux/amd64 command: -u $SMEE_TUNNEL -t http://api:3009/github_webhooks + salesforce_connect: + image: ghcr.io/raspberrypifoundation/heroku-connect + volumes: + - salesforce_connect_data:/var/lib/postgres/data/ + environment: + - POSTGRES_DB=salesforce_development + - POSTGRES_CLONE_DB=salesforce_test + - POSTGRES_PASSWORD=password + - POSTGRES_USER=postgres + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER} -d $${POSTGRES_DB}", + ] + interval: 5s + timeout: 5s + retries: 10 + ports: + - "4101:5432" + volumes: postgres-data: bundle-data: node_modules: + salesforce_connect_data: