diff --git a/app/javascript/styles/components/_notifications.scss b/app/javascript/styles/components/_notifications.scss index 9ab69bc3..07ce4e23 100644 --- a/app/javascript/styles/components/_notifications.scss +++ b/app/javascript/styles/components/_notifications.scss @@ -8,6 +8,7 @@ &__user, &__text { margin-bottom: 0; + white-space: normal; } &__icon { @@ -36,7 +37,7 @@ &-dropdown { min-width: 400px; - & .dropdown-item:hover, + & .dropdown-item:hover, & .dropdown-item:active { background: transparent; color: RGB(var(--body-text)); @@ -55,4 +56,4 @@ margin-bottom: 0; } } -} \ No newline at end of file +} diff --git a/app/models/notification/service_token_expired.rb b/app/models/notification/service_token_expired.rb new file mode 100644 index 00000000..bc61a820 --- /dev/null +++ b/app/models/notification/service_token_expired.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Notification::ServiceTokenExpired < Notification +end diff --git a/app/models/notification/twitter_token_expired.rb b/app/models/notification/twitter_token_expired.rb new file mode 100644 index 00000000..9ddb8447 --- /dev/null +++ b/app/models/notification/twitter_token_expired.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Notification::TwitterTokenExpired < Notification::ServiceTokenExpired +end diff --git a/app/models/user/expired_service_connection.rb b/app/models/user/expired_service_connection.rb new file mode 100644 index 00000000..110dd896 --- /dev/null +++ b/app/models/user/expired_service_connection.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# stub model for notifying about expired service connections +class User::ExpiredServiceConnection < User +end diff --git a/app/models/user/expired_twitter_service_connection.rb b/app/models/user/expired_twitter_service_connection.rb new file mode 100644 index 00000000..bb39250e --- /dev/null +++ b/app/models/user/expired_twitter_service_connection.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class User::ExpiredTwitterServiceConnection < User::ExpiredServiceConnection +end diff --git a/app/views/notifications/type/_expiredtwitterserviceconnection.html.haml b/app/views/notifications/type/_expiredtwitterserviceconnection.html.haml new file mode 100644 index 00000000..36014390 --- /dev/null +++ b/app/views/notifications/type/_expiredtwitterserviceconnection.html.haml @@ -0,0 +1,8 @@ +.media.notification + .notification__icon + %i.fa.fa-2x.fa-fw.fa-twitter + .media-body + %h6.media-heading.notification__user + = t(".heading") + .notification__text + = t(".text_html", settings_sharing: link_to(t(".settings_services"), services_path)) diff --git a/app/workers/share_worker.rb b/app/workers/share_worker.rb index 9f4ebd15..2709c074 100644 --- a/app/workers/share_worker.rb +++ b/app/workers/share_worker.rb @@ -9,8 +9,7 @@ class ShareWorker # @param answer_id [Integer] the user id # @param service [String] the service to post to def perform(user_id, answer_id, service) - service_type = "Services::#{service.camelize}" - user_service = User.find(user_id).services.find_by(type: service_type) + user_service = find_service(user_id, service) user_service.post(Answer.find(answer_id)) rescue ActiveRecord::RecordNotFound @@ -23,11 +22,21 @@ class ShareWorker logger.info "Tried to post answer ##{answer_id} from user ##{user_id} to Twitter but the account is suspended." rescue Twitter::Error::Unauthorized # User's Twitter token has expired or been revoked - # TODO: Notify user if this happens (https://github.com/Retrospring/retrospring/issues/123) logger.info "Tried to post answer ##{answer_id} from user ##{user_id} to Twitter but the token has exired or been revoked." + user_service = find_service(user_id, service) + user_service.destroy + + Object.const_get("Notification::ServiceTokenExpired").create( + target_id: user_id, + target_type: "User::Expired#{service.camelize}ServiceConnection", + recipient_id: user_id, + new: true + ) rescue => e logger.info "failed to post answer #{answer_id} to #{service} for user #{user_id}: #{e.message}" Sentry.capture_exception(e) raise end + + def find_service(user_id, service) = User.find(user_id).services.find_by(type: "Services::#{service.camelize}") end diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index ad5532c3..7d427b81 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -352,6 +352,10 @@ en: link_text: "your comment" follow: heading_html: "followed you %{time} ago" + expiredtwitterserviceconnection: + heading: "Twitter connection expired" + text_html: "If you would like to continue automatically sharing your answers to Twitter, head to %{settings_sharing} and re-connect your account." + settings_services: "Sharing Settings" services: index: title: "Service Settings" diff --git a/spec/workers/share_worker_spec.rb b/spec/workers/share_worker_spec.rb index e9c6d23e..b98337e3 100644 --- a/spec/workers/share_worker_spec.rb +++ b/spec/workers/share_worker_spec.rb @@ -59,6 +59,18 @@ describe ShareWorker do expect(Sidekiq.logger).to have_received(:info).with("Tried to post answer ##{answer.id} from user ##{user.id} to Twitter but the token has exired or been revoked.") end + it "revokes the service connection when Twitter::Error::Unauthorized is raised" do + allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Unauthorized) + subject + expect { ShareWorker.drain }.to change { Services::Twitter.count }.by(-1) + end + + it "sends the user a notification when Twitter::Error::Unauthorized is raised" do + allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Unauthorized) + subject + expect { ShareWorker.drain }.to change { Notification::ServiceTokenExpired.count }.by(1) + end + it "handles Twitter::Error::Forbidden" do allow_any_instance_of(Services::Twitter).to receive(:post).with(answer).and_raise(Twitter::Error::Forbidden) subject