journalduhacker/app/controllers/home_controller.rb
joshua stein 9535b05490 remove story downvoting, add story hiding
stories should either be reported for spam (coming later), upvoted,
or left alone rather than being downvoted for being uninteresting.
since users don't like leaving uninteresting things alone, they can
now hide stories from their view without affecting the story's
score.

hiding is implemented as a Vote with its vote set to 0 and the
reason set to "H"

add a /hidden url which shows all of a user's hidden stories

while i'm here, simplify Vote guts and add some tests to make sure
all the flip-flopping stuff works right
2014-03-03 17:20:21 -06:00

276 lines
7.3 KiB
Ruby

class HomeController < ApplicationController
STORIES_PER_PAGE = 25
# how many points a story has to have to probably get on the front page
HOT_STORY_POINTS = 5
# how many days old a story can be to get on the bottom half of /recent
RECENT_DAYS_OLD = 3
# for rss feeds, load the user's tag filters if a token is passed
before_filter :find_user_from_rss_token, :only => [ :index, :newest ]
def hidden
@stories = find_stories({ :hidden => true })
@heading = @title = "Hidden Stories"
@cur_url = "/hidden"
render :action => "index"
end
def index
@stories = find_stories
@rss_link ||= "<link rel=\"alternate\" type=\"application/rss+xml\" " <<
"title=\"RSS 2.0\" href=\"/rss" <<
(@user ? "?token=#{@user.rss_token}" : "") << "\" />"
@heading = @title = ""
@cur_url = "/"
respond_to do |format|
format.html { render :action => "index" }
format.rss {
if @user && params[:token].present?
@title = "Private feed for #{@user.username}"
end
render :action => "rss", :layout => false
}
format.json { render :json => @stories }
end
end
def newest
@stories = find_stories({ :newest => true })
@heading = @title = "Newest Stories"
@cur_url = "/newest"
@rss_link = "<link rel=\"alternate\" type=\"application/rss+xml\" " <<
"title=\"RSS 2.0 - Newest Items\" href=\"/newest.rss" <<
(@user ? "?token=#{@user.rss_token}" : "") << "\" />"
respond_to do |format|
format.html { render :action => "index" }
format.rss {
if @user && params[:token].present?
@title += " - Private feed for #{@user.username}"
end
render :action => "rss", :layout => false
}
format.json { render :json => @stories }
end
end
def newest_by_user
by_user = User.where(:username => params[:user]).first!
@stories = find_stories({ :by_user => by_user })
@heading = @title = "Newest Stories by #{by_user.username}"
@cur_url = "/newest/#{by_user.username}"
@newest = true
@for_user = by_user.username
render :action => "index"
end
def recent
@stories = find_stories({ :recent => true })
@heading = @title = "Recent Stories"
@cur_url = "/recent"
render :action => "index"
end
def tagged
@tag = Tag.where(:tag => params[:tag]).first!
@stories = find_stories({ :tag => @tag })
@heading = @title = @tag.description.blank?? @tag.tag : @tag.description
@cur_url = tag_url(@tag.tag)
@rss_link = "<link rel=\"alternate\" type=\"application/rss+xml\" " <<
"title=\"RSS 2.0 - Tagged #{CGI.escape(@tag.tag)} " <<
"(#{CGI.escape(@tag.description.to_s)})\" href=\"/t/" +
"#{CGI.escape(@tag.tag)}.rss\" />"
respond_to do |format|
format.html { render :action => "index" }
format.rss { render :action => "rss", :layout => false }
end
end
def privacy
begin
render :action => "privacy"
rescue
render :text => "<div class=\"box wide\">" <<
"You apparently have no privacy." <<
"</div>", :layout => "application"
end
end
def about
begin
render :action => "about"
rescue
render :text => "<div class=\"box wide\">" <<
"A mystery." <<
"</div>", :layout => "application"
end
end
private
def find_stories(how = {})
@page = how[:page] = 1
if params[:page].to_i > 0
@page = how[:page] = params[:page].to_i
end
# guest views have caching, but don't bother for logged-in users or dev or
# when the user has tag filters
if Rails.env.development? || @user || tags_filtered_by_cookie.any?
stories, @show_more = _find_stories(how)
else
stories, @show_more = Rails.cache.fetch("stories " <<
how.sort.map{|k,v| "#{k}=#{v.to_param}" }.join(" "),
:expires_in => 45) do
_find_stories(how)
end
end
stories
end
def _find_stories(how)
stories = Story.where(:is_expired => false)
if @user && !how[:by_user] && !how[:hidden]
# exclude downvoted and hidden items
stories = stories.where(
Story.arel_table[:id].not_in(
Vote.arel_table.where(
Vote.arel_table[:user_id].eq(@user.id)
).where(
Vote.arel_table[:vote].lteq(0)
).where(
Vote.arel_table[:comment_id].eq(nil)
).project(
Vote.arel_table[:story_id]
)
)
)
elsif @user && how[:hidden]
stories = stories.where(
Story.arel_table[:id].in(
Vote.arel_table.where(
Vote.arel_table[:user_id].eq(@user.id)
).where(
Vote.arel_table[:vote].eq(0)
).where(
Vote.arel_table[:comment_id].eq(nil)
).project(
Vote.arel_table[:story_id]
)
)
)
end
filtered_tag_ids = []
if @user
filtered_tag_ids = @user.tag_filters.map{|tf| tf.tag_id }
else
filtered_tag_ids = tags_filtered_by_cookie.map{|t| t.id }
end
if how[:tag]
stories = stories.where(
Story.arel_table[:id].in(
Tagging.arel_table.where(
Tagging.arel_table[:tag_id].eq(how[:tag].id)
).project(
Tagging.arel_table[:story_id]
)
)
)
elsif how[:by_user]
stories = stories.where(:user_id => how[:by_user].id)
elsif filtered_tag_ids.any?
stories = stories.where(
Story.arel_table[:id].not_in(
Tagging.arel_table.where(
Tagging.arel_table[:tag_id].in(filtered_tag_ids)
).project(
Tagging.arel_table[:story_id]
)
)
)
end
if how[:recent] && how[:page] == 1
# try to help recently-submitted stories that didn't gain traction
story_ids = []
10.times do |x|
# grab the list of stories from the past n days, shifting out popular
# stories that did gain traction
story_ids = stories.select(:id, :upvotes, :downvotes).
where(Story.arel_table[:created_at].gt((RECENT_DAYS_OLD + x).days.ago)).
order("stories.created_at DESC").
reject{|s| s.score > HOT_STORY_POINTS }
if story_ids.length > STORIES_PER_PAGE + 1
# keep the top half (newest stories)
keep_ids = story_ids[0 .. ((STORIES_PER_PAGE + 1) * 0.5)]
story_ids = story_ids[keep_ids.length - 1 ... story_ids.length]
# make the bottom half a random selection of older stories
while keep_ids.length <= STORIES_PER_PAGE + 1
story_ids.shuffle!
keep_ids.push story_ids.shift
end
stories = Story.where(:id => keep_ids)
break
end
end
end
stories = stories.includes(
:user, :taggings => :tag
).limit(
STORIES_PER_PAGE + 1
).offset(
(how[:page] - 1) * STORIES_PER_PAGE
).order(
(how[:newest] || how[:recent]) ? "stories.created_at DESC" : "hotness"
).to_a
show_more = false
if stories.count > STORIES_PER_PAGE
show_more = true
stories.pop
end
if @user
votes = Vote.votes_by_user_for_stories_hash(@user.id, stories.map(&:id))
stories.each do |s|
if votes[s.id]
s.vote = votes[s.id]
end
end
end
[ stories, show_more ]
end
end