Add featured hashtags to profiles (#9755)
* Add hashtag filter to profiles GET /@:username/tagged/:hashtag GET /api/v1/accounts/:id/statuses?tagged=:hashtag * Display featured hashtags on public profile * Use separate model for featured tags * Update featured hashtag counters on-write * Limit featured tags to 10gh/stable
parent
d14c276e58
commit
364f2ff9aa
|
@ -57,6 +57,7 @@ class AccountsController < ApplicationController
|
||||||
|
|
||||||
def filtered_statuses
|
def filtered_statuses
|
||||||
default_statuses.tap do |statuses|
|
default_statuses.tap do |statuses|
|
||||||
|
statuses.merge!(hashtag_scope) if tag_requested?
|
||||||
statuses.merge!(only_media_scope) if media_requested?
|
statuses.merge!(only_media_scope) if media_requested?
|
||||||
statuses.merge!(no_replies_scope) unless replies_requested?
|
statuses.merge!(no_replies_scope) unless replies_requested?
|
||||||
end
|
end
|
||||||
|
@ -78,12 +79,15 @@ class AccountsController < ApplicationController
|
||||||
Status.without_replies
|
Status.without_replies
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hashtag_scope
|
||||||
|
Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id)
|
||||||
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find_local!(params[:username])
|
@account = Account.find_local!(params[:username])
|
||||||
end
|
end
|
||||||
|
|
||||||
def older_url
|
def older_url
|
||||||
::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
|
|
||||||
pagination_url(max_id: @statuses.last.id)
|
pagination_url(max_id: @statuses.last.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -92,7 +96,9 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def pagination_url(max_id: nil, min_id: nil)
|
def pagination_url(max_id: nil, min_id: nil)
|
||||||
if media_requested?
|
if tag_requested?
|
||||||
|
short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
|
||||||
|
elsif media_requested?
|
||||||
short_account_media_url(@account, max_id: max_id, min_id: min_id)
|
short_account_media_url(@account, max_id: max_id, min_id: min_id)
|
||||||
elsif replies_requested?
|
elsif replies_requested?
|
||||||
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
|
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
|
||||||
|
@ -109,6 +115,10 @@ class AccountsController < ApplicationController
|
||||||
request.path.ends_with?('/with_replies')
|
request.path.ends_with?('/with_replies')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tag_requested?
|
||||||
|
request.path.ends_with?("/tagged/#{params[:tag]}")
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_status_page(params)
|
def filtered_status_page(params)
|
||||||
if params[:min_id].present?
|
if params[:min_id].present?
|
||||||
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
|
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
|
||||||
|
|
|
@ -33,6 +33,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||||
|
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||||
|
|
||||||
statuses
|
statuses
|
||||||
end
|
end
|
||||||
|
@ -67,6 +68,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
Status.without_reblogs
|
Status.without_reblogs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hashtag_scope
|
||||||
|
Status.tagged_with(Tag.find_by(name: params[:tagged])&.id)
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Settings::FeaturedTagsController < Settings::BaseController
|
||||||
|
layout 'admin'
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :set_featured_tags, only: :index
|
||||||
|
before_action :set_featured_tag, except: [:index, :create]
|
||||||
|
before_action :set_most_used_tags, only: :index
|
||||||
|
|
||||||
|
def index
|
||||||
|
@featured_tag = FeaturedTag.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@featured_tag = current_account.featured_tags.new(featured_tag_params)
|
||||||
|
@featured_tag.reset_data
|
||||||
|
|
||||||
|
if @featured_tag.save
|
||||||
|
redirect_to settings_featured_tags_path
|
||||||
|
else
|
||||||
|
set_featured_tags
|
||||||
|
set_most_used_tags
|
||||||
|
|
||||||
|
render :index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@featured_tag.destroy!
|
||||||
|
redirect_to settings_featured_tags_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_featured_tag
|
||||||
|
@featured_tag = current_account.featured_tags.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_featured_tags
|
||||||
|
@featured_tags = current_account.featured_tags.reject(&:new_record?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_most_used_tags
|
||||||
|
@most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
def featured_tag_params
|
||||||
|
params.require(:featured_tag).permit(:name)
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,6 +32,6 @@ class Settings::ProfilesController < Settings::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = current_user.account
|
@account = current_account
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Settings::SessionsController < Settings::BaseController
|
class Settings::SessionsController < Settings::BaseController
|
||||||
|
before_action :authenticate_user!
|
||||||
before_action :set_session, only: :destroy
|
before_action :set_session, only: :destroy
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|
|
@ -288,3 +288,7 @@
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.directory__tag .trends__item__current {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -153,10 +153,15 @@ $content-width: 840px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.directory__tag a {
|
.directory__tag > a,
|
||||||
|
.directory__tag > div {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.directory__tag .table-action-link .fa {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.directory__tag h4 {
|
.directory__tag h4 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
|
@ -269,7 +269,8 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
a {
|
& > a,
|
||||||
|
& > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -279,7 +280,9 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a {
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
|
@ -287,7 +290,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active a {
|
&.active > a {
|
||||||
background: $ui-highlight-color;
|
background: $ui-highlight-color;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,5 +55,6 @@ module AccountAssociations
|
||||||
|
|
||||||
# Hashtags
|
# Hashtags
|
||||||
has_and_belongs_to_many :tags
|
has_and_belongs_to_many :tags
|
||||||
|
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: featured_tags
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8)
|
||||||
|
# tag_id :bigint(8)
|
||||||
|
# statuses_count :bigint(8) default(0), not null
|
||||||
|
# last_status_at :datetime
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class FeaturedTag < ApplicationRecord
|
||||||
|
belongs_to :account, inverse_of: :featured_tags, required: true
|
||||||
|
belongs_to :tag, inverse_of: :featured_tags, required: true
|
||||||
|
|
||||||
|
delegate :name, to: :tag, allow_nil: true
|
||||||
|
|
||||||
|
validates :name, presence: true
|
||||||
|
validate :validate_featured_tags_limit, on: :create
|
||||||
|
|
||||||
|
def name=(str)
|
||||||
|
self.tag = Tag.find_or_initialize_by(name: str.delete('#').mb_chars.downcase.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment(timestamp)
|
||||||
|
update(statuses_count: statuses_count + 1, last_status_at: timestamp)
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrement(deleted_status_id)
|
||||||
|
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_data
|
||||||
|
self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count
|
||||||
|
self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_featured_tags_limit
|
||||||
|
errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= 10
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,6 +14,7 @@ class Tag < ApplicationRecord
|
||||||
has_and_belongs_to_many :accounts
|
has_and_belongs_to_many :accounts
|
||||||
has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account'
|
has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account'
|
||||||
|
|
||||||
|
has_many :featured_tags, dependent: :destroy, inverse_of: :tag
|
||||||
has_one :account_tag_stat, dependent: :destroy
|
has_one :account_tag_stat, dependent: :destroy
|
||||||
|
|
||||||
HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*'
|
HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*'
|
||||||
|
@ -23,6 +24,7 @@ class Tag < ApplicationRecord
|
||||||
|
|
||||||
scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(Arel.sql('account_tag_stats.accounts_count desc')) }
|
scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(Arel.sql('account_tag_stats.accounts_count desc')) }
|
||||||
scope :hidden, -> { where(account_tag_stats: { hidden: true }) }
|
scope :hidden, -> { where(account_tag_stats: { hidden: true }) }
|
||||||
|
scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
|
||||||
|
|
||||||
delegate :accounts_count,
|
delegate :accounts_count,
|
||||||
:accounts_count=,
|
:accounts_count=,
|
||||||
|
|
|
@ -3,11 +3,21 @@
|
||||||
class ProcessHashtagsService < BaseService
|
class ProcessHashtagsService < BaseService
|
||||||
def call(status, tags = [])
|
def call(status, tags = [])
|
||||||
tags = Extractor.extract_hashtags(status.text) if status.local?
|
tags = Extractor.extract_hashtags(status.text) if status.local?
|
||||||
|
records = []
|
||||||
|
|
||||||
tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
|
tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
|
||||||
tag = Tag.where(name: name).first_or_create(name: name)
|
tag = Tag.where(name: name).first_or_create(name: name)
|
||||||
|
|
||||||
status.tags << tag
|
status.tags << tag
|
||||||
|
records << tag
|
||||||
|
|
||||||
TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility?
|
TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return unless status.public_visibility? || status.unlisted_visibility?
|
||||||
|
|
||||||
|
status.account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag|
|
||||||
|
featured_tag.increment(status.created_at)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -131,6 +131,10 @@ class RemoveStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_hashtags
|
def remove_from_hashtags
|
||||||
|
@account.featured_tags.where(tag_id: @status.tags.pluck(:id)).each do |featured_tag|
|
||||||
|
featured_tag.decrement(@status.id)
|
||||||
|
end
|
||||||
|
|
||||||
return unless @status.public_visibility?
|
return unless @status.public_visibility?
|
||||||
|
|
||||||
@tags.each do |hashtag|
|
@tags.each do |hashtag|
|
||||||
|
|
|
@ -63,4 +63,17 @@
|
||||||
- @endorsed_accounts.each do |account|
|
- @endorsed_accounts.each do |account|
|
||||||
= account_link_to account
|
= account_link_to account
|
||||||
|
|
||||||
|
- @account.featured_tags.each do |featured_tag|
|
||||||
|
.directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
|
||||||
|
= link_to short_account_tag_path(@account, featured_tag.tag) do
|
||||||
|
%h4
|
||||||
|
= fa_icon 'hashtag'
|
||||||
|
= featured_tag.name
|
||||||
|
%small
|
||||||
|
- if featured_tag.last_status_at.nil?
|
||||||
|
= t('accounts.nothing_here')
|
||||||
|
- else
|
||||||
|
%time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
|
||||||
|
.trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true
|
||||||
|
|
||||||
= render 'application/sidebar'
|
= render 'application/sidebar'
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= t('settings.featured_tags')
|
||||||
|
|
||||||
|
= simple_form_for @featured_tag, url: settings_featured_tags_path do |f|
|
||||||
|
= render 'shared/error_messages', object: @featured_tag
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :name, wrapper: :with_block_label, hint: safe_join([t('simple_form.hints.featured_tag.name'), safe_join(@most_used_tags.map { |tag| link_to("##{tag.name}", settings_featured_tags_path(featured_tag: { name: tag.name }), method: :post) }, ', ')], ' ')
|
||||||
|
|
||||||
|
.actions
|
||||||
|
= f.button :button, t('featured_tags.add_new'), type: :submit
|
||||||
|
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
|
- @featured_tags.each do |featured_tag|
|
||||||
|
.directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
|
||||||
|
%div
|
||||||
|
%h4
|
||||||
|
= fa_icon 'hashtag'
|
||||||
|
= featured_tag.name
|
||||||
|
%small
|
||||||
|
- if featured_tag.last_status_at.nil?
|
||||||
|
= t('accounts.nothing_here')
|
||||||
|
- else
|
||||||
|
%time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
|
||||||
|
= table_link_to 'trash', t('filters.index.delete'), settings_featured_tag_path(featured_tag), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||||
|
.trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true
|
|
@ -588,6 +588,10 @@ en:
|
||||||
lists: Lists
|
lists: Lists
|
||||||
mutes: You mute
|
mutes: You mute
|
||||||
storage: Media storage
|
storage: Media storage
|
||||||
|
featured_tags:
|
||||||
|
add_new: Add new
|
||||||
|
errors:
|
||||||
|
limit: You have already featured the maximum amount of hashtags
|
||||||
filters:
|
filters:
|
||||||
contexts:
|
contexts:
|
||||||
home: Home timeline
|
home: Home timeline
|
||||||
|
@ -807,6 +811,7 @@ en:
|
||||||
development: Development
|
development: Development
|
||||||
edit_profile: Edit profile
|
edit_profile: Edit profile
|
||||||
export: Data export
|
export: Data export
|
||||||
|
featured_tags: Featured hashtags
|
||||||
followers: Authorized followers
|
followers: Authorized followers
|
||||||
import: Import
|
import: Import
|
||||||
migrate: Account migration
|
migrate: Account migration
|
||||||
|
|
|
@ -37,6 +37,8 @@ en:
|
||||||
setting_theme: Affects how Mastodon looks when you're logged in from any device.
|
setting_theme: Affects how Mastodon looks when you're logged in from any device.
|
||||||
username: Your username will be unique on %{domain}
|
username: Your username will be unique on %{domain}
|
||||||
whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
|
whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
|
||||||
|
featured_tag:
|
||||||
|
name: 'You might want to use one of these:'
|
||||||
imports:
|
imports:
|
||||||
data: CSV file exported from another Mastodon instance
|
data: CSV file exported from another Mastodon instance
|
||||||
sessions:
|
sessions:
|
||||||
|
@ -110,6 +112,8 @@ en:
|
||||||
username: Username
|
username: Username
|
||||||
username_or_email: Username or Email
|
username_or_email: Username or Email
|
||||||
whole_word: Whole word
|
whole_word: Whole word
|
||||||
|
featured_tag:
|
||||||
|
name: Hashtag
|
||||||
interactions:
|
interactions:
|
||||||
must_be_follower: Block notifications from non-followers
|
must_be_follower: Block notifications from non-followers
|
||||||
must_be_following: Block notifications from people you don't follow
|
must_be_following: Block notifications from people you don't follow
|
||||||
|
|
|
@ -6,6 +6,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
|
|
||||||
primary.item :settings, safe_join([fa_icon('cog fw'), t('settings.settings')]), settings_profile_url do |settings|
|
primary.item :settings, safe_join([fa_icon('cog fw'), t('settings.settings')]), settings_profile_url do |settings|
|
||||||
settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url, highlights_on: %r{/settings/profile|/settings/migration}
|
settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url, highlights_on: %r{/settings/profile|/settings/migration}
|
||||||
|
settings.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_url
|
||||||
settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url
|
settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url
|
||||||
settings.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_notifications_url
|
settings.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_notifications_url
|
||||||
settings.item :password, safe_join([fa_icon('lock fw'), t('auth.security')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete}
|
settings.item :password, safe_join([fa_icon('lock fw'), t('auth.security')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete}
|
||||||
|
|
|
@ -74,6 +74,7 @@ Rails.application.routes.draw do
|
||||||
get '/@:username', to: 'accounts#show', as: :short_account
|
get '/@:username', to: 'accounts#show', as: :short_account
|
||||||
get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
|
get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
|
||||||
get '/@:username/media', to: 'accounts#show', as: :short_account_media
|
get '/@:username/media', to: 'accounts#show', as: :short_account_media
|
||||||
|
get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag
|
||||||
get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
|
get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
|
||||||
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
|
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
|
||||||
|
|
||||||
|
@ -116,6 +117,7 @@ Rails.application.routes.draw do
|
||||||
resource :migration, only: [:show, :update]
|
resource :migration, only: [:show, :update]
|
||||||
|
|
||||||
resources :sessions, only: [:destroy]
|
resources :sessions, only: [:destroy]
|
||||||
|
resources :featured_tags, only: [:index, :create, :destroy]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :media, only: [:show] do
|
resources :media, only: [:show] do
|
||||||
|
|
|
@ -7,6 +7,7 @@ class CreateAccountModerationNotes < ActiveRecord::Migration[5.1]
|
||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|
||||||
add_foreign_key :account_moderation_notes, :accounts, column: :target_account_id
|
add_foreign_key :account_moderation_notes, :accounts, column: :target_account_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateFeaturedTags < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :featured_tags do |t|
|
||||||
|
t.references :account, foreign_key: { on_delete: :cascade }
|
||||||
|
t.references :tag, foreign_key: { on_delete: :cascade }
|
||||||
|
t.bigint :statuses_count, default: 0, null: false
|
||||||
|
t.datetime :last_status_at
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
db/schema.rb
15
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2019_02_01_012802) do
|
ActiveRecord::Schema.define(version: 2019_02_03_180359) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -250,6 +250,17 @@ ActiveRecord::Schema.define(version: 2019_02_01_012802) do
|
||||||
t.index ["status_id"], name: "index_favourites_on_status_id"
|
t.index ["status_id"], name: "index_favourites_on_status_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "featured_tags", force: :cascade do |t|
|
||||||
|
t.bigint "account_id"
|
||||||
|
t.bigint "tag_id"
|
||||||
|
t.bigint "statuses_count", default: 0, null: false
|
||||||
|
t.datetime "last_status_at"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_featured_tags_on_account_id"
|
||||||
|
t.index ["tag_id"], name: "index_featured_tags_on_tag_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "follow_requests", force: :cascade do |t|
|
create_table "follow_requests", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
@ -708,6 +719,8 @@ ActiveRecord::Schema.define(version: 2019_02_01_012802) do
|
||||||
add_foreign_key "custom_filters", "accounts", on_delete: :cascade
|
add_foreign_key "custom_filters", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade
|
add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade
|
||||||
add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade
|
add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade
|
||||||
|
add_foreign_key "featured_tags", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "featured_tags", "tags", on_delete: :cascade
|
||||||
add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade
|
add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade
|
||||||
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
|
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
|
||||||
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
|
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
Fabricator(:featured_tag) do
|
||||||
|
account
|
||||||
|
tag
|
||||||
|
statuses_count 1_337
|
||||||
|
last_status_at Time.now.utc
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe FeaturedTag, type: :model do
|
||||||
|
end
|
Reference in New Issue