diff --git a/app/controllers/api/v3/channels_controller.rb b/app/controllers/api/v3/channels_controller.rb index b11791336..79dc16b01 100644 --- a/app/controllers/api/v3/channels_controller.rb +++ b/app/controllers/api/v3/channels_controller.rb @@ -25,9 +25,12 @@ def subscribe render json: @user end + # Trigger user rebuild when unsubscribing from channel + # this is to clear out unwanted content from FY Feed def unsubscribe @mammoth = Mammoth::Channels.new @user = @mammoth.unsubscribe(channel_id_param, acct_param) + UpdateForYouWorker.perform_async({ acct: acct_param, rebuild: true }) render json: @user end diff --git a/app/controllers/api/v3/timelines/statuses_controller.rb b/app/controllers/api/v3/timelines/statuses_controller.rb new file mode 100644 index 000000000..51c631efc --- /dev/null +++ b/app/controllers/api/v3/timelines/statuses_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Api::V3::Timelines::StatusesController < Api::BaseController + before_action :require_mammoth! + + rescue_from Mammoth::StatusOrigin::NotFound do |e| + render json: { error: e.to_s }, status: 404 + end + + def show + origin = Mammoth::StatusOrigin.instance + @origins = origin.find(status_id_param) + render json: @origins, each_serializer: StatusOriginSerializer + end + + private + + def status_id_param + params.require(:id) + end +end diff --git a/app/lib/mammoth/channels.rb b/app/lib/mammoth/channels.rb index b2e7ec21e..2457f3c87 100644 --- a/app/lib/mammoth/channels.rb +++ b/app/lib/mammoth/channels.rb @@ -13,7 +13,7 @@ class NotFound < StandardError; end def channels_with_statuses list(include_accounts: true).each do |channel| account_ids = account_ids(channel[:accounts]) - channel[:statuses] = statuses_from_channel_accounts(account_ids) + channel[:statuses] = statuses_from_channels(account_ids) end end @@ -21,17 +21,24 @@ def channels_with_statuses # Get Statuses from array of channels # filter out based on per channel threshold def select_channels_with_statuses(channels) + origin = Mammoth::StatusOrigin.instance channels.flat_map do |channel| account_ids = account_ids(channel[:accounts]) - statuses_from_channel_accounts(account_ids).filter_map { |s| engagment_threshold(s, channel[:fy_engagement_threshold]) } + statuses_with_accounts_from_channels(account_ids).filter_map { |s| engagment_threshold(s, channel[:fy_engagement_threshold]) } + .each { |s| origin.add_channel(s, channel) } end end - def statuses_from_channel_accounts(account_ids) + def statuses_from_channels(account_ids) Status.where(account_id: account_ids, created_at: (GO_BACK.hours.ago)..Time.current) end + def statuses_with_accounts_from_channels(account_ids) + Status.includes([:account]).where(account_id: account_ids, + created_at: (GO_BACK.hours.ago)..Time.current) + end + # Check status for Channel's set level of engagment # Filter out polls and replys def engagment_threshold(wrapped_status, channel_engagment_setting) diff --git a/app/lib/mammoth/status_origin.rb b/app/lib/mammoth/status_origin.rb new file mode 100644 index 000000000..59e4f3aad --- /dev/null +++ b/app/lib/mammoth/status_origin.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +# rubocop:disable all +require 'singleton' + +module Mammoth + + class StatusOrigin + include Singleton + include Redisable + class NotFound < StandardError; end + + + # Add Status and Reason to list + def add_channel(status, channel) + list_key = key(status[:id]) + reason = channel_reason(status, channel) + + add_reason(list_key, reason) + end + + def add_mammoth_pick(status) + list_key = key(status[:id]) + reason = mammoth_pick_reason(status) + + add_reason(list_key, reason) + end + + # Add reason by key id + # Expire Reason in 7 days + def add_reason(key, reason) + redis.sadd(key, reason) + redis.expire(key, 7.day.seconds) + end + + def find(status_id) + list_key = key(status_id) + results = redis.smembers(list_key).map { |o| + payload = Oj.load(o, symbol_keys: true) + originating_account = Account.create(payload[:originating_account]) + # StatusOrigin Active Model for serialization + ::StatusOrigin.new(source: payload[:source], channel_id: payload[:channel_id], title: payload[:title], originating_account:originating_account ) + } + # Throw Error if array find is empty + raise NotFound, 'status not found' unless results.length > 0 + return results + end + + private + + # Redis key of a status + # @param [Integer] status id + # @param [Symbol] subtype + # @return [String] + def key(id, subtype = nil) + return "origin:for_you:#{id}" unless subtype + "origin:for_you:#{id}:#{subtype}" + end + + def channel_reason(status, channel) + Oj.dump({source: "SmartList", channel_id: channel[:id], title: channel[:title], originating_account: status.account}) + end + + def mammoth_pick_reason(status) + Oj.dump({source: "MammothPick", originating_account: status.account }) + end + end +end diff --git a/app/models/status_origin.rb b/app/models/status_origin.rb new file mode 100644 index 000000000..0b811bbf3 --- /dev/null +++ b/app/models/status_origin.rb @@ -0,0 +1,11 @@ +# ActiveModel Only for Serialization +class StatusOrigin + include ActiveModel::Model + include ActiveModel::Serialization + + attr_accessor :source, :channel_id, :title, :originating_account + + def initialize(attributes = {}) + super + end +end diff --git a/app/serializers/status_origin_serializer.rb b/app/serializers/status_origin_serializer.rb new file mode 100644 index 000000000..499b266c5 --- /dev/null +++ b/app/serializers/status_origin_serializer.rb @@ -0,0 +1,7 @@ +# Required Source & Originating Account +# Channel_id & Title maybe be null +class StatusOriginSerializer < ActiveModel::Serializer + attributes :source, :title, :channel_id + + belongs_to :originating_account, serializer: REST::AccountSerializer +end diff --git a/app/workers/update_for_you_worker.rb b/app/workers/update_for_you_worker.rb index e64b7ed98..26730fcbe 100644 --- a/app/workers/update_for_you_worker.rb +++ b/app/workers/update_for_you_worker.rb @@ -94,9 +94,7 @@ def push_channels_status user_setting = @user[:for_you_settings] return if user_setting[:from_your_channels].zero? - @personal.statuses_for_enabled_channels(@user) - .filter_map { |s| engagment_threshold(s, user_setting[:from_your_channels], 'channel') } - .each { |s| ForYouFeedWorker.perform_async(s['id'], @account.id, 'personal') } + @personal.statuses_for_enabled_channels(@user).each { |s| ForYouFeedWorker.perform_async(s['id'], @account.id, 'personal') } end # Mammoth Curated OG List @@ -106,9 +104,13 @@ def push_mammoth_curated_status curated_list = Mammoth::CuratedList.new list_statuses = curated_list.curated_list_statuses + origin = Mammoth::StatusOrigin.instance list_statuses.filter_map { |s| engagment_threshold(s, user_setting[:curated_by_mammoth], 'mammoth') } - .each { |s| ForYouFeedWorker.perform_async(s['id'], @account.id, 'personal') } + .each do |s| + origin.add_mammoth_pick(s) + ForYouFeedWorker.perform_async(s['id'], @account.id, 'personal') + end end # Check status for User's level of engagment diff --git a/config/routes.rb b/config/routes.rb index 0a88dd33f..e37be7e3e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -725,6 +725,7 @@ namespace :timelines do resources :channels, only: :show, controller: :channels resource :for_you, only: [:show], controller: 'for_you' do + resources :statuses, only: :show, controller: :statuses get '/me', to: 'for_you#index' put '/me', to: 'for_you#update' end diff --git a/spec/fabricators/status_origin_fabricator.rb b/spec/fabricators/status_origin_fabricator.rb new file mode 100644 index 000000000..37d22f742 --- /dev/null +++ b/spec/fabricators/status_origin_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:status_origin) do + source "MyString" + title "MyString" +end \ No newline at end of file diff --git a/spec/models/status_origin_spec.rb b/spec/models/status_origin_spec.rb new file mode 100644 index 000000000..25257d3f4 --- /dev/null +++ b/spec/models/status_origin_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe StatusOrigin, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end