standardrb

I can't take Rubocop any more. Moving up to Ruby 3 forces us to update RuboCop,
and it comes with dozens of linters that I'd have to evaluate, none of which is
a marginal improvement. I'm done having opinions.
This commit is contained in:
Peter Bhat Harkins 2023-09-01 18:27:36 -05:00
parent bc3ef5c22b
commit c3f8625788
275 changed files with 3292 additions and 3421 deletions

View File

@ -34,8 +34,7 @@ jobs:
bundle exec rspec spec/slow/*_spec.rb
- name: Run linters
run: |
bundle exec rubocop
bundle exec ruumba
bundle exec standardrb
- name: Find leaky gems
run: |
gem install bundler-leak

View File

@ -1,277 +0,0 @@
# Please do not 'fix' style issues without a compelling, metrics-driven
# argument that a style change will materially improve cod equality.
# https://github.com/lobsters/lobsters/pull/460
# Project setup:
require:
- ./extras/prohibit_form_for_and_form_tag.rb
- ./extras/prohibit_safe_navigation.rb
- rubocop-rails
- rubocop-rspec
AllCops:
Include:
- '**/*.rb'
- '**/Rakefile'
- '**/config.ru'
- '**/*.rake'
Exclude:
- Gemfile
- 'bin/**/*'
- 'db/**/*'
- 'vendor/**/*'
UseCache: false
# Cop configuration:
# Bundler
# Gemspec
# Layout
Layout/AccessModifierIndentation:
EnforcedStyle: outdent
Layout/ArrayAlignment:
Enabled: false
Layout/CaseIndentation:
EnforcedStyle: end
Layout/ElseAlignment:
Enabled: false
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/EmptyLinesAroundExceptionHandlingKeywords:
Enabled: false
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
Layout/HashAlignment:
Enabled: false
Layout/LineLength:
Max: 100
Layout/MultilineMethodCallBraceLayout:
Enabled: false
Layout/MultilineMethodCallIndentation:
Enabled: false
Layout/SpaceAroundOperators:
Enabled: false
Layout/SpaceInsideBlockBraces:
EnforcedStyle: space
SpaceBeforeBlockParameters: false
Layout/SpaceInsideRangeLiteral:
Enabled: false
# Lint
Lint/SuppressedException:
Enabled: false
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
# Metrics
Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
Enabled: false
Metrics/BlockNesting:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/ParameterLists:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
# Naming
Naming/AccessorMethodName:
Enabled: false
Naming/MemoizedInstanceVariableName:
Enabled: false
Naming/PredicateName:
Enabled: false
# disabled until class vaiables in extras become constants
Naming/MethodParameterName:
Enabled: false
Naming/VariableName:
Enabled: false
# Performance
# Rails
Rails/Blank:
Enabled: false
Rails/FilePath:
Enabled: false
Rails/HttpStatus:
Enabled: false
Rails/OutputSafety:
Enabled: false
Rails/Present:
UnlessBlank: false
Rails/SkipsModelValidations:
Enabled: false
Rails/HelperInstanceVariable:
Enabled: false
# RSpec
RSpec/ExampleLength:
Enabled: false
RSpec/MultipleExpectations:
Enabled: false
RSpec/DescribedClass:
Enabled: false
RSpec/MessageSpies:
Enabled: false
RSpec/VerifiedDoubles:
Enabled: false
RSpec/NotToNot:
Enabled: false
RSpec/ContextWording:
Enabled: false
RSpec/ExpectChange:
Enabled: false
RSpec/HookArgument:
Enabled: false
RSpec/ExampleWording:
Enabled: false
RSpec/NamedSubject:
Enabled: false
RSpec/NestedGroups:
Enabled: false
RSpec/AnyInstance:
Enabled: false
RSpec/UnspecifiedException:
Enabled: false
RSpec/InstanceVariable:
Enabled: false
RSpec/BeforeAfterAll:
Enabled: false
RSpec/DescribeClass:
Enabled: false
RSpec/LetSetup:
Enabled: false
Capybara/FeatureMethods:
Enabled: false
# Security
# Style
Style/AndOr:
EnforcedStyle: conditionals
Style/BlockDelimiters:
Enabled: false
Style/BlockComments:
Enabled: false
Style/CommentedKeyword:
Enabled: false
Style/ClassVars:
Enabled: false
Style/ConditionalAssignment:
Enabled: false
# It would be nice to have this on, but I'm not up for writing 66 of these in
# the process of addressing the initial 4,489 cop warnings.
Style/Documentation:
Enabled: false
Exclude: [db/migrate/**/*, spec/**/*, test/**/*]
Style/DoubleNegation:
Enabled: false
Style/EachWithObject:
Enabled: false
Style/ExpandPathArguments:
Enabled: false
Style/FormatString:
Enabled: false
Style/FormatStringToken:
Enabled: false
Style/FrozenStringLiteralComment:
EnforcedStyle: never
Style/GuardClause:
Enabled: false
Style/HashEachMethods:
Enabled: true
Style/HashSyntax:
EnforcedStyle: no_mixed_keys
Style/HashTransformKeys:
Enabled: true
Style/HashTransformValues:
Enabled: true
Style/IfInsideElse:
Enabled: false
Style/IfUnlessModifier:
Enabled: false
Style/InverseMethods:
Enabled: false
Style/Lambda:
Enabled: false
Style/LineEndConcatenation:
Enabled: false
Style/MethodDefParentheses:
Enabled: false
Style/MultilineBlockChain:
Enabled: false
Style/MultilineMemoization:
Enabled: false
Style/MultilineTernaryOperator:
Enabled: false
Style/NegatedIf:
EnforcedStyle: postfix
Style/NegatedWhile:
Enabled: false
Style/Next:
Enabled: false
Style/NonNilCheck:
Enabled: false
Style/NumericPredicate:
Enabled: false
Style/ParallelAssignment:
Enabled: false
Style/PercentLiteralDelimiters:
Enabled: false
Style/PerlBackrefs:
Enabled: false
Style/RaiseArgs:
EnforcedStyle: compact
Style/RedundantBegin:
Enabled: false
Style/RedundantParentheses:
Enabled: false
Style/RedundantReturn:
Enabled: false
Style/RedundantSelf:
Enabled: false
Style/RescueModifier:
Enabled: false
Style/RescueStandardError:
EnforcedStyle: implicit
Style/RegexpLiteral:
AllowInnerSlashes: true
Style/SafeNavigation:
Enabled: false
Style/SignalException:
Enabled: false
Style/StringLiterals:
Enabled: false
Style/SymbolArray:
EnforcedStyle: brackets
Style/TrailingCommaInArguments:
Enabled: false
Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: consistent_comma
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: consistent_comma
Style/WhileUntilDo:
Enabled: false
Style/WhileUntilModifier:
Enabled: false
Style/WordArray:
Enabled: false
Style/YodaCondition:
Enabled: false
Style/ZeroLengthPredicate:
Enabled: false

View File

@ -1 +1 @@
3.0.2
3.2.2

View File

@ -1,9 +0,0 @@
require: ./extras/prohibit_form_for_and_form_tag
AllCops:
DisabledByDefault: true
Style/DisallowFormForandFormTag:
Enabled: true
Include:
- '**/*.erb'

4
.standard.yml Normal file
View File

@ -0,0 +1,4 @@
ignore:
# it's mad about the class variables and I don't want to risk the refactor now
- 'extras/**/*':
- Naming/VariableName

35
Gemfile
View File

@ -8,10 +8,10 @@ gem "mysql2"
# gem "pg"
# rails
gem 'scenic'
gem 'scenic-mysql_adapter'
gem "scenic"
gem "scenic-mysql_adapter"
gem "activerecord-typedstore"
gem 'sprockets-rails', '2.3.3'
gem "sprockets-rails", "2.3.3"
# js
gem "jquery-rails", "~> 4.3"
@ -35,33 +35,30 @@ gem "htmlentities"
gem "commonmarker", ">= 0.23.6"
# perf
gem 'flamegraph'
gem 'memory_profiler'
gem 'rack-mini-profiler'
gem 'stackprof'
gem "flamegraph"
gem "memory_profiler"
gem "rack-mini-profiler"
gem "stackprof"
gem "oauth" # for twitter-posting bot
gem "mail" # for parsing incoming mail
gem "ruumba" # tests views
gem "sitemap_generator" # for better search engine indexing
gem "svg-graph", require: 'SVG/Graph/TimeSeries' # for charting, note workaround in lib/time_series.rb
gem 'transaction_retry' # mitigate https://github.com/lobsters/lobsters-ansible/issues/39
gem 'rack-attack' # rate-limiting
gem "svg-graph", require: "SVG/Graph/TimeSeries" # for charting, note workaround in lib/time_series.rb
gem "transaction_retry" # mitigate https://github.com/lobsters/lobsters-ansible/issues/39
gem "rack-attack" # rate-limiting
group :test, :development do
gem 'benchmark-perf'
gem 'capybara'
gem 'database_cleaner'
gem "benchmark-perf"
gem "capybara"
gem "database_cleaner"
gem "listen"
gem 'rspec-rails', '~> 6.0.0.rc1'
gem "rspec-rails", "~> 6.0.0.rc1"
gem "factory_bot_rails"
gem "rubocop", "0.81", require: false
gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "standard"
gem "faker"
gem "byebug"
gem "rb-readline"
gem "vcr"
gem "webmock" # used to support vcr
gem 'simplecov', require: false
gem "simplecov", require: false
end

View File

@ -75,6 +75,7 @@ GEM
public_suffix (>= 2.0.2, < 6.0)
afm (0.2.2)
ast (2.4.2)
base64 (0.1.1)
bcrypt (3.1.18)
benchmark-perf (0.6.0)
builder (3.2.4)
@ -125,12 +126,13 @@ GEM
htmlentities (4.3.4)
i18n (1.13.0)
concurrent-ruby (~> 1.0)
jaro_winkler (1.5.4)
jquery-rails (4.5.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.6.3)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
@ -172,8 +174,9 @@ GEM
oauth-tty (1.0.5)
version_gem (~> 1.1, >= 1.1.1)
parallel (1.23.0)
parser (3.2.2.1)
parser (3.2.2.3)
ast (~> 2.4.1)
racc
pdf-reader (2.11.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
@ -247,24 +250,25 @@ GEM
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-support (3.12.0)
rubocop (0.81.0)
jaro_winkler (~> 1.5.1)
rubocop (1.56.2)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 2.7.0.1)
parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
rexml
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-rails (2.5.2)
activesupport
rack (>= 1.1)
rubocop (>= 0.72.0)
rubocop-rspec (1.41.0)
rubocop (>= 0.68.1)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-performance (1.19.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruumba (0.1.17)
rubocop
scenic (1.7.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
@ -290,6 +294,18 @@ GEM
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
stackprof (0.2.25)
standard (1.31.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.56.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.2)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.2.0)
lint_roller (~> 1.1)
rubocop-performance (~> 1.19.0)
svg-graph (2.2.2)
thor (1.2.1)
timeout (0.3.2)
@ -303,7 +319,7 @@ GEM
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.8.0)
unicode-display_width (2.4.2)
vcr (6.1.0)
version_gem (1.1.2)
webmock (3.18.1)
@ -352,16 +368,13 @@ DEPENDENCIES
rotp
rqrcode
rspec-rails (~> 6.0.0.rc1)
rubocop (= 0.81)
rubocop-rails
rubocop-rspec
ruumba
scenic
scenic-mysql_adapter
simplecov
sitemap_generator
sprockets-rails (= 2.3.3)
stackprof
standard
svg-graph
transaction_retry
uglifier (>= 1.3.0)

View File

@ -1,10 +1,10 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
require File.expand_path("../config/application", __FILE__)
if Rails.env.development? || Rails.env.test?
require 'rubocop/rake_task'
require "rubocop/rake_task"
RuboCop::RakeTask.new
end

View File

@ -6,9 +6,9 @@ class AboutController < ApplicationController
@title = "Resource Not Found"
render action: "404", status: 404
rescue ActionView::MissingTemplate
render status: 404, html: (
render status: 404, html:
"<h1>404</h1><p>Resource not found</p>"
).html_safe, layout: 'application'
.html_safe, layout: "application"
end
def about
@ -16,30 +16,26 @@ class AboutController < ApplicationController
@title = "About"
render action: "about"
rescue ActionView::MissingTemplate
render layout: 'application', html: ("<h1>A mystery.")
render layout: "application", html: "<h1>A mystery."
end
raise "Seriously, write your own about page." if @homeabout
end
def chat
begin
@title = "Chat"
render action: "chat"
rescue ActionView::MissingTemplate
render html: ("<h1>Don't speak. I know what you're thinking.</h1>"),
layout: 'application'
end
@title = "Chat"
render action: "chat"
rescue ActionView::MissingTemplate
render html: "<h1>Don't speak. I know what you're thinking.</h1>",
layout: "application"
end
def privacy
begin
@title = "Privacy Policy"
render action: "privacy"
rescue ActionView::MissingTemplate
render layout: 'application', html: <<-HTML
@title = "Privacy Policy"
render action: "privacy"
rescue ActionView::MissingTemplate
render layout: "application", html: <<-HTML
<blockquote>You have zero privacy anyway. Get over it.</blockquote>
<p>Scott McNealy</p>
HTML
end
HTML
end
end

View File

@ -17,11 +17,11 @@ class ApplicationController < ActionController::Base
CACHE_PAGE = proc { false && @user.blank? && cookies[TAG_FILTER_COOKIE].blank? }
rescue_from ActionController::UnknownFormat do
render plain: '404 Not Found', status: :not_found, content_type: 'text/plain'
render plain: "404 Not Found", status: :not_found, content_type: "text/plain"
end
rescue_from ActionDispatch::Http::MimeNegotiation::InvalidType do
render plain: 'fix the mime type in your HTTP_ACCEPT header',
status: :bad_request, content_type: 'text/plain'
render plain: "fix the mime type in your HTTP_ACCEPT header",
status: :bad_request, content_type: "text/plain"
end
def agent_is_spider?
@ -38,8 +38,8 @@ class ApplicationController < ActionController::Base
end
if session[:u] &&
(user = User.find_by(:session_token => session[:u].to_s)) &&
user.is_active?
(user = User.find_by(session_token: session[:u].to_s)) &&
user.is_active?
@user = user
end
Rails.logger.info(
@ -61,16 +61,14 @@ class ApplicationController < ActionController::Base
def find_user_from_rss_token
if !@user && request[:format] == "rss" && params[:token].to_s.present?
@user = User.where(:rss_token => params[:token].to_s).first
@user = User.where(rss_token: params[:token].to_s).first
end
end
def flag_warning
@flag_warning_int ||= time_interval('1m')
@flag_warning_int ||= time_interval("1m")
return false if Rails.env.development? # expensive because Rails doesn't cache in dev
@show_flag_warning ||= (
@user && !!FlaggedCommenters.new(@flag_warning_int[:param], 1.day).check_list_for(@user)
)
@show_flag_warning ||= @user && !!FlaggedCommenters.new(@flag_warning_int[:param], 1.day).check_list_for(@user)
end
def heinous_inline_partials
@ -78,7 +76,7 @@ class ApplicationController < ActionController::Base
end
def mini_profiler
if @user && @user.is_moderator?
if @user&.is_moderator?
Rack::MiniProfiler.authorize_request
end
end
@ -92,11 +90,11 @@ class ApplicationController < ActionController::Base
# https://web.archive.org/web/20180108083712/http://umaine.edu/lobsterinstitute/files/2011/12/LobsterColorsWeb.pdf
def set_traffic_style
@traffic_intensity = '?'
@traffic_style = 'background-color: #ac130d;'
@traffic_intensity = "?"
@traffic_style = "background-color: #ac130d;"
return true if Rails.application.read_only? ||
agent_is_spider? ||
%w{json rss}.include?(params[:format])
agent_is_spider? ||
%w[json rss].include?(params[:format])
if (skip = TrafficHelper.novelty_logo)
@traffic_style = skip
return
@ -104,7 +102,7 @@ class ApplicationController < ActionController::Base
@traffic_intensity = TrafficHelper.cached_current_intensity
# map intensity to 80-255 so there's always a little red
hex = sprintf('%02x', (@traffic_intensity * 1.75 + 80).round)
hex = sprintf("%02x", (@traffic_intensity * 1.75 + 80).round)
@traffic_style = "background-color: ##{hex}0000;"
return true unless @user
@ -115,7 +113,7 @@ class ApplicationController < ActionController::Base
[6, :yellow, "background-color: ##{hex}#{hex}00;"],
[3, :calico, "background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAC4jAAAuIwF4pT92AAAABmJLR0QA/wD/AP+gvaeTAAACpElEQVQYGQXBWW8bVRgA0Hu/u814NsdxGsUxztJUzaJSVS1CCCTKE7zxxiP/gH+I+lKKQEVCLUlJ5YTsU8f2eJvxbHfjHLz7sKeU2mhNfvl579vnEPKUEUJxji1YoBaIob4m6+cX8Our/m99TBwmpKGV0hZjz+EO06FHOAKlFNKIcE+p8HYo3rwd/Xk8m+pVEjW4EzIFdjopVVG6Nt1ocpc3ALnIhqMRnF3afz6qd2flcMElAOWu3nm4tr6xMh2cyDpprqwBwdjQ0Uz9fXJ9el0lRTOekVQ13DCKvCXVWO7sdl6+/Gp01cbpv/uHPcqGlUKIr50NZq+Pi7mymrt+GOxvbz9+zKjS5OLi1uV/ZeObAC3un4qgt+c0bL8/v5qJ64WbaocIPC2HzbaDGCOeF0ySJI7vzz9eLuZFpfDq2lZWmd/fx6/e3twkuDIiL3KCysV83D+/xZ/1uhYXjuC6lg0BVk2fHPXcQMWD7L+bvJCettzhEPpgzRIxjbe3u6VMCcXWMEY5E9qisqo1QlRLjDVwxqxSQpBW5CFnSB2PaulyRleCSEtNhDPLltjkdQWYCC+gDVF6pHzU8z8/7IKgVFaVtshSWaQxA2Osz4FiokTQrLRrQCLIXzxr/fT94cFWVFlGmXExNQznnbbzaGcVgb0bJqO8kS5BzmusNAMdYN5mPlsihRh5sL7pRYHXQM+OOj/+8MV3Xx+2mmQ8qQZxkmfKSGXq1Odyt9MShByffKLgcc3JsqrHk3Eyumu6LbkYFHcfsjttSaR5OFP29H755nzw/sq8+yMh/sYKYiRL76dxzOqr9RBsmeisnCWqVlZaMIyxgC5U9eEy7p9awj0ByDiQ7XfgmyfRl0fRwZbb7bLVNmOOXynADDY3Hxzs7+WL5XSY/w/0MGrkMYhXjAAAAABJRU5ErkJggg==) no-repeat center"],
[2, :split, "background: linear-gradient(90deg, ##{hex}0000 50%, #0000#{hex} 50%)"],
[2, :albino, "filter: invert(100%);"],
[2, :albino, "filter: invert(100%);"]
# rubocop:enable Layout/LineLength,
].each do |cumulative_odds, name, style|
break unless rand(cumulative_odds) == 0
@ -147,7 +145,7 @@ class ApplicationController < ActionController::Base
true
else
flash[:error] = "You are not authorized to access that resource."
return redirect_to "/"
redirect_to "/"
end
end
end
@ -160,7 +158,7 @@ class ApplicationController < ActionController::Base
true
else
flash[:error] = "You are not authorized to access that resource."
return redirect_to "/"
redirect_to "/"
end
end
end
@ -169,13 +167,13 @@ class ApplicationController < ActionController::Base
if @user
true
else
render :plain => "not logged in", :status => 400
return false
render plain: "not logged in", status: 400
false
end
end
def require_no_user_or_redirect
return redirect_to "/" if @user
redirect_to "/" if @user
end
def show_title_h1
@ -184,7 +182,7 @@ class ApplicationController < ActionController::Base
def tags_filtered_by_cookie
@_tags_filtered ||= Tag.where(
:tag => cookies[TAG_FILTER_COOKIE].to_s.split(",")
tag: cookies[TAG_FILTER_COOKIE].to_s.split(",")
)
end
end

View File

@ -1,5 +1,5 @@
class AvatarsController < ApplicationController
before_action :require_logged_in_user, :only => [:expire]
before_action :require_logged_in_user, only: [:expire]
ALLOWED_SIZES = [16, 32, 100, 200].freeze
@ -8,20 +8,18 @@ class AvatarsController < ApplicationController
def expire
expired = 0
Dir.entries(CACHE_DIR).select {|f|
Dir.entries(CACHE_DIR).select { |f|
f.match(/\A#{@user.username}-(\d+)\.png\z/)
}.each do |f|
begin
Rails.logger.debug "Expiring #{f}"
File.unlink("#{CACHE_DIR}/#{f}")
expired += 1
rescue => e
Rails.logger.error "Failed expiring #{f}: #{e}"
end
Rails.logger.debug "Expiring #{f}"
File.unlink("#{CACHE_DIR}/#{f}")
expired += 1
rescue => e
Rails.logger.error "Failed expiring #{f}: #{e}"
end
flash[:success] = "Your avatar cache has been purged of #{'file'.pluralize(expired)}"
return redirect_to "/settings"
flash[:success] = "Your avatar cache has been purged of #{"file".pluralize(expired)}"
redirect_to "/settings"
end
def show
@ -36,7 +34,7 @@ class AvatarsController < ApplicationController
raise ActionController::RoutingError.new("invalid user name")
end
u = User.where(:username => username).first!
u = User.where(username: username).first!
if !(av = u.fetched_avatar(size))
raise ActionController::RoutingError.new("failed fetching avatar")
@ -53,6 +51,6 @@ class AvatarsController < ApplicationController
File.rename("#{CACHE_DIR}/.#{u.username}-#{size}.png", "#{CACHE_DIR}/#{u.username}-#{size}.png")
response.headers["Expires"] = 1.hour.from_now.httpdate
send_data av, :type => "image/png", :disposition => "inline"
send_data av, type: "image/png", disposition: "inline"
end
end

View File

@ -12,33 +12,33 @@ class CategoriesController < ApplicationController
flash[:success] = "Category #{category.category} has been created"
redirect_to tags_path
else
flash[:error] = "New category not created: #{category.errors.full_messages.join(', ')}"
flash[:error] = "New category not created: #{category.errors.full_messages.join(", ")}"
redirect_to new_category_path
end
end
def edit
@category = Category.where(:category => params[:category_name]).first!
@category = Category.where(category: params[:category_name]).first!
@title = "Edit Category"
end
def update
category = Category.where(:category => params[:category_name]).first!
category = Category.where(category: params[:category_name]).first!
if category.update(category_params)
flash[:success] = "Category #{category.category} has been updated"
redirect_to tags_path
else
flash[:error] = "Category not updated: #{category.errors.full_messages.join(', ')}"
flash[:error] = "Category not updated: #{category.errors.full_messages.join(", ")}"
redirect_to edit_category_path
end
end
private
private
def category_params
params.require(:category).permit(
:category_name,
:category,
:category
).merge(edit_user_id: @user.id)
end
end

View File

@ -4,43 +4,43 @@ class CommentsController < ApplicationController
caches_page :index, :threads, if: CACHE_PAGE
before_action :require_logged_in_user_or_400,
:only => [:create, :preview, :upvote, :flag, :unvote]
before_action :require_logged_in_user, :only => [:upvoted]
only: [:create, :preview, :upvote, :flag, :unvote]
before_action :require_logged_in_user, only: [:upvoted]
before_action :flag_warning, only: [:user_threads]
before_action :show_title_h1
# for rss feeds, load the user's tag filters if a token is passed
before_action :find_user_from_rss_token, :only => [:index]
before_action :find_user_from_rss_token, only: [:index]
def create
if !(story = Story.where(:short_id => params[:story_id]).first) ||
story.is_gone?
return render :plain => "can't find story", :status => 400
if !(story = Story.where(short_id: params[:story_id]).first) ||
story.is_gone?
return render plain: "can't find story", status: 400
end
comment = story.comments.build
comment.comment = params[:comment].to_s
comment.user = @user
if params[:hat_id] && @user.wearable_hats.where(:id => params[:hat_id])
if params[:hat_id] && @user.wearable_hats.where(id: params[:hat_id])
comment.hat_id = params[:hat_id]
end
if params[:parent_comment_short_id].present?
# includes parent story_id to ensure this comment's story_id matches
comment.parent_comment =
Comment.find_by(:story_id => story.id, :short_id => params[:parent_comment_short_id])
Comment.find_by(story_id: story.id, short_id: params[:parent_comment_short_id])
if !comment.parent_comment
return render :json => { :error => "invalid parent comment", :status => 400 }
return render json: {error: "invalid parent comment", status: 400}
end
end
# sometimes on slow connections people resubmit; silently accept it
if (already = Comment.find_by(user: comment.user,
story: comment.story,
parent_comment_id: comment.parent_comment_id,
comment: comment.comment))
self.render_created_comment(already)
story: comment.story,
parent_comment_id: comment.parent_comment_id,
comment: comment.comment))
render_created_comment(already)
return
end
@ -50,11 +50,11 @@ class CommentsController < ApplicationController
end
if comment.valid? && params[:preview].blank? && ActiveRecord::Base.transaction { comment.save }
comment.current_vote = { :vote => 1 }
self.render_created_comment(comment)
comment.current_vote = {vote: 1}
render_created_comment(comment)
else
comment.score = 1
comment.current_vote = { :vote => 1 }
comment.current_vote = {vote: 1}
preview comment
end
@ -62,8 +62,8 @@ class CommentsController < ApplicationController
def render_created_comment(comment)
if request.xhr?
render :partial => "comments/postedreply", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
render partial: "comments/postedreply", layout: false,
content_type: "text/html", locals: {comment: comment}
else
redirect_to comment.path
end
@ -71,46 +71,46 @@ class CommentsController < ApplicationController
def show
if !((comment = find_comment) && comment.is_editable_by_user?(@user))
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
render :partial => "comment",
:layout => false,
:content_type => "text/html",
:locals => {
:comment => comment,
:show_tree_lines => params[:show_tree_lines],
}
render partial: "comment",
layout: false,
content_type: "text/html",
locals: {
comment: comment,
show_tree_lines: params[:show_tree_lines]
}
end
def show_short_id
if !(comment = find_comment)
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
render :json => comment.as_json
render json: comment.as_json
end
def redirect_from_short_id
if (comment = find_comment)
return redirect_to comment.path
redirect_to comment.path
else
return render :plain => "can't find comment", :status => 400
render plain: "can't find comment", status: 400
end
end
def edit
if !((comment = find_comment) && comment.is_editable_by_user?(@user))
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
render :partial => "commentbox", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
render partial: "commentbox", layout: false,
content_type: "text/html", locals: {comment: comment}
end
def reply
if !(parent_comment = find_comment)
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
@story = parent_comment.story
@ -122,15 +122,15 @@ class CommentsController < ApplicationController
if !parent_comment.depth_permits_reply?
ModNote.tattle_on_max_depth_limit(@user, parent_comment)
if request.xhr?
render partial: 'too_deep'
render partial: "too_deep"
else
render '_too_deep'
render "_too_deep"
end
return
end
if request.xhr?
render partial: 'commentbox', locals: { comment: comment }
render partial: "commentbox", locals: {comment: comment}
else
parents = comment.parents.with_thread_attributes.for_presentation
@ -140,55 +140,55 @@ class CommentsController < ApplicationController
c.current_vote = @votes[c.id]
end
end
render '_commentbox', locals: {
render "_commentbox", locals: {
comment: comment,
parents: parents,
parents: parents
}
end
end
def delete
if !((comment = find_comment) && comment.is_deletable_by_user?(@user))
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
comment.delete_for_user(@user, params[:reason])
render :partial => "comment", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
render partial: "comment", layout: false,
content_type: "text/html", locals: {comment: comment}
end
def undelete
if !((comment = find_comment) && comment.is_undeletable_by_user?(@user))
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
comment.undelete_for_user(@user)
render :partial => "comment", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
render partial: "comment", layout: false,
content_type: "text/html", locals: {comment: comment}
end
def disown
if !((comment = find_comment) && comment.is_disownable_by_user?(@user))
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
InactiveUser.disown! comment
comment = find_comment
render :partial => "comment", :layout => false,
:content_type => "text/html", :locals => { :comment => comment }
render partial: "comment", layout: false,
content_type: "text/html", locals: {comment: comment}
end
def update
if !((comment = find_comment) && comment.is_editable_by_user?(@user))
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
comment.comment = params[:comment]
comment.hat_id = nil
if params[:hat_id] && @user.wearable_hats.where(:id => params[:hat_id])
if params[:hat_id] && @user.wearable_hats.where(id: params[:hat_id])
comment.hat_id = params[:hat_id]
end
@ -196,12 +196,12 @@ class CommentsController < ApplicationController
votes = Vote.comment_votes_by_user_for_comment_ids_hash(@user.id, [comment.id])
comment.current_vote = votes[comment.id]
render :partial => "comments/comment",
:layout => false,
:content_type => "text/html",
:locals => { :comment => comment, :show_tree_lines => params[:show_tree_lines] }
render partial: "comments/comment",
layout: false,
content_type: "text/html",
locals: {comment: comment, show_tree_lines: params[:show_tree_lines]}
else
comment.current_vote = { :vote => 1 }
comment.current_vote = {vote: 1}
preview comment
end
@ -209,52 +209,52 @@ class CommentsController < ApplicationController
def unvote
if !(comment = find_comment) || comment.is_gone?
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
Vote.vote_thusly_on_story_or_comment_for_user_because(
0, comment.story_id, comment.id, @user.id, nil
)
render :plain => "ok"
render plain: "ok"
end
def upvote
if !(comment = find_comment) || comment.is_gone?
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
Vote.vote_thusly_on_story_or_comment_for_user_because(
1, comment.story_id, comment.id, @user.id, params[:reason]
)
render :plain => "ok"
render plain: "ok"
end
def flag
if !(comment = find_comment) || comment.is_gone?
return render :plain => "can't find comment", :status => 400
return render plain: "can't find comment", status: 400
end
if !Vote::COMMENT_REASONS[params[:reason]]
return render :plain => "invalid reason", :status => 400
return render plain: "invalid reason", status: 400
end
if !@user.can_flag?(comment)
return render :plain => "not permitted to flag", :status => 400
return render plain: "not permitted to flag", status: 400
end
Vote.vote_thusly_on_story_or_comment_for_user_because(
-1, comment.story_id, comment.id, @user.id, params[:reason]
)
render :plain => "ok"
render plain: "ok"
end
def index
@rss_link ||= {
:title => "RSS 2.0 - Newest Comments",
:href => "/comments.rss" + (@user ? "?token=#{@user.rss_token}" : ""),
title: "RSS 2.0 - Newest Comments",
href: "/comments.rss" + (@user ? "?token=#{@user.rss_token}" : "")
}
@title = "Newest Comments"
@ -262,15 +262,15 @@ class CommentsController < ApplicationController
@page = params[:page].to_i
if @page == 0
@page = 1
elsif @page < 0 || @page > (2 ** 32)
elsif @page < 0 || @page > (2**32)
raise ActionController::RoutingError.new("page out of bounds")
end
@comments = Comment.accessible_to_user(@user)
.not_on_story_hidden_by(@user)
.order("id DESC")
.includes(:user, :hat, :story => :user)
.joins(:story).where.not(stories: { is_deleted: true })
.includes(:user, :hat, story: :user)
.joins(:story).where.not(stories: {is_deleted: true})
.limit(COMMENTS_PER_PAGE)
.offset((@page - 1) * COMMENTS_PER_PAGE)
@ -285,39 +285,39 @@ class CommentsController < ApplicationController
end
respond_to do |format|
format.html { render :action => "index" }
format.html { render action: "index" }
format.rss {
if @user && params[:token].present?
@title = "Private comments feed for #{@user.username}"
end
render :action => "index", :layout => false
render action: "index", layout: false
}
end
end
def upvoted
@rss_link ||= {
:title => "RSS 2.0 - Newest Comments",
:href => upvoted_comments_path(format: :rss) + (@user ? "?token=#{@user.rss_token}" : ""),
title: "RSS 2.0 - Newest Comments",
href: upvoted_comments_path(format: :rss) + (@user ? "?token=#{@user.rss_token}" : "")
}
@title = "Upvoted Comments"
@above = 'saved/subnav'
@above = "saved/subnav"
@page = params[:page].to_i
if @page == 0
@page = 1
elsif @page < 0 || @page > (2 ** 32)
elsif @page < 0 || @page > (2**32)
raise ActionController::RoutingError.new("page out of bounds")
end
@comments = Comment.accessible_to_user(@user)
.where.not(user_id: @user.id)
.order("id DESC")
.includes(:user, :hat, :story => :user)
.joins(:votes).where(votes: { user_id: @user.id, vote: 1 })
.joins(:story).where.not(stories: { is_deleted: true })
.includes(:user, :hat, story: :user)
.joins(:votes).where(votes: {user_id: @user.id, vote: 1})
.joins(:story).where.not(stories: {is_deleted: true})
.limit(COMMENTS_PER_PAGE)
.offset((@page - 1) * COMMENTS_PER_PAGE)
@ -335,7 +335,7 @@ class CommentsController < ApplicationController
@title = "Upvoted comments feed for #{@user.username}"
end
render :action => "index", :layout => false
render action: "index", layout: false
}
end
end
@ -368,30 +368,30 @@ class CommentsController < ApplicationController
end
end
private
private
def preview(comment)
comment.previewing = true
comment.is_deleted = false # show normal preview for deleted comments
render :partial => "comments/commentbox",
:layout => false,
:content_type => "text/html",
:locals => {
:comment => comment,
:show_comment => comment,
:show_tree_lines => params[:show_tree_lines],
}
render partial: "comments/commentbox",
layout: false,
content_type: "text/html",
locals: {
comment: comment,
show_comment: comment,
show_tree_lines: params[:show_tree_lines]
}
end
def find_comment
comment = Comment.where(short_id: params[:id]).first
# convenience to use PK (from external queries) without generally permitting enumeration:
comment ||= Comment.find(params[:id]) if @user && @user.is_admin?
comment ||= Comment.find(params[:id]) if @user&.is_admin?
if @user && comment
comment.current_vote = Vote.where(:user_id => @user.id,
:story_id => comment.story_id, :comment_id => comment.id).first
comment.current_vote = Vote.where(user_id: @user.id,
story_id: comment.story_id, comment_id: comment.id).first
end
comment

View File

@ -2,7 +2,8 @@ class DomainsController < ApplicationController
before_action :require_logged_in_admin
before_action :find_domain, only: [:edit, :update]
def edit; end
def edit
end
def update
if domain_params[:banned_reason].present?
@ -19,7 +20,7 @@ class DomainsController < ApplicationController
end
end
private
private
def domain_params
params.require(:domain).permit(:banned_reason)
@ -30,14 +31,14 @@ private
end
def path_of_form(domain)
prms = { name: domain.domain }
prms = {name: domain.domain}
domain.banned_at ? unban_domain_path(prms) : update_domain_path(prms)
end
helper_method :path_of_form
def caption_of_button(domain)
domain.banned_at ? 'Unban' : 'Ban'
domain.banned_at ? "Unban" : "Ban"
end
helper_method :caption_of_button

View File

@ -6,25 +6,25 @@ class FiltersController < ApplicationController
@title = "Filtered Tags"
@categories = Category.all
.order('category asc, tags.tag asc')
.eager_load(:tags)
.references(:tags)
.where('tags.active = true')
.order("category asc, tags.tag asc")
.eager_load(:tags)
.references(:tags)
.where("tags.active = true")
# perf: three queries is much faster than joining, grouping on tags.id for counts
@story_counts = Tagging.group(:tag_id).count
@filter_counts = TagFilter.group(:tag_id).count
if @user
@filtered_tags = @user.tag_filter_tags.index_by(&:id)
@filtered_tags = if @user
@user.tag_filter_tags.index_by(&:id)
else
@filtered_tags = tags_filtered_by_cookie.index_by(&:id)
tags_filtered_by_cookie.index_by(&:id)
end
end
def update
new_tags = Tag.active.where(:tag => (params[:tags] || {}).keys).to_a
new_tags.keep_if {|t| t.user_can_filter? @user }
new_tags = Tag.active.where(tag: (params[:tags] || {}).keys).to_a
new_tags.keep_if { |t| t.user_can_filter? @user }
if @user
@user.tag_filter_tags = new_tags

View File

@ -1,6 +1,6 @@
class HatsController < ApplicationController
before_action :require_logged_in_user, :except => [:index]
before_action :require_logged_in_moderator, :except => [:build_request, :index, :create_request]
before_action :require_logged_in_user, except: [:index]
before_action :require_logged_in_moderator, except: [:build_request, :index, :create_request]
before_action :show_title_h1
def build_request
@ -32,7 +32,7 @@ class HatsController < ApplicationController
return redirect_to "/hats"
end
render :action => "build_request"
render action: "build_request"
end
def requests_index
@ -49,7 +49,7 @@ class HatsController < ApplicationController
flash[:success] = "Successfully approved hat request."
return redirect_to "/hats/requests"
redirect_to "/hats/requests"
end
def reject_request
@ -58,6 +58,6 @@ class HatsController < ApplicationController
flash[:success] = "Successfully rejected hat request."
return redirect_to "/hats/requests"
redirect_to "/hats/requests"
end
end

View File

@ -4,9 +4,9 @@ class HomeController < ApplicationController
caches_page :index, :newest, :newest_by_user, :recent, :top, if: CACHE_PAGE
# for rss feeds, load the user's tag filters if a token is passed
before_action :find_user_from_rss_token, :only => [:index, :newest, :saved, :upvoted]
before_action :find_user_from_rss_token, only: [:index, :newest, :saved, :upvoted]
before_action { @page = page }
before_action :require_logged_in_user, :only => [:hidden, :saved, :upvoted]
before_action :require_logged_in_user, only: [:hidden, :saved, :upvoted]
before_action :show_title_h1, only: [:top]
def active
@ -14,8 +14,8 @@ class HomeController < ApplicationController
paginate stories.active
}
@title = 'Active Discussions'
@above = 'active'
@title = "Active Discussions"
@above = "active"
respond_to do |format|
format.html { render action: :index }
@ -29,9 +29,9 @@ class HomeController < ApplicationController
}
@title = "Hidden Stories"
@above = 'saved/subnav'
@above = "saved/subnav"
render :action => "index"
render action: "index"
end
def index
@ -40,31 +40,31 @@ class HomeController < ApplicationController
}
@rss_link ||= {
:title => "RSS 2.0",
:href => user_token_link("/rss"),
title: "RSS 2.0",
href: user_token_link("/rss")
}
@comments_rss_link ||= {
:title => "Comments - RSS 2.0",
:href => user_token_link("/comments.rss"),
title: "Comments - RSS 2.0",
href: user_token_link("/comments.rss")
}
@title = ""
@root_path = true
respond_to do |format|
format.html { render :action => "index" }
format.html { render action: "index" }
format.rss {
if @user
@title = "Private feed for #{@user.username}"
render :action => "rss", :layout => false
render action: "rss", layout: false
else
content = Rails.cache.fetch("rss", :expires_in => (60 * 2)) {
render_to_string :action => "rss", :layout => false
content = Rails.cache.fetch("rss", expires_in: (60 * 2)) {
render_to_string action: "rss", layout: false
}
render :plain => content, :layout => false
render plain: content, layout: false
end
}
format.json { render :json => @stories }
format.json { render json: @stories }
end
end
@ -74,23 +74,23 @@ class HomeController < ApplicationController
}
@title = "Newest Stories"
@above = 'stories/subnav'
@above = "stories/subnav"
@rss_link = {
:title => "RSS 2.0 - Newest Items",
:href => user_token_link("/newest.rss"),
title: "RSS 2.0 - Newest Items",
href: user_token_link("/newest.rss")
}
respond_to do |format|
format.html { render :action => "index" }
format.html { render action: "index" }
format.rss {
if @user && params[:token].present?
@title += " - Private feed for #{@user.username}"
end
render :action => "rss", :layout => false
render action: "rss", layout: false
}
format.json { render :json => @stories }
format.json { render json: @stories }
end
end
@ -101,14 +101,14 @@ class HomeController < ApplicationController
@title = "Newest Stories by #{by_user.username}"
@newest_by_user = by_user
@above = 'newest_by_user'
@above = "newest_by_user"
respond_to do |format|
format.html { render :action => "index" }
format.html { render action: "index" }
format.rss {
render :action => "rss", :layout => false
render action: "rss", layout: false
}
format.json { render :json => @stories }
format.json { render json: @stories }
end
end
@ -118,13 +118,13 @@ class HomeController < ApplicationController
}
@title = "Recent Stories"
@above = 'stories/subnav'
@below = 'recent'
@above = "stories/subnav"
@below = "recent"
# our list is unstable because upvoted stories get removed, so point at /newest.rss
@rss_link = { :title => "RSS 2.0 - Newest Items", :href => user_token_link("/newest.rss") }
@rss_link = {title: "RSS 2.0 - Newest Items", href: user_token_link("/newest.rss")}
render :action => "index"
render action: "index"
end
def saved
@ -133,47 +133,47 @@ class HomeController < ApplicationController
}
@rss_link ||= {
:title => "RSS 2.0",
:href => user_token_link("/saved.rss"),
title: "RSS 2.0",
href: user_token_link("/saved.rss")
}
@title = "Saved Stories"
@above = 'saved/subnav'
@above = "saved/subnav"
respond_to do |format|
format.html { render :action => "index" }
format.html { render action: "index" }
format.rss {
if @user
@title = "Private feed of saved stories for #{@user.username}"
end
render :action => "rss", :layout => false
render action: "rss", layout: false
}
format.json { render :json => @stories }
format.json { render json: @stories }
end
end
def category
category_params = params[:category].split(',')
category_params = params[:category].split(",")
@categories = Category.where(category: category_params)
raise ActiveRecord::RecordNotFound unless @categories.length == category_params.length
@stories, @show_more = get_from_cache(categories: category_params.sort.join(',')) do
@stories, @show_more = get_from_cache(categories: category_params.sort.join(",")) do
paginate stories.categories(@categories)
end
@title = @categories.map(&:category).join(' ')
@above = 'category'
@title = @categories.map(&:category).join(" ")
@above = "category"
@rss_link = {
title: "RSS 2.0 - Categorized #{@title}",
href: category_url(params[:category], format: 'rss'),
href: category_url(params[:category], format: "rss")
}
respond_to do |format|
format.html { render :action => "index" }
format.rss { render :action => "rss", :layout => false }
format.json { render :json => @stories }
format.html { render action: "index" }
format.rss { render action: "rss", layout: false }
format.json { render json: @stories }
end
end
@ -184,48 +184,48 @@ class HomeController < ApplicationController
paginate stories.tagged([@tag])
end
@title = [@tag.tag, @tag.description].compact.join(' - ')
@above = 'single_tag'
@title = [@tag.tag, @tag.description].compact.join(" - ")
@above = "single_tag"
@related = Rails.cache.fetch("related_#{@tag.tag}", expires_in: 1.day) {
Tag.related(@tag)
}
@below = 'tags/multi_tag_tip'
@below = "tags/multi_tag_tip"
@rss_link = {
title: "RSS 2.0 - Tagged #{@tag.tag} (#{@tag.description})",
href: "/t/#{@tag.tag}.rss",
href: "/t/#{@tag.tag}.rss"
}
respond_to do |format|
format.html { render :action => "index" }
format.rss { render :action => "rss", :layout => false }
format.json { render :json => @stories }
format.html { render action: "index" }
format.rss { render action: "rss", layout: false }
format.json { render json: @stories }
end
end
def multi_tag
tag_params = params[:tag].split(',')
tag_params = params[:tag].split(",")
@tags = Tag.where(tag: tag_params)
raise ActiveRecord::RecordNotFound unless @tags.length == tag_params.length
@stories, @show_more = get_from_cache(tags: tag_params.sort.join(',')) do
@stories, @show_more = get_from_cache(tags: tag_params.sort.join(",")) do
paginate stories.tagged(@tags)
end
@title = @tags.map do |tag|
[tag.tag, tag.description].compact.join(' - ')
end.join(' ')
@above = 'multi_tag'
[tag.tag, tag.description].compact.join(" - ")
end.join(" ")
@above = "multi_tag"
@rss_link = {
title: "RSS 2.0 - Tagged #{tags_with_description_for_rss(@tags)}",
href: "/t/#{params[:tag]}.rss",
href: "/t/#{params[:tag]}.rss"
}
respond_to do |format|
format.html { render :action => "index" }
format.rss { render :action => "rss", :layout => false }
format.json { render :json => @stories }
format.html { render action: "index" }
format.rss { render action: "rss", layout: false }
format.json { render json: @stories }
end
end
@ -233,21 +233,21 @@ class HomeController < ApplicationController
@domain = Domain.find_by!(domain: params[:id])
@stories, @show_more = get_from_cache(domain: @domain.domain) do
paginate @domain.stories.base(@user).order('id desc')
paginate @domain.stories.base(@user).order("id desc")
end
@title = @domain.domain
@above = 'for_domain'
@above = "for_domain"
@rss_link = {
title: "RSS 2.0 - For #{@domain.domain}",
href: "/domain/#{@domain.domain}.rss",
href: "/domain/#{@domain.domain}.rss"
}
respond_to do |format|
format.html { render :action => "index" }
format.rss { render :action => "rss", :layout => false }
format.json { render :json => @stories }
format.html { render action: "index" }
format.rss { render action: "rss", layout: false }
format.json { render json: @stories }
end
end
@ -258,35 +258,35 @@ class HomeController < ApplicationController
paginate stories.top(length)
}
if length[:dur] > 1
@title = "Top Stories of the Past #{length[:dur]} #{length[:intv]}"
@title = if length[:dur] > 1
"Top Stories of the Past #{length[:dur]} #{length[:intv]}"
else
@title = "Top Stories of the Past #{length[:intv]}"
"Top Stories of the Past #{length[:intv]}"
end
@above = 'stories/subnav'
@above = "stories/subnav"
@rss_link ||= {
:title => "RSS 2.0 - " + @title,
:href => "/top/rss",
title: "RSS 2.0 - " + @title,
href: "/top/rss"
}
respond_to do |format|
format.html { render :action => "index" }
format.rss { render :action => "rss", :layout => false }
format.html { render action: "index" }
format.rss { render action: "rss", layout: false }
end
end
def upvoted
@stories, @show_more = get_from_cache(upvoted: true, user: @user) {
paginate @user.upvoted_stories.includes(:tags).order('votes.id DESC')
paginate @user.upvoted_stories.includes(:tags).order("votes.id DESC")
}
@title = "Upvoted Stories"
@above = 'saved/subnav'
@above = "saved/subnav"
@rss_link = {
:title => "RSS 2.0 - Upvoted Stories",
:href => user_token_link("/upvoted.rss"),
title: "RSS 2.0 - Upvoted Stories",
href: user_token_link("/upvoted.rss")
}
respond_to do |format|
@ -296,13 +296,13 @@ class HomeController < ApplicationController
@title += " - Private feed for #{@user.username}"
end
render :action => "rss", :layout => false
render action: "rss", layout: false
}
format.json { render :json => @stories }
format.json { render json: @stories }
end
end
private
private
def filtered_tag_ids
if @user
@ -320,7 +320,7 @@ private
p = params[:page].to_i
if p == 0
p = 1
elsif p < 0 || p > (2 ** 32)
elsif p < 0 || p > (2**32)
raise ActionController::RoutingError.new("page out of bounds")
end
p
@ -334,9 +334,9 @@ private
if Rails.env.development? || @user || tags_filtered_by_cookie.any?
yield
else
key = opts.merge(page: page).sort.map {|k, v| "#{k}=#{v.to_param}" }.join(" ")
key = opts.merge(page: page).sort.map { |k, v| "#{k}=#{v.to_param}" }.join(" ")
begin
Rails.cache.fetch("stories #{key}", :expires_in => 45, &block)
Rails.cache.fetch("stories #{key}", expires_in: 45, &block)
rescue Errno::ENOENT => e
Rails.logger.error "error fetching stories #{key}: #{e}"
yield
@ -349,6 +349,6 @@ private
end
def tags_with_description_for_rss(tags)
tags.map {|tag| "#{tag.tag} (#{tag.description})" }.join(' ')
tags.map { |tag| "#{tag.tag} (#{tag.description})" }.join(" ")
end
end

View File

@ -1,5 +1,5 @@
class InvitationsController < ApplicationController
before_action :require_logged_in_user, :except => [:build, :create_by_request, :confirm_email]
before_action :require_logged_in_user, except: [:build, :create_by_request, :confirm_email]
before_action :show_title_h1
def build
@ -8,7 +8,7 @@ class InvitationsController < ApplicationController
@invitation_request = InvitationRequest.new
else
flash[:error] = "Public invitation requests are not allowed."
return redirect_to "/login"
redirect_to "/login"
end
end
@ -19,11 +19,11 @@ class InvitationsController < ApplicationController
return redirect_to "/"
end
@invitation_requests = InvitationRequest.where(:is_verified => true)
@invitation_requests = InvitationRequest.where(is_verified: true)
end
def confirm_email
if !(ir = InvitationRequest.where(:code => params[:code].to_s).first)
if !(ir = InvitationRequest.where(code: params[:code].to_s).first)
flash[:error] = "Invalid or expired invitation request"
return redirect_to "/invitations/request"
end
@ -31,9 +31,9 @@ class InvitationsController < ApplicationController
ir.is_verified = true
ir.save!
flash[:success] = "Your invitation request has been validated and " <<
"will now be shown to other logged-in users."
return redirect_to "/invitations/request"
flash[:success] = "Your invitation request has been validated and " \
"will now be shown to other logged-in users."
redirect_to "/invitations/request"
end
def create
@ -52,47 +52,48 @@ class InvitationsController < ApplicationController
i.save!
i.send_email
flash[:success] = "Successfully e-mailed invitation to " <<
params[:email].to_s << "."
params[:email].to_s << "."
rescue => e
Rails.logger.error "Error creating invitation for #{params[:email]}: #{e.message}"
flash[:error] = "Could not send invitation, verify the e-mail " <<
"address is valid."
flash[:error] = "Could not send invitation, verify the e-mail " \
"address is valid."
end
if params[:return_home]
return redirect_to "/"
redirect_to "/"
else
return redirect_to "/settings"
redirect_to "/settings"
end
end
def create_by_request
if Rails.application.allow_invitation_requests?
@invitation_request = InvitationRequest.new(
params.require(:invitation_request).permit(:name, :email, :memo))
params.require(:invitation_request).permit(:name, :email, :memo)
)
@invitation_request.ip_address = request.remote_ip
if @invitation_request.save
flash[:success] = "You have been e-mailed a confirmation to " <<
params[:invitation_request][:email].to_s << "."
return redirect_to "/invitations/request"
params[:invitation_request][:email].to_s << "."
redirect_to "/invitations/request"
else
render :action => :build
render action: :build
end
else
return redirect_to "/login"
redirect_to "/login"
end
end
def send_for_request
if !@user.can_see_invitation_requests?
flash[:error] = "Your account is not permitted to view invitation " <<
"requests."
flash[:error] = "Your account is not permitted to view invitation " \
"requests."
return redirect_to "/"
end
if !(ir = InvitationRequest.where(:code => params[:code].to_s).first)
if !(ir = InvitationRequest.where(code: params[:code].to_s).first)
flash[:error] = "Invalid or expired invitation request"
return redirect_to "/invitations"
end
@ -104,12 +105,12 @@ class InvitationsController < ApplicationController
i.send_email
ir.destroy!
flash[:success] = "Successfully e-mailed invitation to " <<
ir.name.to_s << "."
ir.name.to_s << "."
Rails.logger.info "[u#{@user.id}] sent invitiation for request " <<
ir.inspect
ir.inspect
return redirect_to "/invitations"
redirect_to "/invitations"
end
def delete_request
@ -117,18 +118,18 @@ class InvitationsController < ApplicationController
return redirect_to "/invitations"
end
if !(ir = InvitationRequest.where(:code => params[:code].to_s).first)
if !(ir = InvitationRequest.where(code: params[:code].to_s).first)
flash[:error] = "Invalid or expired invitation request"
return redirect_to "/invitations"
end
ir.destroy!
flash[:success] = "Successfully deleted invitation request from " <<
ir.name.to_s << "."
ir.name.to_s << "."
Rails.logger.info "[u#{@user.id}] deleted invitation request " <<
"from #{ir.inspect}"
Rails.logger.info "[u#{@user.id}] deleted invitation request " \
"from #{ir.inspect}"
return redirect_to "/invitations"
redirect_to "/invitations"
end
end

View File

@ -19,10 +19,10 @@ class KeybaseProofsController < ApplicationController
if Keybase.proof_valid?(kb_username, kb_signature, @user.username)
@user.add_or_update_keybase_proof(kb_username, kb_signature)
@user.save!
return redirect_to Keybase.success_url(kb_username, kb_signature, kb_ua, @user.username)
redirect_to Keybase.success_url(kb_username, kb_signature, kb_ua, @user.username)
else
flash[:error] = "Failed to connect your account to Keybase. Try again from Keybase."
return redirect_to settings_path
redirect_to settings_path
end
end
@ -51,7 +51,7 @@ class KeybaseProofsController < ApplicationController
@user_re = User.username_regex_s[1...-1]
end
private
private
def force_to_json
request.format = :json
@ -60,7 +60,7 @@ private
def check_user_matches
unless case_insensitive_match?(@user.username, params[:username])
flash[:error] = "not logged in as the correct user"
return redirect_to settings_path
redirect_to settings_path
end
end

View File

@ -1,14 +1,18 @@
class LoginBannedError < StandardError; end
class LoginDeletedError < StandardError; end
class LoginTOTPFailedError < StandardError; end
class LoginWipedError < StandardError; end
class LoginFailedError < StandardError; end
class LoginController < ApplicationController
before_action :authenticate_user
before_action :check_for_read_only_mode, :except => [:index]
before_action :check_for_read_only_mode, except: [:index]
before_action :require_no_user_or_redirect,
only: [:index, :login, :forgot_password, :reset_password]
only: [:index, :login, :forgot_password, :reset_password]
before_action :show_title_h1
def logout
@ -25,10 +29,10 @@ class LoginController < ApplicationController
end
def login
if params[:email].to_s.match(/@/)
user = User.where(:email => params[:email]).first
user = if /@/.match?(params[:email].to_s)
User.where(email: params[:email]).first
else
user = User.where(:username => params[:email]).first
User.where(username: params[:email]).first
end
fail_reason = nil
@ -54,7 +58,7 @@ class LoginController < ApplicationController
raise LoginDeletedError
end
if !user.password_digest.to_s.match(/^\$2a\$#{BCrypt::Engine::DEFAULT_COST}\$/)
if !user.password_digest.to_s.match(/^\$2a\$#{BCrypt::Engine::DEFAULT_COST}\$/o)
user.password = user.password_confirmation = params[:password].to_s
user.save
end
@ -82,8 +86,8 @@ class LoginController < ApplicationController
return redirect_to "/"
rescue LoginWipedError
fail_reason = "Your account was banned or deleted before the site changed admins. " <<
"Your email and password hash were wiped for privacy."
fail_reason = "Your account was banned or deleted before the site changed admins. " \
"Your email and password hash were wiped for privacy."
rescue LoginBannedError
fail_reason = "Your account has been banned. Log: #{user.banned_reason}"
rescue LoginDeletedError
@ -118,24 +122,24 @@ class LoginController < ApplicationController
end
if @found_user.is_wiped?
flash.now[:error] = "It's not possible to reset your password " <<
"because your account was deleted before the site changed admins " <<
"and your email address was wiped for privacy."
flash.now[:error] = "It's not possible to reset your password " \
"because your account was deleted before the site changed admins " \
"and your email address was wiped for privacy."
return forgot_password
end
@found_user.initiate_password_reset_for_ip(request.remote_ip)
flash.now[:success] = "Password reset instructions have been e-mailed to you."
return index
index
end
def set_new_password
@title = "Set New Password"
if (m = params[:token].to_s.match(/^(\d+)-/)) &&
(Time.current - Time.zone.at(m[1].to_i)) < 24.hours
@reset_user = User.where(:password_reset_token => params[:token].to_s).first
(Time.current - Time.zone.at(m[1].to_i)) < 24.hours
@reset_user = User.where(password_reset_token: params[:token].to_s).first
end
if @reset_user && !@reset_user.is_banned?
@ -152,30 +156,30 @@ class LoginController < ApplicationController
if @reset_user.save && @reset_user.is_active?
if @reset_user.has_2fa?
flash[:success] = "Your password has been reset."
return redirect_to "/login"
redirect_to "/login"
else
session[:u] = @reset_user.session_token
return redirect_to "/"
redirect_to "/"
end
else
flash[:error] = "Could not reset password."
end
end
else
flash[:error] = "Invalid reset token. It may have already been " <<
"used or you may have copied it incorrectly."
return redirect_to forgot_password_path
flash[:error] = "Invalid reset token. It may have already been " \
"used or you may have copied it incorrectly."
redirect_to forgot_password_path
end
end
def twofa
@title = "Login - Two Factor Authentication"
if (tmpu = find_twofa_user)
Rails.logger.info " Authenticated as user #{tmpu.id} " <<
"(#{tmpu.username}), verifying TOTP"
Rails.logger.info " Authenticated as user #{tmpu.id} " \
"(#{tmpu.username}), verifying TOTP"
else
reset_session
return redirect_to "/login"
redirect_to "/login"
end
end
@ -184,18 +188,18 @@ class LoginController < ApplicationController
if (tmpu = find_twofa_user) && tmpu.authenticate_totp(params[:totp_code])
session[:u] = tmpu.session_token
session.delete(:twofa_u)
return redirect_to "/"
redirect_to "/"
else
flash[:error] = "Your TOTP code did not match. Please try again."
return redirect_to "/login/2fa"
redirect_to "/login/2fa"
end
end
private
private
def find_twofa_user
if session[:twofa_u].present?
return User.where(:session_token => session[:twofa_u]).first
User.where(session_token: session[:twofa_u]).first
end
end
end

View File

@ -1,7 +1,7 @@
class MessagesController < ApplicationController
before_action :require_logged_in_user
before_action :require_logged_in_moderator, only: [:mod_note]
before_action :find_message, :only => [:show, :destroy, :keep_as_new, :mod_note]
before_action :find_message, only: [:show, :destroy, :keep_as_new, :mod_note]
before_action :show_title_h1
def index
@ -21,7 +21,7 @@ class MessagesController < ApplicationController
end
}
format.json {
render :json => @messages
render json: @messages
}
end
end
@ -38,10 +38,10 @@ class MessagesController < ApplicationController
@new_message = Message.new
render :action => "index"
render action: "index"
}
format.json {
render :json => @messages
render json: @messages
}
end
end
@ -59,11 +59,11 @@ class MessagesController < ApplicationController
ModNote.create_from_message(@new_message, @user)
end
flash[:success] = "Your message has been sent to " <<
@new_message.recipient.username.to_s << "."
return redirect_to "/messages"
@new_message.recipient.username.to_s << "."
redirect_to "/messages"
else
@messages = Message.inbox(@user).load
render :action => "index"
render action: "index"
end
end
@ -72,13 +72,13 @@ class MessagesController < ApplicationController
if @message.author
@new_message = Message.new
@new_message.recipient_username = (@message.author_user_id == @user.id ?
@new_message.recipient_username = ((@message.author_user_id == @user.id) ?
@message.recipient.username : @message.author.username)
if @message.subject.match(/^re:/i)
@new_message.subject = @message.subject
@new_message.subject = if /^re:/i.match?(@message.subject)
@message.subject
else
@new_message.subject = "Re: #{@message.subject}"
"Re: #{@message.subject}"
end
end
@ -103,9 +103,9 @@ class MessagesController < ApplicationController
flash[:success] = "Deleted message."
if @message.author_user_id == @user.id
return redirect_to "/messages/sent"
redirect_to "/messages/sent"
else
return redirect_to "/messages"
redirect_to "/messages"
end
end
@ -114,7 +114,7 @@ class MessagesController < ApplicationController
params.each do |k, v|
if (v.to_s == "1") && (m = k.match(/^delete_(.+)$/))
if (message = Message.where(:short_id => m[1]).first)
if (message = Message.where(short_id: m[1]).first)
ok = false
if message.author_user_id == @user.id
message.deleted_by_author = true
@ -133,27 +133,27 @@ class MessagesController < ApplicationController
end
end
flash[:success] = "Deleted #{deleted} #{'message'.pluralize(deleted)}"
flash[:success] = "Deleted #{deleted} #{"message".pluralize(deleted)}"
@user.update_unread_message_count!
return redirect_to "/messages"
redirect_to "/messages"
end
def keep_as_new
@message.has_been_read = false
@message.save
return redirect_to "/messages"
redirect_to "/messages"
end
def mod_note
ModNote.create_from_message(@message, @user)
return redirect_to @message, notice: 'ModNote created'
redirect_to @message, notice: "ModNote created"
end
private
private
def message_params
params.require(:message).permit(
@ -163,7 +163,7 @@ private
end
def find_message
if (@message = Message.where(:short_id => params[:message_id] || params[:id]).first)
if (@message = Message.where(short_id: params[:message_id] || params[:id]).first)
if @message.author_user_id == @user.id || @message.recipient_user_id == @user.id
return true
end
@ -171,6 +171,6 @@ private
flash[:error] = "Could not find message."
redirect_to "/messages"
return false
false
end
end

View File

@ -9,10 +9,10 @@ class ModController < ApplicationController
def index
@title = "Activity by Other Mods"
@moderations = Moderation.all
.eager_load(:moderator, :story, :tag, :user, :comment => [:story, :user])
.eager_load(:moderator, :story, :tag, :user, comment: [:story, :user])
.where("moderator_user_id != ? or moderator_user_id is null", @user.id)
.where('moderations.created_at >= (NOW() - INTERVAL 1 MONTH)')
.order('moderations.id desc')
.where("moderations.created_at >= (NOW() - INTERVAL 1 MONTH)")
.order("moderations.id desc")
end
def flagged_stories
@ -26,7 +26,7 @@ class ModController < ApplicationController
def flagged_comments
@title = "Flagged Comments"
@comments = period(Comment
.eager_load(:user, :hat, :story => :user, :votes => :user)
.eager_load(:user, :hat, story: :user, votes: :user)
.where("comments.flags >= 2")
.where("(select count(*) from votes where
votes.comment_id = comments.id and
@ -43,10 +43,10 @@ class ModController < ApplicationController
@commenters = fc.commenters
end
private
private
def default_periods
@periods = %w{1d 2d 3d 1w 1m}
@periods = %w[1d 2d 3d 1w 1m]
end
def period(query)

View File

@ -4,7 +4,7 @@ class ModNotesController < ModController
def index
@title = "Mod Notes"
@username = params[:username]
query = ModNote.order('created_at desc').includes(:moderator, :user)
query = ModNote.order("created_at desc").includes(:moderator, :user)
if (@username = params[:username])
if (user = User.find_by(username: @username))
@title = "#{@username} Mod Notes"
@ -23,15 +23,15 @@ class ModNotesController < ModController
@mod_note = ModNote.new(mod_note_params)
@mod_note.moderator = @user
if @mod_note.save
redirect_to user_path(@mod_note.user), success: 'Noted'
redirect_to user_path(@mod_note.user), success: "Noted"
else
# This is bad and needs to change if note ever has non-trivial validation
redirect_to user_path(@mod_note.user),
error: "Invalid note and Peter half-assed the error handling"
error: "Invalid note and Peter half-assed the error handling"
end
end
private
private
def mod_note_params
params.require(:mod_note).permit(:username, :note)

View File

@ -5,35 +5,35 @@ class ModerationsController < ApplicationController
def index
@title = "Moderation Log"
@moderators = ['(All)', '(Users)'] + User.moderators.map(&:username)
@moderators = ["(All)", "(Users)"] + User.moderators.map(&:username)
@moderator = params.fetch('moderator', '(All)')
@moderator = params.fetch("moderator", "(All)")
@what = {
:stories => params.dig(:what, :stories),
:comments => params.dig(:what, :comments),
:tags => params.dig(:what, :tags),
:users => params.dig(:what, :users),
:domains => params.dig(:what, :domains),
:categories => params.dig(:what, :categories),
stories: params.dig(:what, :stories),
comments: params.dig(:what, :comments),
tags: params.dig(:what, :tags),
users: params.dig(:what, :users),
domains: params.dig(:what, :domains),
categories: params.dig(:what, :categories)
}
@what.transform_values! { true } if @what.values.none?
@moderations = Moderation.all.eager_load(:moderator,
:story,
:comment,
:tag,
:user,
:domain,
:category)
:story,
:comment,
:tag,
:user,
:domain,
:category)
# filter based on target
@moderations = case @moderator
when '(All)'
when "(All)"
@moderations
when '(Users)'
when "(Users)"
@moderations.where("is_from_suggestions = true")
else
@moderations.joins(:moderator).where(:users => { :username => @moderator })
@moderations.joins(:moderator).where(users: {username: @moderator})
end
# filter based on type of thing moderated
@ -47,13 +47,13 @@ class ModerationsController < ApplicationController
@page = params[:page].to_i
if @page == 0
@page = 1
elsif @page < 0 || @page > (2 ** 32) || @page > @pages
elsif @page < 0 || @page > (2**32) || @page > @pages
raise ActionController::RoutingError.new("page out of bounds")
end
@moderations = @moderations
.offset((@page - 1) * ENTRIES_PER_PAGE)
.order("moderations.created_at desc")
.limit(ENTRIES_PER_PAGE)
.offset((@page - 1) * ENTRIES_PER_PAGE)
.order("moderations.created_at desc")
.limit(ENTRIES_PER_PAGE)
end
end

View File

@ -10,9 +10,9 @@ class RepliesController < ApplicationController
@title = "All Your Replies"
@replies = ReplyingComment
.for_user(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
.for_user(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
apply_current_vote
render :show
end
@ -20,9 +20,9 @@ class RepliesController < ApplicationController
def comments
@title = "Your Comment Replies"
@replies = ReplyingComment
.comment_replies_for(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
.comment_replies_for(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
apply_current_vote
render :show
end
@ -30,9 +30,9 @@ class RepliesController < ApplicationController
def stories
@title = "Your Story Replies"
@replies = ReplyingComment
.story_replies_for(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
.story_replies_for(@user.id)
.offset((@page - 1) * REPLIES_PER_PAGE)
.limit(REPLIES_PER_PAGE)
apply_current_vote
render :show
end
@ -44,7 +44,7 @@ class RepliesController < ApplicationController
render :show
end
private
private
# comments/_comment expects Comment objects to have a comment_vote attribute
# with the current user's vote added by StoriesController.load_user_votes
@ -53,7 +53,7 @@ private
next unless r.current_vote_vote.present?
r.comment.current_vote = {
vote: r.current_vote_vote,
reason: r.current_vote_reason.to_s,
reason: r.current_vote_reason.to_s
}
end
end
@ -70,7 +70,7 @@ private
@page = params[:page].to_i
if @page == 0
@page = 1
elsif @page < 0 || @page > (2 ** 32)
elsif @page < 0 || @page > (2**32)
raise ActionController::RoutingError.new("page out of bounds")
end
end

View File

@ -10,7 +10,7 @@ class SettingsController < ApplicationController
end
def delete_account
unless params[:user][:i_am_sure] == '1'
unless params[:user][:i_am_sure] == "1"
flash[:error] = 'You did not check the "I am sure" checkbox.'
return redirect_to settings_path
end
@ -21,13 +21,13 @@ class SettingsController < ApplicationController
@user.delete!
disown_text = ""
if params[:user][:disown] == '1'
if params[:user][:disown] == "1"
disown_text = " and disowned your stories and comments."
InactiveUser.disown_all_by_author! @user
end
reset_session
flash[:success] = "You have deleted your account#{disown_text}. Bye."
return redirect_to "/"
redirect_to "/"
end
def update
@ -35,15 +35,15 @@ class SettingsController < ApplicationController
@edit_user = @user.clone
if params[:user][:password].empty? ||
@user.authenticate(params[:current_password].to_s)
@user.authenticate(params[:current_password].to_s)
@edit_user.roll_session_token if params[:user][:password]
if @edit_user.update(user_params)
if @edit_user.username != previous_username
Moderation.create!(
is_from_suggestions: true,
user: @edit_user,
action: "changed own username from \"#{previous_username}\" " <<
"to \"#{@edit_user.username}\"",
action: "changed own username from \"#{previous_username}\" " \
"to \"#{@edit_user.username}\""
)
end
session[:u] = @user.session_token if params[:user][:password]
@ -54,7 +54,7 @@ class SettingsController < ApplicationController
flash[:error] = "Your current password was not entered correctly."
end
render :action => "index"
render action: "index"
end
def twofa
@ -69,13 +69,13 @@ class SettingsController < ApplicationController
if @user.has_2fa?
@user.disable_2fa!
flash[:success] = "Two-Factor Authentication has been disabled."
return redirect_to "/settings"
redirect_to "/settings"
else
return redirect_to twofa_enroll_url
redirect_to twofa_enroll_url
end
else
flash[:error] = "Your password was not correct."
return redirect_to twofa_url
redirect_to twofa_url
end
end
@ -91,15 +91,15 @@ class SettingsController < ApplicationController
session[:totp_secret] = ROTP::Base32.random
end
totp = ROTP::TOTP.new(session[:totp_secret], :issuer => Rails.application.name)
totp = ROTP::TOTP.new(session[:totp_secret], issuer: Rails.application.name)
totp_url = totp.provisioning_uri(@user.email)
qrcode = RQRCode::QRCode.new(totp_url)
qr = qrcode.as_svg(offset: 0,
fill: "ffffff",
color: "000",
module_size: 5,
shape_rendering: "crispEdges")
fill: "ffffff",
color: "000",
module_size: 5,
shape_rendering: "crispEdges")
@qr_secret = totp.secret
@qr_svg = "<a href=\"#{totp_url}\">#{qr}</a>"
@ -109,15 +109,15 @@ class SettingsController < ApplicationController
@title = "Two-Factor Authentication"
if ((Time.now.to_i - session[:last_authed].to_i) > TOTP_SESSION_TIMEOUT) ||
!session[:totp_secret]
!session[:totp_secret]
flash[:error] = "Your enrollment period timed out."
return redirect_to twofa_url
redirect_to twofa_url
end
end
def twofa_update
if ((Time.now.to_i - session[:last_authed].to_i) > TOTP_SESSION_TIMEOUT) ||
!session[:totp_secret]
!session[:totp_secret]
flash[:error] = "Your enrollment period timed out."
return redirect_to twofa_url
end
@ -132,11 +132,11 @@ class SettingsController < ApplicationController
flash[:success] = "Two-Factor Authentication has been enabled on your account."
session.delete(:totp_secret)
return redirect_to "/settings"
redirect_to "/settings"
else
flash[:error] = "Your TOTP code was invalid, please verify the " <<
"current code in your TOTP application."
return redirect_to twofa_verify_url
flash[:error] = "Your TOTP code was invalid, please verify the " \
"current code in your TOTP application."
redirect_to twofa_verify_url
end
end
@ -150,10 +150,10 @@ class SettingsController < ApplicationController
session[:pushover_rand] = SecureRandom.hex
return redirect_to Pushover.subscription_url(
:success => "#{Rails.application.root_url}settings/pushover_callback?" <<
redirect_to Pushover.subscription_url(
success: "#{Rails.application.root_url}settings/pushover_callback?" \
"rand=#{session[:pushover_rand]}",
:failure => "#{Rails.application.root_url}settings/",
failure: "#{Rails.application.root_url}settings/"
)
end
@ -175,24 +175,24 @@ class SettingsController < ApplicationController
@user.pushover_user_key = params[:pushover_user_key].to_s
@user.save!
if @user.pushover_user_key.present?
flash[:success] = "Your account is now setup for Pushover notifications."
flash[:success] = if @user.pushover_user_key.present?
"Your account is now setup for Pushover notifications."
else
flash[:success] = "Your account is no longer setup for Pushover notifications."
"Your account is no longer setup for Pushover notifications."
end
return redirect_to "/settings"
redirect_to "/settings"
end
def github_auth
session[:github_state] = SecureRandom.hex
return redirect_to Github.oauth_auth_url(session[:github_state])
redirect_to Github.oauth_auth_url(session[:github_state])
end
def github_callback
if !session[:github_state].present? ||
!params[:code].present? ||
(params[:state].to_s != session[:github_state].to_s)
!params[:code].present? ||
(params[:state].to_s != session[:github_state].to_s)
flash[:error] = "Invalid OAuth state"
return redirect_to "/settings"
end
@ -209,7 +209,7 @@ class SettingsController < ApplicationController
return github_disconnect
end
return redirect_to "/settings"
redirect_to "/settings"
end
def github_disconnect
@ -217,20 +217,20 @@ class SettingsController < ApplicationController
@user.github_username = nil
@user.save!
flash[:success] = "Your GitHub association has been removed."
return redirect_to "/settings"
redirect_to "/settings"
end
def twitter_auth
session[:twitter_state] = SecureRandom.hex
return redirect_to Twitter.oauth_auth_url(session[:twitter_state])
redirect_to Twitter.oauth_auth_url(session[:twitter_state])
rescue OAuth::Unauthorized
flash[:error] = "Twitter says we're not authenticating properly, please message the admin"
return redirect_to "/settings"
redirect_to "/settings"
end
def twitter_callback
if session[:twitter_state].blank? ||
(params[:state].to_s != session[:twitter_state].to_s)
(params[:state].to_s != session[:twitter_state].to_s)
flash[:error] = "Invalid OAuth state"
return redirect_to "/settings"
end
@ -238,7 +238,8 @@ class SettingsController < ApplicationController
session.delete(:twitter_state)
tok, sec, username = Twitter.token_secret_and_user_from_token_and_verifier(
params[:oauth_token], params[:oauth_verifier])
params[:oauth_token], params[:oauth_verifier]
)
if tok.present? && username.present?
@user.twitter_oauth_token = tok
@user.twitter_oauth_token_secret = sec
@ -249,7 +250,7 @@ class SettingsController < ApplicationController
return twitter_disconnect
end
return redirect_to "/settings"
redirect_to "/settings"
end
def twitter_disconnect
@ -258,10 +259,10 @@ class SettingsController < ApplicationController
@user.twitter_oauth_token_secret = nil
@user.save!
flash[:success] = "Your Twitter association has been removed."
return redirect_to "/settings"
redirect_to "/settings"
end
private
private
def user_params
params.require(:user).permit(

View File

@ -1,5 +1,5 @@
class SignupController < ApplicationController
before_action :require_logged_in_user, :check_new_users, :check_can_invite, :only => :invite
before_action :require_logged_in_user, :check_new_users, :check_can_invite, only: :invite
before_action :check_for_read_only_mode, :show_title_h1
def index
@ -9,7 +9,7 @@ class SignupController < ApplicationController
return redirect_to "/"
end
if Rails.application.open_signups?
redirect_to action: :invited, invitation_code: 'open' and return
redirect_to action: :invited, invitation_code: "open" and return
end
end
@ -27,7 +27,7 @@ class SignupController < ApplicationController
end
if !Rails.application.open_signups?
if !(@invitation = Invitation.unused.where(:code => params[:invitation_code].to_s).first)
if !(@invitation = Invitation.unused.where(code: params[:invitation_code].to_s).first)
flash[:error] = "Invalid or expired invitation"
return redirect_to "/signup"
end
@ -44,7 +44,7 @@ class SignupController < ApplicationController
def signup
if !Rails.application.open_signups?
if !(@invitation = Invitation.unused.where(:code => params[:invitation_code].to_s).first)
if !(@invitation = Invitation.unused.where(code: params[:invitation_code].to_s).first)
flash[:error] = "Invalid or expired invitation."
return redirect_to "/signup"
end
@ -59,40 +59,38 @@ class SignupController < ApplicationController
end
if @new_user.save
if @invitation
@invitation.update(used_at: Time.current, new_user: @new_user)
end
@invitation&.update(used_at: Time.current, new_user: @new_user)
session[:u] = @new_user.session_token
flash[:success] = "Welcome to #{Rails.application.name}, " <<
"#{@new_user.username}!"
flash[:success] = "Welcome to #{Rails.application.name}, " \
"#{@new_user.username}!"
if Rails.application.allow_new_users_to_invite?
return redirect_to signup_invite_path
redirect_to signup_invite_path
else
return redirect_to root_path
redirect_to root_path
end
else
render :action => "invited"
render action: "invited"
end
end
private
private
def check_new_users
if !Rails.application.allow_new_users_to_invite? && @user.is_new?
redirect_to root_path, flash: { error: "New users cannot send invites" }
redirect_to root_path, flash: {error: "New users cannot send invites"}
end
end
def check_can_invite
if !@user.can_invite?
redirect_to root_path, flash: { error: "You can't send invites" }
redirect_to root_path, flash: {error: "You can't send invites"}
end
end
def user_params
params.require(:user).permit(
:username, :email, :password, :password_confirmation, :about,
:username, :email, :password, :password_confirmation, :about
)
end
end

View File

@ -7,14 +7,14 @@ class StatsController < ApplicationController
@users_graph = monthly_graph("users_graph", {
graph_title: "Users joining by month",
scale_y_divisions: 100,
scale_y_divisions: 100
}) {
User.group("date_format(created_at, '%Y-%m')").count
}
@active_users_graph = monthly_graph("active_users_graph", {
graph_title: "Active users by month",
scale_y_divisions: 500,
scale_y_divisions: 500
}) {
User.connection.execute <<~SQL
SELECT ym, count(distinct user_id)
@ -32,27 +32,27 @@ class StatsController < ApplicationController
@stories_graph = monthly_graph("stories_graph", {
graph_title: "Stories submitted by month",
scale_y_divisions: 250,
scale_y_divisions: 250
}) {
Story.group("date_format(created_at, '%Y-%m')").count
}
@comments_graph = monthly_graph("comments_graph", {
graph_title: "Comments posted by month",
scale_y_divisions: 1_000,
scale_y_divisions: 1_000
}) {
Comment.group("date_format(created_at, '%Y-%m')").count
}
@votes_graph = monthly_graph("votes_graph", {
graph_title: "Votes cast by month",
scale_y_divisions: 10_000,
scale_y_divisions: 10_000
}) {
Vote.group("date_format(updated_at, '%Y-%m')").count
}
end
private
private
def monthly_graph(cache_key, opts)
Rails.cache.fetch(cache_key, expires_in: 1.day) {
@ -82,12 +82,12 @@ private
area_fill: false,
min_y_value: 0,
number_format: "%d",
show_lines: false,
show_lines: false
}
graph = TimeSeries.new(defaults.merge(opts))
graph.add_data(
data: yield.to_a.flatten,
template: "%Y-%m",
template: "%Y-%m"
)
graph.burn_svg_only
}

View File

@ -2,12 +2,12 @@ class StoriesController < ApplicationController
caches_page :show, if: CACHE_PAGE
before_action :require_logged_in_user_or_400,
:only => [:upvote, :flag, :unvote, :hide, :unhide, :preview, :save, :unsave]
only: [:upvote, :flag, :unvote, :hide, :unhide, :preview, :save, :unsave]
before_action :require_logged_in_user,
:only => [:destroy, :create, :edit, :fetch_url_attributes, :new, :suggest]
before_action :verify_user_can_submit_stories, :only => [:new, :create]
before_action :find_user_story, :only => [:destroy, :edit, :undelete, :update]
before_action :find_story!, :only => [:suggest, :submit_suggestions]
only: [:destroy, :create, :edit, :fetch_url_attributes, :new, :suggest]
before_action :verify_user_can_submit_stories, only: [:new, :create]
before_action :find_user_story, only: [:destroy, :edit, :undelete, :update]
before_action :find_story!, only: [:suggest, :submit_suggestions]
around_action :track_story_reads, only: [:show], if: -> { @user.present? }
before_action :show_title_h1, only: [:new, :edit, :suggest]
@ -24,7 +24,7 @@ class StoriesController < ApplicationController
end
end
return render :action => "new"
render action: "new"
end
def destroy
@ -36,8 +36,8 @@ class StoriesController < ApplicationController
update_story_attributes
if @story.user_id != @user.id && @user.is_moderator? && !@story.moderation_reason.present?
@story.errors.add(:moderation_reason, message: 'is required')
return render :action => "edit"
@story.errors.add(:moderation_reason, message: "is required")
return render action: "edit"
end
@story.is_deleted = true
@ -69,7 +69,7 @@ class StoriesController < ApplicationController
s.fetching_ip = request.remote_ip
s.url = params[:fetch_url]
return render :json => s.fetched_attributes
render json: s.fetched_attributes
end
def new
@ -83,8 +83,8 @@ class StoriesController < ApplicationController
sattrs = @story.fetched_attributes
if sattrs[:url].present? && @story.url != sattrs[:url]
flash.now[:notice] = "Note: URL has been changed to fetched " <<
"canonicalized version"
flash.now[:notice] = "Note: URL has been changed to fetched " \
"canonicalized version"
@story.url = sattrs[:url]
end
@ -108,14 +108,14 @@ class StoriesController < ApplicationController
@story.user_id = @user.id
@story.previewing = true
@story.vote = Vote.new(:vote => 1)
@story.vote = Vote.new(vote: 1)
@story.score = 1
@story.valid?
@story.seen_previous = true
return render :action => "new", :layout => false
render action: "new", layout: false
end
def show
@ -142,12 +142,12 @@ class StoriesController < ApplicationController
@moderation = Moderation
.where(story: @story, comment: nil)
.where("action LIKE '%deleted story%'")
.order('id desc')
.order("id desc")
.first
end
if !@story.can_be_seen_by_user?(@user)
respond_to do |format|
format.html { return render action: '_missing', status: 404 }
format.html { return render action: "_missing", status: 404 }
format.json { raise ActiveRecord::RecordNotFound }
end
end
@ -169,9 +169,9 @@ class StoriesController < ApplicationController
"twitter:site" => "@lobsters",
"twitter:title" => @story.title,
"twitter:description" => @story.comments_count.to_s + " " +
'comment'.pluralize(@story.comments_count),
"comment".pluralize(@story.comments_count),
"twitter:image" => Rails.application.root_url +
"apple-touch-icon-144.png",
"apple-touch-icon-144.png"
}
if @story.user.twitter_username.present?
@ -180,25 +180,25 @@ class StoriesController < ApplicationController
load_user_votes
render :action => "show"
render action: "show"
}
format.json {
render :json => @story.as_json(:with_comments => @comments)
render json: @story.as_json(with_comments: @comments)
}
end
end
def suggest
@title = 'Suggest Story Changes'
@title = "Suggest Story Changes"
if !@story.can_have_suggestions_from_user?(@user)
flash[:error] = "You are not allowed to offer suggestions on that story."
return redirect_to @story.comments_path
end
if (suggested_tags = @story.suggested_taggings.where(:user_id => @user.id)).any?
@story.tags_a = suggested_tags.map {|st| st.tag.tag }
if (suggested_tags = @story.suggested_taggings.where(user_id: @user.id)).any?
@story.tags_a = suggested_tags.map { |st| st.tag.tag }
end
if (tt = @story.suggested_titles.where(:user_id => @user.id).first)
if (tt = @story.suggested_titles.where(user_id: @user.id).first)
@story.title = tt.title
end
end
@ -219,7 +219,7 @@ class StoriesController < ApplicationController
dsug = true
end
sugtags = params[:story][:tags_a].reject {|t| t.to_s.strip == "" }.sort
sugtags = params[:story][:tags_a].reject { |t| t.to_s.strip == "" }.sort
if @story.tags_a.sort != sugtags
@story.save_suggested_tags_a_for_user!(sugtags, @user)
dsug = true
@ -231,7 +231,7 @@ class StoriesController < ApplicationController
end
redirect_to ostory.comments_path
else
render :action => "suggest"
render action: "suggest"
end
end
@ -264,106 +264,106 @@ class StoriesController < ApplicationController
update_story_attributes
if @story.save
return redirect_to @story.comments_path
redirect_to @story.comments_path
else
return render :action => "edit"
render action: "edit"
end
end
def unvote
if !(story = find_story) || story.is_gone?
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
Vote.vote_thusly_on_story_or_comment_for_user_because(
0, story.id, nil, @user.id, nil
)
render :plain => "ok"
render plain: "ok"
end
def upvote
if !(story = find_story) || story.is_gone?
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
if story.merged_into_story
return render :plain => "story has been merged", :status => 400
return render plain: "story has been merged", status: 400
end
Vote.vote_thusly_on_story_or_comment_for_user_because(
1, story.id, nil, @user.id, nil
)
render :plain => "ok"
render plain: "ok"
end
def flag
if !(story = find_story) || story.is_gone?
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
if !Vote::STORY_REASONS[params[:reason]]
return render :plain => "invalid reason", :status => 400
return render plain: "invalid reason", status: 400
end
if !@user.can_flag?(story)
return render :plain => "not permitted to flag", :status => 400
return render plain: "not permitted to flag", status: 400
end
Vote.vote_thusly_on_story_or_comment_for_user_because(
-1, story.id, nil, @user.id, params[:reason]
)
render :plain => "ok"
render plain: "ok"
end
def hide
if !(story = find_story)
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
if story.merged_into_story
return render :plain => "story has been merged", :status => 400
return render plain: "story has been merged", status: 400
end
HiddenStory.hide_story_for_user(story.id, @user.id)
render :plain => "ok"
render plain: "ok"
end
def unhide
if !(story = find_story)
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
HiddenStory.unhide_story_for_user(story.id, @user.id)
render :plain => "ok"
render plain: "ok"
end
def save
if !(story = find_story)
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
if story.merged_into_story
return render :plain => "story has been merged", :status => 400
return render plain: "story has been merged", status: 400
end
SavedStory.save_story_for_user(story.id, @user.id)
render :plain => "ok"
render plain: "ok"
end
def unsave
if !(story = find_story)
return render :plain => "can't find story", :status => 400
return render plain: "can't find story", status: 400
end
SavedStory.where(:user_id => @user.id, :story_id => story.id).delete_all
SavedStory.where(user_id: @user.id, story_id: story.id).delete_all
render :plain => "ok"
render plain: "ok"
end
def check_url_dupe
@ -374,19 +374,19 @@ class StoriesController < ApplicationController
respond_to do |format|
format.html {
return render :partial => "stories/form_errors", :layout => false,
:content_type => "text/html", :locals => { :story => @story }
return render partial: "stories/form_errors", layout: false,
content_type: "text/html", locals: {story: @story}
}
# json: https://github.com/lobsters/lobsters/pull/555
format.json {
similar_stories = @story.public_similar_stories(@user).map(&:as_json)
render :json => @story.as_json.merge(similar_stories: similar_stories)
render json: @story.as_json.merge(similar_stories: similar_stories)
}
end
end
private
private
def get_arranged_comments_from_cache(short_id, &block)
if Rails.env.development? || @user
@ -400,10 +400,10 @@ private
p = params.require(:story).permit(
:title, :url, :description, :moderation_reason, :seen_previous,
:merge_story_short_id, :is_unavailable, :user_is_author, :user_is_following,
:tags_a => [],
tags_a: []
)
if @user && @user.is_moderator?
if @user&.is_moderator?
p
else
p.except(:moderation_reason, :merge_story_short_id, :is_unavailable)
@ -411,23 +411,23 @@ private
end
def update_story_attributes
if @story.url_is_editable_by_user?(@user)
@story.attributes = story_params
@story.attributes = if @story.url_is_editable_by_user?(@user)
story_params
else
@story.attributes = story_params.except(:url)
story_params.except(:url)
end
end
def find_story
story = Story.find_by(:short_id => params[:story_id])
story = Story.find_by(short_id: params[:story_id])
# convenience to use PK (from external queries) without generally permitting enumeration:
story ||= Story.find(params[:id]) if @user && @user.is_admin?
story ||= Story.find(params[:id]) if @user&.is_admin?
if @user && story
story.vote = Vote.find_by(
user: @user,
story: story.id,
comment: nil
comment: nil
).try(:vote)
end
@ -442,32 +442,32 @@ private
end
def find_user_story
if @user.is_moderator?
@story = Story.where(:short_id => params[:story_id] || params[:id]).first
@story = if @user.is_moderator?
Story.where(short_id: params[:story_id] || params[:id]).first
else
@story = Story.where(:user_id => @user.id, :short_id =>
(params[:story_id] || params[:id])).first
Story.where(user_id: @user.id, short_id: (params[:story_id] || params[:id])).first
end
if !@story
flash[:error] = "Could not find story or you are not authorized " <<
"to manage it."
flash[:error] = "Could not find story or you are not authorized " \
"to manage it."
redirect_to "/"
return false
false
end
end
def load_user_votes
if @user
if (v = Vote.where(:user_id => @user.id, :story_id => @story.id, :comment_id => nil).first)
@story.vote = { :vote => v.vote, :reason => v.reason }
if (v = Vote.where(user_id: @user.id, story_id: @story.id, comment_id: nil).first)
@story.vote = {vote: v.vote, reason: v.reason}
end
@story.is_hidden_by_cur_user = @story.is_hidden_by_user?(@user)
@story.is_saved_by_cur_user = @story.is_saved_by_user?(@user)
@votes = Vote.comment_votes_by_user_for_story_hash(
@user.id, (@story.merged_stories.ids).push(@story.id))
@user.id, @story.merged_stories.ids.push(@story.id)
)
@comments.each do |c|
if @votes[c.id]
c.current_vote = @votes[c.id]
@ -479,7 +479,7 @@ private
def verify_user_can_submit_stories
if !@user.can_submit_stories?
flash[:error] = "You are not allowed to submit new stories."
return redirect_to "/"
redirect_to "/"
end
end

View File

@ -5,18 +5,18 @@ class TagsController < ApplicationController
def index
@title = "Tags"
@categories = Category.all.order('category asc').includes(:tags)
@categories = Category.all.order("category asc").includes(:tags)
@tags = Tag.all
if @user
@filtered_tags = @user.tag_filter_tags.index_by(&:id)
@filtered_tags = if @user
@user.tag_filter_tags.index_by(&:id)
else
@filtered_tags = tags_filtered_by_cookie.index_by(&:id)
tags_filtered_by_cookie.index_by(&:id)
end
respond_to do |format|
format.html { render :action => "index" }
format.json { render :json => @tags }
format.html { render action: "index" }
format.json { render json: @tags }
end
end
@ -26,34 +26,34 @@ class TagsController < ApplicationController
end
def create
@title = 'Create Tag'
@title = "Create Tag"
tag = Tag.create(tag_params)
if tag.valid?
flash[:success] = "Tag #{tag.tag} has been created"
redirect_to tags_path
else
flash[:error] = "New tag not created: #{tag.errors.full_messages.join(', ')}"
flash[:error] = "New tag not created: #{tag.errors.full_messages.join(", ")}"
redirect_to new_tag_path
end
end
def edit
@tag = Tag.where(:tag => params[:tag_name]).first!
@tag = Tag.where(tag: params[:tag_name]).first!
@title = "Edit Tag"
end
def update
tag = Tag.where(:tag => params[:tag_name]).first!
tag = Tag.where(tag: params[:tag_name]).first!
if tag.update(tag_params)
flash[:success] = "Tag #{tag.tag} has been updated"
redirect_to tags_path
else
flash[:error] = "Tag not updated: #{tag.errors.full_messages.join(', ')}"
flash[:error] = "Tag not updated: #{tag.errors.full_messages.join(", ")}"
redirect_to edit_tag_path
end
end
private
private
def tag_params
params.require(:tag).permit(
@ -65,7 +65,7 @@ private
:privileged,
:is_media,
:active,
:hotness_mod,
:hotness_mod
).merge(edit_user_id: @user.id)
end
end

View File

@ -1,7 +1,7 @@
class UsersController < ApplicationController
before_action :load_showing_user, :only => [:show, :standing]
before_action :load_showing_user, only: [:show, :standing]
before_action :require_logged_in_moderator,
:only => [:enable_invitation, :disable_invitation, :ban, :unban]
only: [:enable_invitation, :disable_invitation, :ban, :unban]
before_action :flag_warning, only: [:show]
before_action :require_logged_in_user, only: [:standing]
before_action :only_user_or_moderator, only: [:standing]
@ -21,8 +21,8 @@ class UsersController < ApplicationController
end
respond_to do |format|
format.html { render :action => "show" }
format.json { render :json => @showing_user }
format.html { render action: "show" }
format.json { render json: @showing_user }
end
end
@ -31,32 +31,32 @@ class UsersController < ApplicationController
newest_user = User.last.id
# pulling 10k+ users is significant enough memory pressure this is worthwhile
attrs = %w{banned_at created_at deleted_at id invited_by_user_id is_admin is_moderator karma
username}
attrs = %w[banned_at created_at deleted_at id invited_by_user_id is_admin is_moderator karma
username]
if params[:by].to_s == "karma"
content = Rails.cache.fetch("users_by_karma_#{newest_user}", :expires_in => (60 * 60 * 24)) {
content = Rails.cache.fetch("users_by_karma_#{newest_user}", expires_in: (60 * 60 * 24)) {
@users = User.select(*attrs).order("karma DESC, id ASC").to_a
@user_count = @users.length
@title << " By Karma"
render_to_string :action => "list", :layout => nil
render_to_string action: "list", layout: nil
}
render :html => content.html_safe, :layout => "application"
render html: content.html_safe, layout: "application"
elsif params[:moderators]
@users = User.select(*attrs).where("is_admin = ? OR is_moderator = ?", true, true)
.order("id ASC").to_a
@user_count = @users.length
@title = "Moderators and Administrators"
render :action => "list"
render action: "list"
else
content = Rails.cache.fetch("users_tree_#{newest_user}", :expires_in => (60 * 60 * 24)) {
content = Rails.cache.fetch("users_tree_#{newest_user}", expires_in: (60 * 60 * 24)) {
users = User.select(*attrs).order("id DESC").to_a
@user_count = users.length
@users_by_parent = users.group_by(&:invited_by_user_id)
@newest = User.select(*attrs).order("id DESC").limit(10)
render_to_string :action => "tree", :layout => nil
render_to_string action: "tree", layout: nil
}
render :html => content.html_safe, :layout => "application"
render html: content.html_safe, layout: "application"
end
end
@ -65,7 +65,7 @@ class UsersController < ApplicationController
end
def disable_invitation
target = User.where(:username => params[:username]).first
target = User.where(username: params[:username]).first
if !target
flash[:error] = "Invalid user."
redirect_to "/"
@ -73,12 +73,12 @@ class UsersController < ApplicationController
target.disable_invite_by_user_for_reason!(@user, params[:reason])
flash[:success] = "User has had invite capability disabled."
redirect_to user_path(:user => target.username)
redirect_to user_path(user: target.username)
end
end
def enable_invitation
target = User.where(:username => params[:username]).first
target = User.where(username: params[:username]).first
if !target
flash[:error] = "Invalid user."
redirect_to "/"
@ -86,12 +86,12 @@ class UsersController < ApplicationController
target.enable_invite_by_user!(@user)
flash[:success] = "User has had invite capability enabled."
redirect_to user_path(:user => target.username)
redirect_to user_path(user: target.username)
end
end
def ban
buser = User.where(:username => params[:username]).first
buser = User.where(username: params[:username]).first
if !buser
flash[:error] = "Invalid user."
return redirect_to "/"
@ -99,17 +99,17 @@ class UsersController < ApplicationController
if !params[:reason].present?
flash[:error] = "You must give a reason for the ban."
return redirect_to user_path(:user => buser.username)
return redirect_to user_path(user: buser.username)
end
buser.ban_by_user_for_reason!(@user, params[:reason])
flash[:success] = "User has been banned."
return redirect_to user_path(:user => buser.username)
redirect_to user_path(user: buser.username)
end
def unban
buser = User.where(:username => params[:username]).first
buser = User.where(username: params[:username]).first
if !buser
flash[:error] = "Invalid user."
return redirect_to "/"
@ -118,7 +118,7 @@ class UsersController < ApplicationController
buser.unban_by_user!(@user, params[:reason])
flash[:success] = "User has been unbanned."
return redirect_to user_path(:user => buser.username)
redirect_to user_path(user: buser.username)
end
def standing
@ -126,7 +126,7 @@ class UsersController < ApplicationController
int = @flag_warning_int
fc = FlaggedCommenters.new(int[:param], 1.day)
@fc_flagged = fc.commenters.map {|_, c| c[:n_flags] }.sort
@fc_flagged = fc.commenters.map { |_, c| c[:n_flags] }.sort
@flagged_user_stats = fc.check_list_for(@showing_user)
rows = ActiveRecord::Base.connection.exec_query("
@ -144,7 +144,7 @@ class UsersController < ApplicationController
order by 1 asc;
").rows
users = Array.new(@fc_flagged.last.to_i + 1, 0)
rows.each {|r| users[r.first] = r.last }
rows.each { |r| users[r.first] = r.last }
@lookup = rows.to_h
@flagged_comments = @showing_user.comments
@ -152,11 +152,11 @@ class UsersController < ApplicationController
comments.flags > 0 and
comments.created_at >= now() - interval #{int[:dur]} #{int[:intv]}")
.order("id DESC")
.includes(:user, :hat, :story => :user)
.includes(:user, :hat, story: :user)
.joins(:story)
end
private
private
def load_showing_user
# case-insensitive search by username
@ -171,7 +171,7 @@ private
# now a case-sensitive check
if params[:username] != @showing_user.username
redirect_to username: @showing_user.username
return false
false
end
end

View File

@ -6,12 +6,12 @@ module ApplicationHelper
def avatar_img(user, size)
image_tag(
user.avatar_path(size),
:srcset => "#{user.avatar_path(size)} 1x, #{user.avatar_path(size * 2)} 2x",
:class => "avatar",
:size => "#{size}x#{size}",
:alt => "#{user.username} avatar",
:loading => "lazy",
:decoding => "async",
srcset: "#{user.avatar_path(size)} 1x, #{user.avatar_path(size * 2)} 2x",
class: "avatar",
size: "#{size}x#{size}",
alt: "#{user.username} avatar",
loading: "lazy",
decoding: "async"
)
end
@ -19,7 +19,7 @@ module ApplicationHelper
html = ""
unless object.errors.blank?
html << "<div class=\"flash-error\">"
html << "<h2>#{pluralize(object.errors.count, 'error')} prohibited this \
html << "<h2>#{pluralize(object.errors.count, "error")} prohibited this \
#{object.class.name.downcase} from being saved</h2>"
html << "<p>There were the problems with the following fields:</p>"
html << "<ul>"
@ -35,28 +35,28 @@ module ApplicationHelper
# limitation: this can't handle generating links based on a hash of options,
# like { controller: ..., action: ... }
def link_to_different_page(text, path, options = {})
current = request.path.sub(/\/page\/\d+$/, '')
path.sub!(/\/page\/\d+$/, '')
current = request.path.sub(/\/page\/\d+$/, "")
path.sub!(/\/page\/\d+$/, "")
options[:class] = :current_page if current == path
link_to text, path, options
end
def link_post button_label, link, options = {}
options.reverse_merge class_name: nil, confirm: nil
render partial: 'helpers/link_post', locals: {
render partial: "helpers/link_post", locals: {
button_label: button_label,
link: link,
class_name: options[:class_name],
confirm: options[:confirm],
confirm: options[:confirm]
}
end
def page_numbers_for_pagination(max, cur)
if max <= MAX_PAGES
return (1 .. max).to_a
return (1..max).to_a
end
pages = (cur - (MAX_PAGES / 2) + 1 .. cur + (MAX_PAGES / 2) - 1).to_a
pages = (cur - (MAX_PAGES / 2) + 1..cur + (MAX_PAGES / 2) - 1).to_a
while pages[0] < 1
pages.push pages.last + 1

View File

@ -1,9 +1,9 @@
module IntervalHelper
TIME_INTERVALS = { "h" => "Hour",
"d" => "Day",
"w" => "Week",
"m" => "Month",
"y" => "Year", }.freeze
TIME_INTERVALS = {"h" => "Hour",
"d" => "Day",
"w" => "Week",
"m" => "Month",
"y" => "Year"}.freeze
def time_interval(param)
if (m = param.to_s.match(/\A(\d+)([#{TIME_INTERVALS.keys.join}])\z/))
@ -12,10 +12,10 @@ module IntervalHelper
param: param,
dur: dur,
intv: TIME_INTERVALS[m[2]],
human: "#{dur == 1 ? '' : dur} #{TIME_INTERVALS[m[2]]}".downcase.pluralize(dur).chomp,
human: "#{(dur == 1) ? "" : dur} #{TIME_INTERVALS[m[2]]}".downcase.pluralize(dur).chomp
}
else
{ input: '1w', dur: 1, intv: "Week", human: 'week' }
{input: "1w", dur: 1, intv: "Week", human: "week"}
end
end
end

View File

@ -4,6 +4,6 @@ module KeybaseProofsHelper
end
def keybase_proof_link(kb_sig)
File.join Keybase.BASE_URL, kb_sig[:kb_username], "sigchain\##{kb_sig[:sig_hash]}"
File.join Keybase.BASE_URL, kb_sig[:kb_username], "sigchain##{kb_sig[:sig_hash]}"
end
end

View File

@ -9,11 +9,11 @@ module StoriesHelper
end
if Moderation.joins(:story)
.where(
"stories.user_id = ? AND moderations.created_at > ?",
@user.id,
5.days.ago
).exists?
.where(
"stories.user_id = ? AND moderations.created_at > ?",
@user.id,
5.days.ago
).exists?
return true
end

View File

@ -30,10 +30,10 @@ module TrafficHelper
end
def self.cache_traffic!
low, high = self.traffic_range
Keystore.put('traffic:low', low)
Keystore.put('traffic:high', high)
Keystore.put('traffic:intensity', current_intensity(low, high))
low, high = traffic_range
Keystore.put("traffic:low", low)
Keystore.put("traffic:high", high)
Keystore.put("traffic:intensity", current_intensity(low, high))
end
def self.current_activity
@ -50,11 +50,11 @@ module TrafficHelper
def self.current_intensity(low, high)
return 0.5 if low.nil? || high.nil? || high == low
activity = [low, current_activity, high].sort[1]
[0, ((activity - low)*1.0/(high - low) * 100).round, 100].sort[1]
[0, ((activity - low) * 1.0 / (high - low) * 100).round, 100].sort[1]
end
def self.cached_current_intensity
Keystore.value_for('traffic:intensity') || 0.5
Keystore.value_for("traffic:intensity") || 0.5
end
# rubocop:disable Layout/LineLength

View File

@ -32,18 +32,18 @@ module UsersHelper
def styled_user_link user, content = nil, css_classes = []
if content.is_a?(Story) && content.user_is_author?
css_classes.push 'user_is_author'
css_classes.push "user_is_author"
end
if content.is_a?(Comment) && content.story &&
content.story.user_is_author? && content.story.user_id == user.id
css_classes.push 'user_is_author'
content.story.user_is_author? && content.story.user_id == user.id
css_classes.push "user_is_author"
end
if !user.is_active?
css_classes.push 'inactive_user'
css_classes.push "inactive_user"
end
if user.is_new?
css_classes.push 'new_user'
css_classes.push "new_user"
end
link_to(user.username, user, class: css_classes)
@ -59,9 +59,9 @@ module UsersHelper
end
end
private
private
def user_is_moderator?
@user && @user.is_moderator?
@user&.is_moderator?
end
end

View File

@ -1,3 +1,3 @@
class ApplicationMailer < ActionMailer::Base
default :from => "#{Rails.application.name} <nobody@#{Rails.application.domain}>"
default from: "#{Rails.application.name} <nobody@#{Rails.application.domain}>"
end

View File

@ -4,10 +4,10 @@ class BanNotification < ApplicationMailer
@reason = reason
mail(
:from => "#{@banner.username} <nobody@#{Rails.application.domain}>",
:replyto => "#{@banner.username} <#{@banner.email}>",
:to => user.email,
:subject => "[#{Rails.application.name}] You have been banned"
from: "#{@banner.username} <nobody@#{Rails.application.domain}>",
replyto: "#{@banner.username} <#{@banner.email}>",
to: user.email,
subject: "[#{Rails.application.name}] You have been banned"
)
end
end

View File

@ -4,8 +4,8 @@ class EmailMessage < ApplicationMailer
@user = user
mail(
:to => user.email,
:subject => "[#{Rails.application.name}] Private Message from " <<
to: user.email,
subject: "[#{Rails.application.name}] Private Message from " \
"#{message.author_username}: #{message.subject}"
)
end

View File

@ -4,8 +4,8 @@ class EmailReply < ApplicationMailer
@user = user
mail(
:to => user.email,
:subject => "[#{Rails.application.name}] Reply from " <<
to: user.email,
subject: "[#{Rails.application.name}] Reply from " \
"#{comment.user.username} on #{comment.story.title}"
)
end
@ -15,8 +15,8 @@ class EmailReply < ApplicationMailer
@user = user
mail(
:to => user.email,
:subject => "[#{Rails.application.name}] Mention from " <<
to: user.email,
subject: "[#{Rails.application.name}] Mention from " \
"#{comment.user.username} on #{comment.story.title}"
)
end

View File

@ -4,7 +4,7 @@ class InvitationRequestMailer < ApplicationMailer
mail(
to: invitation_request.email,
subject: "[#{Rails.application.name}] Confirm your invitation " <<
subject: "[#{Rails.application.name}] Confirm your invitation " \
"request to " << Rails.application.name
)
end

View File

@ -4,8 +4,8 @@ class PasswordReset < ApplicationMailer
@ip = ip
mail(
:to => user.email,
:subject => "[#{Rails.application.name}] Reset your password"
to: user.email,
subject: "[#{Rails.application.name}] Reset your password"
)
end
end

View File

@ -2,5 +2,5 @@ class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
# https://stackoverflow.com/questions/50026344/composing-activerecord-scopes-with-selects
scope :select_fix, -> { select(self.arel_table.project(Arel.star)) }
scope :select_fix, -> { select(arel_table.project(Arel.star)) }
end

View File

@ -1,33 +1,33 @@
class Category < ApplicationRecord
has_many :tags,
-> { order('tag asc') },
dependent: :restrict_with_error,
inverse_of: :category
-> { order("tag asc") },
dependent: :restrict_with_error,
inverse_of: :category
has_many :stories, through: :tags
after_save :log_modifications
attr_accessor :edit_user_id
validates :category, length: { maximum: 25 }, presence: true,
uniqueness: { case_sensitive: false },
format: { with: /\A[A-Za-z0-9_\-]+\z/ }
validates :category, length: {maximum: 25}, presence: true,
uniqueness: {case_sensitive: false},
format: {with: /\A[A-Za-z0-9_\-]+\z/}
def to_param
self.category
category
end
def log_modifications
Moderation.create do |m|
if self.id_previously_changed?
m.action = 'Created new category ' +
self.attributes.map {|f, c| "with #{f} '#{c}'" }.join(', ')
m.action = if id_previously_changed?
"Created new category " +
attributes.map { |f, c| "with #{f} '#{c}'" }.join(", ")
else
m.action = "Updating category #{self.category}, " + self.saved_changes
.map {|f, c| "changed #{f} from '#{c[0]}' to '#{c[1]}'" } .join(', ')
"Updating category #{category}, " + saved_changes
.map { |f, c| "changed #{f} from '#{c[0]}' to '#{c[1]}'" }.join(", ")
end
m.moderator_user_id = @edit_user_id
m.category_id = self.id
m.category_id = id
end
end
end

View File

@ -1,21 +1,19 @@
require 'set'
class Comment < ApplicationRecord
belongs_to :user
belongs_to :story,
:inverse_of => :comments
inverse_of: :comments
has_many :votes,
:dependent => :delete_all
dependent: :delete_all
belongs_to :parent_comment,
:class_name => "Comment",
:inverse_of => false,
:optional => true
class_name: "Comment",
inverse_of: false,
optional: true
has_one :moderation,
:class_name => "Moderation",
:inverse_of => :comment,
:dependent => :destroy
class_name: "Moderation",
inverse_of: :comment,
dependent: :destroy
belongs_to :hat,
:optional => true
optional: true
has_many :taggings, through: :story
attr_accessor :current_vote, :previewing
@ -23,15 +21,15 @@ class Comment < ApplicationRecord
attribute :reply_count, :integer
before_validation on: :create do
self.assign_short_id_and_score
self.assign_initial_confidence
self.assign_thread_id
assign_short_id_and_score
assign_initial_confidence
assign_thread_id
end
after_create :record_initial_upvote, :mark_submitter, :deliver_reply_notifications,
:deliver_mention_notifications, :log_hat_use
:deliver_mention_notifications, :log_hat_use
after_create do
# fire this once after record_initial_upvote
self.update_score_and_recalculate! 0, 0
update_score_and_recalculate! 0, 0
end
after_destroy :unassign_votes
@ -39,20 +37,20 @@ class Comment < ApplicationRecord
scope :not_deleted, -> { where(is_deleted: false) }
scope :not_moderated, -> { where(is_moderated: false) }
scope :active, -> { not_deleted.not_moderated }
scope :accessible_to_user, ->(user) { user && user.is_moderator? ? all : active }
scope :accessible_to_user, ->(user) { (user && user.is_moderator?) ? all : active }
scope :for_presentation, -> {
includes(:user, :hat, :moderation => :moderator, :story => :user, :votes => :user)
includes(:user, :hat, moderation: :moderator, story: :user, votes: :user)
}
scope :not_on_story_hidden_by, ->(user) {
user ? where.not(
HiddenStory.select('TRUE')
.where(Arel.sql('hidden_stories.story_id = stories.id'))
HiddenStory.select("TRUE")
.where(Arel.sql("hidden_stories.story_id = stories.id"))
.by(user).arel.exists
) : where('true')
) : where("true")
}
# workaround: if this select is in #parents, calling .count produces invalid SQL
scope :with_thread_attributes, -> {
select('comments.*, comments_recursive.depth as depth, comments_recursive.reply_count')
select("comments.*, comments_recursive.depth as depth, comments_recursive.reply_count")
}
FLAGGABLE_DAYS = 7
@ -74,39 +72,39 @@ class Comment < ApplicationRecord
# but in practice all deep reply chains have gone off-topic and/or tuned into flamewars.
MAX_DEPTH = 18
SCORE_RANGE_TO_HIDE = (-2 .. 4).freeze
SCORE_RANGE_TO_HIDE = (-2..4)
validates :short_id, length: { maximum: 10 }
validates :short_id, length: {maximum: 10}
validates :user_id, presence: true
validates :story_id, presence: true
validates :markeddown_comment, length: { maximum: 16_777_215 }
validates :comment, presence: { with: true, message: "cannot be empty." }
validates :markeddown_comment, length: {maximum: 16_777_215}
validates :comment, presence: {with: true, message: "cannot be empty."}
validate do
self.parent_comment && self.parent_comment.is_gone? &&
parent_comment&.is_gone? &&
errors.add(:base, "Comment was deleted by the author or a mod while you were writing.")
self.parent_comment && !self.parent_comment.depth_permits_reply? &&
ModNote.tattle_on_max_depth_limit(self.user, self.parent_comment) &&
parent_comment && !parent_comment.depth_permits_reply? &&
ModNote.tattle_on_max_depth_limit(user, parent_comment) &&
errors.add(:base, "You have replied too greedily and too deep.")
(m = self.comment.to_s.strip.match(/\A(t)his([\.!])?$\z/i)) &&
errors.add(:base, (m[1] == "T" ? "N" : "n") + "ope" + m[2].to_s)
(m = comment.to_s.strip.match(/\A(t)his([\.!])?$\z/i)) &&
errors.add(:base, ((m[1] == "T") ? "N" : "n") + "ope" + m[2].to_s)
self.comment.to_s.strip.match(/\Atl;?dr.?$\z/i) &&
comment.to_s.strip.match(/\Atl;?dr.?$\z/i) &&
errors.add(:base, "Wow! A blue car!")
self.comment.to_s.strip.match(/\A([[[:upper:]][[:punct:]]] )+[[[:upper:]][[:punct:]]]?$\z/) &&
comment.to_s.strip.match(/\A([[[:upper:]][[:punct:]]] )+[[[:upper:]][[:punct:]]]?$\z/) &&
errors.add(:base, "D O N ' T")
self.comment.to_s.strip.match(/\A(me too|nice)([\.!])?\z/i) &&
comment.to_s.strip.match(/\A(me too|nice)([\.!])?\z/i) &&
errors.add(:base, "Please just upvote the parent post instead.")
self.hat.present? && self.user.wearable_hats.exclude?(self.hat) &&
hat.present? && user.wearable_hats.exclude?(hat) &&
errors.add(:hat, "not wearable by user")
# .try so tests don't need to persist a story and user
self.story.try(:accepting_comments?) ||
story.try(:accepting_comments?) ||
errors.add(:base, "Story is no longer accepting comments.")
end
@ -115,7 +113,7 @@ class Comment < ApplicationRecord
Comment.all.find_each do |c|
c.markeddown_comment = c.generated_markeddown_comment
c.save(:validate => false)
c.save(validate: false)
end
Comment.record_timestamps = true
@ -133,23 +131,23 @@ class Comment < ApplicationRecord
:is_moderated,
:score,
:flags,
{ :parent_comment => self.parent_comment && self.parent_comment.short_id },
{ :comment => (self.is_gone? ? "<em>#{self.gone_text}</em>" : :markeddown_comment) },
{ :comment_plain => (self.is_gone? ? self.gone_text : :comment) },
{parent_comment: parent_comment&.short_id},
{comment: (is_gone? ? "<em>#{gone_text}</em>" : :markeddown_comment)},
{comment_plain: (is_gone? ? gone_text : :comment)},
:url,
:depth,
{ :commenting_user => :user },
{commenting_user: :user}
]
js = {}
h.each do |k|
if k.is_a?(Symbol)
js[k] = self.send(k)
js[k] = send(k)
elsif k.is_a?(Hash)
if k.values.first.is_a?(Symbol)
js[k.keys.first] = self.send(k.values.first)
js[k.keys.first] = if k.values.first.is_a?(Symbol)
send(k.values.first)
else
js[k.keys.first] = k.values.first
k.values.first
end
end
end
@ -158,10 +156,10 @@ class Comment < ApplicationRecord
end
def assign_initial_confidence
self.confidence = self.calculated_confidence
self.confidence = calculated_confidence
# 3 byte placeholder, immediately replaced by after_create callback calling
# update_score_and_recalculate! to fill in the autogenerated 'id' value
self.confidence_order = [0, 0, 0].pack('CCC')
self.confidence_order = [0, 0, 0].pack("CCC")
end
def assign_short_id_and_score
@ -170,20 +168,20 @@ class Comment < ApplicationRecord
end
def assign_thread_id
if self.parent_comment.present?
self.thread_id = self.parent_comment.thread_id
self.thread_id = if parent_comment.present?
parent_comment.thread_id
else
self.thread_id = Keystore.incremented_value_for("thread_id")
Keystore.incremented_value_for("thread_id")
end
end
# http://evanmiller.org/how-not-to-sort-by-average-rating.html
# https://github.com/reddit/reddit/blob/master/r2/r2/lib/db/_sorts.pyx
def calculated_confidence
n = (self.score + self.flags * 2).to_f
return 0 if n == 0.0
return 0 if self.score == 0 && flags == 0
n = (self.score + flags * 2).to_f
upvotes = self.score + self.flags
upvotes = self.score + flags
z = 1.281551565545 # 80% confidence
p = upvotes.to_f / n
@ -191,7 +189,7 @@ class Comment < ApplicationRecord
right = z * Math.sqrt((p * ((1.0 - p) / n)) + (z * (z / (4.0 * n * n))))
under = 1.0 + ((1.0 / n) * z * z)
return (left - right) / under
(left - right) / under
end
# rate-limit users in heated reply chains, called by controller so that authors can preview
@ -223,7 +221,7 @@ class Comment < ApplicationRecord
def comment=(com)
self[:comment] = com.to_s.rstrip
self.markeddown_comment = self.generated_markeddown_comment
self.markeddown_comment = generated_markeddown_comment
end
def delete_for_user(user, reason = nil)
@ -231,11 +229,11 @@ class Comment < ApplicationRecord
self.is_deleted = true
if user.is_moderator? && user.id != self.user_id
if user.is_moderator? && user.id != user_id
self.is_moderated = true
m = Moderation.new
m.comment_id = self.id
m.comment_id = id
m.moderator_user_id = user.id
m.action = "deleted comment"
@ -245,20 +243,20 @@ class Comment < ApplicationRecord
m.save
User.update_counters self.user_id, karma: (self.votes.count * -2)
User.update_counters user_id, karma: (votes.count * -2)
end
self.save(:validate => false)
save(validate: false)
Comment.record_timestamps = true
self.story.update_comments_count!
story.update_comments_count!
self.user.refresh_counts!
end
def deliver_mention_notifications
self.plaintext_comment.scan(/\B\@([\w\-]+)/).flatten.uniq.each do |mention|
if (u = User.active.find_by(:username => mention))
if u.id == self.user.id
plaintext_comment.scan(/\B@([\w\-]+)/).flatten.uniq.each do |mention|
if (u = User.active.find_by(username: mention))
if u.id == user.id
next
end
@ -272,11 +270,11 @@ class Comment < ApplicationRecord
if u.pushover_mentions?
u.pushover!(
:title => "#{Rails.application.name} mention by " <<
"#{self.user.username} on #{self.story.title}",
:message => self.plaintext_comment,
:url => self.url,
:url_title => "Reply to #{self.user.username}",
title: "#{Rails.application.name} mention by " \
"#{user.username} on #{story.title}",
message: plaintext_comment,
url: url,
url_title: "Reply to #{user.username}"
)
end
end
@ -285,14 +283,14 @@ class Comment < ApplicationRecord
def users_following_thread
users_following_thread = Set.new
if self.user.id != self.story.user.id && self.story.user_is_following
users_following_thread << self.story.user
if user.id != story.user.id && story.user_is_following
users_following_thread << story.user
end
if self.parent_comment_id &&
(u = self.parent_comment.try(:user)) &&
u.id != self.user.id &&
u.is_active?
if parent_comment_id &&
(u = parent_comment.try(:user)) &&
u.id != user.id &&
u.is_active?
users_following_thread << u
end
@ -311,11 +309,11 @@ class Comment < ApplicationRecord
if u.pushover_replies?
u.pushover!(
:title => "#{Rails.application.name} reply from " <<
"#{self.user.username} on #{self.story.title}",
:message => self.plaintext_comment,
:url => self.url,
:url_title => "Reply to #{self.user.username}",
title: "#{Rails.application.name} reply from " \
"#{user.username} on #{story.title}",
message: plaintext_comment,
url: url,
url_title: "Reply to #{user.username}"
)
end
end
@ -325,17 +323,17 @@ class Comment < ApplicationRecord
# Top-level replies (eg parent_comment_id == null) have depth 0, then each reply is +1.
# Alternate definition: depth is the number of ancestor comments.
return false if self.new_record? # can't reply to unsaved comments
return false if new_record? # can't reply to unsaved comments
# Most commonly, depth is set by merged_comments. But we need to count parents when executing as
# a validation on reply.
self.depth ||= self.parents.count
self.depth ||= parents.count
depth < MAX_DEPTH
end
def generated_markeddown_comment
Markdowner.to_html(self.comment)
Markdowner.to_html(comment)
end
# TODO: race condition: if two votes arrive at the same time, the second one
@ -359,19 +357,19 @@ class Comment < ApplicationRecord
UPDATE comments SET
score = (select coalesce(sum(vote), 0) from votes where comment_id = comments.id),
flags = (select count(*) from votes where comment_id = comments.id and vote = -1),
confidence = #{self.calculated_confidence},
confidence = #{calculated_confidence},
confidence_order = concat(lpad(char(65536 - floor(((confidence - -0.2) * 65535) / 1.2) using binary), 2, '0'), char(id & 0xff using binary))
WHERE id = #{self.id.to_i}
WHERE id = #{id.to_i}
SQL
self.story.recalculate_hotness!
story.recalculate_hotness!
end
def gone_text
if self.is_moderated?
if is_moderated?
"Comment removed by moderator " <<
self.moderation.try(:moderator).try(:username).to_s << ": " <<
(self.moderation.try(:reason) || "No reason given")
elsif self.user.is_banned?
moderation.try(:moderator).try(:username).to_s << ": " <<
(moderation.try(:reason) || "No reason given")
elsif user.is_banned?
"Comment from banned user removed"
else
"Comment removed by author"
@ -379,41 +377,41 @@ class Comment < ApplicationRecord
end
def has_been_edited?
self.updated_at && (self.updated_at - self.created_at > 1.minute)
updated_at && (updated_at - created_at > 1.minute)
end
def is_deletable_by_user?(user)
if user && user.is_moderator?
return true
elsif user && user.id == self.user_id
return self.created_at >= DELETEABLE_DAYS.days.ago
if user&.is_moderator?
true
elsif user && user.id == user_id
created_at >= DELETEABLE_DAYS.days.ago
else
return false
false
end
end
def is_disownable_by_user?(user)
user && user.id == self.user_id && self.created_at && self.created_at < DELETEABLE_DAYS.days.ago
user && user.id == user_id && created_at && created_at < DELETEABLE_DAYS.days.ago
end
def is_flaggable?
if self.created_at && self.score > FLAGGABLE_MIN_SCORE
Time.current - self.created_at <= FLAGGABLE_DAYS.days
if created_at && self.score > FLAGGABLE_MIN_SCORE
Time.current - created_at <= FLAGGABLE_DAYS.days
else
false
end
end
def is_editable_by_user?(user)
if user && user.id == self.user_id
if self.is_moderated?
return false
if user && user.id == user_id
if is_moderated?
false
else
return (Time.current.to_i - (self.updated_at ? self.updated_at.to_i :
self.created_at.to_i) < (60 * MAX_EDIT_MINS))
(Time.current.to_i - (updated_at ? updated_at.to_i :
created_at.to_i) < (60 * MAX_EDIT_MINS))
end
else
return false
false
end
end
@ -422,42 +420,42 @@ class Comment < ApplicationRecord
end
def is_undeletable_by_user?(user)
if user && user.is_moderator?
return true
elsif user && user.id == self.user_id && !self.is_moderated?
return true
if user&.is_moderator?
true
elsif user && user.id == user_id && !is_moderated?
true
else
return false
false
end
end
def log_hat_use
return unless self.hat && self.hat.modlog_use
return unless hat&.modlog_use
m = Moderation.new
m.created_at = self.created_at
m.comment_id = self.id
m.created_at = created_at
m.comment_id = id
m.moderator_user_id = user.id
m.action = "used #{self.hat.hat} hat"
m.action = "used #{hat.hat} hat"
m.save!
end
def mark_submitter
Keystore.increment_value_for("user:#{self.user_id}:comments_posted")
Keystore.increment_value_for("user:#{user_id}:comments_posted")
end
def mailing_list_message_id
[
"comment",
self.short_id,
self.is_from_email ? "email" : nil,
created_at.to_i,
short_id,
is_from_email ? "email" : nil,
created_at.to_i
].reject(&:!).join(".") << "@" << Rails.application.domain
end
# all direct ancestors of this comment, oldest first
def parents
return Comment.none if self.parent_comment_id.nil?
return Comment.none if parent_comment_id.nil?
# starts from parent_comment_id so it works on new records
@parents ||= Comment
@ -470,7 +468,7 @@ class Comment < ApplicationRecord
parent_comment_id,
0 as depth,
(select count(*) from comments where parent_comment_id = id) as reply_count
from comments where id = #{self.parent_comment_id}
from comments where id = #{parent_comment_id}
union all
select
parents.target_id,
@ -483,11 +481,11 @@ class Comment < ApplicationRecord
) as comments_recursive on comments.id = comments_recursive.id
SQL
)
.order('id asc')
.order("id asc")
end
def path
self.story.comments_path + "#c_#{self.short_id}"
story.comments_path + "#c_#{short_id}"
end
def plaintext_comment
@ -497,16 +495,16 @@ class Comment < ApplicationRecord
def record_initial_upvote
Vote.vote_thusly_on_story_or_comment_for_user_because(
1, self.story_id, self.id, self.user_id, nil, false
1, story_id, id, user_id, nil, false
)
self.story.update_comments_count!
story.update_comments_count!
end
def score_for_user(u)
if self.show_score_to_user?(u)
if show_score_to_user?(u)
score
elsif u && u.can_flag?(self)
elsif u&.can_flag?(self)
"~"
else
"&nbsp;".html_safe
@ -514,37 +512,37 @@ class Comment < ApplicationRecord
end
def short_id_url
Rails.application.root_url + "c/#{self.short_id}"
Rails.application.root_url + "c/#{short_id}"
end
def show_score_to_user?(u)
return true if u && u.is_moderator?
return true if u&.is_moderator?
# hide score on new/near-zero comments to cut down on threads about voting
# also hide if user has flagged the story/comment to make retaliatory flagging less fun
(
(self.created_at && self.created_at < 36.hours.ago) ||
(created_at && created_at < 36.hours.ago) ||
!SCORE_RANGE_TO_HIDE.include?(self.score)
) && (!current_vote || current_vote[:vote] >= 0)
end
def to_param
self.short_id
short_id
end
def unassign_votes
self.story.update_comments_count!
story.update_comments_count!
end
def url
self.story.comments_url + "#c_#{self.short_id}"
story.comments_url + "#c_#{short_id}"
end
def vote_summary_for_user(u)
r_counts = {}
r_users = {}
# don't includes(:user) here and assume the caller did this already
self.votes.each do |v|
votes.each do |v|
r_counts[v.reason.to_s] ||= 0
r_counts[v.reason.to_s] += v.vote
@ -552,12 +550,12 @@ class Comment < ApplicationRecord
r_users[v.reason.to_s].push v.user.username
end
r_counts.keys.map {|k|
r_counts.keys.map { |k|
next if k == ""
o = "#{r_counts[k]} #{Vote::ALL_COMMENT_REASONS[k]}"
if u && u.is_moderator? && self.user_id != u.id
o << " (#{r_users[k].join(', ')})"
if u && u.is_moderator? && user_id != u.id
o << " (#{r_users[k].join(", ")})"
end
o
}.compact.join(", ")
@ -571,19 +569,19 @@ class Comment < ApplicationRecord
if user.is_moderator?
self.is_moderated = false
if user.id != self.user_id
if user.id != user_id
m = Moderation.new
m.comment_id = self.id
m.comment_id = id
m.moderator_user_id = user.id
m.action = "undeleted comment"
m.save
end
end
self.save(:validate => false)
save(validate: false)
Comment.record_timestamps = true
self.story.update_comments_count!
story.update_comments_count!
self.user.refresh_counts!
end
@ -593,7 +591,7 @@ class Comment < ApplicationRecord
thread_ids = Comment
.where(user: user)
.group(:thread_id)
.order('id desc')
.order("id desc")
.limit(20)
.pluck(:thread_id)
return Comment.none if thread_ids.empty?
@ -609,7 +607,7 @@ class Comment < ApplicationRecord
cast(confidence_order as char(#{Comment::COP_LENGTH}) character set binary) as confidence_order_path
from comments c
where
thread_id in (#{thread_ids.join(', ')}) and
thread_id in (#{thread_ids.join(", ")}) and
parent_comment_id is null
union all
select
@ -626,7 +624,7 @@ class Comment < ApplicationRecord
) as comments_recursive on comments.id = comments_recursive.id
SQL
)
.order('comments.thread_id desc, comments_recursive.confidence_order_path')
.order("comments.thread_id desc, comments_recursive.confidence_order_path")
.select('
comments.*,
comments_recursive.depth as depth,
@ -669,7 +667,7 @@ class Comment < ApplicationRecord
) as comments_recursive on comments.id = comments_recursive.id
SQL
)
.order('comments_recursive.confidence_order_path')
.order("comments_recursive.confidence_order_path")
.select('
comments.*,
comments_recursive.depth as depth,

View File

@ -1,10 +1,10 @@
class Domain < ApplicationRecord
has_many :stories # rubocop:disable Rails/HasManyOrHasOneDependent
belongs_to :banned_by_user,
:class_name => "User",
:inverse_of => false,
:optional => true
validates :banned_reason, :length => { :maximum => 200 }
class_name: "User",
inverse_of: false,
optional: true
validates :banned_reason, length: {maximum: 200}
validates :domain, presence: true
@ -12,12 +12,12 @@ class Domain < ApplicationRecord
self.banned_at = Time.current
self.banned_by_user_id = banner.id
self.banned_reason = reason
self.save!
save!
m = Moderation.new
m.moderator_user_id = banner.id
m.domain = self
m.action = 'Banned'
m.action = "Banned"
m.reason = reason
m.save!
end
@ -26,12 +26,12 @@ class Domain < ApplicationRecord
self.banned_at = nil
self.banned_by_user_id = nil
self.banned_reason = nil
self.save!
save!
m = Moderation.new
m.moderator_user_id = banner.id
m.domain = self
m.action = 'Unbanned'
m.action = "Unbanned"
m.reason = reason
m.save!
end
@ -41,7 +41,7 @@ class Domain < ApplicationRecord
end
def n_submitters
self.stories.count('distinct user_id')
stories.count("distinct user_id")
end
def to_param

View File

@ -19,7 +19,7 @@ class FlaggedCommenters
# aggregates for all commenters; not just those receiving flags
def aggregates
Rails.cache.fetch("aggregates_#{interval}_#{cache_time}", expires_in: self.cache_time) {
Rails.cache.fetch("aggregates_#{interval}_#{cache_time}", expires_in: cache_time) {
ActiveRecord::Base.connection.exec_query("
select
stddev(sum_flags) as stddev,
@ -52,7 +52,7 @@ class FlaggedCommenters
def commenters
Rails.cache.fetch("flagged_commenters_#{interval}_#{cache_time}",
expires_in: self.cache_time) {
expires_in: cache_time) {
rank = 0
User.active.joins(:comments)
.where("comments.created_at >= ?", period)
@ -71,7 +71,7 @@ class FlaggedCommenters
.having("n_comments > 4 and n_stories > 1 and n_flags >= 10 and percent_flagged > 10")
.order("sigma desc")
.limit(30)
.each_with_object({}) {|u, hash|
.each_with_object({}) { |u, hash|
hash[u.id] = {
username: u.username,
rank: rank += 1,
@ -81,7 +81,7 @@ class FlaggedCommenters
n_flags: u.n_flags,
average_flags: u.average_flags,
stddev: 0,
percent_flagged: u.percent_flagged,
percent_flagged: u.percent_flagged
}
}
}

View File

@ -5,50 +5,50 @@ class Hat < ApplicationRecord
after_create :log_moderation
validates :user, :granted_by_user, :hat, presence: true
validates :hat, :link, length: { maximum: 255 }
validates :hat, :link, length: {maximum: 255}
scope :active, -> { joins(:user).where(doffed_at: nil).merge(User.active) }
def doff_by_user_with_reason(user, reason)
m = Moderation.new
m.user_id = self.user_id
m.user_id = user_id
m.moderator_user_id = user.id
m.action = "Doffed hat \"#{self.hat}\": #{reason}"
m.action = "Doffed hat \"#{hat}\": #{reason}"
m.save!
self.doffed_at = Time.current
self.save!
save!
end
def destroy_by_user_with_reason(user, reason)
m = Moderation.new
m.user_id = self.user_id
m.user_id = user_id
m.moderator_user_id = user.id
m.action = "Revoked hat \"#{self.hat}\": #{reason}"
m.action = "Revoked hat \"#{hat}\": #{reason}"
m.save!
self.destroy
destroy
end
def to_html_label
hl = (self.link.present? && self.link.match(/^https?:\/\//))
hl = (link.present? && link.match(/^https?:\/\//))
h = "<span class=\"hat " <<
"hat_#{self.hat.gsub(/[^A-Za-z0-9]/, '_').downcase}\" " <<
"title=\"Granted #{self.created_at.strftime('%Y-%m-%d')}"
h = "<span class=\"hat " \
"hat_#{hat.gsub(/[^A-Za-z0-9]/, "_").downcase}\" " \
"title=\"Granted #{created_at.strftime("%Y-%m-%d")}"
if !hl && self.link.present?
h << " - #{ERB::Util.html_escape(self.sanitized_link)}"
if !hl && link.present?
h << " - #{ERB::Util.html_escape(sanitized_link)}"
end
h << "\">" <<
h << "\">" \
"<span class=\"crown\">"
if hl
h << "<a href=\"#{ERB::Util.html_escape(self.link)}\" target=\"_blank\">"
h << "<a href=\"#{ERB::Util.html_escape(link)}\" target=\"_blank\">"
end
h << ERB::Util.html_escape(self.hat)
h << ERB::Util.html_escape(hat)
if hl
h << "</a>"
@ -60,16 +60,16 @@ class Hat < ApplicationRecord
end
def to_txt
"(#{self.hat}) "
"(#{hat}) "
end
def log_moderation
m = Moderation.new
m.created_at = self.created_at
m.user_id = self.user_id
m.moderator_user_id = self.granted_by_user_id
m.action = "Granted hat \"#{self.hat}\"" + (self.link.present? ?
" (#{self.link})" : "")
m.created_at = created_at
m.user_id = user_id
m.moderator_user_id = granted_by_user_id
m.action = "Granted hat \"#{hat}\"" + (link.present? ?
" (#{link})" : "")
m.save
end

View File

@ -1,42 +1,42 @@
class HatRequest < ApplicationRecord
belongs_to :user
validates :hat, presence: true, length: { maximum: 255 }
validates :link, presence: true, length: { maximum: 255 }
validates :comment, presence: true, length: { maximum: 65_535 }
validates :hat, presence: true, length: {maximum: 255}
validates :link, presence: true, length: {maximum: 255}
validates :comment, presence: true, length: {maximum: 65_535}
attr_accessor :rejection_comment
def approve_by_user_for_reason!(user, reason)
self.transaction do
transaction do
h = Hat.new
h.user_id = self.user_id
h.user_id = user_id
h.granted_by_user_id = user.id
h.hat = self.hat
h.link = self.link
h.hat = hat
h.link = link
h.save!
m = Message.new
m.author_user_id = user.id
m.recipient_user_id = self.user_id
m.subject = "Your hat \"#{self.hat}\" has been approved"
m.recipient_user_id = user_id
m.subject = "Your hat \"#{hat}\" has been approved"
m.body = reason
m.save!
self.destroy
destroy
end
end
def reject_by_user_for_reason!(user, reason)
self.transaction do
transaction do
m = Message.new
m.author_user_id = user.id
m.recipient_user_id = self.user_id
m.subject = "Your request for hat \"#{self.hat}\" has been rejected"
m.recipient_user_id = user_id
m.subject = "Your request for hat \"#{hat}\" has been rejected"
m.body = reason
m.save!
self.destroy
destroy
end
end
end

View File

@ -5,14 +5,12 @@ class HiddenStory < ApplicationRecord
scope :by, ->(user) { where(user: user) }
def self.hide_story_for_user(story_id, user_id)
HiddenStory.where(:user_id => user_id, :story_id =>
story_id).first_or_initialize.save!
HiddenStory.where(user_id: user_id, story_id: story_id).first_or_initialize.save!
ReadRibbon.hide_replies_for(story_id, user_id)
end
def self.unhide_story_for_user(story_id, user_id)
HiddenStory.where(:user_id => user_id, :story_id =>
story_id).delete_all
HiddenStory.where(user_id: user_id, story_id: story_id).delete_all
ReadRibbon.unhide_replies_for(story_id, user_id)
end
end

View File

@ -1,6 +1,6 @@
module InactiveUser
def self.inactive_user
@inactive_user ||= User.find_by!(username: 'inactive-user')
@inactive_user ||= User.find_by!(username: "inactive-user")
end
def self.disown! comment_or_story
@ -12,13 +12,13 @@ module InactiveUser
def self.disown_all_by_author! author
# leave attribution on deleted stuff, which is generally very relevant to mods
# when looking back at returning users
author.stories.not_deleted(nil).update_all(:user_id => inactive_user.id)
author.comments.active.update_all(:user_id => inactive_user.id)
author.stories.not_deleted(nil).update_all(user_id: inactive_user.id)
author.comments.active.update_all(user_id: inactive_user.id)
refresh_counts! author
end
def self.refresh_counts! user
user.refresh_counts! if user
user&.refresh_counts!
inactive_user.refresh_counts!
end
end

View File

@ -1,24 +1,24 @@
class Invitation < ApplicationRecord
belongs_to :user
belongs_to :new_user, class_name: 'User', inverse_of: nil, optional: true
belongs_to :new_user, class_name: "User", inverse_of: nil, optional: true
scope :used, -> { where.not(:used_at => nil) }
scope :unused, -> { where(:used_at => nil) }
scope :used, -> { where.not(used_at: nil) }
scope :unused, -> { where(used_at: nil) }
validate do
unless email.to_s.match(/\A[^@ ]+@[^ @]+\.[^ @]+\z/)
unless /\A[^@ ]+@[^ @]+\.[^ @]+\z/.match?(email.to_s)
errors.add(:email, "is not valid")
end
end
validates :code, :email, :memo, length: { maximum: 255 }
validates :code, :email, :memo, length: {maximum: 255}
before_validation :create_code, :on => :create
before_validation :create_code, on: :create
def create_code
10.times do
self.code = Utils.random_str(15)
return unless Invitation.exists?(:code => self.code)
return unless Invitation.exists?(code: code)
end
raise "too many hash collisions"
end

View File

@ -1,33 +1,33 @@
class InvitationRequest < ApplicationRecord
validates :name,
:presence => true,
:length => { maximum: 255 }
presence: true,
length: {maximum: 255}
validates :email,
:format => { :with => /\A[^@ ]+@[^@ ]+\.[^@ ]+\Z/ },
:presence => true,
:length => { maximum: 255 }
format: {with: /\A[^@ ]+@[^@ ]+\.[^@ ]+\Z/},
presence: true,
length: {maximum: 255}
validates :memo,
:format => { :with => /https?:\/\// },
:length => { maximum: 255 }
validates :code, :ip_address, :length => { maximum: 255 }
format: {with: /https?:\/\//},
length: {maximum: 255}
validates :code, :ip_address, length: {maximum: 255}
before_validation :create_code
after_create :send_email
def self.verified_count
InvitationRequest.where(:is_verified => true).count
InvitationRequest.where(is_verified: true).count
end
def create_code
10.times do
self.code = Utils.random_str(15)
return unless InvitationRequest.exists?(:code => self.code)
return unless InvitationRequest.exists?(code: code)
end
raise "too many hash collisions"
end
def markeddown_memo
Markdowner.to_html(self.memo)
Markdowner.to_html(memo)
end
def send_email

View File

@ -3,28 +3,28 @@ class Keystore < ApplicationRecord
self.primary_key = "key"
validates :key, presence: true, length: { maximum: MAX_KEY_LENGTH }
validates :key, presence: true, length: {maximum: MAX_KEY_LENGTH}
def self.get(key)
self.find_by(key: key)
find_by(key: key)
end
def self.value_for(key)
self.where(key: key).limit(1).pluck(:value).first
where(key: key).limit(1).pluck(:value).first
end
def self.put(key, value)
validate_input_key(key)
if Keystore.connection.adapter_name == "SQLite"
Keystore.connection.execute("INSERT OR REPLACE INTO " <<
"#{Keystore.table_name} (`key`, `value`) VALUES " <<
Keystore.connection.execute("INSERT OR REPLACE INTO " \
"#{Keystore.table_name} (`key`, `value`) VALUES " \
"(#{q(key)}, #{q(value)})")
elsif Keystore.connection.adapter_name =~ /Mysql/
Keystore.connection.execute("INSERT INTO #{Keystore.table_name} (" +
"`key`, `value`) VALUES (#{q(key)}, #{q(value)}) ON DUPLICATE KEY " +
elsif /Mysql/.match?(Keystore.connection.adapter_name)
Keystore.connection.execute("INSERT INTO #{Keystore.table_name} (" \
"`key`, `value`) VALUES (#{q(key)}, #{q(value)}) ON DUPLICATE KEY " \
"UPDATE `value` = #{q(value)}")
else
kv = self.find_or_create_key_for_update(key, value)
kv = find_or_create_key_for_update(key, value)
kv.value = value
kv.save!
end
@ -32,40 +32,40 @@ class Keystore < ApplicationRecord
end
def self.increment_value_for(key, amount = 1)
self.incremented_value_for(key, amount)
incremented_value_for(key, amount)
end
def self.incremented_value_for(key, amount = 1)
validate_input_key(key)
Keystore.transaction do
if Keystore.connection.adapter_name == "SQLite"
Keystore.connection.execute("INSERT OR IGNORE INTO " <<
"#{Keystore.table_name} (`key`, `value`) VALUES " <<
Keystore.connection.execute("INSERT OR IGNORE INTO " \
"#{Keystore.table_name} (`key`, `value`) VALUES " \
"(#{q(key)}, 0)")
Keystore.connection.execute("UPDATE #{Keystore.table_name} " <<
Keystore.connection.execute("UPDATE #{Keystore.table_name} " \
"SET `value` = `value` + #{q(amount)} WHERE `key` = #{q(key)}")
elsif Keystore.connection.adapter_name =~ /Mysql/
Keystore.connection.execute("INSERT INTO #{Keystore.table_name} (" +
"`key`, `value`) VALUES (#{q(key)}, #{q(amount)}) ON DUPLICATE KEY " +
elsif /Mysql/.match?(Keystore.connection.adapter_name)
Keystore.connection.execute("INSERT INTO #{Keystore.table_name} (" \
"`key`, `value`) VALUES (#{q(key)}, #{q(amount)}) ON DUPLICATE KEY " \
"UPDATE `value` = `value` + #{q(amount)}")
else
kv = self.find_or_create_key_for_update(key, 0)
kv = find_or_create_key_for_update(key, 0)
kv.value = kv.value.to_i + amount
kv.save!
return kv.value
end
self.value_for(key)
value_for(key)
end
end
def self.find_or_create_key_for_update(key, init = nil)
loop do
found = self.lock(true).find_by(:key => key)
found = lock(true).find_by(key: key)
return found if found
begin
self.create! do |kv|
create! do |kv|
kv.key = key
kv.value = init
kv.save!
@ -77,11 +77,11 @@ class Keystore < ApplicationRecord
end
def self.decrement_value_for(key, amount = -1)
self.increment_value_for(key, amount)
increment_value_for(key, amount)
end
def self.decremented_value_for(key, amount = -1)
self.incremented_value_for(key, amount)
incremented_value_for(key, amount)
end
# deliberately no lock/transaction as TrafficHelper is on the hot path of every request

View File

@ -1,44 +1,44 @@
class Message < ApplicationRecord
belongs_to :recipient,
:class_name => "User",
:foreign_key => "recipient_user_id",
:inverse_of => :received_messages
class_name: "User",
foreign_key: "recipient_user_id",
inverse_of: :received_messages
belongs_to :author,
:class_name => "User",
:foreign_key => "author_user_id",
:inverse_of => :sent_messages,
:optional => true
class_name: "User",
foreign_key: "author_user_id",
inverse_of: :sent_messages,
optional: true
belongs_to :hat,
:optional => true
optional: true
attribute :mod_note, :boolean
attr_reader :recipient_username
validates :subject, length: { :in => 1..100 }
validates :body, length: { :maximum => (64 * 1024) }
validates :short_id, length: { maximum: 30 }
validates :subject, length: {in: 1..100}
validates :body, length: {maximum: (64 * 1024)}
validates :short_id, length: {maximum: 30}
validate :hat do
next if hat.blank?
if author.blank? || author.wearable_hats.exclude?(hat)
errors.add(:hat, 'not wearable by author')
errors.add(:hat, "not wearable by author")
end
end
scope :inbox, ->(user) {
where(
recipient: user,
deleted_by_recipient: false,
).preload(:author, :hat, :recipient).order('id asc')
deleted_by_recipient: false
).preload(:author, :hat, :recipient).order("id asc")
}
scope :outbox, ->(user) {
where(
author: user,
deleted_by_author: false,
).preload(:author, :hat, :recipient).order('id asc')
deleted_by_author: false
).preload(:author, :hat, :recipient).order("id asc")
}
scope :unread, -> { where(:has_been_read => false, :deleted_by_recipient => false) }
scope :unread, -> { where(has_been_read: false, deleted_by_recipient: false) }
before_validation :assign_short_id, :on => :create
before_validation :assign_short_id, on: :create
after_create :deliver_email_notifications
after_save :update_unread_counts
after_save :check_for_both_deleted
@ -51,13 +51,13 @@ class Message < ApplicationRecord
:subject,
:body,
:deleted_by_author,
:deleted_by_recipient,
:deleted_by_recipient
]
h = super(:only => attrs)
h = super(only: attrs)
h[:author_username] = self.author.try(:username)
h[:recipient_username] = self.recipient.try(:username)
h[:author_username] = author.try(:username)
h[:recipient_username] = recipient.try(:username)
h
end
@ -67,42 +67,42 @@ class Message < ApplicationRecord
end
def author_username
if self.author
self.author.username
if author
author.username
else
"System"
end
end
def check_for_both_deleted
if self.deleted_by_author? && self.deleted_by_recipient?
self.destroy
if deleted_by_author? && deleted_by_recipient?
destroy
end
end
def update_unread_counts
self.recipient.update_unread_message_count!
recipient.update_unread_message_count!
end
def deliver_email_notifications
return if Rails.env.development?
if self.recipient.email_messages?
if recipient.email_messages?
begin
EmailMessage.notify(self, self.recipient).deliver_now
EmailMessage.notify(self, recipient).deliver_now
rescue => e
Rails.logger.error "error e-mailing #{self.recipient.email}: #{e}"
Rails.logger.error "error e-mailing #{recipient.email}: #{e}"
end
end
if self.recipient.pushover_messages?
self.recipient.pushover!(
:title => "#{Rails.application.name} message from " <<
"#{self.author_username}: #{self.subject}",
:message => self.plaintext_body,
:url => self.url,
:url_title => (self.author ? "Reply to #{self.author_username}" :
"View message"),
if recipient.pushover_messages?
recipient.pushover!(
title: "#{Rails.application.name} message from " \
"#{author_username}: #{subject}",
message: plaintext_body,
url: url,
url_title: (author ? "Reply to #{author_username}" :
"View message")
)
end
end
@ -110,7 +110,7 @@ class Message < ApplicationRecord
def recipient_username=(username)
self.recipient_user_id = nil
if (u = User.find_by(:username => username))
if (u = User.find_by(username: username))
self.recipient_user_id = u.id
@recipient_username = username
else
@ -119,19 +119,19 @@ class Message < ApplicationRecord
end
def linkified_body
Markdowner.to_html(self.body)
Markdowner.to_html(body)
end
def plaintext_body
# TODO: linkify then strip tags and convert entities back
self.body.to_s
body.to_s
end
def to_param
self.short_id
short_id
end
def url
Rails.application.root_url + "messages/#{self.short_id}"
Rails.application.root_url + "messages/#{short_id}"
end
end

View File

@ -2,16 +2,16 @@ class ModNote < ApplicationRecord
extend TimeAgoInWords
belongs_to :moderator,
class_name: "User",
foreign_key: "moderator_user_id",
inverse_of: :moderations
class_name: "User",
foreign_key: "moderator_user_id",
inverse_of: :moderations
belongs_to :user,
inverse_of: :mod_notes
inverse_of: :mod_notes
scope :recent, -> { where('created_at >= ?', 1.week.ago).order('created_at desc') }
scope :for, ->(user) { includes(:moderator).where('user_id = ?', user).order('created_at desc') }
scope :recent, -> { where("created_at >= ?", 1.week.ago).order("created_at desc") }
scope :for, ->(user) { includes(:moderator).where("user_id = ?", user).order("created_at desc") }
validates :note, :markeddown_note, presence: true, length: { maximum: 65_535 }
validates :note, :markeddown_note, presence: true, length: {maximum: 65_535}
delegate :username, to: :user
@ -28,15 +28,15 @@ class ModNote < ApplicationRecord
def note=(n)
self[:note] = n.to_s.strip
self.markeddown_note = self.generated_markeddown
self.markeddown_note = generated_markeddown
end
def generated_markeddown
Markdowner.to_html(self.note)
Markdowner.to_html(note)
end
def self.create_from_message(message, moderator)
user = moderator.id == message.recipient.id && message.author ?
user = (moderator.id == message.recipient.id && message.author) ?
message.author : message.recipient
ModNote.create!(
@ -44,7 +44,7 @@ class ModNote < ApplicationRecord
user: user,
created_at: message.created_at,
note: <<~NOTE
*#{message.author ? message.author.username : '(System)'} #{message.hat ? message.hat.to_txt : ''}-> #{message.recipient.username}*: #{message.subject}
*#{message.author ? message.author.username : "(System)"} #{message.hat ? message.hat.to_txt : ""}-> #{message.recipient.username}*: #{message.subject}
#{message.body}
NOTE
@ -69,22 +69,22 @@ class ModNote < ApplicationRecord
moderator: InactiveUser.inactive_user,
user: redeemer,
created_at: Time.current,
note: "Attempted to redeem invitation code #{invitation.code} while logged in:\n" +
"- sent by: [#{sender.username}](#{sender_url})\n" +
"- created_at: #{invitation.created_at}\n" +
"- used_at: #{invitation.used_at || 'unused'}\n" +
"- email: #{invitation.email}\n" +
note: "Attempted to redeem invitation code #{invitation.code} while logged in:\n" \
"- sent by: [#{sender.username}](#{sender_url})\n" \
"- created_at: #{invitation.created_at}\n" \
"- used_at: #{invitation.used_at || "unused"}\n" \
"- email: #{invitation.email}\n" \
"- memo: #{invitation.memo}"
)
create_without_dupe!(
moderator: InactiveUser.inactive_user,
user: sender,
created_at: Time.current,
note: "Sent invitation #{invitation.code} another user tried to redeem while logged in:\n" +
"- attempted redeemer: [#{redeemer.username}](#{redeemer_url})\n" +
"- created_at: #{invitation.created_at}\n" +
"- used_at: #{invitation.used_at || 'unused'}\n" +
"- email: #{invitation.email}\n" +
note: "Sent invitation #{invitation.code} another user tried to redeem while logged in:\n" \
"- attempted redeemer: [#{redeemer.username}](#{redeemer_url})\n" \
"- created_at: #{invitation.created_at}\n" \
"- used_at: #{invitation.used_at || "unused"}\n" \
"- email: #{invitation.email}\n" \
"- memo: #{invitation.memo}"
)
end
@ -102,7 +102,7 @@ class ModNote < ApplicationRecord
moderator: InactiveUser.inactive_user,
user: user,
created_at: Time.current,
note: "Hit max comment depth replying to [#{parent_comment.short_id}](#{comment_url}) " +
note: "Hit max comment depth replying to [#{parent_comment.short_id}](#{comment_url}) " \
"by [#{parent_comment.user.username}](#{parent_author_url})"
)
end
@ -112,12 +112,12 @@ class ModNote < ApplicationRecord
moderator: InactiveUser.inactive_user,
user: story.user,
created_at: Time.current,
note: "Attempted to submit a story with tag(s) not allowed to new users:\n" +
"- user joined: #{time_ago_in_words(story.user.created_at)}\n" +
"- url: #{story.url}\n" +
"- title: #{story.title}\n" +
"- user_is_author: #{story.user_is_author}\n" +
"- tags: #{story.tags_a.join(' ')}\n" +
note: "Attempted to submit a story with tag(s) not allowed to new users:\n" \
"- user joined: #{time_ago_in_words(story.user.created_at)}\n" \
"- url: #{story.url}\n" \
"- title: #{story.title}\n" \
"- user_is_author: #{story.user_is_author}\n" \
"- tags: #{story.tags_a.join(" ")}\n" \
"- description: #{story.description}\n"
)
end
@ -127,12 +127,12 @@ class ModNote < ApplicationRecord
moderator: InactiveUser.inactive_user,
user: story.user,
created_at: Time.current,
note: "Attempted to post a story from a #{reason} domain:\n" +
"- user joined: #{time_ago_in_words(story.user.created_at)}\n" +
"- url: #{story.url}\n" +
"- title: #{story.title}\n" +
"- user_is_author: #{story.user_is_author}\n" +
"- tags: #{story.tags_a.join(' ')}\n" +
note: "Attempted to post a story from a #{reason} domain:\n" \
"- user joined: #{time_ago_in_words(story.user.created_at)}\n" \
"- url: #{story.url}\n" \
"- title: #{story.title}\n" \
"- user_is_author: #{story.user_is_author}\n" \
"- tags: #{story.tags_a.join(" ")}\n" \
"- description: #{story.description}\n"
)
end

View File

@ -1,21 +1,21 @@
class Moderation < ApplicationRecord
belongs_to :moderator,
:class_name => "User",
:foreign_key => "moderator_user_id",
:inverse_of => :moderations,
:optional => true
class_name: "User",
foreign_key: "moderator_user_id",
inverse_of: :moderations,
optional: true
belongs_to :comment,
:optional => true
optional: true
belongs_to :domain,
:optional => true
optional: true
belongs_to :story,
:optional => true
optional: true
belongs_to :tag,
:optional => true
optional: true
belongs_to :user,
:optional => true
optional: true
belongs_to :category,
:optional => true
optional: true
scope :for, ->(user) {
left_outer_joins(:story, :comment)
@ -26,50 +26,50 @@ class Moderation < ApplicationRecord
comments.user_id = ?", user, user, user)
}
validates :action, :reason, length: { maximum: 16_777_215 }
validates :action, :reason, length: {maximum: 16_777_215}
validate :one_foreign_key_present
after_create :send_message_to_moderated
def send_message_to_moderated
m = Message.new
m.author_user_id = self.moderator_user_id
m.author_user_id = moderator_user_id
# mark as deleted by author so they don't fill up moderator message boxes
m.deleted_by_author = true
if self.story
m.recipient_user_id = self.story.user_id
if story
m.recipient_user_id = story.user_id
m.subject = "Your story has been edited by " <<
(self.is_from_suggestions? ? "user suggestions" : "a moderator")
m.body = "Your story [#{self.story.title}](" <<
"#{self.story.comments_url}) has been edited with the following " <<
"changes:\n" <<
"\n" <<
"> *#{self.action}*\n"
(is_from_suggestions? ? "user suggestions" : "a moderator")
m.body = "Your story [#{story.title}](" \
"#{story.comments_url}) has been edited with the following " \
"changes:\n" \
"\n" \
"> *#{action}*\n"
if self.reason.present?
m.body << "\n" <<
"The reason given:\n" <<
"\n" <<
"> *#{self.reason}*\n" <<
"\n" <<
if reason.present?
m.body << "\n" \
"The reason given:\n" \
"\n" \
"> *#{reason}*\n" \
"\n" \
"Maybe the guidelines on topicality are useful: https://lobste.rs/about#topicality"
end
elsif self.comment
m.recipient_user_id = self.comment.user_id
elsif comment
m.recipient_user_id = comment.user_id
m.subject = "Your comment has been moderated"
m.body = "Your comment on [#{self.comment.story.title}](" <<
"#{self.comment.story.comments_url}) has been moderated:\n" <<
"\n" <<
self.comment.comment.split("\n").map {|l| "> " << l }.join("\n")
m.body = "Your comment on [#{comment.story.title}](" \
"#{comment.story.comments_url}) has been moderated:\n" \
"\n" <<
comment.comment.split("\n").map { |l| "> " << l }.join("\n")
if self.reason.present?
m.body << "\n" <<
"The reason given:\n" <<
"\n" <<
"> *#{self.reason}*\n"
if reason.present?
m.body << "\n" \
"The reason given:\n" \
"\n" \
"> *#{reason}*\n"
end
else
@ -79,13 +79,13 @@ class Moderation < ApplicationRecord
return if m.recipient_user_id == m.author_user_id
m.body << "\n" <<
m.body << "\n" \
"*This is an automated message.*"
m.save
end
protected
protected
def one_foreign_key_present
fks = [comment_id, domain_id, story_id, category_id, tag_id, user_id].compact.length

View File

@ -8,7 +8,7 @@ class ReadRibbon < ApplicationRecord
# StoriesController uses .bump and RepliesController uses update_all, etc.
def self.expire_old_ribbons!
self.where("updated_at < ?", 1.year.ago).delete_all
where("updated_at < ?", 1.year.ago).delete_all
end
def self.hide_replies_for(story_id, user_id)
@ -30,7 +30,7 @@ class ReadRibbon < ApplicationRecord
if new_record?
save
else
self.update_column(:updated_at, Time.now.utc)
update_column(:updated_at, Time.now.utc)
end
end
end

View File

@ -6,14 +6,14 @@ class ReplyingComment < ApplicationRecord
scope :for_user, ->(user_id) {
where(user_id: user_id)
.order(comment_created_at: :desc)
.preload(:comment => [:story, :user])
.preload(comment: [:story, :user])
}
scope :unread_replies_for, ->(user_id) { for_user(user_id).where(is_unread: true) }
scope :comment_replies_for,
->(user_id) { for_user(user_id).where('parent_comment_id is not null') }
scope :story_replies_for, ->(user_id) { for_user(user_id).where('parent_comment_id is null') }
->(user_id) { for_user(user_id).where("parent_comment_id is not null") }
scope :story_replies_for, ->(user_id) { for_user(user_id).where("parent_comment_id is null") }
protected
protected
# This is a view, not a real table
def readonly?

View File

@ -5,7 +5,6 @@ class SavedStory < ApplicationRecord
scope :by, ->(user) { where(user: user) }
def self.save_story_for_user(story_id, user_id)
SavedStory.where(:user_id => user_id, :story_id =>
story_id).first_or_initialize.save!
SavedStory.where(user_id: user_id, story_id: story_id).first_or_initialize.save!
end
end

View File

@ -8,7 +8,7 @@ class Search
attr_accessor :results, :page, :total_results, :per_page
attr_writer :what
validates :q, length: { :minimum => 2 }
validates :q, length: {minimum: 2}
def initialize
@q = ""
@ -31,17 +31,17 @@ class Search
end
def to_url_params
[:q, :what, :order].map {|p| "#{p}=#{CGI.escape(self.send(p).to_s)}" }.join("&amp;")
[:q, :what, :order].map { |p| "#{p}=#{CGI.escape(send(p).to_s)}" }.join("&amp;")
end
def page_count
total = self.total_results.to_i
total = total_results.to_i
if total == -1 || total > self.max_matches
total = self.max_matches
if total == -1 || total > max_matches
total = max_matches
end
((total - 1) / self.per_page.to_i) + 1
((total - 1) / per_page.to_i) + 1
end
def what
@ -55,8 +55,8 @@ class Search
def with_tags(base, tag_scopes)
base
.joins({ :taggings => :tag }, :user).left_outer_joins(:story_text)
.where(:tags => { :tag => tag_scopes })
.joins({taggings: :tag}, :user).left_outer_joins(:story_text)
.where(tags: {tag: tag_scopes})
.having("COUNT(stories.id) = ?", tag_scopes.length)
.group("stories.id")
end
@ -71,7 +71,7 @@ class Search
base.joins(story: [:domain])
else
fail "Can't handle #{base.class}"
end.where('domains.domain = ?', domain)
end.where("domains.domain = ?", domain)
end
def with_stories_matching_tags(base, tag_scopes)
@ -88,7 +88,7 @@ class Search
# extract domain query since it must be done separately
domain = nil
tag_scopes = []
words = self.q.to_s.split(" ").reject {|w|
words = q.to_s.split(" ").reject { |w|
if (m = w.match(/^domain:(.+)$/))
domain = m[1]
elsif (m = w.match(/^tag:(.+)$/))
@ -100,7 +100,7 @@ class Search
base = nil
case self.what
case what
when "stories"
base = Story.unmerged.where(is_deleted: false).includes(:domain, :tags, :taggings)
if domain.present?
@ -115,41 +115,41 @@ class Search
if qwords.present?
base.where!(
"(#{title_match_sql} OR " +
"#{description_match_sql} OR " +
"(#{title_match_sql} OR " \
"#{description_match_sql} OR " \
"#{story_text_match_sql})"
)
if tag_scopes.present?
self.results = with_tags(base, tag_scopes)
else
base = base.includes({ :taggings => :tag }, :user).left_outer_joins(:story_text)
base = base.includes({taggings: :tag}, :user).left_outer_joins(:story_text)
self.results = base.select(
["stories.*", title_match_sql, description_match_sql, story_text_match_sql].join(', ')
["stories.*", title_match_sql, description_match_sql, story_text_match_sql].join(", ")
)
end
else
if tag_scopes.present?
self.results = with_tags(base, tag_scopes)
self.results = if tag_scopes.present?
with_tags(base, tag_scopes)
else
self.results = base.includes({ :taggings => :tag }, :user).left_outer_joins(:story_text)
base.includes({taggings: :tag}, :user).left_outer_joins(:story_text)
end
end
self.total_results = self.results.dup.count("stories.id")
self.total_results = results.dup.count("stories.id")
case self.order
case order
when "relevance"
if qwords.present?
self.results.order!(Arel.sql("((#{title_match_sql}) * 2) + " +
"((#{description_match_sql}) * 1.5) + " +
results.order!(Arel.sql("((#{title_match_sql}) * 2) + " \
"((#{description_match_sql}) * 1.5) + " \
"(#{story_text_match_sql}) DESC"))
else
self.results.order!("stories.created_at DESC")
results.order!("stories.created_at DESC")
end
when "newest"
self.results.order!("stories.created_at DESC")
results.order!("stories.created_at DESC")
when "points"
self.results.order!("score DESC")
results.order!("score DESC")
end
when "comments"
@ -164,59 +164,58 @@ class Search
base = base.where(Arel.sql("MATCH(comment) AGAINST('#{qwords}' IN BOOLEAN MODE)"))
end
self.results = base.select(
"comments.*, " +
"comments.*, " \
"MATCH(comment) AGAINST('#{qwords}' IN BOOLEAN MODE) AS rel_comment"
).includes(:user, :story)
self.total_results = self.results.dup.count("comments.id")
self.total_results = results.dup.count("comments.id")
case self.order
case order
when "relevance"
self.results.order!("rel_comment DESC")
results.order!("rel_comment DESC")
when "newest"
self.results.order!("created_at DESC")
results.order!("comments.created_at DESC")
when "points"
self.results.order!("score DESC")
results.order!("score DESC")
end
end
# with_tags uses group_by, so count returns a hash
self.total_results = self.total_results.count if self.total_results.is_a? Hash
self.total_results = total_results.count if total_results.is_a? Hash
if self.page > self.page_count
self.page = self.page_count
if page > page_count
self.page = page_count
end
if self.page < 1
if page < 1
self.page = 1
end
self.results = self.results
.limit(self.per_page)
.offset((self.page - 1) * self.per_page)
self.results = results
.limit(per_page)
.offset((page - 1) * per_page)
# if a user is logged in, fetch their votes for what's on the page
if user
case what
when "stories"
self.results = self.results.mod_preload?(user)
votes = Vote.story_votes_by_user_for_story_ids_hash(user.id, self.results.map(&:id))
self.results = results.mod_preload?(user)
votes = Vote.story_votes_by_user_for_story_ids_hash(user.id, results.map(&:id))
self.results.each do |r|
results.each do |r|
if votes[r.id]
r.vote = votes[r.id]
end
end
when "comments"
votes = Vote.comment_votes_by_user_for_comment_ids_hash(user.id, self.results.map(&:id))
votes = Vote.comment_votes_by_user_for_comment_ids_hash(user.id, results.map(&:id))
self.results.each do |r|
results.each do |r|
if votes[r.id]
r.current_vote = votes[r.id]
end
end
end
end
rescue ActiveRecord::StatementInvalid
# more likely the user has entered invalid boolean mode operators than our
# code is broken (not that I really trust this hairy class)

View File

@ -2,14 +2,14 @@ class ShortId
attr_accessor :klass, :generation_attempts
def initialize(klass)
self.klass = klass
self.klass = klass
self.generation_attempts = 0
end
def generate
until (generated_id = candidate_id) && generated_id.valid? do
until (generated_id = candidate_id) && generated_id.valid?
self.generation_attempts += 1
raise 'too many hash collisions' if generation_attempts == 10
raise "too many hash collisions" if generation_attempts == 10
end
generated_id.to_s
end
@ -23,7 +23,7 @@ class ShortId
def initialize(klass)
self.klass = klass
self.id = generate_id
self.id = generate_id
end
def to_s

View File

@ -13,10 +13,10 @@ class StoriesPaginator
def get
with_pagination_info @scope.limit(per_page + 1)
.offset((@page - 1) * per_page)
.includes(:domain, :user, :taggings => :tag)
.includes(:domain, :user, taggings: :tag)
end
private
private
def with_pagination_info(scope)
scope = scope.to_a
@ -30,10 +30,8 @@ private
if @user
votes = Vote.votes_by_user_for_stories_hash(@user.id, scope.map(&:id))
hs = HiddenStory.where(:user_id => @user.id, :story_id =>
scope.map(&:id)).map(&:story_id)
ss = SavedStory.where(:user_id => @user.id, :story_id =>
scope.map(&:id)).map(&:story_id)
hs = HiddenStory.where(user_id: @user.id, story_id: scope.map(&:id)).map(&:story_id)
ss = SavedStory.where(user_id: @user.id, story_id: scope.map(&:id)).map(&:story_id)
scope.each do |s|
if votes[s.id]

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ class StoryRepository
def hottest
hottest = Story.base(@user).positive_ranked.not_hidden_by(@user)
hottest = hottest.filter_tags(@params[:exclude_tags] || [])
hottest.order('hotness')
hottest.order("hotness")
end
def hidden
@ -33,8 +33,8 @@ class StoryRepository
from comments
where comments.story_id = stories.id
) as latest_comment_id')
.where('created_at >= ?', 3.days.ago)
.order('latest_comment_id desc')
.where("created_at >= ?", 3.days.ago)
.order("latest_comment_id desc")
end
def newest_by_user(user)
@ -53,7 +53,7 @@ class StoryRepository
end
def top(length)
top = Story.base(@user).where("created_at >= (NOW() - INTERVAL " <<
top = Story.base(@user).where("created_at >= (NOW() - INTERVAL " \
"#{length[:dur]} #{length[:intv].upcase})")
top.order("score DESC")
end

View File

@ -3,7 +3,7 @@ class StoryText < ApplicationRecord
belongs_to :story, foreign_key: :id, inverse_of: :story_text
validates :body, presence: true, length: { :maximum => 16_777_215 }
validates :body, presence: true, length: {maximum: 16_777_215}
def self.fill_cache!(story)
return nil unless story.url.present?
@ -25,6 +25,6 @@ class StoryText < ApplicationRecord
return false
end
self.where(id: story).exists?
where(id: story).exists?
end
end

View File

@ -1,54 +1,54 @@
class Tag < ApplicationRecord
belongs_to :category
has_many :taggings, :dependent => :delete_all
has_many :stories, :through => :taggings
has_many :tag_filters, :dependent => :destroy
has_many :taggings, dependent: :delete_all
has_many :stories, through: :taggings
has_many :tag_filters, dependent: :destroy
has_many :filtering_users,
:class_name => "User",
:through => :tag_filters,
:source => :user,
:dependent => :delete_all
class_name: "User",
through: :tag_filters,
source: :user,
dependent: :delete_all
after_save :log_modifications
attr_accessor :edit_user_id, :stories_count
attr_writer :filtered_count
validates :tag, length: { maximum: 25 }, presence: true,
uniqueness: { case_sensitive: true },
format: { with: /\A[A-Za-z0-9_\-\+]+\z/ }
validates :description, length: { maximum: 100 }
validates :hotness_mod, inclusion: { in: -10..10 }
validates :permit_by_new_users, :privileged, inclusion: { in: [true, false] }
validates :tag, length: {maximum: 25}, presence: true,
uniqueness: {case_sensitive: true},
format: {with: /\A[A-Za-z0-9_\-\+]+\z/}
validates :description, length: {maximum: 100}
validates :hotness_mod, inclusion: {in: -10..10}
validates :permit_by_new_users, :privileged, inclusion: {in: [true, false]}
scope :active, -> { where(active: true) }
scope :not_permitted_for_new_users, -> { where(permit_by_new_users: false) }
scope :related, ->(tag) {
active
.joins(:taggings)
.where(taggings: { story_id: Tagging.where(tag: tag).select(:story_id) })
.where(taggings: {story_id: Tagging.where(tag: tag).select(:story_id)})
.where.not(id: [tag, 67]) # 67 = programming, the catch-all
.where.not(is_media: true)
.group(:id)
.order(Arel.sql('COUNT(*) desc'))
.order(Arel.sql("COUNT(*) desc"))
.limit(8)
}
def to_param
self.tag
tag
end
def self.all_with_filtered_counts_for(user)
counts = TagFilter.group(:tag_id).count
Tag.active.order(:tag).select {|t| t.valid_for?(user) }.map {|t|
Tag.active.order(:tag).select { |t| t.valid_for?(user) }.map { |t|
t.filtered_count = counts[t.id].to_i
t
}
end
def category_name
self.category && self.category.category
category&.category
end
def category_name=(category)
@ -56,15 +56,15 @@ class Tag < ApplicationRecord
end
def css_class
"tag tag_#{self.tag}" << (self.is_media?? " tag_is_media" : "")
"tag tag_#{tag}" << (is_media? ? " tag_is_media" : "")
end
def user_can_filter?(user)
self.active? && (!self.privileged? || user.try(:is_moderator?))
active? && (!privileged? || user.try(:is_moderator?))
end
def valid_for?(user)
if self.privileged?
if privileged?
!!user.try(:is_moderator?)
else
true
@ -72,19 +72,19 @@ class Tag < ApplicationRecord
end
def filtered_count
@filtered_count ||= TagFilter.where(:tag_id => self.id).count
@filtered_count ||= TagFilter.where(tag_id: id).count
end
def log_modifications
Moderation.create do |m|
if self.id_previously_changed?
m.action = 'Created new tag ' + self.attributes.map {|f, c| "with #{f} '#{c}'" }.join(', ')
m.action = if id_previously_changed?
"Created new tag " + attributes.map { |f, c| "with #{f} '#{c}'" }.join(", ")
else
m.action = "Updating tag #{self.tag}, " + self.saved_changes
.map {|f, c| "changed #{f} from '#{c[0]}' to '#{c[1]}'" } .join(', ')
"Updating tag #{tag}, " + saved_changes
.map { |f, c| "changed #{f} from '#{c[0]}' to '#{c[1]}'" }.join(", ")
end
m.moderator_user_id = @edit_user_id
m.tag_id = self.id
m.tag_id = id
end
end
end

View File

@ -1,4 +1,4 @@
class Tagging < ApplicationRecord
belongs_to :tag, :inverse_of => :taggings
belongs_to :story, :inverse_of => :taggings
belongs_to :tag, inverse_of: :taggings
belongs_to :story, inverse_of: :taggings
end

View File

@ -1,73 +1,73 @@
class User < ApplicationRecord
has_many :stories, -> { includes :user }, :inverse_of => :user
has_many :stories, -> { includes :user }, inverse_of: :user
has_many :comments,
:inverse_of => :user,
:dependent => :restrict_with_exception
inverse_of: :user,
dependent: :restrict_with_exception
has_many :sent_messages,
:class_name => "Message",
:foreign_key => "author_user_id",
:inverse_of => :author,
:dependent => :restrict_with_exception
class_name: "Message",
foreign_key: "author_user_id",
inverse_of: :author,
dependent: :restrict_with_exception
has_many :received_messages,
:class_name => "Message",
:foreign_key => "recipient_user_id",
:inverse_of => :recipient,
:dependent => :restrict_with_exception
has_many :tag_filters, :dependent => :destroy
class_name: "Message",
foreign_key: "recipient_user_id",
inverse_of: :recipient,
dependent: :restrict_with_exception
has_many :tag_filters, dependent: :destroy
has_many :tag_filter_tags,
:class_name => "Tag",
:through => :tag_filters,
:source => :tag,
:dependent => :delete_all
class_name: "Tag",
through: :tag_filters,
source: :tag,
dependent: :delete_all
belongs_to :invited_by_user,
:class_name => "User",
:inverse_of => false,
:optional => true
class_name: "User",
inverse_of: false,
optional: true
belongs_to :banned_by_user,
:class_name => "User",
:inverse_of => false,
:optional => true
class_name: "User",
inverse_of: false,
optional: true
belongs_to :disabled_invite_by_user,
:class_name => "User",
:inverse_of => false,
:optional => true
has_many :invitations, :dependent => :destroy
class_name: "User",
inverse_of: false,
optional: true
has_many :invitations, dependent: :destroy
has_many :mod_notes,
:inverse_of => :user,
:dependent => :restrict_with_exception
inverse_of: :user,
dependent: :restrict_with_exception
has_many :moderations,
:inverse_of => :moderator,
:dependent => :restrict_with_exception
has_many :votes, :dependent => :destroy
has_many :voted_stories, -> { where('votes.comment_id' => nil) },
:through => :votes,
:source => :story
inverse_of: :moderator,
dependent: :restrict_with_exception
has_many :votes, dependent: :destroy
has_many :voted_stories, -> { where("votes.comment_id" => nil) },
through: :votes,
source: :story
has_many :upvoted_stories,
-> {
where('votes.comment_id' => nil, 'votes.vote' => 1)
.where('stories.user_id != votes.user_id')
},
:through => :votes,
:source => :story
has_many :hats, :dependent => :destroy
has_many :wearable_hats, -> { where('doffed_at is null') },
:class_name => "Hat",
:inverse_of => :user
-> {
where("votes.comment_id" => nil, "votes.vote" => 1)
.where("stories.user_id != votes.user_id")
},
through: :votes,
source: :story
has_many :hats, dependent: :destroy
has_many :wearable_hats, -> { where("doffed_at is null") },
class_name: "Hat",
inverse_of: :user
has_secure_password
typed_store :settings do |s|
s.string :prefers_color_scheme, :default => "system"
s.boolean :email_notifications, :default => false
s.boolean :email_replies, :default => false
s.boolean :pushover_replies, :default => false
s.string :prefers_color_scheme, default: "system"
s.boolean :email_notifications, default: false
s.boolean :email_replies, default: false
s.boolean :pushover_replies, default: false
s.string :pushover_user_key
s.boolean :email_messages, :default => false
s.boolean :pushover_messages, :default => false
s.boolean :email_mentions, :default => false
s.boolean :show_avatars, :default => true
s.boolean :show_story_previews, :default => false
s.boolean :show_submitted_story_threads, :default => false
s.boolean :email_messages, default: false
s.boolean :pushover_messages, default: false
s.boolean :email_mentions, default: false
s.boolean :show_avatars, default: true
s.boolean :show_story_previews, default: false
s.boolean :show_submitted_story_threads, default: false
s.string :totp_secret
s.string :github_oauth_token
s.string :github_username
@ -78,49 +78,49 @@ class User < ApplicationRecord
s.string :homepage
end
validates :prefers_color_scheme, inclusion: %w(system light dark)
validates :prefers_color_scheme, inclusion: %w[system light dark]
validates :email,
:length => { :maximum => 100 },
:format => { :with => /\A[^@ ]+@[^@ ]+\.[^@ ]+\Z/ },
:uniqueness => { :case_sensitive => false }
length: {maximum: 100},
format: {with: /\A[^@ ]+@[^@ ]+\.[^@ ]+\Z/},
uniqueness: {case_sensitive: false}
validates :homepage,
:format => {
:with => /\A(?:https?|gemini|gopher):\/\/[^\/\s]+\.[^.\/\s]+(\/.*)?\Z/,
},
:allow_blank => true
format: {
with: /\A(?:https?|gemini|gopher):\/\/[^\/\s]+\.[^.\/\s]+(\/.*)?\Z/
},
allow_blank: true
validates :password, :presence => true, :on => :create
validates :password, presence: true, on: :create
VALID_USERNAME = /[A-Za-z0-9][A-Za-z0-9_-]{0,24}/.freeze
VALID_USERNAME = /[A-Za-z0-9][A-Za-z0-9_-]{0,24}/
validates :username,
:format => { :with => /\A#{VALID_USERNAME}\z/ },
:length => { :maximum => 50 },
:uniqueness => { :case_sensitive => false }
format: {with: /\A#{VALID_USERNAME}\z/o},
length: {maximum: 50},
uniqueness: {case_sensitive: false}
validates :password_reset_token,
:length => { :maximum => 75 }
length: {maximum: 75}
validates :session_token,
:length => { :maximum => 75 }
length: {maximum: 75}
validates :about,
:length => { :maximum => 16_777_215 }
length: {maximum: 16_777_215}
validates :rss_token,
:length => { :maximum => 75 }
length: {maximum: 75}
validates :mailing_list_token,
:length => { :maximum => 75 }
length: {maximum: 75}
validates :banned_reason,
:length => { :maximum => 200 }
length: {maximum: 200}
validates :disabled_invite_reason,
:length => { :maximum => 200 }
length: {maximum: 200}
validates_each :username do |record, attr, value|
if BANNED_USERNAMES.include?(value.to_s.downcase) || value.starts_with?('tag-')
if BANNED_USERNAMES.include?(value.to_s.downcase) || value.starts_with?("tag-")
record.errors.add(attr, "is not permitted")
end
end
scope :active, -> { where(:banned_at => nil, :deleted_at => nil) }
scope :active, -> { where(banned_at: nil, deleted_at: nil) }
scope :moderators, -> {
where('
is_moderator = True OR
@ -129,15 +129,15 @@ class User < ApplicationRecord
}
before_save :check_session_token
before_validation :on => :create do
self.create_rss_token
self.create_mailing_list_token
before_validation on: :create do
create_rss_token
create_mailing_list_token
end
BANNED_USERNAMES = ["admin", "administrator", "contact", "fraud", "guest",
"help", "hostmaster", "lobster", "lobsters", "mailer-daemon", "moderator",
"moderators", "nobody", "postmaster", "root", "security", "support",
"sysop", "webmaster", "enable", "new", "signup",].freeze
"sysop", "webmaster", "enable", "new", "signup"].freeze
# days old accounts are considered new for
NEW_USER_DAYS = 70
@ -169,59 +169,59 @@ class User < ApplicationRecord
:username,
:created_at,
:is_admin,
:is_moderator,
:is_moderator
]
if !self.is_admin?
if !is_admin?
attrs.push :karma
end
attrs.push :homepage, :about
h = super(:only => attrs)
h = super(only: attrs)
h[:avatar_url] = self.avatar_url
h[:avatar_url] = avatar_url
h[:invited_by_user] = User.where(id: invited_by_user_id).pluck(:username).first
if self.github_username.present?
h[:github_username] = self.github_username
if github_username.present?
h[:github_username] = github_username
end
if self.twitter_username.present?
h[:twitter_username] = self.twitter_username
if twitter_username.present?
h[:twitter_username] = twitter_username
end
if self.keybase_signatures.present?
h[:keybase_signatures] = self.keybase_signatures
if keybase_signatures.present?
h[:keybase_signatures] = keybase_signatures
end
h
end
def authenticate_totp(code)
totp = ROTP::TOTP.new(self.totp_secret)
totp = ROTP::TOTP.new(totp_secret)
totp.verify(code)
end
def avatar_path(size = 100)
ActionController::Base.helpers.image_path(
"/avatars/#{self.username}-#{size}.png",
"/avatars/#{username}-#{size}.png",
skip_pipeline: true
)
end
def avatar_url(size = 100)
ActionController::Base.helpers.image_url(
"/avatars/#{self.username}-#{size}.png",
"/avatars/#{username}-#{size}.png",
skip_pipeline: true
)
end
def average_karma
if self.karma == 0
if karma == 0
0
else
self.karma.to_f / (self.stories_submitted_count + self.comments_posted_count)
karma.to_f / (stories_submitted_count + comments_posted_count)
end
end
@ -230,23 +230,23 @@ class User < ApplicationRecord
self.disabled_invite_at = Time.current
self.disabled_invite_by_user_id = disabler.id
self.disabled_invite_reason = reason
self.save!
save!
msg = Message.new
msg.deleted_by_author = true
msg.author_user_id = disabler.id
msg.recipient_user_id = self.id
msg.recipient_user_id = id
msg.subject = "Your invite privileges have been revoked"
msg.body = "The reason given:\n" <<
"\n" <<
"> *#{reason}*\n" <<
"\n" <<
"*This is an automated message.*"
msg.body = "The reason given:\n" \
"\n" \
"> *#{reason}*\n" \
"\n" \
"*This is an automated message.*"
msg.save!
m = Moderation.new
m.moderator_user_id = disabler.id
m.user_id = self.id
m.user_id = id
m.action = "Disabled invitations"
m.reason = reason
m.save!
@ -261,12 +261,12 @@ class User < ApplicationRecord
self.banned_by_user_id = banner.id
self.banned_reason = reason
BanNotification.notify(self, banner, reason) unless self.deleted_at?
self.delete!
BanNotification.notify(self, banner, reason) unless deleted_at?
delete!
m = Moderation.new
m.moderator_user_id = banner.id
m.user_id = self.id
m.user_id = id
m.action = "Banned"
m.reason = reason
m.save!
@ -290,59 +290,59 @@ class User < ApplicationRecord
return true
end
elsif obj.is_a?(Comment) && obj.is_flaggable?
return self.karma >= MIN_KARMA_TO_FLAG
return karma >= MIN_KARMA_TO_FLAG
end
false
end
def can_invite?
!self.is_new? && !banned_from_inviting? && self.can_submit_stories?
!is_new? && !banned_from_inviting? && can_submit_stories?
end
def can_offer_suggestions?
!self.is_new? && (self.karma >= MIN_KARMA_TO_SUGGEST)
!is_new? && (karma >= MIN_KARMA_TO_SUGGEST)
end
def can_see_invitation_requests?
can_invite? && (self.is_moderator? ||
(self.karma >= MIN_KARMA_FOR_INVITATION_REQUESTS))
can_invite? && (is_moderator? ||
(karma >= MIN_KARMA_FOR_INVITATION_REQUESTS))
end
def can_submit_stories?
self.karma >= MIN_KARMA_TO_SUBMIT_STORIES
karma >= MIN_KARMA_TO_SUBMIT_STORIES
end
def check_session_token
if self.session_token.blank?
self.roll_session_token
if session_token.blank?
roll_session_token
end
end
def create_mailing_list_token
if self.mailing_list_token.blank?
if mailing_list_token.blank?
self.mailing_list_token = Utils.random_str(10)
end
end
def create_rss_token
if self.rss_token.blank?
if rss_token.blank?
self.rss_token = Utils.random_str(60)
end
end
def comments_posted_count
Keystore.value_for("user:#{self.id}:comments_posted").to_i
Keystore.value_for("user:#{id}:comments_posted").to_i
end
def comments_deleted_count
Keystore.value_for("user:#{self.id}:comments_deleted").to_i
Keystore.value_for("user:#{id}:comments_deleted").to_i
end
def fetched_avatar(size = 100)
gravatar_url = "https://www.gravatar.com/avatar/" <<
Digest::MD5.hexdigest(self.email.strip.downcase) <<
"?r=pg&d=identicon&s=#{size}"
Digest::MD5.hexdigest(email.strip.downcase) <<
"?r=pg&d=identicon&s=#{size}"
begin
s = Sponge.new
@ -359,81 +359,81 @@ class User < ApplicationRecord
end
def refresh_counts!
Keystore.put("user:#{self.id}:stories_submitted", self.stories.count)
Keystore.put("user:#{self.id}:comments_posted", self.comments.active.count)
Keystore.put("user:#{self.id}:comments_deleted", self.comments.deleted.count)
Keystore.put("user:#{id}:stories_submitted", stories.count)
Keystore.put("user:#{id}:comments_posted", comments.active.count)
Keystore.put("user:#{id}:comments_deleted", comments.deleted.count)
end
def delete!
User.transaction do
self.comments
comments
.where("score < 0")
.find_each {|c| c.delete_for_user(self) }
.find_each { |c| c.delete_for_user(self) }
self.sent_messages.each do |m|
sent_messages.each do |m|
m.deleted_by_author = true
m.save
end
self.received_messages.each do |m|
received_messages.each do |m|
m.deleted_by_recipient = true
m.save
end
self.invitations.destroy_all
invitations.destroy_all
self.roll_session_token
roll_session_token
self.deleted_at = Time.current
self.good_riddance?
self.save!
good_riddance?
save!
end
end
def undelete!
User.transaction do
self.sent_messages.each do |m|
sent_messages.each do |m|
m.deleted_by_author = false
m.save
end
self.received_messages.each do |m|
received_messages.each do |m|
m.deleted_by_recipient = false
m.save
end
self.deleted_at = nil
self.save!
save!
end
end
def disable_2fa!
self.totp_secret = nil
self.save!
save!
end
# ensures some users talk to a mod before reactivating
def good_riddance?
return if self.is_banned? # https://www.youtube.com/watch?v=UcZzlPGnKdU
self.email = "#{self.username}@lobsters.example" if \
self.karma < 0 ||
(self.comments.where('created_at >= now() - interval 30 day AND is_deleted').count +
self.stories.where('created_at >= now() - interval 30 day AND is_deleted AND is_moderated')
.count >= 3) ||
FlaggedCommenters.new('90d').check_list_for(self)
return if is_banned? # https://www.youtube.com/watch?v=UcZzlPGnKdU
self.email = "#{username}@lobsters.example" if \
karma < 0 ||
(comments.where("created_at >= now() - interval 30 day AND is_deleted").count +
stories.where("created_at >= now() - interval 30 day AND is_deleted AND is_moderated")
.count >= 3) ||
FlaggedCommenters.new("90d").check_list_for(self)
end
def grant_moderatorship_by_user!(user)
User.transaction do
self.is_moderator = true
self.save!
save!
m = Moderation.new
m.moderator_user_id = user.id
m.user_id = self.id
m.user_id = id
m.action = "Granted moderator status"
m.save!
h = Hat.new
h.user_id = self.id
h.user_id = id
h.granted_by_user_id = user.id
h.hat = "Sysop"
h.save!
@ -444,13 +444,13 @@ class User < ApplicationRecord
def initiate_password_reset_for_ip(ip)
self.password_reset_token = "#{Time.current.to_i}-#{Utils.random_str(30)}"
self.save!
save!
PasswordReset.password_reset_link(self, ip).deliver_now
end
def has_2fa?
self.totp_secret.present?
totp_secret.present?
end
def is_active?
@ -463,23 +463,23 @@ class User < ApplicationRecord
# user was deleted/banned before a server move, see lib/tasks/privacy_wipe
def is_wiped?
password_digest == '*'
password_digest == "*"
end
def is_new?
return true unless self.created_at # unsaved object; in signup flow or a test
self.created_at > NEW_USER_DAYS.days.ago
return true unless created_at # unsaved object; in signup flow or a test
created_at > NEW_USER_DAYS.days.ago
end
def add_or_update_keybase_proof(kb_username, kb_signature)
self.keybase_signatures ||= []
self.remove_keybase_proof(kb_username)
self.keybase_signatures.push('kb_username' => kb_username, 'sig_hash' => kb_signature)
remove_keybase_proof(kb_username)
self.keybase_signatures.push("kb_username" => kb_username, "sig_hash" => kb_signature)
end
def remove_keybase_proof(kb_username)
self.keybase_signatures ||= []
self.keybase_signatures.reject! {|kbsig| kbsig['kb_username'] == kb_username }
self.keybase_signatures.reject! { |kbsig| kbsig["kb_username"] == kb_username }
end
def roll_session_token
@ -487,47 +487,47 @@ class User < ApplicationRecord
end
def is_heavy_self_promoter?
total_count = self.stories_submitted_count
total_count = stories_submitted_count
if total_count < MIN_STORIES_CHECK_SELF_PROMOTION
false
else
authored = self.stories.where(:user_is_author => true).count
authored = stories.where(user_is_author: true).count
authored.to_f / total_count >= HEAVY_SELF_PROMOTER_PROPORTION
end
end
def linkified_about
Markdowner.to_html(self.about)
Markdowner.to_html(about)
end
def most_common_story_tag
Tag.active.joins(
:stories
).where(
:stories => { :user_id => self.id, :is_deleted => false }
stories: {user_id: id, is_deleted: false}
).group(
Tag.arel_table[:id]
).order(
Arel.sql('COUNT(*) desc')
Arel.sql("COUNT(*) desc")
).first
end
def pushover!(params)
if self.pushover_user_key.present?
Pushover.push(self.pushover_user_key, params)
if pushover_user_key.present?
Pushover.push(pushover_user_key, params)
end
end
def recent_threads(amount, include_submitted_stories: false, for_user: user)
comments = self.comments.accessible_to_user(for_user)
thread_ids = comments.group(:thread_id).order('MAX(created_at) DESC').limit(amount)
thread_ids = comments.group(:thread_id).order("MAX(created_at) DESC").limit(amount)
.pluck(:thread_id)
if include_submitted_stories && self.show_submitted_story_threads
if include_submitted_stories && show_submitted_story_threads
thread_ids += Comment.joins(:story)
.where(:stories => { :user_id => self.id }).group(:thread_id)
.where(stories: {user_id: id}).group(:thread_id)
.order("MAX(comments.created_at) DESC").limit(amount).pluck(:thread_id)
thread_ids = thread_ids.uniq.sort.reverse[0, amount]
@ -537,11 +537,11 @@ class User < ApplicationRecord
end
def stories_submitted_count
Keystore.value_for("user:#{self.id}:stories_submitted").to_i
Keystore.value_for("user:#{id}:stories_submitted").to_i
end
def stories_deleted_count
Keystore.value_for("user:#{self.id}:stories_deleted").to_i
Keystore.value_for("user:#{id}:stories_deleted").to_i
end
def to_param
@ -553,11 +553,11 @@ class User < ApplicationRecord
self.banned_by_user_id = nil
self.banned_reason = nil
self.deleted_at = nil
self.save!
save!
m = Moderation.new
m.moderator_user_id = unbanner.id
m.user_id = self.id
m.user_id = id
m.action = "Unbanned"
m.reason = reason
m.save!
@ -570,11 +570,11 @@ class User < ApplicationRecord
self.disabled_invite_at = nil
self.disabled_invite_by_user_id = nil
self.disabled_invite_reason = nil
self.save!
save!
m = Moderation.new
m.moderator_user_id = mod.id
m.user_id = self.id
m.user_id = id
m.action = "Enabled invitations"
m.save!
end
@ -583,22 +583,22 @@ class User < ApplicationRecord
end
def unread_message_count
@unread_message_count ||= Keystore.value_for("user:#{self.id}:unread_messages").to_i
@unread_message_count ||= Keystore.value_for("user:#{id}:unread_messages").to_i
end
def update_unread_message_count!
@unread_message_count = self.received_messages.unread.count
Keystore.put("user:#{self.id}:unread_messages", @unread_message_count)
@unread_message_count = received_messages.unread.count
Keystore.put("user:#{id}:unread_messages", @unread_message_count)
end
def clear_unread_replies!
Rails.cache.delete("user:#{self.id}:unread_replies")
Rails.cache.delete("user:#{id}:unread_replies")
end
def unread_replies_count
@unread_replies_count ||=
Rails.cache.fetch("user:#{self.id}:unread_replies", expires_in: 2.minutes) {
ReplyingComment.where(user_id: self.id, is_unread: true).count
Rails.cache.fetch("user:#{id}:unread_replies", expires_in: 2.minutes) {
ReplyingComment.where(user_id: id, is_unread: true).count
}
end
@ -607,9 +607,9 @@ class User < ApplicationRecord
end
def votes_for_others
self.votes.left_outer_joins(:story, :comment)
votes.left_outer_joins(:story, :comment)
.includes(comment: :user, story: :user)
.where("(votes.comment_id is not null and comments.user_id <> votes.user_id) OR " <<
.where("(votes.comment_id is not null and comments.user_id <> votes.user_id) OR " \
"(votes.comment_id is null and stories.user_id <> votes.user_id)")
.order("id DESC")
end

View File

@ -5,10 +5,10 @@ class Vote < ApplicationRecord
validates :vote, presence: true
validates :reason,
length: { is: 1 },
allow_blank: true
length: {is: 1},
allow_blank: true
scope :comments_flags, ->(comments, user=nil) {
scope :comments_flags, ->(comments, user = nil) {
q = where(comment: comments, vote: -1)
user ? q.where(user: user) : q.all
}
@ -20,10 +20,10 @@ class Vote < ApplicationRecord
"T" => "Troll",
"U" => "Unkind",
"S" => "Spam",
"" => "Cancel",
"" => "Cancel"
}.freeze
ALL_COMMENT_REASONS = COMMENT_REASONS.merge({
"I" => "Incorrect",
"I" => "Incorrect"
}).freeze
# don't forget to edit the explanations on /about
@ -32,18 +32,18 @@ class Vote < ApplicationRecord
"A" => "Already Posted",
"B" => "Broken Link",
"S" => "Spam",
"" => "Cancel",
"" => "Cancel"
}.freeze
ALL_STORY_REASONS = STORY_REASONS.merge({
"Q" => "Low Quality",
"Q" => "Low Quality"
}).freeze
def self.votes_by_user_for_stories_hash(user, stories)
votes = {}
Vote.where(:user_id => user, :story_id => stories,
:comment_id => nil).find_each do |v|
votes[v.story_id] = { :vote => v.vote, :reason => v.reason }
Vote.where(user_id: user, story_id: stories,
comment_id: nil).find_each do |v|
votes[v.story_id] = {vote: v.vote, reason: v.reason}
end
votes
@ -53,11 +53,11 @@ class Vote < ApplicationRecord
votes = {}
Vote.where(
:user_id => user_id, :story_id => story_id
user_id: user_id, story_id: story_id
).where(
"comment_id IS NOT NULL"
).find_each do |v|
votes[v.comment_id] = { :vote => v.vote, :reason => v.reason }
votes[v.comment_id] = {vote: v.vote, reason: v.reason}
end
votes
@ -67,14 +67,13 @@ class Vote < ApplicationRecord
if story_ids.empty?
{}
else
votes = self.where(
:user_id => user_id,
:comment_id => nil,
:story_id => story_ids,
votes = where(
user_id: user_id,
comment_id: nil,
story_id: story_ids
)
votes.inject({}) do |memo, v|
memo[v.story_id] = { :vote => v.vote, :reason => v.reason }
memo
votes.each_with_object({}) do |v, memo|
memo[v.story_id] = {vote: v.vote, reason: v.reason}
end
end
end
@ -83,13 +82,12 @@ class Vote < ApplicationRecord
if comment_ids.empty?
{}
else
votes = self.where(
:user_id => user_id,
:comment_id => comment_ids,
votes = where(
user_id: user_id,
comment_id: comment_ids
)
votes.inject({}) do |memo, v|
memo[v.comment_id] = { :vote => v.vote, :reason => v.reason }
memo
votes.each_with_object({}) do |v, memo|
memo[v.comment_id] = {vote: v.vote, reason: v.reason}
end
end
end
@ -97,21 +95,21 @@ class Vote < ApplicationRecord
def self.vote_thusly_on_story_or_comment_for_user_because(
new_vote, story_id, comment_id, user_id, reason, update_counters = true
)
v = Vote.where(:user_id => user_id, :story_id => story_id,
:comment_id => comment_id).first_or_initialize
v = Vote.where(user_id: user_id, story_id: story_id,
comment_id: comment_id).first_or_initialize
return if !v.new_record? && v.vote == new_vote # done if there's no change
score_delta = new_vote - v.vote.to_i
if v.vote == -1
flag_delta = if v.vote == -1
# we know there's a change, so we must be removing a flag
flag_delta = -1
-1
elsif new_vote == -1
# we know there's a change, so we must be adding a flag
flag_delta = 1
1
else
# change from 1 to 0 or 0 to 1, so number of flags doesn't change
flag_delta = 0
0
end
if new_vote == 0
@ -132,10 +130,10 @@ class Vote < ApplicationRecord
end
def target
if self.comment_id
Comment.find(self.comment_id)
if comment_id
Comment.find(comment_id)
else
Story.find(self.story_id)
Story.find(story_id)
end
end
end

View File

@ -1,24 +1,24 @@
# see https://keybase.io/docs/proof_integration_guide#1-config
JSON.pretty_generate({
"version": 1,
"domain": @domain,
"display_name": @name,
"description": @description,
"brand_color": @brand_color,
"logo": {
"svg_black": @logo_black,
"svg_full": @logo_full
version: 1,
domain: @domain,
display_name: @name,
description: @description,
brand_color: @brand_color,
logo: {
svg_black: @logo_black,
svg_full: @logo_full
},
"username": {
"re": @user_re,
"min": 1,
"max": 25
username: {
re: @user_re,
min: 1,
max: 25
},
"prefill_url": @prefill_url,
"profile_url": @profile_url,
"check_url": @check_url,
"check_path": ["keybase_signatures"],
"avatar_path": ["avatar_url"],
"contact": @contacts
prefill_url: @prefill_url,
profile_url: @profile_url,
check_url: @check_url,
check_path: ["keybase_signatures"],
avatar_path: ["avatar_url"],
contact: @contacts
})

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
@ -26,4 +24,4 @@ end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("oauth", "oauth")
load Gem.bin_path("oauth-tty", "oauth")

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby3.0
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

27
bin/racc Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'racc' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("racc", "racc")

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,27 @@
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rails' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("railties", "rails")

View File

@ -1,4 +1,27 @@
#!/usr/bin/env ruby
require_relative '../config/boot'
require 'rake'
Rake.application.run
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rake' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("rake", "rake")

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

27
bin/ruby-memory-profiler Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'ruby-memory-profiler' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("memory_profiler", "ruby-memory-profiler")

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

View File

@ -1,4 +1,4 @@
#!/usr/bin/env ruby2.7
#!/usr/bin/env ruby
# frozen_string_literal: true
#
@ -8,11 +8,9 @@
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("../bundle", __FILE__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/

27
bin/stackprof Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'stackprof' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("stackprof", "stackprof")

27
bin/stackprof-flamegraph.pl Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'stackprof-flamegraph.pl' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("stackprof", "stackprof-flamegraph.pl")

27
bin/stackprof-gprof2dot.py Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'stackprof-gprof2dot.py' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("stackprof", "stackprof-gprof2dot.py")

Some files were not shown because too many files have changed in this diff Show More