[deploy] Rubocop: fix violations of Layout/LineLength (#9197)
This commit is contained in:
parent
9f9236164c
commit
5b62811c98
211 changed files with 1572 additions and 589 deletions
|
|
@ -43,7 +43,7 @@ checks:
|
|||
plugins:
|
||||
rubocop:
|
||||
enabled: true
|
||||
channel: rubocop-0-71
|
||||
channel: rubocop-0-86
|
||||
brakeman:
|
||||
enabled: true
|
||||
eslint:
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ Layout/EmptyLinesAroundAttributeAccessor:
|
|||
# Enabled: false
|
||||
# StyleGuide: '#heredoc-argument-closing-parentheses'
|
||||
|
||||
Layout/LineLength:
|
||||
Description: 'Checks that line length does not exceed the configured limit.'
|
||||
AutoCorrect: true # this is false by default
|
||||
Exclude:
|
||||
- docs/Gemfile
|
||||
- Gemfile
|
||||
|
||||
# Layout/MultilineArrayLineBreaks:
|
||||
# Description: >-
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ require:
|
|||
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2020-07-06 08:14:17 UTC using RuboCop version 0.86.0.
|
||||
# on 2020-07-08 07:58:23 UTC using RuboCop version 0.86.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 279
|
||||
# Offense count: 278
|
||||
# Configuration parameters: IgnoredMethods.
|
||||
Metrics/AbcSize:
|
||||
Max: 62
|
||||
|
|
@ -37,14 +37,16 @@ Performance/OpenStruct:
|
|||
- 'spec/models/github_repo_spec.rb'
|
||||
- 'spec/requests/github_repos_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Offense count: 4
|
||||
# Configuration parameters: Max.
|
||||
RSpec/ExampleLength:
|
||||
Exclude:
|
||||
- 'spec/initializers/rack/attack_spec.rb'
|
||||
- 'spec/liquid_tags/user_subscription_tag_spec.rb'
|
||||
- 'spec/models/comment_spec.rb'
|
||||
- 'spec/requests/display_ad_events_spec.rb'
|
||||
|
||||
# Offense count: 920
|
||||
# Offense count: 930
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 12
|
||||
|
||||
|
|
@ -78,13 +80,12 @@ Rails/OutputSafety:
|
|||
- 'app/models/display_ad.rb'
|
||||
- 'app/models/message.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# Offense count: 4
|
||||
# Configuration parameters: Include.
|
||||
# Include: app/models/**/*.rb
|
||||
Rails/UniqueValidationWithoutIndex:
|
||||
Exclude:
|
||||
- 'app/models/article.rb'
|
||||
- 'app/models/follow.rb'
|
||||
|
||||
# Offense count: 38
|
||||
# Cop supports --auto-correct.
|
||||
|
|
@ -99,10 +100,3 @@ Style/ClassAndModuleChildren:
|
|||
Style/SingleLineBlockParams:
|
||||
Exclude:
|
||||
- 'app/labor/markdown_fixer.rb'
|
||||
|
||||
# Offense count: 555
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Max: 308
|
||||
|
|
|
|||
|
|
@ -15,9 +15,16 @@ class BlackBox
|
|||
if article.decorate.cached_tag_list_array.include?("watercooler")
|
||||
reaction_points = (reaction_points * 0.8).to_i # watercooler posts shouldn't get as much love in feed
|
||||
end
|
||||
function_caller.call("blackbox-production-articleHotness",
|
||||
{ article: article, user: article.user }.to_json).to_i +
|
||||
reaction_points + recency_bonus + super_recent_bonus + super_super_recent_bonus + today_bonus + two_day_bonus + four_day_bonus
|
||||
|
||||
article_hotness = function_caller.call(
|
||||
"blackbox-production-articleHotness",
|
||||
{ article: article, user: article.user }.to_json,
|
||||
).to_i
|
||||
|
||||
(
|
||||
article_hotness + reaction_points + recency_bonus + super_recent_bonus +
|
||||
super_super_recent_bonus + today_bonus + two_day_bonus + four_day_bonus
|
||||
)
|
||||
end
|
||||
|
||||
def comment_quality_score(comment)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ module Api
|
|||
|
||||
def validate_date_params
|
||||
raise ArgumentError, "Required 'start' parameter is missing" if analytics_params[:start].blank?
|
||||
raise ArgumentError, "Date parameters 'start' or 'end' must be in the format of 'yyyy-mm-dd'" unless valid_date_params?
|
||||
|
||||
message = "Date parameters 'start' or 'end' must be in the format of 'yyyy-mm-dd'"
|
||||
raise ArgumentError, message unless valid_date_params?
|
||||
end
|
||||
|
||||
def analytics_params
|
||||
|
|
|
|||
|
|
@ -117,8 +117,10 @@ module Api
|
|||
|
||||
def allowed_to_change_org_id?
|
||||
potential_user = @article&.user || @user
|
||||
if @article.nil? || OrganizationMembership.exists?(user: potential_user, organization_id: params.dig("article", "organization_id"))
|
||||
OrganizationMembership.exists?(user: potential_user, organization_id: params.dig("article", "organization_id"))
|
||||
if @article.nil? || OrganizationMembership.exists?(user: potential_user,
|
||||
organization_id: params.dig("article", "organization_id"))
|
||||
OrganizationMembership.exists?(user: potential_user,
|
||||
organization_id: params.dig("article", "organization_id"))
|
||||
elsif potential_user == @user
|
||||
potential_user.org_admin?(params.dig("article", "organization_id")) ||
|
||||
@user.any_admin?
|
||||
|
|
|
|||
|
|
@ -267,8 +267,12 @@ class ArticlesController < ApplicationController
|
|||
if updated && @article.published && @article.saved_changes["published"] == [false, true]
|
||||
Notification.send_to_followers(@article, "Published")
|
||||
elsif @article.saved_changes["published"] == [true, false]
|
||||
Notification.remove_all_by_action_without_delay(notifiable_ids: @article.id, notifiable_type: "Article", action: "Published")
|
||||
Notification.remove_all(notifiable_ids: @article.comments.pluck(:id), notifiable_type: "Comment") if @article.comments.exists?
|
||||
Notification.remove_all_by_action_without_delay(notifiable_ids: @article.id, notifiable_type: "Article",
|
||||
action: "Published")
|
||||
if @article.comments.exists?
|
||||
Notification.remove_all(notifiable_ids: @article.comments.pluck(:id),
|
||||
notifiable_type: "Comment")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ class AsyncInfoController < ApplicationController
|
|||
|
||||
def shell_version
|
||||
set_surrogate_key_header "shell-version-endpoint"
|
||||
# shell_version will change on every deploy. *Technically* could be only on changes to assets and shell, but this is more fool-proof.
|
||||
# shell_version will change on every deploy.
|
||||
# *Technically* could be only on changes to assets and shell, but this is more fool-proof.
|
||||
shell_version = ApplicationConfig["HEROKU_SLUG_COMMIT"]
|
||||
render json: { version: Rails.env.production? ? shell_version : rand(1000) }.to_json
|
||||
end
|
||||
|
|
@ -57,7 +58,8 @@ class AsyncInfoController < ApplicationController
|
|||
name: @user.name,
|
||||
username: @user.username,
|
||||
profile_image_90: ProfileImage.new(@user).get(width: 90),
|
||||
followed_tags: @user.cached_followed_tags.to_json(only: %i[id name bg_color_hex text_color_hex hotness_score], methods: [:points]),
|
||||
followed_tags: @user.cached_followed_tags.to_json(only: %i[id name bg_color_hex text_color_hex hotness_score],
|
||||
methods: [:points]),
|
||||
followed_podcast_ids: @user.cached_following_podcasts_ids,
|
||||
reading_list_ids: ReadingList.new(@user).cached_ids_of_articles,
|
||||
blocked_user_ids: @user.all_blocking.pluck(:blocked_id),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
end
|
||||
|
||||
def find_by_chat_channel_id
|
||||
@membership = ChatChannelMembership.where(chat_channel_id: params[:chat_channel_id], user_id: current_user.id).first!
|
||||
@membership = ChatChannelMembership.where(chat_channel_id: params[:chat_channel_id],
|
||||
user_id: current_user.id).first!
|
||||
authorize @membership
|
||||
render json: @membership.to_json(
|
||||
only: %i[id status viewable_by chat_channel_id last_opened_at],
|
||||
|
|
@ -32,7 +33,9 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
def create_membership_request
|
||||
chat_channel = ChatChannel.find_by(id: channel_membership_request_params[:chat_channel_id])
|
||||
authorize chat_channel, :update?
|
||||
usernames = channel_membership_request_params[:invitation_usernames].split(",").map { |username| username.strip.delete("@") }
|
||||
usernames = channel_membership_request_params[:invitation_usernames].split(",").map do |username|
|
||||
username.strip.delete("@")
|
||||
end
|
||||
users = User.where(username: usernames)
|
||||
invitations_sent = chat_channel.invite_users(users: users, membership_role: "member", inviter: current_user)
|
||||
message = if invitations_sent.zero?
|
||||
|
|
@ -51,7 +54,8 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
if existing_membership.present? && %w[active joining_request].exclude?(existing_membership.status)
|
||||
status = existing_membership.update(status: "joining_request", role: "member")
|
||||
else
|
||||
membership = ChatChannelMembership.new(user_id: current_user.id, chat_channel_id: chat_channel.id, role: "member", status: "joining_request")
|
||||
membership = ChatChannelMembership.new(user_id: current_user.id, chat_channel_id: chat_channel.id,
|
||||
role: "member", status: "joining_request")
|
||||
status = membership.save
|
||||
end
|
||||
if status
|
||||
|
|
@ -69,8 +73,14 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
@chat_channel_membership.destroy
|
||||
message = "Invitation removed."
|
||||
else
|
||||
send_chat_action_message("@#{current_user.username} removed @#{@chat_channel_membership.user.username} from #{@chat_channel_membership.channel_name}", current_user, @chat_channel_membership.chat_channel_id, "removed_from_channel")
|
||||
membership = @chat_channel_membership
|
||||
message = "@#{current_user.username} removed @#{membership.user.username} from #{membership.channel_name}"
|
||||
send_chat_action_message(
|
||||
message, current_user, @chat_channel_membership.chat_channel_id, "removed_from_channel"
|
||||
)
|
||||
|
||||
@chat_channel_membership.update(status: "removed_from_channel")
|
||||
|
||||
message = "Removed #{@chat_channel_membership.user.name}"
|
||||
end
|
||||
|
||||
|
|
@ -81,7 +91,10 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
@chat_channel = ChatChannel.find(params[:chat_channel_id])
|
||||
authorize @chat_channel, :update?
|
||||
@chat_channel_membership = @chat_channel.chat_channel_memberships.find(params[:membership_id])
|
||||
respond_to_invitation(@chat_channel_membership.status) if permitted_params[:user_action].present? && @chat_channel_membership.status == "joining_request"
|
||||
|
||||
return unless permitted_params[:user_action].present? && @chat_channel_membership.status == "joining_request"
|
||||
|
||||
respond_to_invitation(@chat_channel_membership.status)
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
@ -95,7 +108,8 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
authorize @chat_channel_membership
|
||||
@chat_channel_membership.update(permitted_params)
|
||||
if @chat_channel_membership.errors.any?
|
||||
render json: { success: false, errors: @chat_channel_membership.errors.full_messages, message: "Failed to update settings." }, status: :bad_request
|
||||
render json: { success: false, errors: @chat_channel_membership.errors.full_messages,
|
||||
message: "Failed to update settings." }, status: :bad_request
|
||||
else
|
||||
render json: { success: true, message: "Personal settings updated." }, status: :ok
|
||||
end
|
||||
|
|
@ -105,11 +119,13 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
chat_channel_membership = ChatChannelMembership.find_by(id: params[:id])
|
||||
authorize chat_channel_membership
|
||||
channel_name = chat_channel_membership.chat_channel.channel_name
|
||||
send_chat_action_message("@#{current_user.username} left #{chat_channel_membership.channel_name}", current_user, chat_channel_membership.chat_channel_id, "left_channel")
|
||||
send_chat_action_message("@#{current_user.username} left #{chat_channel_membership.channel_name}", current_user,
|
||||
chat_channel_membership.chat_channel_id, "left_channel")
|
||||
chat_channel_membership.update(status: "left_channel")
|
||||
message = "You have left the channel #{channel_name}. It may take a moment to be removed from your list."
|
||||
if chat_channel_membership.errors.any?
|
||||
render json: { success: false, message: "Failed to update membership", errors: chat_channel_membership.errors.full_messages }, status: :bad_request
|
||||
render json: { success: false, message: "Failed to update membership",
|
||||
errors: chat_channel_membership.errors.full_messages }, status: :bad_request
|
||||
else
|
||||
render json: { success: true, message: message }, status: :ok
|
||||
end
|
||||
|
|
@ -129,6 +145,7 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
if permitted_params[:user_action] == "accept"
|
||||
@chat_channel_membership.update(status: "active")
|
||||
channel_name = @chat_channel_membership.chat_channel.channel_name
|
||||
|
||||
if previous_status == "pending"
|
||||
send_chat_action_message(
|
||||
"@#{current_user.username} joined #{@chat_channel_membership.channel_name}",
|
||||
|
|
@ -136,7 +153,8 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
@chat_channel_membership.chat_channel_id,
|
||||
"joined",
|
||||
)
|
||||
flash[:settings_notice] = "Invitation to #{channel_name} accepted. It may take a moment to show up in your list."
|
||||
|
||||
notice = "Invitation to #{channel_name} accepted. It may take a moment to show up in your list."
|
||||
else
|
||||
send_chat_action_message(
|
||||
"@#{current_user.username} added @#{@chat_channel_membership.user.username}",
|
||||
|
|
@ -150,13 +168,16 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
channel_invite_email.
|
||||
deliver_later
|
||||
|
||||
flash[:settings_notice] = "Accepted request of #{@chat_channel_membership.user.username} to join #{channel_name}."
|
||||
notice = "Accepted request of #{@chat_channel_membership.user.username} to join #{channel_name}."
|
||||
end
|
||||
else
|
||||
@chat_channel_membership.update(status: "rejected")
|
||||
flash[:settings_notice] = "Invitation rejected."
|
||||
|
||||
notice = "Invitation rejected."
|
||||
end
|
||||
|
||||
flash[:settings_notice] = notice
|
||||
|
||||
membership_user = MembershipUserPresenter.new(@chat_channel_membership).as_json
|
||||
|
||||
respond_to do |format|
|
||||
|
|
@ -174,7 +195,8 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
|
||||
def send_chat_action_message(message, user, channel_id, action)
|
||||
temp_message_id = (0...20).map { ("a".."z").to_a[rand(8)] }.join
|
||||
message = Message.create("message_markdown" => message, "user_id" => user.id, "chat_channel_id" => channel_id, "chat_action" => action)
|
||||
message = Message.create("message_markdown" => message, "user_id" => user.id, "chat_channel_id" => channel_id,
|
||||
"chat_action" => action)
|
||||
pusher_message_created(false, message, temp_message_id)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ class ChatChannelsController < ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
@chat_messages = @chat_channel.messages.includes(:user).order("created_at DESC").offset(params[:message_offset]).limit(50)
|
||||
@chat_messages = @chat_channel.messages.
|
||||
includes(:user).
|
||||
order(created_at: :desc).
|
||||
offset(params[:message_offset]).
|
||||
limit(50)
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
@ -38,7 +42,8 @@ class ChatChannelsController < ApplicationController
|
|||
flash[:error] = chat_channel.errors.full_messages.to_sentence
|
||||
else
|
||||
if chat_channel_params[:discoverable].to_i.zero?
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: chat_channel.id, role: "member", status: "active")
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: chat_channel.id,
|
||||
role: "member", status: "active")
|
||||
else
|
||||
ChatChannelMembership.find_by(user_id: SiteConfig.mascot_user_id)&.destroy
|
||||
end
|
||||
|
|
@ -52,10 +57,12 @@ class ChatChannelsController < ApplicationController
|
|||
def update_channel
|
||||
chat_channel = ChatChannelUpdateService.perform(@chat_channel, chat_channel_params)
|
||||
if chat_channel.errors.any?
|
||||
render json: { success: false, errors: chat_channel.errors.full_messages, message: "Channel settings updation failed. Try again later." }, success: :bad_request
|
||||
render json: { success: false, errors: chat_channel.errors.full_messages,
|
||||
message: "Channel settings updation failed. Try again later." }, success: :bad_request
|
||||
else
|
||||
if chat_channel_params[:discoverable]
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: chat_channel.id, role: "member", status: "active")
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: chat_channel.id,
|
||||
role: "member", status: "active")
|
||||
else
|
||||
ChatChannelMembership.find_by(user_id: SiteConfig.mascot_user_id)&.destroy
|
||||
end
|
||||
|
|
@ -162,10 +169,11 @@ class ChatChannelsController < ApplicationController
|
|||
|
||||
def render_unopened_json_response
|
||||
@chat_channels_memberships = if session_current_user_id
|
||||
ChatChannelMembership.where(user_id: session_current_user_id).includes(%i[chat_channel user]).
|
||||
ChatChannelMembership.where(user_id: session_current_user_id).
|
||||
where(has_unopened_messages: true).
|
||||
where(show_global_badge_notification: true).
|
||||
where.not(status: %w[removed_from_channel left_channel]).
|
||||
includes(%i[chat_channel user]).
|
||||
order("chat_channel_memberships.updated_at DESC")
|
||||
else
|
||||
[]
|
||||
|
|
@ -187,14 +195,24 @@ class ChatChannelsController < ApplicationController
|
|||
|
||||
def render_unopened_ids_response
|
||||
@unopened_ids = ChatChannelMembership.where(user_id: session_current_user_id).includes(:chat_channel).
|
||||
where(has_unopened_messages: true).where.not(status: %w[removed_from_channel left_channel]).pluck(:chat_channel_id)
|
||||
where(has_unopened_messages: true).where.not(status: %w[removed_from_channel
|
||||
left_channel]).pluck(:chat_channel_id)
|
||||
render json: { unopened_ids: @unopened_ids }
|
||||
end
|
||||
|
||||
def render_joining_request_json_response
|
||||
requested_memberships_id = current_user.chat_channel_memberships.includes(:chat_channel).
|
||||
where(chat_channels: { discoverable: true }, role: "mod").pluck(:chat_channel_id).map { |membership_id| ChatChannel.find_by(id: membership_id).requested_memberships }.flatten.map(&:id)
|
||||
@chat_channels_memberships = ChatChannelMembership.includes(%i[user chat_channel]).where(id: requested_memberships_id)
|
||||
requested_memberships_id = current_user.
|
||||
chat_channel_memberships.
|
||||
includes(:chat_channel).
|
||||
where(chat_channels: { discoverable: true }, role: "mod").
|
||||
pluck(:chat_channel_id).
|
||||
map { |membership_id| ChatChannel.find_by(id: membership_id).requested_memberships }.
|
||||
flatten.
|
||||
map(&:id)
|
||||
|
||||
@chat_channels_memberships = ChatChannelMembership.
|
||||
includes(%i[user chat_channel]).
|
||||
where(id: requested_memberships_id)
|
||||
|
||||
render "index.json"
|
||||
end
|
||||
|
|
@ -227,6 +245,7 @@ class ChatChannelsController < ApplicationController
|
|||
else
|
||||
@chat_channel.adjusted_slug(current_user)
|
||||
end
|
||||
Pusher.trigger("private-message-notifications-#{session_current_user_id}", "message-opened", { channel_type: @chat_channel.channel_type, adjusted_slug: adjusted_slug }.to_json)
|
||||
Pusher.trigger("private-message-notifications-#{session_current_user_id}", "message-opened",
|
||||
{ channel_type: @chat_channel.channel_type, adjusted_slug: adjusted_slug }.to_json)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -239,7 +239,9 @@ class CommentsController < ApplicationController
|
|||
@comment.hidden_by_commentable_user = false
|
||||
if @comment.save
|
||||
@commentable = @comment&.commentable
|
||||
@commentable&.update_column(:any_comments_hidden, @commentable.comments.pluck(:hidden_by_commentable_user).include?(true))
|
||||
@commentable&.update_columns(
|
||||
any_comments_hidden: @commentable.comments.pluck(:hidden_by_commentable_user).include?(true),
|
||||
)
|
||||
render json: { hidden: "false" }, status: :ok
|
||||
else
|
||||
render json: { errors: @comment.errors_as_sentence, status: 422 }, status: :unprocessable_entity
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ module ValidRequest
|
|||
if request.referer.present?
|
||||
request.referer.start_with?(URL.url)
|
||||
else
|
||||
raise ::ActionController::InvalidAuthenticityToken, ::ApplicationController::NULL_ORIGIN_MESSAGE if request.origin == "null"
|
||||
null_origin = request.origin == "null"
|
||||
raise ::ActionController::InvalidAuthenticityToken, ::ApplicationController::NULL_ORIGIN_MESSAGE if null_origin
|
||||
|
||||
request.origin.nil? || request.origin.gsub("https", "http") == request.base_url.gsub("https", "http")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ class EmailAuthorizationsController < ApplicationController
|
|||
raise ActionController::RoutingError, "Not Found" unless current_user == user
|
||||
|
||||
email_authorization = user.email_authorizations.order("created_at DESC").first
|
||||
raise ActionController::RoutingError, "Not Found" unless email_authorization.confirmation_token == params[:confirmation_token]
|
||||
correct_token = email_authorization.confirmation_token == params[:confirmation_token]
|
||||
raise ActionController::RoutingError, "Not Found" unless correct_token
|
||||
|
||||
email_authorization.update(verified_at: Time.current)
|
||||
redirect_to root_path
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ class FollowsController < ApplicationController
|
|||
|
||||
return render plain: following_them_check unless params[:followable_type] == "User"
|
||||
|
||||
following_you_check = FollowChecker.new(User.find_by(id: params[:id]), params[:followable_type], current_user.id).cached_follow_check
|
||||
following_you_check = FollowChecker.new(User.find_by(id: params[:id]), params[:followable_type],
|
||||
current_user.id).cached_follow_check
|
||||
|
||||
if following_them_check && following_you_check
|
||||
render plain: "mutual"
|
||||
|
|
@ -34,7 +35,8 @@ class FollowsController < ApplicationController
|
|||
"self"
|
||||
else
|
||||
following_them_check = FollowChecker.new(current_user, params[:followable_type], id).cached_follow_check
|
||||
following_you_check = FollowChecker.new(User.find_by(id: id), params[:followable_type], current_user.id).cached_follow_check
|
||||
following_you_check = FollowChecker.new(User.find_by(id: id), params[:followable_type],
|
||||
current_user.id).cached_follow_check
|
||||
if following_them_check && following_you_check
|
||||
"mutual"
|
||||
elsif following_you_check
|
||||
|
|
|
|||
|
|
@ -3,15 +3,18 @@ class HtmlVariantsController < ApplicationController
|
|||
|
||||
def index
|
||||
authorize HtmlVariant
|
||||
@html_variants = if params[:state] == "mine"
|
||||
current_user.html_variants.order("created_at DESC").includes(:user).page(params[:page]).per(30)
|
||||
elsif params[:state] == "admin"
|
||||
HtmlVariant.where(published: true, approved: false).order("created_at DESC").includes(:user).page(params[:page]).per(30)
|
||||
elsif params[:state].present?
|
||||
HtmlVariant.where(published: true, approved: true, group: params[:state]).order("success_rate DESC").includes(:user).page(params[:page]).per(30)
|
||||
else
|
||||
HtmlVariant.where(published: true, approved: true).order("success_rate DESC").includes(:user).page(params[:page]).per(30)
|
||||
end
|
||||
|
||||
relation = if params[:state] == "mine"
|
||||
current_user.html_variants.order(created_at: :desc)
|
||||
elsif params[:state] == "admin"
|
||||
HtmlVariant.where(published: true, approved: false).order(created_at: :desc)
|
||||
elsif params[:state].present?
|
||||
HtmlVariant.where(published: true, approved: true, group: params[:state]).order(success_rate: :desc)
|
||||
else
|
||||
HtmlVariant.where(published: true, approved: true).order(success_rate: :desc)
|
||||
end
|
||||
|
||||
@html_variants = relation.includes(:user).page(params[:page]).per(30)
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ class Internal::ArticlesController < Internal::ApplicationController
|
|||
|
||||
def index
|
||||
@pending_buffer_updates = BufferUpdate.where(status: "pending").includes(:article)
|
||||
@user_buffer_updates = BufferUpdate.where(status: "sent_direct", approver_user_id: current_user.id).where("created_at > ?", 24.hours.ago)
|
||||
@user_buffer_updates = BufferUpdate.where(status: "sent_direct", approver_user_id: current_user.id).where(
|
||||
"created_at > ?", 24.hours.ago
|
||||
)
|
||||
|
||||
case params[:state]
|
||||
when /not-buffered/
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ class Internal::ConfigsController < Internal::ApplicationController
|
|||
onboarding_params |
|
||||
job_params
|
||||
|
||||
params[:site_config][:email_addresses][:default] = ApplicationConfig["DEFAULT_EMAIL"] if params[:site_config][:email_addresses].present?
|
||||
has_emails = params[:site_config][:email_addresses].present?
|
||||
params[:site_config][:email_addresses][:default] = ApplicationConfig["DEFAULT_EMAIL"] if has_emails
|
||||
params.require(:site_config).permit(
|
||||
allowed_params,
|
||||
authentication_providers: [],
|
||||
|
|
@ -57,7 +58,8 @@ class Internal::ConfigsController < Internal::ApplicationController
|
|||
|
||||
def extra_authorization_and_confirmation
|
||||
not_authorized unless current_user.has_role?(:single_resource_admin, Config) # Special additional permission
|
||||
not_authorized if params[:confirmation] != "My username is @#{current_user.username} and this action is 100% safe and appropriate."
|
||||
confirmation_message = "My username is @#{current_user.username} and this action is 100% safe and appropriate."
|
||||
not_authorized if params[:confirmation] != confirmation_message
|
||||
end
|
||||
|
||||
def clean_up_params
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ class Internal::OrganizationMembershipsController < Internal::ApplicationControl
|
|||
if organization && organization_membership.save
|
||||
flash[:success] = "User was successfully added to #{organization.name}"
|
||||
elsif organization.blank?
|
||||
flash[:danger] = "Organization ##{organization_membership_params[:organization_id]} does not exist. Perhaps a typo?"
|
||||
message = "Organization ##{organization_membership_params[:organization_id]} does not exist. Perhaps a typo?"
|
||||
flash[:danger] = message
|
||||
else
|
||||
flash[:danger] = organization_membership.errors.full_messages
|
||||
end
|
||||
|
|
@ -29,7 +30,8 @@ class Internal::OrganizationMembershipsController < Internal::ApplicationControl
|
|||
if organization_membership.destroy
|
||||
flash[:success] = "User was successfully removed from org ##{organization_membership.organization_id}"
|
||||
else
|
||||
flash[:danger] = "Something went wrong with removing the user from org ##{organization_membership.organization_id}"
|
||||
message = "Something went wrong with removing the user from org ##{organization_membership.organization_id}"
|
||||
flash[:danger] = message
|
||||
end
|
||||
redirect_to internal_user_path(organization_membership.user_id)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ class Internal::PagesController < Internal::ApplicationController
|
|||
private
|
||||
|
||||
def page_params
|
||||
allowed_params = %i[title slug body_markdown body_html body_json description template is_top_level_path social_image]
|
||||
allowed_params = %i[title slug body_markdown body_html body_json description template is_top_level_path
|
||||
social_image]
|
||||
params.require(:page).permit(allowed_params)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ class Internal::PermissionsController < Internal::ApplicationController
|
|||
layout "internal"
|
||||
|
||||
def index
|
||||
@users = User.with_role(:admin).union(User.with_role(:super_admin)).union(User.with_role(:single_resource_admin, :any)).page(params[:page]).per(50)
|
||||
@users = User.with_role(:admin).
|
||||
union(User.with_role(:super_admin)).
|
||||
union(User.with_role(:single_resource_admin, :any)).
|
||||
page(params[:page]).
|
||||
per(50)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ class Internal::PodcastsController < Internal::ApplicationController
|
|||
select("podcasts.*, count(podcast_episodes) as episodes_count").
|
||||
group("podcasts.id").order("podcasts.created_at DESC").
|
||||
page(params[:page]).per(50)
|
||||
@podcasts = @podcasts.where("podcasts.title ILIKE :search", search: "%#{params[:search]}%") if params[:search].present?
|
||||
|
||||
return if params[:search].blank?
|
||||
|
||||
@podcasts = @podcasts.where("podcasts.title ILIKE :search", search: "%#{params[:search]}%")
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class Internal::SponsorshipsController < Internal::ApplicationController
|
|||
private
|
||||
|
||||
def sponsorship_params
|
||||
params.require(:sponsorship).permit(%i[status expires_at tagline url blurb_html featured_number instructions instructions_updated_at])
|
||||
params.require(:sponsorship).permit(%i[status expires_at tagline url blurb_html featured_number instructions
|
||||
instructions_updated_at])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ class Internal::ToolsController < Internal::ApplicationController
|
|||
end
|
||||
|
||||
def bust_link(link)
|
||||
link.sub!("https://#{ApplicationConfig['APP_DOMAIN']}", "") if link.starts_with?("https://#{ApplicationConfig['APP_DOMAIN']}")
|
||||
if link.starts_with?("https://#{ApplicationConfig['APP_DOMAIN']}")
|
||||
link.sub!("https://#{ApplicationConfig['APP_DOMAIN']}",
|
||||
"")
|
||||
end
|
||||
CacheBuster.bust(link)
|
||||
CacheBuster.bust("#{link}/")
|
||||
CacheBuster.bust("#{link}?i=i")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ class Internal::UsersController < Internal::ApplicationController
|
|||
joins(:organization).
|
||||
order("organizations.name ASC").
|
||||
includes(:organization)
|
||||
@last_email_verification_date = @user.email_authorizations.where.not(verified_at: nil).order("created_at DESC").first&.verified_at || "Never"
|
||||
@last_email_verification_date = @user.email_authorizations.
|
||||
where.not(verified_at: nil).
|
||||
order("created_at DESC").first&.verified_at || "Never"
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
@ -55,7 +57,10 @@ class Internal::UsersController < Internal::ApplicationController
|
|||
@user = User.find(params[:id])
|
||||
begin
|
||||
Moderator::DeleteUser.call(admin: current_user, user: @user, user_params: user_params)
|
||||
flash[:success] = "@#{@user.username} (email: #{@user.email.presence || 'no email'}, user_id: #{@user.id}) has been fully deleted. If requested, old content may have been ghostified. If this is a GDPR delete, delete them from Mailchimp & Google Analytics."
|
||||
message = "@#{@user.username} (email: #{@user.email.presence || 'no email'}, user_id: #{@user.id}) " \
|
||||
"has been fully deleted. If requested, old content may have been ghostified. " \
|
||||
"If this is a GDPR delete, delete them from Mailchimp & Google Analytics."
|
||||
flash[:success] = message
|
||||
rescue StandardError => e
|
||||
flash[:danger] = e.message
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ class ListingsController < ApplicationController
|
|||
|
||||
DASHBOARD_JSON_OPTIONS = {
|
||||
only: %i[
|
||||
title tag_list created_at expires_at bumped_at updated_at category id user_id slug organization_id location published
|
||||
title tag_list created_at expires_at bumped_at updated_at category id
|
||||
user_id slug organization_id location published
|
||||
],
|
||||
include: {
|
||||
author: { only: %i[username name], methods: %i[username profile_image_90] }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ class ModerationsController < ApplicationController
|
|||
where("score > -5 AND score < 5").
|
||||
order("published_at DESC").limit(70)
|
||||
articles = articles.cached_tagged_with(params[:tag]) if params[:tag].present?
|
||||
articles = articles.where("nth_published_by_author > 0 AND nth_published_by_author < 4 AND published_at > ?", 7.days.ago) if params[:state] == "new-authors"
|
||||
if params[:state] == "new-authors"
|
||||
articles = articles.where("nth_published_by_author > 0 AND nth_published_by_author < 4 AND published_at > ?",
|
||||
7.days.ago)
|
||||
end
|
||||
@articles = articles.includes(:user).to_json(JSON_OPTIONS)
|
||||
@tag = Tag.find_by(name: params[:tag]) || not_found if params[:tag].present?
|
||||
@current_user_tags = current_user.moderator_for_tags
|
||||
|
|
@ -41,7 +44,9 @@ class ModerationsController < ApplicationController
|
|||
has_no_relevant_adjustments = @adjustments.pluck(:tag_id).intersection(tag_mod_tag_ids).size.zero?
|
||||
can_be_adjusted = @moderatable.tags.pluck(:id).intersection(tag_mod_tag_ids).size.positive?
|
||||
|
||||
@should_show_adjust_tags = tag_mod_tag_ids.size.positive? && ((has_room_for_tags && has_no_relevant_adjustments) || (!has_room_for_tags && has_no_relevant_adjustments && can_be_adjusted))
|
||||
@should_show_adjust_tags = tag_mod_tag_ids.size.positive? &&
|
||||
((has_room_for_tags && has_no_relevant_adjustments) ||
|
||||
(!has_room_for_tags && has_no_relevant_adjustments && can_be_adjusted))
|
||||
|
||||
render template: "moderations/actions_panel"
|
||||
end
|
||||
|
|
@ -50,13 +55,15 @@ class ModerationsController < ApplicationController
|
|||
|
||||
def load_article
|
||||
authorize(User, :moderation_routes?)
|
||||
|
||||
@tag_adjustment = TagAdjustment.new
|
||||
@moderatable = Article.find_by(slug: params[:slug])
|
||||
not_found unless @moderatable
|
||||
@tag_moderator_tags = Tag.with_role(:tag_moderator, current_user)
|
||||
@adjustments = TagAdjustment.where(article_id: @moderatable.id)
|
||||
@already_adjusted_tags = @adjustments.map(&:tag_name).join(", ")
|
||||
@allowed_to_adjust = @moderatable.class.name == "Article" && (current_user.has_role?(:super_admin) || @tag_moderator_tags.any?)
|
||||
@allowed_to_adjust = @moderatable.class.name == "Article" && (
|
||||
current_user.has_role?(:super_admin) || @tag_moderator_tags.any?)
|
||||
@hidden_comments = @moderatable.comments.where(hidden_by_commentable_user: true)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
class NotificationSubscriptionsController < ApplicationController
|
||||
def show
|
||||
result = if current_user
|
||||
NotificationSubscription.where(user_id: current_user.id, notifiable_id: params[:notifiable_id], notifiable_type: params[:notifiable_type]).
|
||||
NotificationSubscription.where(user_id: current_user.id, notifiable_id: params[:notifiable_id],
|
||||
notifiable_type: params[:notifiable_type]).
|
||||
first&.to_json(only: %i[config]) || { config: "not_subscribed" }
|
||||
end
|
||||
respond_to do |format|
|
||||
|
|
@ -12,9 +13,12 @@ class NotificationSubscriptionsController < ApplicationController
|
|||
def upsert
|
||||
not_found unless current_user
|
||||
|
||||
@notification_subscription = NotificationSubscription.find_or_initialize_by(user_id: current_user.id,
|
||||
notifiable_id: params[:notifiable_id],
|
||||
notifiable_type: params[:notifiable_type].capitalize)
|
||||
@notification_subscription = NotificationSubscription.find_or_initialize_by(
|
||||
user_id: current_user.id,
|
||||
notifiable_id: params[:notifiable_id],
|
||||
notifiable_type: params[:notifiable_type].capitalize,
|
||||
)
|
||||
|
||||
if params[:config] == "not_subscribed"
|
||||
@notification_subscription.delete
|
||||
@notification_subscription.notifiable.update(receive_notifications: false) if current_user_is_author?
|
||||
|
|
@ -24,9 +28,9 @@ class NotificationSubscriptionsController < ApplicationController
|
|||
@notification_subscription.notifiable.update(receive_notifications: true) if receive_notifications
|
||||
@notification_subscription.save
|
||||
end
|
||||
result = @notification_subscription.persisted?
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: result }
|
||||
format.json { render json: @notification_subscription.persisted? }
|
||||
format.html { redirect_to request.referer }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ class OrganizationsController < ApplicationController
|
|||
authorize @organization
|
||||
if @organization.save
|
||||
rate_limiter.track_limit_by_action(:organization_creation)
|
||||
@organization_membership = OrganizationMembership.create!(organization_id: @organization.id, user_id: current_user.id, type_of_user: "admin")
|
||||
@organization_membership = OrganizationMembership.create!(organization_id: @organization.id,
|
||||
user_id: current_user.id, type_of_user: "admin")
|
||||
flash[:settings_notice] = "Your organization was successfully created and you are an admin."
|
||||
redirect_to "/settings/organization/#{@organization.id}"
|
||||
else
|
||||
|
|
@ -41,7 +42,8 @@ class OrganizationsController < ApplicationController
|
|||
redirect_to "/settings/organization"
|
||||
else
|
||||
@org_organization_memberships = @organization.organization_memberships.includes(:user)
|
||||
@organization_membership = OrganizationMembership.find_by(user_id: current_user.id, organization_id: @organization.id)
|
||||
@organization_membership = OrganizationMembership.find_by(user_id: current_user.id,
|
||||
organization_id: @organization.id)
|
||||
|
||||
render template: "users/edit"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
class PageViewsController < ApplicationMetalController
|
||||
# ApplicationMetalController because we do not need all bells and whistles of ApplicationController, so should help performance.
|
||||
# ApplicationMetalController because we do not need all bells and whistles of ApplicationController.
|
||||
# It should help performance.
|
||||
include ActionController::Head
|
||||
|
||||
def create
|
||||
|
|
@ -18,7 +19,8 @@ class PageViewsController < ApplicationMetalController
|
|||
|
||||
def update
|
||||
if session_current_user_id
|
||||
page_view = PageView.order("created_at DESC").find_or_create_by(article_id: params[:id], user_id: session_current_user_id)
|
||||
page_view = PageView.order("created_at DESC").find_or_create_by(article_id: params[:id],
|
||||
user_id: session_current_user_id)
|
||||
unless page_view.new_record?
|
||||
page_view.update_column(:time_tracked_in_seconds, page_view.time_tracked_in_seconds + 15)
|
||||
end
|
||||
|
|
@ -49,7 +51,10 @@ class PageViewsController < ApplicationMetalController
|
|||
page_views_from_google_com = @article.page_views.where(referrer: "https://www.google.com/")
|
||||
|
||||
organic_count = page_views_from_google_com.sum(:counts_for_number_of_views)
|
||||
@article.update_column(:organic_page_views_count, organic_count) if organic_count > @article.organic_page_views_count
|
||||
if organic_count > @article.organic_page_views_count
|
||||
@article.update_column(:organic_page_views_count,
|
||||
organic_count)
|
||||
end
|
||||
|
||||
organic_count_past_week_count = page_views_from_google_com.
|
||||
where("created_at > ?", 1.week.ago).sum(:counts_for_number_of_views)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ class PollVotesController < ApplicationController
|
|||
|
||||
def create
|
||||
@poll_option = PollOption.find(poll_vote_params[:poll_option_id])
|
||||
@poll_vote = PollVote.create(poll_option_id: @poll_option&.id, user_id: current_user.id, poll_id: @poll_option.poll_id)
|
||||
@poll_vote = PollVote.create(poll_option_id: @poll_option&.id, user_id: current_user.id,
|
||||
poll_id: @poll_option.poll_id)
|
||||
@poll = @poll_option.reload.poll
|
||||
render json: { voting_data: @poll.voting_data,
|
||||
poll_id: @poll.id,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ class RatingVotesController < ApplicationController
|
|||
|
||||
def create
|
||||
authorize RatingVote
|
||||
rating_vote = RatingVote.where(user_id: current_user.id, article_id: rating_vote_params[:article_id]).first || RatingVote.new
|
||||
rating_vote = RatingVote.where(user_id: current_user.id,
|
||||
article_id: rating_vote_params[:article_id]).first || RatingVote.new
|
||||
rating_vote.user_id = current_user.id
|
||||
rating_vote.article_id = rating_vote_params[:article_id]
|
||||
rating_vote.rating = rating_vote_params[:rating].to_f
|
||||
|
|
@ -18,7 +19,9 @@ class RatingVotesController < ApplicationController
|
|||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json { render json: { error: rating_vote.errors.full_messages.to_sentence }, status: :unprocessable_entity }
|
||||
format.json do
|
||||
render json: { error: rating_vote.errors.full_messages.to_sentence }, status: :unprocessable_entity
|
||||
end
|
||||
format.html { render json: { result: "Not Upserted Successfully" } }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -83,7 +83,10 @@ class ReactionsController < ApplicationController
|
|||
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
||||
|
||||
Notification.send_reaction_notification(reaction, reaction.target_user)
|
||||
Notification.send_reaction_notification(reaction, reaction.reactable.organization) if reaction.reaction_on_organization_article?
|
||||
if reaction.reaction_on_organization_article?
|
||||
Notification.send_reaction_notification(reaction,
|
||||
reaction.reactable.organization)
|
||||
end
|
||||
|
||||
result = "create"
|
||||
|
||||
|
|
@ -103,7 +106,8 @@ class ReactionsController < ApplicationController
|
|||
end
|
||||
|
||||
def cached_user_public_comment_reactions(user, comment_ids)
|
||||
cache = Rails.cache.fetch("cached-user-#{user.id}-reaction-ids-#{user.public_reactions_count}", expires_in: 24.hours) do
|
||||
cache = Rails.cache.fetch("cached-user-#{user.id}-reaction-ids-#{user.public_reactions_count}",
|
||||
expires_in: 24.hours) do
|
||||
user.reactions.public_category.where(reactable_type: "Comment").each_with_object({}) do |r, h|
|
||||
h[r.reactable_id] = r.attributes
|
||||
end
|
||||
|
|
@ -128,7 +132,10 @@ class ReactionsController < ApplicationController
|
|||
reaction.destroy
|
||||
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
||||
Notification.send_reaction_notification_without_delay(reaction, reaction.target_user)
|
||||
Notification.send_reaction_notification_without_delay(reaction, reaction.reactable.organization) if reaction.reaction_on_organization_article?
|
||||
if reaction.reaction_on_organization_article?
|
||||
Notification.send_reaction_notification_without_delay(reaction,
|
||||
reaction.reactable.organization)
|
||||
end
|
||||
"destroy"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,11 @@ class SearchController < ApplicationController
|
|||
end
|
||||
|
||||
def chat_channels
|
||||
search_user_id = chat_channel_params[:user_id].present? ? [current_user.id, SiteConfig.mascot_user_id] : [current_user.id]
|
||||
search_user_id = if chat_channel_params[:user_id].present?
|
||||
[current_user.id, SiteConfig.mascot_user_id]
|
||||
else
|
||||
[current_user.id]
|
||||
end
|
||||
ccm_docs = Search::ChatChannelMembership.search_documents(
|
||||
params: chat_channel_params.merge(user_id: search_user_id).to_h,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ class SitemapsController < ApplicationController
|
|||
rescue ArgumentError
|
||||
not_found
|
||||
end
|
||||
@articles = Article.published.where("published_at > ? AND published_at < ? AND score > ?", date, date.end_of_month, 3).pluck(:path, :last_comment_at)
|
||||
|
||||
@articles = Article.published.
|
||||
where("published_at > ? AND published_at < ? AND score > ?", date, date.end_of_month, 3).
|
||||
pluck(:path, :last_comment_at)
|
||||
|
||||
set_surrogate_controls(date)
|
||||
set_cache_control_headers(@max_age,
|
||||
stale_while_revalidate: @stale_while_revalidate,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ class SocialPreviewsController < ApplicationController
|
|||
|
||||
def comment
|
||||
@comment = Comment.find(params[:id])
|
||||
@tag_badges = Badge.where(id: Tag.where(name: @comment.commentable&.decorate&.cached_tag_list_array).pluck(:badge_id))
|
||||
|
||||
badge_ids = Tag.where(name: @comment.commentable&.decorate&.cached_tag_list_array).pluck(:badge_id)
|
||||
@tag_badges = Badge.where(id: badge_ids)
|
||||
|
||||
set_respond
|
||||
end
|
||||
|
|
@ -58,7 +60,8 @@ class SocialPreviewsController < ApplicationController
|
|||
end
|
||||
format.png do
|
||||
html = render_to_string(template, formats: :html, layout: false)
|
||||
redirect_to HtmlCssToImage.fetch_url(html: html, css: PNG_CSS, google_fonts: "Roboto|Roboto+Condensed"), status: :found
|
||||
redirect_to HtmlCssToImage.fetch_url(html: html, css: PNG_CSS,
|
||||
google_fonts: "Roboto|Roboto+Condensed"), status: :found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ class Stories::FeedsController < ApplicationController
|
|||
"more_comments_experiment" => :more_comments_experiment,
|
||||
"more_experience_level_weight_experiment" => :more_experience_level_weight_experiment,
|
||||
"more_tag_weight_randomized_at_end_experiment" => :more_tag_weight_randomized_at_end_experiment,
|
||||
"more_experience_level_weight_randomized_at_end_experiment" => :more_experience_level_weight_randomized_at_end_experiment,
|
||||
"more_experience_level_weight_randomized_at_end_experiment" =>
|
||||
:more_experience_level_weight_randomized_at_end_experiment,
|
||||
"more_comments_randomized_at_end_experiment" => :more_comments_randomized_at_end_experiment,
|
||||
"more_comments_medium_weight_randomized_at_end_experiment" => :more_comments_medium_weight_randomized_at_end_experiment,
|
||||
"more_comments_minimal_weight_randomized_at_end_experiment" => :more_comments_minimal_weight_randomized_at_end_experiment,
|
||||
"more_comments_medium_weight_randomized_at_end_experiment" =>
|
||||
:more_comments_medium_weight_randomized_at_end_experiment,
|
||||
"more_comments_minimal_weight_randomized_at_end_experiment" =>
|
||||
:more_comments_minimal_weight_randomized_at_end_experiment,
|
||||
"mix_of_everything_experiment" => :mix_of_everything_experiment
|
||||
}.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ class StoriesController < ApplicationController
|
|||
campaign_articles_scope = Article.tagged_with(SiteConfig.campaign_featured_tags, any: true).
|
||||
where("published_at > ? AND score > ?", 4.weeks.ago, 0).
|
||||
order("hotness_score DESC")
|
||||
campaign_articles_scope = campaign_articles_scope.where(approved: true) if SiteConfig.campaign_articles_require_approval?
|
||||
|
||||
requires_approval = SiteConfig.campaign_articles_require_approval?
|
||||
campaign_articles_scope = campaign_articles_scope.where(approved: true) if requires_approval
|
||||
|
||||
@campaign_articles_count = campaign_articles_scope.count
|
||||
@latest_campaign_articles = campaign_articles_scope.limit(5).pluck(:path, :title, :comments_count, :created_at)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ class TagAdjustmentsController < ApplicationController
|
|||
service.update_tags_and_notify
|
||||
tag = tag_adjustment.tag
|
||||
respond_to do |format|
|
||||
format.json { render json: { status: "Success", result: tag_adjustment.adjustment_type, colors: { bg: tag.bg_color_hex, text: tag.text_color_hex } } }
|
||||
format.json do
|
||||
render json: { status: "Success", result: tag_adjustment.adjustment_type,
|
||||
colors: { bg: tag.bg_color_hex, text: tag.text_color_hex } }
|
||||
end
|
||||
format.html { redirect_to "#{URI.parse(article.path).path}/mod" }
|
||||
end
|
||||
else
|
||||
|
|
@ -40,11 +43,18 @@ class TagAdjustmentsController < ApplicationController
|
|||
|
||||
def destroy
|
||||
authorize User, :moderation_routes?
|
||||
tag_adjustment = TagAdjustment.find(params[:id])
|
||||
tag_adjustment.destroy
|
||||
@article = Article.find(tag_adjustment.article_id)
|
||||
@article.update!(tag_list: @article.tag_list.add(tag_adjustment.tag_name)) if tag_adjustment.adjustment_type == "removal"
|
||||
@article.update!(tag_list: @article.tag_list.remove(tag_adjustment.tag_name)) if tag_adjustment.adjustment_type == "addition"
|
||||
|
||||
adjustment = TagAdjustment.find(params[:id])
|
||||
adjustment.destroy
|
||||
|
||||
@article = Article.find(adjustment.article_id)
|
||||
|
||||
removal_type = adjustment.adjustment_type == "removal"
|
||||
@article.update!(tag_list: @article.tag_list.add(adjustment.tag_name)) if removal_type
|
||||
|
||||
addition_type = adjustment.adjustment_type == "addition"
|
||||
@article.update!(tag_list: @article.tag_list.remove(adjustment.tag_name)) if addition_type
|
||||
|
||||
respond_to do |format|
|
||||
# TODO: add tag adjustment removal async route in actions panel
|
||||
format.json { render json: { result: "Tag adjustment destroyed" } }
|
||||
|
|
|
|||
|
|
@ -93,11 +93,13 @@ class UsersController < ApplicationController
|
|||
set_tabs("account")
|
||||
|
||||
if destroy_request_in_progress?
|
||||
flash[:settings_notice] = "You have already requested account deletion. Please, check your email for further instructions."
|
||||
notice = "You have already requested account deletion. Please, check your email for further instructions."
|
||||
flash[:settings_notice] = notice
|
||||
redirect_to user_settings_path(@tab)
|
||||
elsif @user.email?
|
||||
Users::RequestDestroy.call(@user)
|
||||
flash[:settings_notice] = "You have requested account deletion. Please, check your email for further instructions."
|
||||
notice = "You have requested account deletion. Please, check your email for further instructions."
|
||||
flash[:settings_notice] = notice
|
||||
redirect_to user_settings_path(@tab)
|
||||
else
|
||||
flash[:settings_notice] = "Please, provide an email to delete your account."
|
||||
|
|
@ -204,7 +206,8 @@ class UsersController < ApplicationController
|
|||
adminable = User.find(params[:user_id])
|
||||
org = Organization.find_by(id: params[:organization_id])
|
||||
|
||||
not_authorized unless current_user.org_admin?(org) && OrganizationMembership.exists?(user: adminable, organization: org)
|
||||
not_authorized unless current_user.org_admin?(org) && OrganizationMembership.exists?(user: adminable,
|
||||
organization: org)
|
||||
|
||||
OrganizationMembership.find_by(user_id: adminable.id, organization_id: org.id).update(type_of_user: "admin")
|
||||
flash[:settings_notice] = "#{adminable.name} is now an admin."
|
||||
|
|
@ -306,7 +309,8 @@ class UsersController < ApplicationController
|
|||
authorize @organization, :part_of_org?
|
||||
|
||||
@org_organization_memberships = @organization.organization_memberships.includes(:user)
|
||||
@organization_membership = OrganizationMembership.find_by(user_id: current_user.id, organization_id: @organization.id)
|
||||
@organization_membership = OrganizationMembership.find_by(user_id: current_user.id,
|
||||
organization_id: @organization.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ module ArticlesHelper
|
|||
def has_vid?(article)
|
||||
return if article.processed_html.blank?
|
||||
|
||||
article.processed_html.include?("youtube.com/embed/") || article.processed_html.include?("player.vimeo.com") || article.comments_blob.include?("youtube")
|
||||
article.processed_html.include?("youtube.com/embed/") ||
|
||||
article.processed_html.include?("player.vimeo.com") ||
|
||||
article.comments_blob.include?("youtube")
|
||||
end
|
||||
|
||||
def collection_link_class(current_article, linked_article)
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@ module AssignTagModerator
|
|||
end
|
||||
|
||||
def self.add_to_chat_channels(user, tag)
|
||||
ChatChannel.find_by(slug: "tag-moderators").add_users(user) if user.chat_channels.where(slug: "tag-moderators").none?
|
||||
channels = user.chat_channels
|
||||
|
||||
ChatChannel.find_by(slug: "tag-moderators").add_users(user) unless channels.exists?(slug: "tag-moderators")
|
||||
|
||||
if tag.mod_chat_channel_id
|
||||
ChatChannel.find(tag.mod_chat_channel_id).add_users(user) if user.chat_channels.where(id: tag.mod_chat_channel_id).none?
|
||||
ChatChannel.find(tag.mod_chat_channel_id).add_users(user) unless channels.exists?(id: tag.mod_chat_channel_id)
|
||||
else
|
||||
channel = ChatChannel.create_with_users(
|
||||
users: ([user] + User.with_role(:mod_relations_admin)).flatten.uniq,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ module BadgeRewarder
|
|||
|
||||
def self.award_yearly_club_badges
|
||||
(1..3).each do |i|
|
||||
message = "Happy #{ApplicationConfig['COMMUNITY_NAME']} birthday! Can you believe it's been #{i} #{'year'.pluralize(i)} already?!"
|
||||
message = "Happy #{ApplicationConfig['COMMUNITY_NAME']} birthday! " \
|
||||
"Can you believe it's been #{i} #{'year'.pluralize(i)} already?!"
|
||||
badge = Badge.find_by!(slug: "#{YEARS[i]}-year-club")
|
||||
User.where("created_at < ? AND created_at > ?", i.year.ago, i.year.ago - 2.days).find_each do |user|
|
||||
achievement = BadgeAchievement.create(
|
||||
|
|
@ -62,13 +63,16 @@ module BadgeRewarder
|
|||
award_badges(
|
||||
[winning_article.user.username],
|
||||
tag.badge.slug,
|
||||
"Congratulations on posting the most beloved [##{tag.name}](#{URL.tag(tag)}) post from the past seven days! Your winning post was [#{winning_article.title}](#{URL.article(winning_article)}). (You can only win once per badge-eligible tag)",
|
||||
"Congratulations on posting the most beloved [##{tag.name}](#{URL.tag(tag)}) post " \
|
||||
"from the past seven days! " \
|
||||
"Your winning post was [#{winning_article.title}](#{URL.article(winning_article)}). " \
|
||||
"(You can only win once per badge-eligible tag)",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.award_contributor_badges_from_github(since = 1.day.ago, message_markdown = "Thank you so much for your contributions!")
|
||||
def self.award_contributor_badges_from_github(since = 1.day.ago, msg = "Thank you so much for your contributions!")
|
||||
badge = Badge.find_by!(slug: "dev-contributor")
|
||||
|
||||
REPOSITORIES.each do |repo|
|
||||
|
|
@ -77,7 +81,7 @@ module BadgeRewarder
|
|||
authors_uids = commits.map { |commit| commit.author.id }
|
||||
Identity.github.where(uid: authors_uids).find_each do |i|
|
||||
BadgeAchievement.where(user_id: i.user_id, badge_id: badge.id).first_or_create(
|
||||
rewarding_context_message_markdown: message_markdown,
|
||||
rewarding_context_message_markdown: msg,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -85,11 +89,14 @@ module BadgeRewarder
|
|||
|
||||
def self.award_streak_badge(num_weeks)
|
||||
# No credit for super low quality
|
||||
article_user_ids = Article.published.where("published_at > ? AND score > ?", 1.week.ago, MINIMUM_QUALITY).pluck(:user_id)
|
||||
article_user_ids = Article.published.
|
||||
where("published_at > ? AND score > ?", 1.week.ago, MINIMUM_QUALITY).
|
||||
pluck(:user_id)
|
||||
message = if num_weeks == LONGEST_STREAK_WEEKS
|
||||
LONGEST_STREAK_MESSAGE
|
||||
else
|
||||
"Congrats on achieving this streak! Consistent writing is hard. The next streak badge you can get is the #{num_weeks * 2} Week Badge. 😉"
|
||||
"Congrats on achieving this streak! Consistent writing is hard. " \
|
||||
"The next streak badge you can get is the #{num_weeks * 2} Week Badge. 😉"
|
||||
end
|
||||
users = User.where(id: article_user_ids).where("articles_count >= ?", num_weeks)
|
||||
usernames = []
|
||||
|
|
@ -97,7 +104,8 @@ module BadgeRewarder
|
|||
count = 0
|
||||
num_weeks.times do |i|
|
||||
num = i + 1
|
||||
count += 1 if user.articles.published.where("published_at > ? AND published_at < ?", num.weeks.ago, (num - 1).weeks.ago).any?
|
||||
count += 1 if user.articles.published.where("published_at > ? AND published_at < ?", num.weeks.ago,
|
||||
(num - 1).weeks.ago).any?
|
||||
end
|
||||
usernames << user.username if count >= num_weeks
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,13 +27,15 @@ class Bufferizer
|
|||
end
|
||||
|
||||
def main_tweet!
|
||||
BufferUpdate.buff!(@article.id, twitter_buffer_text, ApplicationConfig["BUFFER_TWITTER_ID"], "twitter", nil, @admin_id)
|
||||
BufferUpdate.buff!(@article.id, twitter_buffer_text, ApplicationConfig["BUFFER_TWITTER_ID"], "twitter", nil,
|
||||
@admin_id)
|
||||
@article.update(last_buffered: Time.current)
|
||||
end
|
||||
|
||||
def facebook_post!
|
||||
BufferUpdate.buff!(@article.id, fb_buffer_text, ApplicationConfig["BUFFER_FACEBOOK_ID"], "facebook", nil, @admin_id)
|
||||
BufferUpdate.buff!(@article.id, fb_buffer_text + social_tags, ApplicationConfig["BUFFER_LINKEDIN_ID"], "linkedin", nil, @admin_id)
|
||||
BufferUpdate.buff!(@article.id, fb_buffer_text + social_tags, ApplicationConfig["BUFFER_LINKEDIN_ID"], "linkedin",
|
||||
nil, @admin_id)
|
||||
@article.update(facebook_last_buffered: Time.current)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ class ErrorMessageCleaner
|
|||
end
|
||||
|
||||
def clean
|
||||
if error_message.include? "expected key while parsing a block mapping at line"
|
||||
"There was a problem parsing the front-matter YAML. Perhaps you need to escape a quote or a colon or something. Email #{SiteConfig.email_addresses[:default]} if you are having trouble."
|
||||
if error_message.include?("expected key while parsing a block mapping at line")
|
||||
"There was a problem parsing the front-matter YAML. Perhaps you need to escape a quote " \
|
||||
"or a colon or something. Email #{SiteConfig.email_addresses[:default]} if you are having trouble."
|
||||
else
|
||||
error_message
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ class FollowChecker
|
|||
def cached_follow_check
|
||||
return false unless follower
|
||||
|
||||
Rails.cache.fetch("user-#{follower.id}-#{follower.updated_at.rfc3339}/is_following_#{followable_type}_#{followable_id}", expires_in: 20.hours) do
|
||||
cache_key = "user-#{follower.id}-#{follower.updated_at.rfc3339}/is_following_#{followable_type}_#{followable_id}"
|
||||
Rails.cache.fetch(cache_key, expires_in: 20.hours) do
|
||||
followable = if followable_type == "Tag"
|
||||
Tag.find(followable_id)
|
||||
elsif followable_type == "Organization"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class GeneratedImage
|
|||
return resource.main_image if resource.main_image.present?
|
||||
return resource.video_thumbnail_url if resource.video_thumbnail_url.present?
|
||||
|
||||
cloudinary_generated_url "/article/#{resource.id}?bust=#{resource.comments_count}-#{resource.title}-#{resource.published}"
|
||||
path = "/article/#{resource.id}?bust=#{resource.comments_count}-#{resource.title}-#{resource.published}"
|
||||
cloudinary_generated_url(path)
|
||||
end
|
||||
|
||||
def cloudinary_generated_url(path)
|
||||
|
|
|
|||
|
|
@ -273,7 +273,9 @@ class MarkdownParser
|
|||
def wrap_all_images_in_links(html)
|
||||
doc = Nokogiri::HTML.fragment(html)
|
||||
doc.search("p img").each do |image|
|
||||
image.swap("<a href='#{image.attr('src')}' class='article-body-image-wrapper'>#{image}</a>") unless image.parent.name == "a"
|
||||
next if image.parent.name == "a"
|
||||
|
||||
image.swap("<a href='#{image.attr('src')}' class='article-body-image-wrapper'>#{image}</a>")
|
||||
end
|
||||
doc.to_html
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
class CodepenTag < LiquidTagBase
|
||||
PARTIAL = "liquids/codepen".freeze
|
||||
URL_REGEXP = /\A(http|https):\/\/(codepen\.io|codepen\.io\/team)\/[a-zA-Z0-9_\-]{1,30}\/pen\/([a-zA-Z]{5,7})\/{0,1}\z/.freeze
|
||||
URL_REGEXP =
|
||||
/\A(http|https):\/\/(codepen\.io|codepen\.io\/team)\/[a-zA-Z0-9_\-]{1,30}\/pen\/([a-zA-Z]{5,7})\/{0,1}\z/.
|
||||
freeze
|
||||
|
||||
def initialize(_tag_name, link, _parse_context)
|
||||
super
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
class CodesandboxTag < LiquidTagBase
|
||||
PARTIAL = "liquids/codesandbox".freeze
|
||||
OPTIONS_REGEXP = /\A(initialpath=([a-zA-Z0-9\-_\/.@%])+)\Z|\A(module=([a-zA-Z0-9\-_\/.@%])+)\Z|\A(runonclick=((0|1){1}))\Z|\Aview=(editor|split|preview)\Z/.freeze
|
||||
OPTIONS_REGEXP =
|
||||
/\A(initialpath=([a-zA-Z0-9\-_\/.@%])+)\Z|
|
||||
\A(module=([a-zA-Z0-9\-_\/.@%])+)\Z|
|
||||
\A(runonclick=((0|1){1}))\Z|
|
||||
\Aview=(editor|split|preview)\Z/x.
|
||||
freeze
|
||||
|
||||
def initialize(_tag_name, id, _parse_context)
|
||||
super
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
class GistTag < LiquidTagBase
|
||||
PARTIAL = "liquids/gist".freeze
|
||||
VALID_LINK_REGEXP =
|
||||
/\Ahttps:\/\/gist\.github\.com\/([a-zA-Z0-9](-?[a-zA-Z0-9]){0,38})\/([a-zA-Z0-9]){1,32}(\/[a-zA-Z0-9]+)?\Z/.
|
||||
freeze
|
||||
|
||||
def initialize(_tag_name, link, _parse_context)
|
||||
super
|
||||
|
|
@ -46,8 +49,7 @@ class GistTag < LiquidTagBase
|
|||
end
|
||||
|
||||
def valid_link?(link)
|
||||
(link =~ /\Ahttps:\/\/gist\.github\.com\/([a-zA-Z0-9](-?[a-zA-Z0-9]){0,38})\/([a-zA-Z0-9]){1,32}(\/[a-zA-Z0-9]+)?\Z/)&.
|
||||
zero?
|
||||
(link =~ VALID_LINK_REGEXP)&.zero?
|
||||
end
|
||||
|
||||
def valid_option?(option)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ class RedditTag < LiquidTagBase
|
|||
validate_url
|
||||
|
||||
# Requests to Reddit require a custom `User-Agent` header to prevent 429 errors
|
||||
json = HTTParty.get("#{@url}.json", headers: { "User-Agent" => "#{ApplicationConfig['COMMUNITY_NAME']} (#{URL.url})" })
|
||||
json = HTTParty.get("#{@url}.json",
|
||||
headers: { "User-Agent" => "#{ApplicationConfig['COMMUNITY_NAME']} (#{URL.url})" })
|
||||
|
||||
# The JSON response is an array with two items.
|
||||
# The first one is the post itself, the second one are the comments
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ class SpotifyTag < LiquidTagBase
|
|||
end
|
||||
|
||||
def raise_error
|
||||
raise StandardError, "Invalid Spotify Link - Be sure you're using the uri of a specific track, album, artist, playlist, or podcast episode."
|
||||
msg = "Invalid Spotify Link - Be sure you're using the uri of a specific track, " \
|
||||
"album, artist, playlist, or podcast episode."
|
||||
raise StandardError, msg
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,10 @@ class StackexchangeTag < LiquidTagBase
|
|||
|
||||
def handle_response_error(response, input)
|
||||
raise StandardError, "Calling StackExchange API failed: #{response&.error_message}" if response.code != 200
|
||||
raise StandardError, "Couldn't find a post with that ID: {% #{tag_name} #{input} %}" if response["items"].length.zero?
|
||||
|
||||
return unless response["items"].length.zero?
|
||||
|
||||
raise StandardError, "Couldn't find a post with that ID: {% #{tag_name} #{input} %}"
|
||||
end
|
||||
|
||||
def get_data(input)
|
||||
|
|
@ -71,13 +74,17 @@ class StackexchangeTag < LiquidTagBase
|
|||
|
||||
id = input.split(" ")[0]
|
||||
|
||||
post_response = HTTParty.get("#{API_URL}posts/#{id}?site=#{@site}&filter=#{FILTERS['post']}&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}")
|
||||
url = "#{API_URL}posts/#{id}?site=#{@site}&filter=#{FILTERS['post']}" \
|
||||
"&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}"
|
||||
post_response = HTTParty.get(url)
|
||||
|
||||
handle_response_error(post_response, input)
|
||||
|
||||
@post_type = post_response["items"][0]["post_type"]
|
||||
|
||||
final_response = HTTParty.get("#{API_URL}#{@post_type.pluralize}/#{id}?site=#{@site}&filter=#{FILTERS[@post_type]}&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}")
|
||||
url = "#{API_URL}#{@post_type.pluralize}/#{id}?site=#{@site}" \
|
||||
"&filter=#{FILTERS[@post_type]}&key=#{ApplicationConfig['STACK_EXCHANGE_APP_KEY']}"
|
||||
final_response = HTTParty.get(url)
|
||||
|
||||
handle_response_error(final_response, input)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ class Article < ApplicationRecord
|
|||
|
||||
has_many :comments, as: :commentable, inverse_of: :commentable
|
||||
has_many :top_comments,
|
||||
-> { where("comments.score > ? AND ancestry IS NULL and hidden_by_commentable_user is FALSE and deleted is FALSE", 10).order("comments.score DESC") },
|
||||
lambda {
|
||||
where(
|
||||
"comments.score > ? AND ancestry IS NULL and hidden_by_commentable_user is FALSE and deleted is FALSE",
|
||||
10,
|
||||
).order("comments.score DESC")
|
||||
},
|
||||
as: :commentable,
|
||||
inverse_of: :commentable,
|
||||
class_name: "Comment"
|
||||
|
|
@ -78,7 +83,9 @@ class Article < ApplicationRecord
|
|||
after_save :bust_cache, :detect_human_language
|
||||
after_save :notify_slack_channel_about_publication
|
||||
|
||||
after_update_commit :update_notifications, if: proc { |article| article.notifications.any? && !article.saved_changes.empty? }
|
||||
after_update_commit :update_notifications, if: proc { |article|
|
||||
article.notifications.any? && !article.saved_changes.empty?
|
||||
}
|
||||
after_commit :async_score_calc, :update_main_image_background_hex, :touch_collection, on: %i[create update]
|
||||
after_commit :index_to_elasticsearch, on: %i[create update]
|
||||
after_commit :sync_related_elasticsearch_docs, on: %i[create update]
|
||||
|
|
@ -167,9 +174,19 @@ class Article < ApplicationRecord
|
|||
order(column => dir.to_sym)
|
||||
}
|
||||
|
||||
scope :feed, -> { published.includes(:taggings).select(:id, :published_at, :processed_html, :user_id, :organization_id, :title, :path, :cached_tag_list) }
|
||||
scope :feed, lambda {
|
||||
published.includes(:taggings).
|
||||
select(
|
||||
:id, :published_at, :processed_html, :user_id, :organization_id, :title, :path, :cached_tag_list
|
||||
)
|
||||
}
|
||||
|
||||
scope :with_video, -> { published.where.not(video: [nil, ""]).where.not(video_thumbnail_url: [nil, ""]).where("score > ?", -4) }
|
||||
scope :with_video, lambda {
|
||||
published.
|
||||
where.not(video: [nil, ""]).
|
||||
where.not(video_thumbnail_url: [nil, ""]).
|
||||
where("score > ?", -4)
|
||||
}
|
||||
|
||||
scope :eager_load_serialized_data, -> { includes(:user, :organization, :tags) }
|
||||
|
||||
|
|
@ -195,8 +212,11 @@ class Article < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.seo_boostable(tag = nil, time_ago = 18.days.ago)
|
||||
time_ago = 5.days.ago if time_ago == "latest" # Time ago sometimes returns this phrase instead of a date
|
||||
time_ago = 75.days.ago if time_ago.nil? # Time ago sometimes is given as nil and should then be the default. I know, sloppy.
|
||||
# Time ago sometimes returns this phrase instead of a date
|
||||
time_ago = 5.days.ago if time_ago == "latest"
|
||||
|
||||
# Time ago sometimes is given as nil and should then be the default. I know, sloppy.
|
||||
time_ago = 75.days.ago if time_ago.nil?
|
||||
|
||||
relation = Article.published.
|
||||
order(organic_page_views_past_month_count: :desc).
|
||||
|
|
@ -353,7 +373,9 @@ class Article < ApplicationRecord
|
|||
private
|
||||
|
||||
def search_score
|
||||
calculated_score = hotness_score.to_i + ((comments_count * 3).to_i + public_reactions_count.to_i * 300 * user.reputation_modifier * score.to_i)
|
||||
comments_score = (comments_count * 3).to_i
|
||||
partial_score = (comments_score + public_reactions_count.to_i * 300 * user.reputation_modifier * score.to_i)
|
||||
calculated_score = hotness_score.to_i + partial_score
|
||||
calculated_score.to_i
|
||||
end
|
||||
|
||||
|
|
@ -457,7 +479,10 @@ class Article < ApplicationRecord
|
|||
self.published_at = parse_date(front_matter["date"]) if published
|
||||
self.main_image = determine_image(front_matter)
|
||||
self.canonical_url = front_matter["canonical_url"] if front_matter["canonical_url"].present?
|
||||
self.description = front_matter["description"] if front_matter["description"].present? || front_matter["title"].present? # Do this if frontmatte exists at all
|
||||
|
||||
update_description = front_matter["description"].present? || front_matter["title"].present?
|
||||
self.description = front_matter["description"] if update_description
|
||||
|
||||
self.collection_id = nil if front_matter["title"].present?
|
||||
self.collection_id = Collection.find_series(front_matter["series"], user).id if front_matter["series"].present?
|
||||
end
|
||||
|
|
@ -497,7 +522,8 @@ class Article < ApplicationRecord
|
|||
end
|
||||
|
||||
def remove_tag_adjustments_from_tag_list
|
||||
tags_to_remove = TagAdjustment.where(article_id: id, adjustment_type: "removal", status: "committed").pluck(:tag_name)
|
||||
tags_to_remove = TagAdjustment.where(article_id: id, adjustment_type: "removal",
|
||||
status: "committed").pluck(:tag_name)
|
||||
tag_list.remove(tags_to_remove, parse: true) if tags_to_remove.present?
|
||||
end
|
||||
|
||||
|
|
@ -510,12 +536,20 @@ class Article < ApplicationRecord
|
|||
end
|
||||
|
||||
def validate_video
|
||||
return errors.add(:published, "cannot be set to true if video is still processing") if published && video_state == "PROGRESSING"
|
||||
return errors.add(:video, "cannot be added by member without permission") if video.present? && user.created_at > 2.weeks.ago
|
||||
if published && video_state == "PROGRESSING"
|
||||
return errors.add(:published,
|
||||
"cannot be set to true if video is still processing")
|
||||
end
|
||||
|
||||
return unless video.present? && user.created_at > 2.weeks.ago
|
||||
|
||||
errors.add(:video, "cannot be added by member without permission")
|
||||
end
|
||||
|
||||
def validate_collection_permission
|
||||
errors.add(:collection_id, "must be one you have permission to post to") if collection && collection.user_id != user_id
|
||||
return unless collection && collection.user_id != user_id
|
||||
|
||||
errors.add(:collection_id, "must be one you have permission to post to")
|
||||
end
|
||||
|
||||
def past_or_present_date
|
||||
|
|
@ -525,7 +559,9 @@ class Article < ApplicationRecord
|
|||
end
|
||||
|
||||
def canonical_url_must_not_have_spaces
|
||||
errors.add(:canonical_url, "must not have spaces") if canonical_url.to_s.match?(/[[:space:]]/)
|
||||
return unless canonical_url.to_s.match?(/[[:space:]]/)
|
||||
|
||||
errors.add(:canonical_url, "must not have spaces")
|
||||
end
|
||||
|
||||
def create_slug
|
||||
|
|
@ -596,9 +632,12 @@ class Article < ApplicationRecord
|
|||
end
|
||||
|
||||
def set_nth_published_at
|
||||
published_article_ids = user.articles.published.order("published_at ASC").pluck(:id)
|
||||
return unless nth_published_by_author.zero? && published
|
||||
|
||||
published_article_ids = user.articles.published.order(published_at: :asc).pluck(:id)
|
||||
index = published_article_ids.index(id)
|
||||
self.nth_published_by_author = (index || published_article_ids.size) + 1 if nth_published_by_author.zero? && published
|
||||
|
||||
self.nth_published_by_author = (index || published_article_ids.size) + 1
|
||||
end
|
||||
|
||||
def title_to_slug
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ class BackupData < ApplicationRecord
|
|||
validates :instance_id, :instance_type, :json_data, presence: true
|
||||
|
||||
def self.backup!(instance)
|
||||
BackupData.create!(instance_type: instance.class.name, instance_id: instance.id, instance_user_id: instance.user_id, json_data: instance.attributes)
|
||||
BackupData.create!(instance_type: instance.class.name, instance_id: instance.id,
|
||||
instance_user_id: instance.user_id, json_data: instance.attributes)
|
||||
end
|
||||
|
||||
def recover!
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ class BufferUpdate < ApplicationRecord
|
|||
validate :validate_body_text_recent_uniqueness, :validate_suggestion_limit
|
||||
validates :status, inclusion: { in: %w[pending sent_direct confirmed dismissed] }
|
||||
|
||||
def self.buff!(article_id, text, buffer_profile_id_code, social_service_name = "twitter", tag_id = nil, admin_id = nil)
|
||||
def self.buff!(
|
||||
article_id, text, buffer_profile_id_code, social_service_name = "twitter", tag_id = nil, admin_id = nil
|
||||
)
|
||||
buffer_response = send_to_buffer(text, buffer_profile_id_code)
|
||||
create(
|
||||
article_id: article_id,
|
||||
|
|
@ -23,7 +25,8 @@ class BufferUpdate < ApplicationRecord
|
|||
buffer_update = BufferUpdate.find(buffer_update_id)
|
||||
if status == "confirmed"
|
||||
buffer_response = send_to_buffer(body_text, buffer_update.buffer_profile_id_code)
|
||||
buffer_update.update!(buffer_response: buffer_response, status: status, approver_user_id: admin_id, body_text: body_text)
|
||||
buffer_update.update!(buffer_response: buffer_response, status: status, approver_user_id: admin_id,
|
||||
body_text: body_text)
|
||||
else
|
||||
buffer_update.update!(status: status, approver_user_id: admin_id)
|
||||
end
|
||||
|
|
@ -56,14 +59,18 @@ class BufferUpdate < ApplicationRecord
|
|||
def validate_body_text_recent_uniqueness
|
||||
return if persisted?
|
||||
|
||||
if BufferUpdate.where(body_text: body_text, article_id: article_id, tag_id: tag_id, social_service_name: social_service_name).
|
||||
where("created_at > ?", 2.minutes.ago).any?
|
||||
errors.add(:body_text, "\"#{body_text}\" has already been submitted very recently")
|
||||
end
|
||||
relation = BufferUpdate.
|
||||
where(body_text: body_text, article_id: article_id, tag_id: tag_id, social_service_name: social_service_name).
|
||||
where("created_at > ?", 2.minutes.ago)
|
||||
|
||||
return unless relation.any?
|
||||
|
||||
errors.add(:body_text, "\"#{body_text}\" has already been submitted very recently")
|
||||
end
|
||||
|
||||
def validate_suggestion_limit
|
||||
return unless BufferUpdate.where(article_id: article_id, tag_id: tag_id, social_service_name: social_service_name).count > 2
|
||||
return unless BufferUpdate.where(article_id: article_id, tag_id: tag_id,
|
||||
social_service_name: social_service_name).count > 2
|
||||
|
||||
errors.add(:article_id, "already has multiple suggestions for #{social_service_name}")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,11 +10,19 @@ class ChatChannel < ApplicationRecord
|
|||
has_many :chat_channel_memberships, dependent: :destroy
|
||||
has_many :users, through: :chat_channel_memberships
|
||||
|
||||
has_many :active_memberships, -> { where status: "active" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :pending_memberships, -> { where status: "pending" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :rejected_memberships, -> { where status: "rejected" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :active_memberships, lambda {
|
||||
where status: "active"
|
||||
}, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :pending_memberships, lambda {
|
||||
where status: "pending"
|
||||
}, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :rejected_memberships, lambda {
|
||||
where status: "rejected"
|
||||
}, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :mod_memberships, -> { where role: "mod" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :requested_memberships, -> { where status: "joining_request" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :requested_memberships, lambda {
|
||||
where status: "joining_request"
|
||||
}, class_name: "ChatChannelMembership", inverse_of: :chat_channel
|
||||
has_many :active_users, through: :active_memberships, class_name: "User", source: :user
|
||||
has_many :pending_users, through: :pending_memberships, class_name: "User", source: :user
|
||||
has_many :rejected_users, through: :rejected_memberships, class_name: "User", source: :user
|
||||
|
|
@ -67,7 +75,12 @@ class ChatChannel < ApplicationRecord
|
|||
raise "Invalid direct channel" if invalid_direct_channel?(users, channel_type)
|
||||
|
||||
usernames = users.map(&:username).sort
|
||||
slug = channel_type == "direct" ? usernames.join("/") : contrived_name.to_s.parameterize + "-" + rand(100_000).to_s(26)
|
||||
slug = if channel_type == "direct"
|
||||
usernames.join("/")
|
||||
else
|
||||
"#{contrived_name.to_s.parameterize}-#{rand(100_000).to_s(26)}"
|
||||
end
|
||||
|
||||
contrived_name = "Direct chat between " + usernames.join(" and ") if channel_type == "direct"
|
||||
channel = find_or_create_chat_channel(channel_type, slug, contrived_name)
|
||||
if channel_type == "direct"
|
||||
|
|
@ -125,7 +138,8 @@ class ChatChannel < ApplicationRecord
|
|||
invitation_sent += 1
|
||||
end
|
||||
else
|
||||
membership = ChatChannelMembership.create(user_id: user.id, chat_channel_id: id, role: membership_role, status: "pending")
|
||||
membership = ChatChannelMembership.create(user_id: user.id, chat_channel_id: id, role: membership_role,
|
||||
status: "pending")
|
||||
if membership.persisted?
|
||||
NotifyMailer.with(membership: membership, inviter: inviter).channel_invite_email.deliver_later
|
||||
invitation_sent += 1
|
||||
|
|
|
|||
|
|
@ -165,7 +165,9 @@ class Comment < ApplicationRecord
|
|||
def wrap_timestamps_if_video_present!
|
||||
return unless commentable_type != "PodcastEpisode" && commentable.video.present?
|
||||
|
||||
self.processed_html = processed_html.gsub(/(([0-9]:)?)(([0-5][0-9]|[0-9])?):[0-5][0-9]/) { |string| "<a href='#{commentable.path}?t=#{string}'>#{string}</a>" }
|
||||
self.processed_html = processed_html.gsub(/(([0-9]:)?)(([0-5][0-9]|[0-9])?):[0-5][0-9]/) do |string|
|
||||
"<a href='#{commentable.path}?t=#{string}'>#{string}</a>"
|
||||
end
|
||||
end
|
||||
|
||||
def shorten_urls!
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ class EmailAuthorization < ApplicationRecord
|
|||
before_create :generate_confirmation_token
|
||||
belongs_to :user
|
||||
|
||||
# uuid_issue is a specific case where a user deletes their old auth account and recreates it,
|
||||
# leaving us with the incorrect uuid
|
||||
TYPES = %w[merge_request account_lockout uuid_issue account_ownership].freeze
|
||||
# uuid_issue is a specific case where a user deletes their old auth account and recreates it, leaving us with the incorrect uuid
|
||||
|
||||
validates :type_of, presence: true
|
||||
validates :type_of, inclusion: { in: TYPES }
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ class HtmlVariant < ApplicationRecord
|
|||
scope :relevant, -> { where(approved: true, published: true) }
|
||||
|
||||
def calculate_success_rate!
|
||||
self.success_rate = html_variant_successes.size.to_f / (html_variant_trials.size * 10.0) # x10 because we only capture every 10th
|
||||
# x10 because we only capture every 10th
|
||||
self.success_rate = html_variant_successes.size.to_f / (html_variant_trials.size * 10.0)
|
||||
save!
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ class Identity < ApplicationRecord
|
|||
|
||||
validates :provider, inclusion: { in: Authentication::Providers.available.map(&:to_s) }
|
||||
validates :uid, :provider, presence: true
|
||||
validates :uid, uniqueness: { scope: :provider }, if: proc { |identity| identity.uid_changed? || identity.provider_changed? }
|
||||
validates :uid, uniqueness: { scope: :provider }, if: proc { |identity|
|
||||
identity.uid_changed? || identity.provider_changed?
|
||||
}
|
||||
validates :user_id, presence: true
|
||||
validates :user_id, uniqueness: { scope: :provider }, if: proc { |identity| identity.user_id_changed? || identity.provider_changed? }
|
||||
validates :user_id, uniqueness: { scope: :provider }, if: proc { |identity|
|
||||
identity.user_id_changed? || identity.provider_changed?
|
||||
}
|
||||
|
||||
# TODO: [thepracticaldev/oss] should this be transitioned to JSON?
|
||||
serialize :auth_data_dump
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
module Languages
|
||||
LIST = { "en" => "English", "ja" => "Japanese", "es" => "Spanish", "fr" => "French", "it" => "Italian", "pt" => "Portuguese" }.freeze
|
||||
LIST = { "en" => "English", "ja" => "Japanese", "es" => "Spanish", "fr" => "French", "it" => "Italian",
|
||||
"pt" => "Portuguese" }.freeze
|
||||
|
||||
module_function
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,10 @@ class Listing < ApplicationRecord
|
|||
|
||||
def restrict_markdown_input
|
||||
markdown_string = body_markdown.to_s
|
||||
errors.add(:body_markdown, "has too many linebreaks. No more than 12 allowed.") if markdown_string.scan(/(?=\n)/).count > 12
|
||||
if markdown_string.scan(/(?=\n)/).count > 12
|
||||
errors.add(:body_markdown,
|
||||
"has too many linebreaks. No more than 12 allowed.")
|
||||
end
|
||||
errors.add(:body_markdown, "is not allowed to include images.") if markdown_string.include?("![")
|
||||
errors.add(:body_markdown, "is not allowed to include liquid tags.") if markdown_string.include?("{% ")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ class Message < ApplicationRecord
|
|||
html
|
||||
end
|
||||
|
||||
# rubocop:disable Layout/LineLength
|
||||
def append_rich_links(html)
|
||||
doc = Nokogiri::HTML(html)
|
||||
doc.css("a").each do |anchor|
|
||||
|
|
@ -143,6 +144,7 @@ class Message < ApplicationRecord
|
|||
end
|
||||
html
|
||||
end
|
||||
# rubocop:enable Layout/LineLength
|
||||
|
||||
def handle_slash_command(html)
|
||||
response = if html.to_s.strip == "<p>/call</p>"
|
||||
|
|
@ -195,15 +197,21 @@ class Message < ApplicationRecord
|
|||
end
|
||||
|
||||
def rich_link_article(link)
|
||||
Article.find_by(slug: link["href"].split("/")[4].split("?")[0]) if link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/") && link["href"].split("/")[4]
|
||||
return unless link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/") && link["href"].split("/")[4]
|
||||
|
||||
Article.find_by(slug: link["href"].split("/")[4].split("?")[0])
|
||||
end
|
||||
|
||||
def rich_link_tag(link)
|
||||
Tag.find_by(name: link["href"].split("/t/")[1].split("/")[0]) if link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/t/")
|
||||
return unless link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/t/")
|
||||
|
||||
Tag.find_by(name: link["href"].split("/t/")[1].split("/")[0])
|
||||
end
|
||||
|
||||
def rich_user_link(link)
|
||||
User.find_by(username: link["href"].split("/")[3].split("/")[0]) if link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/")
|
||||
return unless link["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/")
|
||||
|
||||
User.find_by(username: link["href"].split("/")[3].split("/")[0])
|
||||
end
|
||||
|
||||
def send_email_if_appropriate
|
||||
|
|
|
|||
|
|
@ -39,12 +39,21 @@ class Page < ApplicationRecord
|
|||
end
|
||||
|
||||
def body_present
|
||||
errors.add(:body_markdown, "must exist if body_html or body_json doesn't exist.") if body_markdown.blank? && body_html.blank? && body_json.blank?
|
||||
return unless body_markdown.blank? && body_html.blank? && body_json.blank?
|
||||
|
||||
errors.add(:body_markdown, "must exist if body_html or body_json doesn't exist.")
|
||||
end
|
||||
|
||||
def unique_slug_including_users_and_orgs
|
||||
slug_exists = User.exists?(username: slug) || Organization.exists?(slug: slug) || Podcast.exists?(slug: slug) || slug.include?("sitemap-")
|
||||
errors.add(:slug, "is taken.") if slug_exists
|
||||
slug_exists = (
|
||||
User.exists?(username: slug) ||
|
||||
Organization.exists?(slug: slug) ||
|
||||
Podcast.exists?(slug: slug) ||
|
||||
slug.include?("sitemap-")
|
||||
)
|
||||
return unless slug_exists
|
||||
|
||||
errors.add(:slug, "is taken.")
|
||||
end
|
||||
|
||||
def bust_cache
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ class PathRedirect < ApplicationRecord
|
|||
validates :new_path, presence: true
|
||||
|
||||
# Validate old_path != new_path
|
||||
validates :old_path, exclusion: { in: ->(path_redirect) { [path_redirect.new_path] }, message: "the old_path cannot be the same as the new_path" }
|
||||
validates :old_path, exclusion: { in: lambda { |path_redirect|
|
||||
[path_redirect.new_path]
|
||||
}, message: "the old_path cannot be the same as the new_path" }
|
||||
|
||||
validates :source, inclusion: { in: SOURCES }, allow_blank: true
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ class PollVote < ApplicationRecord
|
|||
counter_culture :poll_option
|
||||
counter_culture :poll
|
||||
|
||||
validates :poll_id, presence: true, uniqueness: { scope: :user_id } # In the future we'll remove this constraint if/when we allow multi-answer polls
|
||||
# In the future we'll remove this constraint if/when we allow multi-answer polls
|
||||
validates :poll_id, presence: true, uniqueness: { scope: :user_id }
|
||||
|
||||
validates :poll_option_id, presence: true, uniqueness: { scope: :user_id }
|
||||
validate :one_vote_per_poll_per_user
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class RatingVote < ApplicationRecord
|
|||
private
|
||||
|
||||
def permissions
|
||||
errors.add(:user_id, "is not permitted to take this action.") if context == "explicit" && !user&.trusted && user_id != article&.user_id
|
||||
return unless context == "explicit" && !user&.trusted && user_id != article&.user_id
|
||||
|
||||
errors.add(:user_id, "is not permitted to take this action.")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ class Reaction < ApplicationRecord
|
|||
|
||||
def cached_any_reactions_for?(reactable, user, category)
|
||||
class_name = reactable.class.name == "ArticleDecorator" ? "Article" : reactable.class.name
|
||||
cache_name = "any_reactions_for-#{class_name}-#{reactable.id}-#{user.reactions_count}-#{user.public_reactions_count}-#{category}"
|
||||
cache_name = "any_reactions_for-#{class_name}-#{reactable.id}-" \
|
||||
"#{user.reactions_count}-#{user.public_reactions_count}-#{category}"
|
||||
Rails.cache.fetch(cache_name, expires_in: 24.hours) do
|
||||
Reaction.where(reactable_id: reactable.id, reactable_type: class_name, user: user, category: category).any?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ class SiteConfig < RailsSettings::Base
|
|||
|
||||
# User Experience
|
||||
# These are the default UX settings, which can be overridded by individual user preferences.
|
||||
field :feed_style, type: :string, default: "basic" # basic (current default), rich (cover image on all posts), compact (more minimal)
|
||||
# basic (current default), rich (cover image on all posts), compact (more minimal)
|
||||
field :feed_style, type: :string, default: "basic"
|
||||
|
||||
# Broadcast
|
||||
field :welcome_notifications_live_at, type: :date
|
||||
|
|
|
|||
|
|
@ -29,7 +29,12 @@ class TagAdjustment < ApplicationRecord
|
|||
end
|
||||
|
||||
def article_tag_list
|
||||
errors.add(:tag_id, "selected for removal is not a current live tag.") if adjustment_type == "removal" && article.tag_list.none? { |tag| tag.casecmp(tag_name).zero? }
|
||||
if adjustment_type == "removal" && article.tag_list.none? do |tag|
|
||||
tag.casecmp(tag_name).zero?
|
||||
end
|
||||
errors.add(:tag_id,
|
||||
"selected for removal is not a current live tag.")
|
||||
end
|
||||
errors.add(:base, "4 tags max per article.") if adjustment_type == "addition" && article.tag_list.count > 3
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@ class User < ApplicationRecord
|
|||
FONTS = %w[default sans_serif monospace comic_sans open_dyslexic].freeze
|
||||
GITLAB_URL_REGEXP = /\A(http(s)?:\/\/)?(www.gitlab.com|gitlab.com)\/.*\z/.freeze
|
||||
INBOXES = %w[open private].freeze
|
||||
INSTAGRAM_URL_REGEXP = /\A(http(s)?:\/\/)?(?:www.)?instagram.com\/(?=.{1,30}\/?$)([a-zA-Z\d_]\.?)*[a-zA-Z\d_]+\/?\z/.freeze
|
||||
INSTAGRAM_URL_REGEXP =
|
||||
/\A(http(s)?:\/\/)?(?:www.)?instagram.com\/(?=.{1,30}\/?$)([a-zA-Z\d_]\.?)*[a-zA-Z\d_]+\/?\z/.freeze
|
||||
|
||||
LINKEDIN_URL_REGEXP = /\A(http(s)?:\/\/)?(www.linkedin.com|linkedin.com|[A-Za-z]{2}.linkedin.com)\/.*\z/.freeze
|
||||
MEDIUM_URL_REGEXP = /\A(http(s)?:\/\/)?(www.medium.com|medium.com)\/.*\z/.freeze
|
||||
NAVBARS = %w[default static].freeze
|
||||
STACKOVERFLOW_URL_REGEXP = /\A(http(s)?:\/\/)?(((www|pt|ru|es|ja).)?stackoverflow.com|(www.)?stackexchange.com)\/.*\z/.freeze
|
||||
STACKOVERFLOW_URL_REGEXP =
|
||||
/\A(http(s)?:\/\/)?(((www|pt|ru|es|ja).)?stackoverflow.com|(www.)?stackexchange.com)\/.*\z/.freeze
|
||||
|
||||
YOUTUBE_URL_REGEXP = /\A(http(s)?:\/\/)?(www.youtube.com|youtube.com)\/.*\z/.freeze
|
||||
STREAMING_PLATFORMS = %w[twitch].freeze
|
||||
THEMES = %w[default night_theme pink_theme minimal_light_theme ten_x_hacker_theme].freeze
|
||||
|
|
@ -45,22 +49,30 @@ class User < ApplicationRecord
|
|||
acts_as_followable
|
||||
acts_as_follower
|
||||
|
||||
has_many :source_authored_user_subscriptions, class_name: "UserSubscription", foreign_key: :author_id, inverse_of: :author, dependent: :destroy
|
||||
has_many :source_authored_user_subscriptions, class_name: "UserSubscription",
|
||||
foreign_key: :author_id, inverse_of: :author, dependent: :destroy
|
||||
has_many :subscribers, through: :source_authored_user_subscriptions, dependent: :destroy
|
||||
has_many :subscribed_to_user_subscriptions, class_name: "UserSubscription", foreign_key: :subscriber_id, inverse_of: :subscriber, dependent: :destroy
|
||||
has_many :subscribed_to_user_subscriptions, class_name: "UserSubscription",
|
||||
foreign_key: :subscriber_id, inverse_of: :subscriber, dependent: :destroy
|
||||
|
||||
has_many :access_grants, class_name: "Doorkeeper::AccessGrant", foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :delete_all
|
||||
has_many :access_tokens, class_name: "Doorkeeper::AccessToken", foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :delete_all
|
||||
has_many :affected_feedback_messages, class_name: "FeedbackMessage", inverse_of: :affected, foreign_key: :affected_id, dependent: :nullify
|
||||
has_many :access_grants, class_name: "Doorkeeper::AccessGrant",
|
||||
foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :delete_all
|
||||
has_many :access_tokens, class_name: "Doorkeeper::AccessToken",
|
||||
foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :delete_all
|
||||
has_many :affected_feedback_messages, class_name: "FeedbackMessage",
|
||||
inverse_of: :affected, foreign_key: :affected_id, dependent: :nullify
|
||||
has_many :api_secrets, dependent: :destroy
|
||||
has_many :articles, dependent: :destroy
|
||||
has_many :audit_logs, dependent: :nullify
|
||||
has_many :authored_notes, inverse_of: :author, class_name: "Note", foreign_key: :author_id, dependent: :delete_all
|
||||
has_many :backup_data, foreign_key: "instance_user_id", inverse_of: :instance_user, class_name: "BackupData", dependent: :delete_all
|
||||
has_many :backup_data, foreign_key: "instance_user_id",
|
||||
inverse_of: :instance_user, class_name: "BackupData", dependent: :delete_all
|
||||
has_many :badge_achievements, dependent: :destroy
|
||||
has_many :badges, through: :badge_achievements
|
||||
has_many :blocked_blocks, class_name: "UserBlock", foreign_key: :blocked_id, inverse_of: :blocked, dependent: :delete_all
|
||||
has_many :blocker_blocks, class_name: "UserBlock", foreign_key: :blocker_id, inverse_of: :blocker, dependent: :delete_all
|
||||
has_many :blocked_blocks, class_name: "UserBlock",
|
||||
foreign_key: :blocked_id, inverse_of: :blocked, dependent: :delete_all
|
||||
has_many :blocker_blocks, class_name: "UserBlock",
|
||||
foreign_key: :blocker_id, inverse_of: :blocker, dependent: :delete_all
|
||||
has_many :chat_channel_memberships, dependent: :destroy
|
||||
has_many :chat_channels, through: :chat_channel_memberships
|
||||
has_many :listings, dependent: :destroy
|
||||
|
|
@ -82,7 +94,8 @@ class User < ApplicationRecord
|
|||
has_many :notification_subscriptions, dependent: :destroy
|
||||
has_many :user_optional_fields, dependent: :destroy
|
||||
has_many :notifications, dependent: :destroy
|
||||
has_many :offender_feedback_messages, class_name: "FeedbackMessage", inverse_of: :offender, foreign_key: :offender_id, dependent: :nullify
|
||||
has_many :offender_feedback_messages, class_name: "FeedbackMessage",
|
||||
inverse_of: :offender, foreign_key: :offender_id, dependent: :nullify
|
||||
has_many :organization_memberships, dependent: :destroy
|
||||
has_many :organizations, through: :organization_memberships
|
||||
has_many :page_views, dependent: :destroy
|
||||
|
|
@ -91,7 +104,8 @@ class User < ApplicationRecord
|
|||
has_many :profile_pins, as: :profile, inverse_of: :profile, dependent: :delete_all
|
||||
has_many :rating_votes, dependent: :destroy
|
||||
has_many :reactions, dependent: :destroy
|
||||
has_many :reporter_feedback_messages, class_name: "FeedbackMessage", inverse_of: :reporter, foreign_key: :reporter_id, dependent: :nullify
|
||||
has_many :reporter_feedback_messages, class_name: "FeedbackMessage",
|
||||
inverse_of: :reporter, foreign_key: :reporter_id, dependent: :nullify
|
||||
has_many :response_templates, inverse_of: :user, dependent: :destroy
|
||||
has_many :tweets, dependent: :destroy
|
||||
has_many :webhook_endpoints, class_name: "Webhook::Endpoint", inverse_of: :user, dependent: :delete_all
|
||||
|
|
@ -236,7 +250,8 @@ class User < ApplicationRecord
|
|||
else
|
||||
languages = []
|
||||
language_settings.each_key do |setting|
|
||||
languages << setting.split("prefer_language_")[1] if language_settings[setting] && setting.include?("prefer_language_")
|
||||
to_split = language_settings[setting] && setting.include?("prefer_language_")
|
||||
languages << setting.split("prefer_language_")[1] if to_split
|
||||
end
|
||||
@preferred_languages_array = languages
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ class UserSubscription < ApplicationRecord
|
|||
|
||||
validates :author_id, presence: true
|
||||
validates :subscriber_email, presence: true
|
||||
validates :subscriber_id, presence: true, uniqueness: { scope: %i[subscriber_email user_subscription_sourceable_type user_subscription_sourceable_id] }
|
||||
validates :subscriber_id, presence: true, uniqueness: { scope: %i[subscriber_email user_subscription_sourceable_type
|
||||
user_subscription_sourceable_id] }
|
||||
validates :user_subscription_sourceable_id, presence: true
|
||||
validates :user_subscription_sourceable_type, presence: true, inclusion: { in: ALLOWED_TYPES }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ class LiquidTagPolicy
|
|||
return true unless record.class.const_defined?("VALID_ROLES")
|
||||
raise Pundit::NotAuthorizedError, "No user found" unless user
|
||||
# Manually raise error to use a custom error message
|
||||
raise Pundit::NotAuthorizedError, "User is not permitted to use this liquid tag" unless user_permitted_to_use_liquid_tag?
|
||||
raise Pundit::NotAuthorizedError, "User is not permitted to use this liquid tag" unless user_allowed_to_use_tag?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_permitted_to_use_liquid_tag?
|
||||
def user_allowed_to_use_tag?
|
||||
record.class::VALID_ROLES.any? { |valid_role| user_has_valid_role?(valid_role) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
class RenderedMarkdownScrubber < Rails::Html::PermitScrubber
|
||||
def initialize
|
||||
super
|
||||
self.tags = %w[a abbr add aside b blockquote br button center cite code col colgroup dd del dl dt em em figcaption h1 h2 h3 h4 h5 h6 hr i img kbd li mark ol p pre q rp rt ruby small source span strong sub sup table tbody td tfoot th thead time tr u ul video]
|
||||
self.attributes = %w[alt class colspan data-conversation data-lang data-no-instant data-url em height href id loop name ref rel rowspan size span src start strong title type value width]
|
||||
|
||||
self.tags = %w[
|
||||
a abbr add aside b blockquote br button center cite code col colgroup dd del dl dt em em figcaption
|
||||
h1 h2 h3 h4 h5 h6 hr i img kbd li mark ol p pre q rp rt ruby small source span strong sub sup table
|
||||
tbody td tfoot th thead time tr u ul video
|
||||
]
|
||||
|
||||
self.attributes = %w[
|
||||
alt class colspan data-conversation data-lang data-no-instant data-url em height href id loop
|
||||
name ref rel rowspan size span src start strong title type value width
|
||||
]
|
||||
end
|
||||
|
||||
def allowed_node?(node)
|
||||
|
|
|
|||
|
|
@ -90,8 +90,11 @@ module Articles
|
|||
end
|
||||
|
||||
def user_editor_v1
|
||||
body = "---\ntitle: \npublished: false\ndescription: " \
|
||||
"\ntags: \n//cover_image: https://direct_url_to_image.jpg\n---\n\n"
|
||||
|
||||
Article.new(
|
||||
body_markdown: "---\ntitle: \npublished: false\ndescription: \ntags: \n//cover_image: https://direct_url_to_image.jpg\n---\n\n",
|
||||
body_markdown: body,
|
||||
processed_html: "",
|
||||
user_id: @user&.id,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ module Articles
|
|||
article = save_article
|
||||
|
||||
if article.persisted?
|
||||
NotificationSubscription.create(user: user, notifiable_id: article.id, notifiable_type: "Article", config: "all_comments")
|
||||
NotificationSubscription.create(user: user, notifiable_id: article.id, notifiable_type: "Article",
|
||||
config: "all_comments")
|
||||
Notification.send_to_followers(article, "Published") if article.published?
|
||||
|
||||
dispatch_event(article)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ module Articles
|
|||
def call(article, event_dispatcher = Webhook::DispatchEvent)
|
||||
article.destroy!
|
||||
Notification.remove_all_without_delay(notifiable_ids: article.id, notifiable_type: "Article")
|
||||
Notification.remove_all(notifiable_ids: article.comments.pluck(:id), notifiable_type: "Comment") if article.comments.exists?
|
||||
if article.comments.exists?
|
||||
Notification.remove_all(notifiable_ids: article.comments.pluck(:id),
|
||||
notifiable_type: "Comment")
|
||||
end
|
||||
event_dispatcher.call("article_destroyed", article) if article.published?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ module Articles
|
|||
end
|
||||
|
||||
def published_articles_by_tag
|
||||
articles = Article.published.limited_column_select.includes(top_comments: :user).page(@page).per(@number_of_articles)
|
||||
articles = Article.published.limited_column_select.
|
||||
includes(top_comments: :user).
|
||||
page(@page).per(@number_of_articles)
|
||||
articles = articles.cached_tagged_with(@tag) if @tag.present? # More efficient than tagged_with
|
||||
articles
|
||||
end
|
||||
|
|
@ -240,7 +242,9 @@ module Articles
|
|||
featured_story = hot_stories.where.not(main_image: nil).first
|
||||
if user_signed_in
|
||||
hot_story_count = hot_stories.count
|
||||
offset = RANDOM_OFFSET_VALUES.select { |i| i < hot_story_count }.sample # random offset, weighted more towards zero
|
||||
offset = RANDOM_OFFSET_VALUES.select do |i|
|
||||
i < hot_story_count
|
||||
end.sample # random offset, weighted more towards zero
|
||||
hot_stories = hot_stories.offset(offset)
|
||||
new_stories = Article.published.
|
||||
where("score > ?", -15).
|
||||
|
|
|
|||
|
|
@ -44,8 +44,12 @@ module Articles
|
|||
|
||||
# remove related notifications if unpublished
|
||||
if article.saved_changes["published"] == [true, false]
|
||||
Notification.remove_all_by_action_without_delay(notifiable_ids: article.id, notifiable_type: "Article", action: "Published")
|
||||
Notification.remove_all(notifiable_ids: article.comments.pluck(:id), notifiable_type: "Comment") if article.comments.exists?
|
||||
Notification.remove_all_by_action_without_delay(notifiable_ids: article.id, notifiable_type: "Article",
|
||||
action: "Published")
|
||||
if article.comments.exists?
|
||||
Notification.remove_all(notifiable_ids: article.comments.pluck(:id),
|
||||
notifiable_type: "Comment")
|
||||
end
|
||||
end
|
||||
# don't send only if article keeps being unpublished
|
||||
dispatch_event(article) if article.published || was_published
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ module Broadcasts
|
|||
attr_reader :user, :notification_enqueued
|
||||
|
||||
def send_welcome_notification
|
||||
return if user.created_at > 3.hours.ago || received_notification?(welcome_broadcast) || commented_on_welcome_thread?
|
||||
return if
|
||||
user.created_at > 3.hours.ago ||
|
||||
received_notification?(welcome_broadcast) ||
|
||||
commented_on_welcome_thread?
|
||||
|
||||
Notification.send_welcome_notification(user.id, welcome_broadcast.id)
|
||||
# Setting @notification_enqueued here prevents us from sending a user two welcome notifications in one day.
|
||||
|
|
@ -36,14 +39,20 @@ module Broadcasts
|
|||
end
|
||||
|
||||
def send_authentication_notification
|
||||
return if user.created_at > 1.day.ago || authenticated_with_all_providers? || received_notification?(authentication_broadcast)
|
||||
return if
|
||||
user.created_at > 1.day.ago ||
|
||||
authenticated_with_all_providers? ||
|
||||
received_notification?(authentication_broadcast)
|
||||
|
||||
Notification.send_welcome_notification(user.id, authentication_broadcast.id)
|
||||
@notification_enqueued = true
|
||||
end
|
||||
|
||||
def send_feed_customization_notification
|
||||
return if user.created_at > 3.days.ago || user_is_following_tags? || received_notification?(customize_feed_broadcast)
|
||||
return if
|
||||
user.created_at > 3.days.ago ||
|
||||
user_is_following_tags? ||
|
||||
received_notification?(customize_feed_broadcast)
|
||||
|
||||
Notification.send_welcome_notification(user.id, customize_feed_broadcast.id)
|
||||
@notification_enqueued = true
|
||||
|
|
@ -57,7 +66,10 @@ module Broadcasts
|
|||
end
|
||||
|
||||
def send_discuss_and_ask_notification
|
||||
return if user.created_at > 6.days.ago || (asked_a_question && started_a_discussion) || received_notification?(discuss_and_ask_broadcast)
|
||||
return if
|
||||
user.created_at > 6.days.ago ||
|
||||
(asked_a_question && started_a_discussion) ||
|
||||
received_notification?(discuss_and_ask_broadcast)
|
||||
|
||||
Notification.send_welcome_notification(user.id, discuss_and_ask_broadcast.id)
|
||||
@notification_enqueued = true
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ module Credits
|
|||
# to avoid N+1 on purchases, we load them by type separately
|
||||
purchase_types = credits_purchases_with_purchase.map(&:purchase_type).uniq.compact
|
||||
purchase_types.each do |purchase_type|
|
||||
credits_purchases_by_type = credits_purchases_with_purchase.select { |row| row.purchase_type == purchase_type }
|
||||
credits_purchases_by_type = credits_purchases_with_purchase.select do |row|
|
||||
row.purchase_type == purchase_type
|
||||
end
|
||||
purchase_set = purchase_type.constantize.where(id: credits_purchases_by_type.map(&:purchase_id))
|
||||
credits_purchases_by_type.each do |credit_purchase|
|
||||
purchase = purchase_set.detect { |set| set.id == credit_purchase.purchase_id }
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ module Mentions
|
|||
def create_mention_for(user)
|
||||
return if user_has_comment_notifications?(user)
|
||||
|
||||
mention = Mention.create(user_id: user.id, mentionable_id: @notifiable.id, mentionable_type: @notifiable.class.name)
|
||||
mention = Mention.create(user_id: user.id, mentionable_id: @notifiable.id,
|
||||
mentionable_type: @notifiable.class.name)
|
||||
# mentionable_type = model that created the mention, user = user to be mentioned
|
||||
Notification.send_mention_notification(mention)
|
||||
mention
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ module Moderator
|
|||
new_name = "spam_#{rand(1_000_000)}"
|
||||
new_username = "spam_#{rand(1_000_000)}"
|
||||
end
|
||||
user.update_columns(name: new_name, username: new_username, old_username: user.username, profile_updated_at: Time.current)
|
||||
user.update_columns(name: new_name, username: new_username, old_username: user.username,
|
||||
profile_updated_at: Time.current)
|
||||
CacheBuster.bust("/#{user.old_username}")
|
||||
end
|
||||
|
||||
|
|
@ -42,7 +43,8 @@ module Moderator
|
|||
twitter_username: nil, github_username: nil, website_url: "", summary: "",
|
||||
location: "", education: "", employer_name: "", employer_url: "", employment_title: "",
|
||||
mostly_work_with: "", currently_learning: "", currently_hacking_on: "", available_for: "",
|
||||
email_public: false, facebook_url: nil, youtube_url: nil, dribbble_url: nil, medium_url: nil, stackoverflow_url: nil,
|
||||
email_public: false, facebook_url: nil, youtube_url: nil, dribbble_url: nil,
|
||||
medium_url: nil, stackoverflow_url: nil,
|
||||
behance_url: nil, linkedin_url: nil, gitlab_url: nil, instagram_url: nil, mastodon_url: nil,
|
||||
twitch_url: nil, feed_url: nil
|
||||
)
|
||||
|
|
|
|||
|
|
@ -31,9 +31,15 @@ module Moderator
|
|||
private
|
||||
|
||||
def handle_identities
|
||||
raise "The user being deleted already has two identities. Are you sure this is the right user to be deleted? If so, a super admin will need to do this from the console to be safe." if @delete_user.identities.count > 1
|
||||
error_message = "The user being deleted already has two identities. " \
|
||||
"Are you sure this is the right user to be deleted? " \
|
||||
"If so, a super admin will need to do this from the console to be safe."
|
||||
raise error_message if @delete_user.identities.count.positive?
|
||||
|
||||
return true if @keep_user.identities.count > 1 || @delete_user.identities.none? || @keep_user.identities.last.provider == @delete_user.identities.last.provider
|
||||
return true if
|
||||
@keep_user.identities.count.positive? ||
|
||||
@delete_user.identities.none? ||
|
||||
@keep_user.identities.last.provider == @delete_user.identities.last.provider
|
||||
|
||||
@delete_user.identities.first.update_columns(user_id: @keep_user.id)
|
||||
end
|
||||
|
|
@ -63,7 +69,8 @@ module Moderator
|
|||
end
|
||||
|
||||
def merge_chat_mentions
|
||||
@delete_user.chat_channel_memberships.update_all(user_id: @keep_user.id) if @delete_user.chat_channel_memberships.any?
|
||||
any_memberships = @delete_user.chat_channel_memberships.any?
|
||||
@delete_user.chat_channel_memberships.update_all(user_id: @keep_user.id) if any_memberships
|
||||
@delete_user.mentions.update_all(user_id: @keep_user.id) if @delete_user.mentions.any?
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,12 @@ module Notifications
|
|||
|
||||
def followers
|
||||
followers = notifiable.user.followers_scoped.where(subscription_status: "all_articles").map(&:follower)
|
||||
followers += notifiable.organization.followers_scoped.where(subscription_status: "all_articles").map(&:follower) if notifiable.organization_id
|
||||
|
||||
if notifiable.organization_id
|
||||
org_followers = notifiable.organization.followers_scoped.where(subscription_status: "all_articles")
|
||||
followers += org_followers.map(&:follower)
|
||||
end
|
||||
|
||||
followers.uniq.compact
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@ module Notifications
|
|||
def call
|
||||
return unless receiver.is_a?(User) || receiver.is_a?(Organization)
|
||||
|
||||
reaction_siblings = Reaction.public_category.where(reactable_id: reaction.reactable_id, reactable_type: reaction.reactable_type).
|
||||
reaction_siblings = Reaction.public_category.where(reactable_id: reaction.reactable_id,
|
||||
reactable_type: reaction.reactable_type).
|
||||
where.not(reactions: { user_id: reaction.reactable_user_id }).
|
||||
preload(:reactable).includes(:user).where.not(users: { id: nil }).
|
||||
order("reactions.created_at DESC")
|
||||
|
||||
aggregated_reaction_siblings = reaction_siblings.map { |reaction| { category: reaction.category, created_at: reaction.created_at, user: user_data(reaction.user) } }
|
||||
aggregated_reaction_siblings = reaction_siblings.map do |reaction|
|
||||
{ category: reaction.category, created_at: reaction.created_at, user: user_data(reaction.user) }
|
||||
end
|
||||
|
||||
notification_params = {
|
||||
notifiable_type: reaction.reactable_type,
|
||||
|
|
@ -50,7 +53,10 @@ module Notifications
|
|||
|
||||
previous_siblings_size = 0
|
||||
notification = Notification.find_or_initialize_by(notification_params)
|
||||
previous_siblings_size = notification.json_data["reaction"]["aggregated_siblings"].size if notification.json_data
|
||||
|
||||
old_json_data = notification.json_data
|
||||
previous_siblings_size = notification.json_data["reaction"]["aggregated_siblings"].size if old_json_data
|
||||
|
||||
notification.json_data = json_data
|
||||
notification.notified_at = Time.current
|
||||
notification.read = false if json_data[:reaction][:aggregated_siblings].size > previous_siblings_size
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ module Search
|
|||
class ChatChannelMembership < Base
|
||||
INDEX_NAME = "chat_channel_memberships_#{Rails.env}".freeze
|
||||
INDEX_ALIAS = "chat_channel_memberships_#{Rails.env}_alias".freeze
|
||||
MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/chat_channel_memberships.json"), symbolize_names: true).freeze
|
||||
MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/chat_channel_memberships.json"),
|
||||
symbolize_names: true).freeze
|
||||
DEFAULT_PAGE = 0
|
||||
DEFAULT_PER_PAGE = 30
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ module Streams
|
|||
end
|
||||
|
||||
def call
|
||||
user_resp = HTTParty.get("https://api.twitch.tv/helix/users", query: { login: user.twitch_username }, headers: authentication_request_headers)
|
||||
user_resp = HTTParty.get("https://api.twitch.tv/helix/users", query: { login: user.twitch_username },
|
||||
headers: authentication_request_headers)
|
||||
|
||||
# user_resp["data"].first["id"]
|
||||
twitch_user_id = user_resp.try(:[], "data").to_a.first.try(:[], "id")
|
||||
|
|
@ -45,7 +46,8 @@ module Streams
|
|||
end
|
||||
|
||||
def twitch_stream_updates_url_for_user(user)
|
||||
Rails.application.routes.url_helpers.user_twitch_stream_updates_url(user_id: user.id, host: ApplicationConfig["APP_DOMAIN"])
|
||||
Rails.application.routes.url_helpers.user_twitch_stream_updates_url(user_id: user.id,
|
||||
host: ApplicationConfig["APP_DOMAIN"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ class TagAdjustmentCreationService
|
|||
article.update!(tag_list: article.tag_list.remove(removed_tags))
|
||||
end
|
||||
|
||||
article.update!(tag_list: article.tag_list.add(@tag_adjustment.tag_name)) if @tag_adjustment.adjustment_type == "addition"
|
||||
update_tag_list = @tag_adjustment.adjustment_type == "addition"
|
||||
article.update!(tag_list: article.tag_list.add(@tag_adjustment.tag_name)) if update_tag_list
|
||||
end
|
||||
|
||||
def creation_args
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ module UserSubscriptions
|
|||
end
|
||||
|
||||
def call
|
||||
cache_key = "user-#{user.id}-#{user.updated_at.rfc3339}-#{user.subscribed_to_user_subscriptions_count}/is_subscribed_#{source_type}_#{source_id}"
|
||||
cache_key = "user-#{user.id}-#{user.updated_at.rfc3339}-#{user.subscribed_to_user_subscriptions_count}/" \
|
||||
"is_subscribed_#{source_type}_#{source_id}"
|
||||
Rails.cache.fetch(cache_key, expires_in: 24.hours) do
|
||||
UserSubscription.where(
|
||||
subscriber_id: user.id,
|
||||
|
|
|
|||
|
|
@ -6,11 +6,21 @@ module Metrics
|
|||
def perform
|
||||
# Articles published in the past 24 hours with at least 15 "score" (positive/negative reactions)
|
||||
articles_min_15_score_past_24h = Article.published.where("score >= ? AND published_at > ?", 15, 1.day.ago).size
|
||||
DatadogStatsClient.count("articles.min_15_score_past_24h", articles_min_15_score_past_24h, tags: ["resource:articles"])
|
||||
DatadogStatsClient.count(
|
||||
"articles.min_15_score_past_24h",
|
||||
articles_min_15_score_past_24h,
|
||||
tags: ["resource:articles"],
|
||||
)
|
||||
|
||||
# Articles published in the past 24 hours with at least 15 "score" (positive/negative reactions)
|
||||
articles_min_15_comment_score_past_24h = Article.published.where("comment_score >= ? AND published_at > ?", 15, 1.day.ago).size
|
||||
DatadogStatsClient.count("articles.min_15_comment_score_past_24h", articles_min_15_comment_score_past_24h, tags: ["resource:articles"])
|
||||
articles_min_15_comment_score_past_24h = Article.published.
|
||||
where("comment_score >= ? AND published_at > ?", 15, 1.day.ago).
|
||||
size
|
||||
DatadogStatsClient.count(
|
||||
"articles.min_15_comment_score_past_24h",
|
||||
articles_min_15_comment_score_past_24h,
|
||||
tags: ["resource:articles"],
|
||||
)
|
||||
|
||||
# Articles published in the past 24 which were that user's first article
|
||||
first_articles_past_24h = Article.where(nth_published_by_author: 1).where("published_at > ?", 1.day.ago).size
|
||||
|
|
@ -18,17 +28,27 @@ module Metrics
|
|||
|
||||
# Users who signed up in the past 24 hours who have made at least 1 comment so far
|
||||
new_users_min_1_comment_past_24h = User.where("comments_count >= ? AND created_at > ?", 1, 24.hours.ago).size
|
||||
DatadogStatsClient.count("users.new_min_1_comment_past_24h", new_users_min_1_comment_past_24h, tags: ["resource:users"])
|
||||
DatadogStatsClient.count(
|
||||
"users.new_min_1_comment_past_24h",
|
||||
new_users_min_1_comment_past_24h,
|
||||
tags: ["resource:users"],
|
||||
)
|
||||
|
||||
# Total negative reactions in the past 24 hours
|
||||
nagative_reactions_past_24h = Reaction.where("points < 0").where("created_at > ?", 1.day.ago).size
|
||||
DatadogStatsClient.count("reactions.negative_past_24h", nagative_reactions_past_24h, tags: ["resource:reactions"])
|
||||
|
||||
# Total abuse (etc.) reports in the past 24 hours
|
||||
reports_past_24_hours = FeedbackMessage.where(category: ["spam", "other", "rude or vulgar", "harassment"]).where("created_at > ?", 1.day.ago).size
|
||||
DatadogStatsClient.count("feedback_messages.reports_past_24_hours", reports_past_24_hours, tags: ["resource:feedback_messages"])
|
||||
categories = ["spam", "other", "rude or vulgar", "harassment"]
|
||||
reports_past_24_hours = FeedbackMessage.where(category: categories).where("created_at > ?", 1.day.ago).size
|
||||
DatadogStatsClient.count(
|
||||
"feedback_messages.reports_past_24_hours",
|
||||
reports_past_24_hours,
|
||||
tags: ["resource:feedback_messages"],
|
||||
)
|
||||
|
||||
# Counts of total days active (1-7) in the past week, e.g. 5000 users visited once this week, 3500 visited twice, etc.
|
||||
# Counts of total days active (1-7) in the past week,
|
||||
# e.g. 5000 users visited once this week, 3500 visited twice, etc.
|
||||
get_days_active_past_week_counts
|
||||
end
|
||||
|
||||
|
|
@ -37,11 +57,16 @@ module Metrics
|
|||
def get_days_active_past_week_counts
|
||||
ids_by_day = []
|
||||
7.times do |i|
|
||||
ids_by_day << PageView.where("created_at > ? AND created_at < ?", (i + 1).days.ago, i.days.ago).where.not(user_id: nil).pluck(:user_id).uniq
|
||||
id = PageView.
|
||||
where("created_at > ? AND created_at < ?", (i + 1).days.ago, i.days.ago).
|
||||
where.not(user_id: nil).
|
||||
pluck(:user_id).uniq
|
||||
ids_by_day << id
|
||||
end
|
||||
flat_id_list = ids_by_day.flatten.uniq
|
||||
non_new_user_ids = User.where("created_at < ?", 7.days.ago).where(id: flat_id_list).pluck(:id)
|
||||
new_user_ids = User.where("created_at > ? AND created_at < ?", 8.days.ago, 7.days.ago).where(id: flat_id_list).pluck(:id)
|
||||
new_user_ids = User.where("created_at > ? AND created_at < ?", 8.days.ago,
|
||||
7.days.ago).where(id: flat_id_list).pluck(:id)
|
||||
record_active_days_of_group(ids_by_day, non_new_user_ids, "established")
|
||||
record_active_days_of_group(ids_by_day, new_user_ids, "new")
|
||||
end
|
||||
|
|
@ -51,7 +76,8 @@ module Metrics
|
|||
distinct_user_values = user_ids_by_day.flatten.group_by(&:itself).transform_values(&:count).values
|
||||
distinct_counts = distinct_user_values.group_by(&:itself).transform_values(&:count)
|
||||
distinct_counts.each_key do |key|
|
||||
DatadogStatsClient.count("users.active_days_past_week", distinct_counts[key], tags: ["resource:users", "group:#{group}", "day_count:#{key}"])
|
||||
DatadogStatsClient.count("users.active_days_past_week", distinct_counts[key],
|
||||
tags: ["resource:users", "group:#{group}", "day_count:#{key}"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ module Metrics
|
|||
else
|
||||
model::SEARCH_CLASS.document_count
|
||||
end
|
||||
DatadogStatsClient.gauge("elasticsearch.document_count", document_count, tags: ["table_name:#{model.table_name}"])
|
||||
DatadogStatsClient.gauge("elasticsearch.document_count", document_count,
|
||||
tags: ["table_name:#{model.table_name}"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ module Podcasts
|
|||
# * :podcast_id [Integer]
|
||||
# * :limit [Integer] (1_000) - number of episodes that will be fetched for this podcast
|
||||
# * :force_update [Integer] (false) - if set to true, existing episodes' urls will be re-checked
|
||||
# the episodes' `https` and `reachable` fields will be updated accordingly if needed
|
||||
# the episodes' `https` and `reachable` fields will be updated
|
||||
# accordingly if needed
|
||||
def perform(podcast_data = {})
|
||||
# Sidekiq turns arguments into Strings so the Ruby keyword argument sorcery doesn't work here
|
||||
# prevent any mismatch between String keys and Symbol keys
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ module Reactions
|
|||
reaction = Reaction.find_by(id: reaction_id)
|
||||
return unless reaction&.reactable
|
||||
|
||||
CacheBuster.bust reaction.user.path
|
||||
CacheBuster.bust(reaction.user.path)
|
||||
if reaction.reactable_type == "Article"
|
||||
CacheBuster.bust "/reactions?article_id=#{reaction.reactable_id}"
|
||||
CacheBuster.bust("/reactions?article_id=#{reaction.reactable_id}")
|
||||
elsif reaction.reactable_type == "Comment"
|
||||
path = "/reactions?commentable_id=#{reaction.reactable.commentable_id}&commentable_type=#{reaction.reactable.commentable_type}"
|
||||
path = "/reactions?commentable_id=#{reaction.reactable.commentable_id}&" \
|
||||
"commentable_type=#{reaction.reactable.commentable_type}"
|
||||
CacheBuster.bust(path)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -132,7 +132,9 @@ Rails.application.configure do
|
|||
# Check if there are any data update scripts to run during startup
|
||||
if %w[c console runner s server].include?(ENV["COMMAND"])
|
||||
if DataUpdateScript.scripts_to_run?
|
||||
raise "Data update scripts need to be run before you can start the application. Please run 'rails data_updates:run'"
|
||||
message = "Data update scripts need to be run before you can start the application. " \
|
||||
"Please run 'rails data_updates:run'"
|
||||
raise message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ Honeybadger.configure do |config|
|
|||
config.before_notify do |notice|
|
||||
notice.fingerprint = if notice.error_message&.include?("SIGTERM") && notice.component&.include?("fetch_all_rss")
|
||||
notice.error_message
|
||||
elsif (msg_key = MESSAGE_FINGERPRINTS.keys.detect { |k, _v| notice.error_message&.include?(k) })
|
||||
elsif (msg_key = MESSAGE_FINGERPRINTS.keys.detect do |k, _v|
|
||||
notice.error_message&.include?(k)
|
||||
end)
|
||||
MESSAGE_FINGERPRINTS[msg_key]
|
||||
elsif (cmp_key = COMPONENT_FINGERPRINTS.keys.detect { |k, _v| notice.component&.include?(k) })
|
||||
COMPONENT_FINGERPRINTS[cmp_key]
|
||||
|
|
|
|||
|
|
@ -32,5 +32,7 @@ end
|
|||
Warden::Manager.after_authentication do |_record, warden, _options|
|
||||
clean_up_for_winning_strategy = !warden.winning_strategy.respond_to?(:clean_up_csrf?) ||
|
||||
warden.winning_strategy.clean_up_csrf?
|
||||
warden.cookies.delete(ActionController::RequestForgeryProtection::COOKIE_NAME) if Devise.clean_up_csrf_token_on_authentication && clean_up_for_winning_strategy
|
||||
|
||||
delete_cookie = Devise.clean_up_csrf_token_on_authentication && clean_up_for_winning_strategy
|
||||
warden.cookies.delete(ActionController::RequestForgeryProtection::COOKIE_NAME) if delete_cookie
|
||||
end
|
||||
|
|
|
|||
42
db/seeds.rb
42
db/seeds.rb
|
|
@ -66,7 +66,8 @@ users_in_random_order = seeder.create_if_none(User, num_users) do
|
|||
twitter_username: Faker::Internet.username(specifier: name),
|
||||
email_comment_notifications: false,
|
||||
email_follower_notifications: false,
|
||||
email: Faker::Internet.email(name: name, separators: "+", domain: Faker::Internet.domain_word.first(20)), # Emails limited to 50 characters
|
||||
# Emails limited to 50 characters
|
||||
email: Faker::Internet.email(name: name, separators: "+", domain: Faker::Internet.domain_word.first(20)),
|
||||
confirmed_at: Time.current,
|
||||
password: "password",
|
||||
)
|
||||
|
|
@ -255,16 +256,32 @@ end
|
|||
|
||||
seeder.create_if_none(Broadcast) do
|
||||
broadcast_messages = {
|
||||
set_up_profile: "Welcome to DEV! 👋 I'm Sloan, the community mascot and I'm here to help get you started. Let's begin by <a href='/settings'>setting up your profile</a>!",
|
||||
welcome_thread: "Sloan here again! 👋 DEV is a friendly community. Why not introduce yourself by leaving a comment in <a href='/welcome'>the welcome thread</a>!",
|
||||
twitter_connect: "You're on a roll! 🎉 Do you have a Twitter account? Consider <a href='/settings'>connecting it</a> so we can @mention you if we share your post via our Twitter account <a href='https://twitter.com/thePracticalDev'>@thePracticalDev</a>.",
|
||||
github_connect: "You're on a roll! 🎉 Do you have a GitHub account? Consider <a href='/settings'>connecting it</a> so you can pin any of your repos to your profile.",
|
||||
customize_feed: "Hi, it's me again! 👋 Now that you're a part of the DEV community, let's focus on personalizing your content. You can start by <a href='/tags'>following some tags</a> to help customize your feed! 🎉",
|
||||
customize_experience: "Sloan here! 👋 Did you know that that you can customize your DEV experience? Try changing <a href='settings/ux'>your font and theme</a> and find the best style for you!",
|
||||
start_discussion: "Sloan here! 👋 I noticed that you haven't <a href='https://dev.to/t/discuss'>started a discussion</a> yet. Starting a discussion is easy to do; just click on 'Write a Post' in the sidebar of the tag page to get started!",
|
||||
ask_question: "Sloan here! 👋 I noticed that you haven't <a href='https://dev.to/t/explainlikeimfive'>asked a question</a> yet. Asking a question is easy to do; just click on 'Write a Post' in the sidebar of the tag page to get started!",
|
||||
discuss_and_ask: "Sloan here! 👋 I noticed that you haven't <a href='https://dev.to/t/explainlikeimfive'>asked a question</a> or <a href='https://dev.to/t/discuss'>started a discussion</a> yet. It's easy to do both of these; just click on 'Write a Post' in the sidebar of the tag page to get started!",
|
||||
download_app: "Sloan here, with one last tip! 👋 Have you downloaded the DEV mobile app yet? Consider <a href='https://dev.to/downloads'>downloading</a> it so you can access all of your favorite DEV content on the go!"
|
||||
set_up_profile: "Welcome to DEV! 👋 I'm Sloan, the community mascot and I'm here to help get you started. " \
|
||||
"Let's begin by <a href='/settings'>setting up your profile</a>!",
|
||||
welcome_thread: "Sloan here again! 👋 DEV is a friendly community. " \
|
||||
"Why not introduce yourself by leaving a comment in <a href='/welcome'>the welcome thread</a>!",
|
||||
twitter_connect: "You're on a roll! 🎉 Do you have a Twitter account? " \
|
||||
"Consider <a href='/settings'>connecting it</a> so we can @mention you if we share your post " \
|
||||
"via our Twitter account <a href='https://twitter.com/thePracticalDev'>@thePracticalDev</a>.",
|
||||
github_connect: "You're on a roll! 🎉 Do you have a GitHub account? " \
|
||||
"Consider <a href='/settings'>connecting it</a> so you can pin any of your repos to your profile.",
|
||||
customize_feed: "Hi, it's me again! 👋 Now that you're a part of the DEV community, let's focus on personalizing " \
|
||||
"your content. You can start by <a href='/tags'>following some tags</a> to help customize your feed! 🎉",
|
||||
customize_experience: "Sloan here! 👋 Did you know that that you can customize your DEV experience? " \
|
||||
"Try changing <a href='settings/ux'>your font and theme</a> and find the best style for you!",
|
||||
start_discussion: "Sloan here! 👋 I noticed that you haven't " \
|
||||
"<a href='https://dev.to/t/discuss'>started a discussion</a> yet. Starting a discussion is easy to do; " \
|
||||
"just click on 'Write a Post' in the sidebar of the tag page to get started!",
|
||||
ask_question: "Sloan here! 👋 I noticed that you haven't " \
|
||||
"<a href='https://dev.to/t/explainlikeimfive'>asked a question</a> yet. Asking a question is easy to do; " \
|
||||
"just click on 'Write a Post' in the sidebar of the tag page to get started!",
|
||||
discuss_and_ask: "Sloan here! 👋 I noticed that you haven't " \
|
||||
"<a href='https://dev.to/t/explainlikeimfive'>asked a question</a> or " \
|
||||
"<a href='https://dev.to/t/discuss'>started a discussion</a> yet. It's easy to do both of these; " \
|
||||
"just click on 'Write a Post' in the sidebar of the tag page to get started!",
|
||||
download_app: "Sloan here, with one last tip! 👋 Have you downloaded the DEV mobile app yet? " \
|
||||
"Consider <a href='https://dev.to/downloads'>downloading</a> it so you can access all " \
|
||||
"of your favorite DEV content on the go!"
|
||||
}
|
||||
|
||||
broadcast_messages.each do |type, message|
|
||||
|
|
@ -479,7 +496,8 @@ num_path_redirects = 2 * SEEDS_MULTIPLIER
|
|||
|
||||
seeder.create_if_none(PathRedirect, num_path_redirects) do
|
||||
articles_for_old_paths = Article.where(published: true).order(Arel.sql("RANDOM()")).limit(num_path_redirects)
|
||||
articles_for_new_paths = Article.where.not(id: articles_for_old_paths.map(&:id), published: false).order(Arel.sql("RANDOM()")).limit(num_path_redirects)
|
||||
articles_for_new_paths = Article.where.not(id: articles_for_old_paths.map(&:id),
|
||||
published: false).order(Arel.sql("RANDOM()")).limit(num_path_redirects)
|
||||
|
||||
articles_for_old_paths.each_with_index do |old_article, i|
|
||||
new_article = articles_for_new_paths[i]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
module DataUpdateScripts
|
||||
class UpdatePublicReactionsCountFromPositiveReactionsCount
|
||||
def run
|
||||
# ActiveRecord::Base.connection.execute("UPDATE articles SET public_reactions_count = positive_reactions_count, previous_public_reactions_count = previous_positive_reactions_count")
|
||||
# ActiveRecord::Base.connection.execute("UPDATE articles SET public_reactions_count = positive_reactions_count,
|
||||
# previous_public_reactions_count = previous_positive_reactions_count")
|
||||
# ActiveRecord::Base.connection.execute("UPDATE comments SET public_reactions_count = positive_reactions_count")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue