app/controllers/protips_controller.rb
class ProtipsController < ApplicationController
before_action :access_required, only: [:new, :create, :edit, :update, :destroy, :me]
before_action :require_skills_first, only: [:new, :create]
before_action :lookup_protip, only: %i(show edit update destroy upvote tag flag feature delete_tag)
before_action :reformat_tags, only: [:create, :update]
before_action :verify_ownership, only: [:edit, :update, :destroy]
before_action :ensure_single_tag, only: [:subscribe, :unsubscribe]
before_action :limit_results, only: [:topic, :team, :search, :user, :date]
before_action :track_search, only: [:search], unless: :is_admin?
before_action :require_admin!, only: [:feature, :flag, :delete_tag, :admin]
before_action :determine_scope, only: [:trending, :popular, :fresh, :liked]
before_action :lookup_user_data, only: [:trending, :popular, :fresh, :liked, :search]
respond_to :html
respond_to :json, except: [:show]
respond_to :js, only: [:subscribe, :unsubscribe, :show]
layout :choose_protip_layout
# root /
#GET /p(.:format)
def index
trending
end
# GET /p/t/trending(.:format)
def trending
@context = "trending"
track_discovery
@protips = cached_version(:trending_score, @scope, search_options)
find_a_job_for(@protips) unless @protips.empty?
render :index
end
# GET /p/popular(.:format)
def popular
@context = "popular"
track_discovery
@protips = cached_version(:popular_score, @scope, search_options)
find_a_job_for(@protips)
render :index
end
# GET /p/fresh(.:format)
def fresh
redirect_to_signup_if_unauthenticated(protips_path, "You must login/signup to view fresh protips from coders, teams and networks you follow") do
@context = "fresh"
track_discovery
@protips = cached_version(:created_at, @scope, search_options)
find_a_job_for(@protips)
render :index
end
end
# GET /p/liked(.:format)
def liked
redirect_to_signup_if_unauthenticated(protips_path, "You must login/signup to view protips you have liked/upvoted") do
@context = "liked"
track_discovery
@protips = Protip::Search.new(Protip, Protip::Search::Query.new("upvoters:#{current_user.id}"), @scope, Protip::Search::Sort.new(:created_at), nil, search_options).execute
find_a_job_for(@protips)
render :index
end
end
# GET /p/u/:username(.:format)
def user
user_params = params.permit(:username, :page, :per_page)
user = User.find_by_username(params[:username]) unless params[:username].blank?
return redirect_to(protips_path) if user.nil?
@protips = protips_for_user(user,user_params)
@topics = [user.username]
@topic = "author:#{user.username}"
@topic_user = user
@query = @topic
render :topic
end
# GET /p/team/:team_slug(.:format)
def team
team_params = params.permit(:team_slug, :page, :per_page)
team = Team.where(slug: team_params[:team_slug].downcase).first unless team_params[:team_slug].blank?
return redirect_to(protips_path) if team.nil?
@protips = Protip.search_trending_by_team(team.slug, nil, team_params[:page], team_params[:per_page])
@topics = [team.slug]
@topic = "team:#{team.slug}"
@topic_user = team
render :topic
end
# GET /p/d/:date(/:start)(.:format)
def date
date_params = params.permit(:date, :query, :page, :per_page)
date = Date.current if date_params[:date].downcase == "today"
date = Date.current.advance(days: -1) if params[:date].downcase == "yesterday"
date = Date.strptime(date_params[:date], "%m%d%Y") if date.nil?
return redirect_to(protips_path) unless is_admin? and date
@protips = Protip.search_trending_by_date(date_params[:query], date, date_params[:page], date_params[:per_page])
@topics = [date.to_s]
@topic = date.to_s
@topic_user = nil
@query = "created_at:#{date.to_date.to_s}"
render :topic
end
# GET /p/me(.:format)
def me
me_params = params.permit(:section, :page, :per_page)
@topics = @topic = Protip::USER_SCOPE
section = me_params[:section]
query = "#{section}:#{current_user.username}" if section
@protips = Protip.search(query, [], page: me_params[:page], per_page: me_params[:per_page] || 12)
@topic_user = nil
end
# GET /p/dpvbbg(.:format)
# GET /gh(.:format)
# GET /p/:id/:slug(.:format)
def show
show_params = if is_admin?
params.permit(:reply_to, :q, :t, :i, :p)
else
params.permit(:reply_to)
end
return redirect_to protip_missing_destination, notice: "The pro tip you were looking for no longer exists" if @protip.nil?
return redirect_to protip_path(@protip.public_id<<'/'<<@protip.friendly_id, :p => params[:p], :q => params[:q]) if params[:slug]!=@protip.friendly_id
@comments = @protip.comments
@reply_to = show_params[:reply_to]
@next_protip = Protip.search_next(show_params[:q], show_params[:t], show_params[:i], show_params[:p]) if is_admin?
@reviewer = Protip.valid_reviewers.select { |reviewer| @protip.viewed_by?(reviewer) }.first
@job = @protip.best_matching_job || Opportunity.uncached { Opportunity.order('RANDOM()').first }
track_views(@protip)
respond_with @protip
end
# GET /p/random(.:format)
def random
@protip = Protip.random(1).first
render :show
end
# GET /p/new(.:format)
def new
new_params = params.permit(:topic_list)
prefilled_topics = (new_params[:topic_list] || '').split('+').collect(&:strip)
@protip = Protip.new(topic_list: prefilled_topics)
respond_with @protip
end
# GET /p/:id/edit(.:format)
def edit
respond_with @protip
end
# POST /p(.:format)
def create
create_params = if params[:protip] && params[:protip].keys.present?
params.require(:protip).permit(:title, :body, :user_id, :topic_list)
else
{}
end
@protip = current_user.protips.build(create_params)
respond_to do |format|
if @protip.save
record_event('created protip')
format.html { redirect_to protip_path(@protip.public_id), notice: 'Protip was successfully created.' }
format.json { render json: @protip, status: :created, location: @protip }
else
format.html { render action: "new" }
format.json { render json: @protip.errors, status: :unprocessable_entity }
end
end
end
# protips_update GET|PUT /protips/update(.:format) protips#update
def update
# strong_parameters will intentionally fail if a key is present but has an empty hash. :(
update_params = if params[:protip] && params[:protip].keys.present?
params.require(:protip).permit(:title, :body, :user_id, :topic_list)
else
{}
end
#
# TODO: Ensure that the protip id is the actual id of the logged in user
#
respond_to do |format|
if @protip.update_attributes(update_params)
format.html { redirect_to protip_path(@protip.public_id), notice: 'Protip was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: @protip.errors, status: :unprocessable_entity }
end
end
end
def destroy
return head(:forbidden) unless @protip.owned_by?(current_user)
@protip.destroy
respond_to do |format|
format.html { redirect_to(protips_url) }
format.json { head :ok }
end
end
# POST /p/:id/upvote(.:format)
def upvote
@protip.upvote_by(viewing_user, tracking_code, request.remote_ip)
@protip
end
# POST /p/:id/tag(.:format)
def tag
tag_params = params.permit(:topic_list)
@protip.topic_list.add(tag_params[:topic_list]) unless tag_params[:topic_list].nil?
end
# PUT /p/t(/*tags)/subscribe(.:format)
def subscribe
tags = params.permit(:tags)
redirect_to_signup_if_unauthenticated(view_context.topic_protips_path(tags)) do
current_user.subscribe_to(tags)
respond_to do |format|
format.json { head :ok }
end
end
end
# PUT /p/t(/*tags)/unsubscribe(.:format)
def unsubscribe
tags = params.permit(:tags)
redirect_to_signup_if_unauthenticated(view_context.topic_protips_path(tags)) do
current_user.unsubscribe_from(tags)
respond_to do |format|
format.json { head :ok }
end
end
end
# POST /p/:id/report_inappropriate(.:format)
def report_inappropriate
protip_public_id = params[:id]
protip = Protip.find_by_public_id!(protip_public_id)
if protip.report_spam && cookies["report_inappropriate-#{protip_public_id}"].nil?
opts = { user_id: current_user, ip: request.remote_ip}
::AbuseMailer.report_inappropriate(protip_public_id,opts).deliver
cookies["report_inappropriate-#{protip_public_id}"] = true
render json: {flagged: true}
else
render json: {flagged: false}
end
end
# POST /p/:id/flag(.:format)
def flag
times_to_flag = is_moderator? ? Protip::MIN_FLAG_THRESHOLD : 1
times_to_flag.times do
@protip.flag
end
@protip.mark_as_spam
respond_to do |format|
if @protip.save
format.json { head :ok }
else
format.json { render json: @protip.errors, status: :unprocessable_entity }
end
end
end
def unflag
times_to_flag = is_moderator? ? Protip::MIN_FLAG_THRESHOLD : 1
times_to_flag.times do
@protip.unflag
end
respond_to do |format|
if @protip.save
format.json { head :ok }
else
format.json { render json: @protip.errors, status: :unprocessable_entity }
end
end
end
# POST /p/:id/feature(.:format)
def feature
#TODO change with @protip.toggle_featured_state!
if @protip.featured?
@protip.unfeature!
else
@protip.feature!
end
respond_to do |format|
if @protip.save
format.json { head :ok }
else
format.json { render json: @protip.errors, status: :unprocessable_entity }
end
end
end
#POST /p/:id/delete_tag/:topic(.:format) protips#delete_tag {:topic=>/[A-Za-z0-9#\$\+\-_\.(%23)(%24)(%2B)]+/}
def delete_tag
@protip.topic_list.remove(params.permit(:topic))
respond_to do |format|
if @protip.save
format.html { redirect_to protip_path(@protip) }
format.json { head :ok }
else
format.html { redirect_to protip_path(@protip) }
format.json { render json: @protip.errors, status: :unprocessable_entity }
end
end
end
# GET /p/admin(.:format)
def admin
admin_params = params.permit(:page, :per_page)
@query = "created_automagically:false only_link:false reviewed:false created_at:[#{Time.new(2012, 8, 22).strftime('%Y-%m-%dT%H:%M:%S')} TO *] sort:created_at asc"
@protips = Protip.search(@query, [], page: admin_params[:page], per_page: admin_params[:per_page] || 20)
render :topic
end
# GET /p/t/by_tags(.:format)
def by_tags
by_tags_params = params.permit(:page, :per_page)
page = by_tags_params[:page] || 1
per_page = by_tags_params[:per_page] || 100
@tags = ActsAsTaggableOn::Tag.joins('inner join taggings on taggings.tag_id = tags.id').group('tags.id').order('count(tag_id) desc').page(page).per(per_page)
end
# POST /p/preview(.:format)
def preview
preview_params = params.require(:protip).permit(:title, :body)
preview_params.delete(:topic_list) if preview_params[:topic_list].blank?
protip = Protip.new(preview_params)
protip.updated_at = protip.created_at = Time.now
protip.user = current_user
protip.public_id = "xxxxxx"
render partial: 'protip', locals: { protip: protip, mode: 'preview', include_comments: false, job: nil }
end
# POST - GET /p/search(.:format)
def search
search_params = params.permit(:search)
@context = 'search'
query_string, @scope = expand_query(search_params[:search])
facets = top_tags_facet << Protip::Search::Facet.new('suggested-networks', :terms, :networks, [size: 4])
@protips = Protip::Search.new(
Protip,
Protip::Search::Query.new(query_string),
@scope,
Protip::Search::Sort.new(:trending_score),
facets,
search_options
).execute
@suggested_networks = suggested_networks
find_a_job_for(@protips)
render :index
end
private
# Return protips for a user
# If the user is banned, grab protips from their association
# because the tip will have been removed from the search index.
#
# @param [ Hash ] params - Should contain :page and :per_page key/values
def protips_for_user(user,params)
if user.banned? then user.protips.page(params[:page]).per(params[:per_page])
else Protip.search_trending_by_user(user.username, nil, [], params[:page], params[:per_page])
end
end
def expand_query(query_string)
scopes = []
query = query_string.nil? ? '' : query_string.dup
query.scan(/#([\w\.\-#]+)/).flatten.reduce(query) do |query, tag|
query.slice!("##{tag}")
scopes << Protip::Search::Scope.new(:network, tag)
query
end
[query, scopes.blank? ? nil : scopes.reduce(&:<<)]
end
def lookup_protip
@protip = Protip.find_by_public_id!(params[:id])
end
def choose_protip_layout
if [:show, :random, :new, :edit, :create, :update].include?(action_name.to_sym)
'protip'
elsif [:subscribe, :unsubscribe].include?(action_name.to_sym)
false
else
'application'
end
end
def search_options
search_options_params = params.permit(:page, :per_page)
{
page: (signed_in? && search_options_params[:page].try(:to_i)) || 1,
per_page: [(signed_in? && search_options_params[:per_page].try(:to_i)) || 18, Protip::PAGESIZE].min,
load: { include: [:user, :likes, :protip_links, :comments] }
}
end
def reformat_tags
tags = params[:protip].delete(:topics)
params[:protip][:topics] = (tags.is_a?(Array) ? tags : tags.split(",")) unless tags.blank?
end
def tagged_user_or_logged_in
User.where(username: params[:tags]).first || ((params[:tags].nil? and signed_in?) ? current_user : nil)
end
def verify_ownership
lookup_protip
redirect_to(root_url) unless (is_admin? or (@protip && @protip.owned_by?(current_user)))
end
def limit_results
params[:per_page] = Protip::PAGESIZE if params[:per_page].nil? or (params[:per_page].to_i > Protip::PAGESIZE and !is_admin?)
end
def ensure_single_tag
if params[:tags].split("/").size > 1
respond_to do |format|
flash[:error] = "You cannot subscribe to a group of topics"
format.html { render status: :not_implemented }
end
end
end
def parse_query(query_string)
tags = query_string.scan(/tagged:(?:"(.+)"|(\S+))/i).flatten.compact || []
query = query_string.dup
tags.map { |tag| query.slice!("tagged:#{tag}") }
query.gsub!(Protip::USER_SCOPE_REGEX[:author], "author:#{current_user.username}")
query.gsub!(Protip::USER_SCOPE_REGEX[:bookmark], "bookmark:#{current_user.username}")
query.gsub!(/!!/, "author:#{current_user.username} bookmark:#{current_user.username}")
[query, tags]
end
def track_search
record_event('Protip search', query: params[:search])
end
def track_views(protip)
unless is_admin?
viewing_user.track_protip_view!(protip) if viewing_user
end
protip.viewed_by(viewing_user || session_id)
end
def tracking_code
(current_user && current_user.tracking_code) || cookies[:trc]
end
def redirect_to_networks
redirect_to(networks_path)
end
def protip_missing_destination
signed_in? ? root_path : networks_path
end
def determine_scope
@scope = Protip::Search::Scope.new(:user, current_user) if params[:scope] == "following" && signed_in?
end
def private_scope?
params[:scope] == "following"
end
def track_discovery
record_event('Discover', what: @context, scope: @scope)
end
def cached_version(sort, scope, options)
Rails.cache.fetch(['v2', 'protips_by', sort, *options.to_a.flatten], expires_in: 3.minutes) do
protips_sorted_by(sort, scope, options)
end
end
def protips_sorted_by(sort, scope, search_opts)
Protip::Search.new(Protip, nil, scope, Protip::Search::Sort.new(sort), top_tags_facet, search_opts).execute
end
def lookup_user_data
if signed_in? && !request.xhr?
@upvoted_protips_public_ids = Rails.cache.fetch(['v1', 'upvotes', current_user.id], expires_in: 5.minutes) { current_user.upvoted_protips_public_ids }
end
end
def suggested_networks
if @protips.respond_to?(:facets)
@protips.facets['suggested-networks']['terms'].map { |h| h['term'] }
else
#gets top 10 tags for the protips and picks up associated networks
Network.tagged_with(@protips.flat_map(&:tags).reduce(Hash.new(0)) { |h, t| h[t] += 1; h }.sort_by { |k, v| -v }.first(10).flatten.values_at(*(0..20).step(2))).limit(4).pluck(:slug)
end
end
def find_a_job_for(protips)
return Opportunity.random.first unless protips.present?
topics = get_topics_from_protips(protips)
@job = ::Opportunity.based_on(topics).to_a.sample || Opportunity.random.first
end
def top_tags_facet
Protip::Search::Facet.new('top-tags', :terms, :tags, [size: 8])
end
def get_topics_from_protips(protips)
if protips.respond_to?(:results) && !protips.facets.blank?
topics = protips.facets['top-tags']['terms'].map { |h| h['term'] }
end
topics = protips.flat_map(&:tags).uniq.first(8) if topics.blank? && protips.present?
topics
end
def require_skills_first
if current_user.skills.empty?
flash[:error] = "Please improve your profile by adding some skills before posting Pro Tips"
redirect_to badge_path(username: current_user.username, anchor: 'add-skill')
end
end
end