hummingbird-me/kitsu-server

View on GitHub
app/models/feed/notification_presenter.rb

Summary

Maintainability
A
0 mins
Test Coverage
F
32%
# frozen_string_literal: true

class Feed
  # Encapsulates the generation of text from a Stream notification
  class NotificationPresenter
    # The base URL used when constructing links to notifications
    CLIENT_URL = 'https://kitsu.io/'

    attr_reader :activity, :user

    delegate :group, to: :activity
    delegate :id, to: :activity

    # @param activity [Feed::Activity] the activity object to generate a notification for
    # @param user [User] the user from whose perspective we are viewing this notification
    def initialize(activity, user)
      @activity = activity
      @user = user
    end

    # @return [Hash] the reference to the notification for the mobile app
    def reference
      case subject
      when Post, Comment, GroupInvite then reference_for(subject)
      when PostLike, CommentLike then reference_for(subject)
      when Follow then reference_for(subject)
      when Episode, Chapter then reference_for(actor)
      end
    end

    # @return [String] the full URL for the notification in the web app
    def url
      "#{CLIENT_URL}#{path}?notification=#{activity.id}"
    end

    # @return [String] the human-readable textual representation of the notification
    def message
      actor_name = actor.name if actor.respond_to?(:name)

      case verb
      when :aired
        translation_key = "aired.#{actor.class.name.underscore}"
        translate(translation_key, number: subject.number, title: actor.canonical_title)
      when :follow, :post_like, :comment_like, :invited
        translate(verb, actor: actor_name)
      when :post
        translate('mention.post', actor: actor_name)
      when :mention
        translate('mention.comment', actor: actor_name)
      when :reply
        author_name = target&.user&.name
        translate("reply.#{reply_type.join('.')}", actor: actor_name, author: author_name)
      end
    end

    # @return [Symbol] the verb of the activity
    def verb
      case activity.verb
      when 'comment'
        if activity.mentioned_users.include?(user.id)
          :mention
        else
          :reply
        end
      else
        activity.verb.to_sym
      end
    end

    # @return [Symbol] the setting that applies to this notification
    def setting_type
      case verb.to_s
      when /_like\z/ then :likes
      when 'invited' then :invites
      when 'comment' then :replies
      when 'media_reaction_vote' then :reaction_votes
      when 'aired' then :airing
      else verb.to_s.pluralize.to_sym
      end
    end

    # @param user [User] the user to get the setting for
    # @return [NotificationSetting] the user's setting for this notifications
    def setting
      setting = NotificationSetting.setting_types[setting_type]
      NotificationSetting.where(user_id: @user, setting_type: setting).first
    end

    private

    # @return [String] the path to view the notification in the web app
    def path
      path_for(reference)
    end

    # @param obj [Hash] the reference to generate a path for
    # @return [String] the path to view this in the web app
    def path_for(ref)
      "#{ref[:type]}/#{ref[:id]}"
    end

    # @param obj [ActiveRecord::Base] the object to generate a reference for
    # @return [Hash] the reference to this object
    def reference_for(obj)
      type = obj.class.name.underscore.pluralize.dasherize
      id = obj.id
      { type:, id: }
    end

    # For a reply, figure out what *kind* of reply it is.  This code sucks, but we don't have a
    # better solution right now.
    #
    # @return [Array<Symbol>] the path of the translation
    def reply_type
      reply_to_user = load_ref(activity.reply_to_user)
      if target.user.id == user.id then %i[post you] # X replied to your post
      elsif reply_to_user.id == user.id then %i[comment you] # X replied to your comment
      elsif target.user.id == actor.id then %i[post themself] # X replied to their own post
      elsif target.is_a?(Post) then %i[post author] # X replied to Y's post
      else
        %i[post unknown]
      end
    end

    # Split a Stream-style reference string
    # @return [Array<String,Integer>] the type and id from the reference
    def split_ref(ref)
      type, id = ref.split(':')
      [type, id.to_i]
    end

    # @return [ActiveRecord::Base] the record this reference names
    def load_ref(ref)
      type, id = split_ref(ref)
      type.safe_constantize&.find_by(id:)
    end

    # @return [ActiveRecord::Base] the target of the activity
    def target
      @target ||= load_ref(activity.target)
    end

    # @return [ActiveRecord::Base] the subject of the activity
    def subject
      @subject ||= load_ref(activity.object)
    end

    # @return [User] the actor of the activity
    def actor
      @actor ||= load_ref(activity.actor)
    end

    # @return [String] the translated string
    def translate(*args, **kwargs)
      kwargs[:scope] ||= %i[notifications]
      I18n.t(*args, **kwargs)
    end
  end
end