-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move App Systemd Tasks to Active Jobs #552
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class ApplicationJob < ActiveJob::Base | ||
# Automatically retry jobs that encountered a deadlock | ||
# retry_on ActiveRecord::Deadlocked | ||
|
||
# Most jobs are safe to ignore if the underlying records are no longer available | ||
# discard_on ActiveJob::DeserializationError | ||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,86 @@ | ||||||
require 'logger' | ||||||
|
||||||
class PullEmailJob < ApplicationJob | ||||||
queue_as :default | ||||||
|
||||||
def perform(*args) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can have no arguments if you aren't using any. |
||||||
STDOUT.sync = true | ||||||
|
||||||
logger = Logger.new(STDOUT) | ||||||
logger.level = Logger::INFO | ||||||
Comment on lines
+7
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if we still want these |
||||||
|
||||||
Rails.application.credentials.fetch(:email) { raise 'Could not find `email` credentials!' } | ||||||
Rails.application.credentials.email.fetch(:email) { raise 'Could not find `email` in `email` credentials!' } | ||||||
Rails.application.credentials.email.fetch(:name) { raise 'Could not find `name` in `email` credentials!' } | ||||||
Rails.application.credentials.email.fetch(:port) { raise 'Could not find `port` in `email` credentials!' } | ||||||
Rails.application.credentials.email.fetch(:host) { raise 'Could not find `host` in `email` credentials!' } | ||||||
Rails.application.credentials.email.fetch(:ssl) { raise 'Could not find `ssl` in `email` credentials!' } | ||||||
# Cannot used nested hashes in credentials without [] in Rails 6 | ||||||
# https://blog.saeloun.com/2021/06/02/rails-access-nested-secrects-by-method-call/ | ||||||
if Rails.application.credentials.email[:oauth].nil? && Rails.application.credentials.email[:password].nil? | ||||||
raise 'Could not find `oauth` or `password` in `email` credentials!' | ||||||
elsif !Rails.application.credentials.email[:oauth].nil? && !Rails.application.credentials.email[:password].nil? | ||||||
raise 'Found both `oauth` and `password` in `email` credentials!' | ||||||
elsif !Rails.application.credentials.email[:oauth].nil? | ||||||
Rails.application.credentials.email[:oauth].fetch(:site) { raise 'Could not find `site` in `email.oauth` credentials!' } | ||||||
Rails.application.credentials.email[:oauth].fetch(:authorize_url) { raise 'Could not find `authorize_url` in `email.oauth` credentials!' } | ||||||
Rails.application.credentials.email[:oauth].fetch(:token_url) { raise 'Could not find `token_url` in `email.oauth` credentials!' } | ||||||
Rails.application.credentials.email[:oauth].fetch(:refresh_token) { raise 'Could not find `refresh_token` in `email.oauth` credentials!' } | ||||||
Rails.application.credentials.email[:oauth].fetch(:client_id) { raise 'Could not find `client_id` in `email.oauth` credentials!' } | ||||||
Rails.application.credentials.email[:oauth].fetch(:client_secret) { raise 'Could not find `client_secret` in `email.oauth` credentials!' } | ||||||
end | ||||||
config = Rails.application.credentials.email | ||||||
|
||||||
reconnectSleep = 1 | ||||||
|
||||||
Comment on lines
+34
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
logger.info("Logging in to mailbox #{config[:email]}") | ||||||
|
||||||
begin | ||||||
imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) | ||||||
imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server" | ||||||
if imap.auth_capable?("XOAUTH2") && !Rails.application.credentials.email[:oauth].nil? | ||||||
oauth_client = OAuth2::Client.new(config[:oauth][:client_id], config[:oauth][:client_secret], {site: config[:oauth][:site], authorize_url: config[:oauth][:authorize_url], token_url: config[:oauth][:token_url]}) | ||||||
access_token = OAuth2::AccessToken.from_hash(oauth_client, refresh_token: config[:oauth][:refresh_token]).refresh! | ||||||
imap.authenticate('XOAUTH2', config[:email], access_token.token) | ||||||
elsif imap.auth_capable?("PLAIN") | ||||||
imap.authenticate("PLAIN", config[:email], config[:password]) | ||||||
# Should not use deprecated LOGIN method | ||||||
# elsif !imap.capability?("LOGINDISABLED") | ||||||
# imap.login(config[:email], config[:password]) | ||||||
else | ||||||
raise "No acceptable authentication mechanisms" | ||||||
end | ||||||
rescue Net::IMAP::NoResponseError, SocketError, Faraday::ConnectionFailed => error | ||||||
logger.error("Could not authenticate for #{config[:email]}, error: #{error.message}") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we no longer anticipate rate-limits or connection drops, I changed this (and one below) to log an error. I figure that while it is an error, it is not likely to be one resulting from our app logic, but should it raise an error instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think yes it should raise an error so that the job is properly marked as failed. We can then set |
||||||
return | ||||||
end | ||||||
|
||||||
begin | ||||||
imap.select(config[:name]) | ||||||
|
||||||
while true | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want this |
||||||
logger.info("Pulling emails for #{config[:email]}") | ||||||
query = ["BEFORE", Net::IMAP.format_date(Time.now + 1.day)] | ||||||
latest = Email.order("timestamp DESC").first | ||||||
query = ["SINCE", Net::IMAP.format_date(latest.timestamp)] if latest | ||||||
|
||||||
ids = imap.search(query) | ||||||
imap.fetch(ids, "BODY.PEEK[]").each do |msg| | ||||||
mail = Mail.new(msg.attr["BODY[]"]) | ||||||
|
||||||
unless Email.where(message_id: mail.message_id).exists? | ||||||
begin | ||||||
unless Email.create_from_mail(mail) | ||||||
logger.error("Could not pull message #{mail.message_id}") | ||||||
end | ||||||
rescue Exception => e | ||||||
logger.error("Exception while loading message #{mail.message_id}: " + e.to_s + "\n" + e.backtrace.join("\n")) | ||||||
end | ||||||
end | ||||||
end | ||||||
end | ||||||
rescue Net::IMAP::Error, EOFError, Errno::ECONNRESET => e | ||||||
logger.error("Disconnected for mailbox #{config[:email]}") | ||||||
end | ||||||
end | ||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
class SendEventSlackNotificationsJob < ApplicationJob | ||
queue_as :default | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This job probably needs a A later PR could break this into two jobs: One to send notifications for each event and one to schedule a notification job for each event (so that it isn't restricted to on the hour). This would also need to keep track of events that have already been notified (and clear that record if the time changes to a later time). |
||
def perform(*args) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, unsure that we have to specify args if not used. |
||
Rails.application.routes.default_url_options = Rails.application.config.action_mailer.default_url_options | ||
|
||
STDOUT.sync = true | ||
|
||
logger = Logger.new(STDOUT) | ||
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if we want these still for STDOUT |
||
|
||
|
||
Rails.application.credentials.fetch(:slack) { raise 'Could not find `slack` credentials!' } | ||
Rails.application.credentials.slack.fetch(:token) { raise 'Could not find `token` in `slack` credentials!' } | ||
env_config = Rails.application.credentials.slack | ||
|
||
logger.info("Logging into Slack") | ||
Slack.configure do |config| | ||
config.token = env_config[:token] | ||
end | ||
client = Slack::Web::Client.new | ||
|
||
channel = | ||
if Rails.env.development? | ||
"#bot-testing" | ||
elsif Rails.env.staging? | ||
"#bot-testing" | ||
else | ||
"#events" | ||
end | ||
channel_social = | ||
if Rails.env.development? | ||
"#bot-testing" | ||
elsif Rails.env.staging? | ||
"#bot-testing" | ||
else | ||
"#social" | ||
end | ||
|
||
startdate = DateTime.now | ||
enddate = 1.hour.from_now | ||
|
||
calls = Eventdate.where(events: {textable: true, status: Event::Event_Status_Group_Not_Cancelled}).call_between(startdate, enddate).includes(:event).references(:event) | ||
strikes = Eventdate.where(events: {textable: true, status: Event::Event_Status_Group_Not_Cancelled}).strike_between(startdate, enddate).includes(:event).references(:event) | ||
|
||
def message_gen(msg, event_url, eventdate) | ||
[ | ||
msg, | ||
{ | ||
type: "section", | ||
text: { | ||
type: "mrkdwn", | ||
text: msg + "\n_" + eventdate.locations.join(", ") + "_" | ||
}, | ||
accessory: { | ||
type: "button", | ||
text: { | ||
type: "plain_text", | ||
text: "View on Tracker", | ||
emoji: true, | ||
}, | ||
url: event_url | ||
} | ||
} | ||
] | ||
end | ||
|
||
messages = [] | ||
messages_social = [] | ||
calls.each do |eventdate| | ||
event_url = Rails.application.routes.url_helpers.url_for(eventdate.event).to_s | ||
msg = "Call for <" + event_url + "|" + eventdate.event.title + "> - " + eventdate.description + " is at " + eventdate.effective_call.strftime("%H:%M") | ||
messages.push(message_gen(msg, event_url, eventdate)) | ||
messages_social.push(message_gen(msg, event_url, eventdate)) if eventdate.event.textable_social | ||
end | ||
strikes.each do |eventdate| | ||
event_url = Rails.application.routes.url_helpers.url_for(eventdate.event).to_s | ||
msg = "Strike for <" + event_url + "|" + eventdate.event.title + "> - " + eventdate.description + " is at " + eventdate.effective_strike.strftime("%H:%M") | ||
messages.push(message_gen(msg, event_url, eventdate)) | ||
messages_social.push(message_gen(msg, event_url, eventdate)) if eventdate.event.textable_social | ||
end | ||
|
||
messages_text = messages.map { |msg| msg[0] } | ||
messages_blocks = messages.map { |msg| msg[1] } | ||
messages_social_text = messages_social.map { |msg| msg[0] } | ||
messages_social_blocks = messages_social.map { |msg| msg[1] } | ||
|
||
unless messages.empty? | ||
message_text = messages_text.join("\n") | ||
|
||
logger.info("Sending message") | ||
client.chat_postMessage(channel: channel, text: message_text, as_user: true, blocks: messages_blocks) | ||
end | ||
|
||
unless messages_social.empty? | ||
message_text = messages_social_text.join("\n") | ||
|
||
logger.info("Sending social message") | ||
client.chat_postMessage(channel: channel_social, text: message_text, as_user: true, blocks: messages_blocks) | ||
end | ||
|
||
end | ||
end |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This job probably needs a
before_perform
to schedule the job occurring on the next 20 minute interval. Or, see other comment below we can have a separate job scheduling this job that incorporates the IMAP idle notifications.