diff --git a/.env.example b/.env.example index a7b8499..7229bf3 100644 --- a/.env.example +++ b/.env.example @@ -44,6 +44,9 @@ SENTRY_DSN=your_sentry_dsn_here WILDCARD_AIRTABLE_KEY=your_airtable_key_here WILDCARD_HOST=your_wildcard_host_here +# key for updating loops via airtable +LOOPS_AIRTABLE_PAT=your_airtable_key_here + # GitHub oauth used for github signin GITHUB_CLIENT_ID=your_github_client_id_here GITHUB_CLIENT_SECRET=your_github_client_secret_here diff --git a/Gemfile b/Gemfile index ced7155..5e95028 100644 --- a/Gemfile +++ b/Gemfile @@ -85,6 +85,8 @@ gem "ahoy_matey" gem "geocoder" gem "ahoy_captain", git: "https://github.com/johnmcdowall/ahoy_captain.git", branch: "fix_importmaps" +gem "norairrecord", "~> 0.3.0" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" diff --git a/Gemfile.lock b/Gemfile.lock index 7c9f48b..b11c315 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -176,6 +176,9 @@ GEM multipart-post (~> 2.0) faraday-net_http (3.4.0) net-http (>= 0.5.0) + faraday-net_http_persistent (2.3.0) + faraday (~> 2.5) + net-http-persistent (>= 4.0.4, < 5) ffi (1.17.1-aarch64-linux-gnu) ffi (1.17.1-aarch64-linux-musl) ffi (1.17.1-arm-linux-gnu) @@ -282,6 +285,8 @@ GEM multipart-post (2.4.1) net-http (0.6.0) uri + net-http-persistent (4.0.5) + connection_pool (~> 2.2) net-imap (0.5.6) date net-protocol @@ -311,6 +316,10 @@ GEM racc (~> 1.4) nokogiri (1.18.8-x86_64-linux-musl) racc (~> 1.4) + norairrecord (0.3.0) + faraday (>= 1.0, < 3.0) + faraday-net_http_persistent + net-http-persistent ostruct (0.6.1) pagy (9.3.4) paper_trail (16.0.0) @@ -546,6 +555,7 @@ DEPENDENCIES letter_opener letter_opener_web (~> 3.0) memory_profiler + norairrecord (~> 0.3.0) paper_trail pg propshaft diff --git a/app/jobs/update_airtable_user_data_job.rb b/app/jobs/update_airtable_user_data_job.rb new file mode 100644 index 0000000..4e767dc --- /dev/null +++ b/app/jobs/update_airtable_user_data_job.rb @@ -0,0 +1,32 @@ +class UpdateAirtableUserDataJob < ApplicationJob + queue_as :latency_5m + + Table = Norairrecord.table(ENV["LOOPS_AIRTABLE_PAT"], "app6VcLJoYFbDdGWK", "tblnzmotZ55MFBfV4") + + def perform + users_with_heartbeats.includes(:email_addresses).find_in_batches(batch_size: 100) do |batch| + records = [] + batch.each do |user| + first_heartbeat_time = user.heartbeats.with_valid_timestamps.order(time: :asc).limit(1).pluck(:time).first + first_direct_heartbeat_time = user.heartbeats.direct_entry.with_valid_timestamps.order(time: :asc).limit(1).pluck(:time).first + first_test_heartbeat_time = user.heartbeats.test_entry.with_valid_timestamps.order(time: :asc).limit(1).pluck(:time).first + next if first_heartbeat_time > Time.now.to_i + user.email_addresses.map do |email_address| + records << Table.new({ + email: email_address.email, + first_direct_heartbeat_time: first_direct_heartbeat_time ? Time.at(first_direct_heartbeat_time.to_i).iso8601 : nil, + first_test_heartbeat_time: first_test_heartbeat_time ? Time.at(first_test_heartbeat_time.to_i).iso8601 : nil, + first_heartbeat_time: Time.at(first_heartbeat_time.to_i).iso8601 # airtable expects milliseconds + }) + end + end + Table.batch_upsert(records, "email") + end + end + + private + + def users_with_heartbeats + User.where(id: Heartbeat.with_valid_timestamps.group(:user_id).pluck(:user_id)) + end +end diff --git a/config/initializers/good_job.rb b/config/initializers/good_job.rb index cf4e6c2..98d7cf3 100644 --- a/config/initializers/good_job.rb +++ b/config/initializers/good_job.rb @@ -53,6 +53,10 @@ Rails.application.configure do cron: "* * * * *", class: "CleanupExpiredEmailVerificationRequestsJob" }, + update_airtable_user_data: { + cron: "0 13 * * *", + class: "UpdateAirtableUserDataJob" + }, cache_active_user_graph_data_job: { cron: "*/10 * * * *", class: "Cache::ActiveUsersGraphDataJob",