docbrown/app/controllers/articles_controller.rb
Lisa Sy 54c071856e
[deploy] Update top-header for logged-out user (#9874)
* Create first start at desktop top header updates

* Update mobile styling of top header

* Stylize sign in widget card

* Update nav menu and top bar

* Changes based on user being logged-in or not

* Uses user_signed_in? on top nav bar

* Fix lingering old cloudinary helper method

* Trigger CI

* Fixes specs

* Adds redirect_using_referer_spec, fixes other specs & cleanup

* Adds nav-menu.scss to layouts/_styles.html.erb for inline & small edit in referer check

* Remove logged-out styles to make it more uniform with logged in

* Remove nav-menu.scss file

* cleanup markup and JS a little

* Fixes FB auth specs to new login links

* Makes sure unauthenticated /new redirects back to editor

* CI fix Sing in with -> Continue with

* Update db/schema.rb

* Update db/schema.rb

* Update db/schema.rb

Co-authored-by: Fernando Valverde <fdov88@gmail.com>
Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
2020-08-31 18:12:22 -04:00

314 lines
11 KiB
Ruby

class ArticlesController < ApplicationController
include ApplicationHelper
before_action :authenticate_user!, except: %i[feed new]
before_action :set_article, only: %i[edit manage update destroy stats]
before_action :raise_suspended, only: %i[new create update]
before_action :set_cache_control_headers, only: %i[feed]
after_action :verify_authorized
FEED_ALLOWED_TAGS = %w[
a b blockquote br center cite code col colgroup dd del div dl dt em em h1 h2
h3 h4 h5 h6 i iframe img li ol p pre q small span strong sup table tbody td
tfoot th thead time tr u ul
].freeze
FEED_ALLOWED_ATTRIBUTES = %w[
alt class colspan data-conversation data-lang em height href id ref rel
rowspan size span src start strong title value width
].freeze
RESTRICTED_LIQUID_TAGS = [UserSubscriptionTag].freeze
def feed
skip_authorization
@articles = Article.feed.order(published_at: :desc).page(params[:page].to_i).per(12)
@articles = if params[:username]
handle_user_or_organization_feed
elsif params[:tag]
handle_tag_feed
else
@articles.where(featured: true).includes(:user)
end
unless @articles&.any?
not_found
end
set_surrogate_key_header "feed"
set_cache_control_headers(10.minutes.to_i, stale_while_revalidate: 30, stale_if_error: 1.day.to_i)
@allowed_tags = FEED_ALLOWED_TAGS
@allowed_attributes = FEED_ALLOWED_ATTRIBUTES
render layout: false, locals: {
articles: @articles,
user: @user,
tag: @tag,
allowed_tags: FEED_ALLOWED_TAGS,
allowed_attributes: FEED_ALLOWED_ATTRIBUTES
}
end
def new
base_editor_assigments
@article, needs_authorization = Articles::Builder.call(@user, @tag, @prefill)
if needs_authorization
authorize(Article)
else
skip_authorization
store_location_for(:user, request.path)
end
end
def edit
authorize @article
@version = @article.has_frontmatter? ? "v1" : "v2"
@user = @article.user
@organizations = @user&.organizations
set_user_approved_liquid_tags
end
def manage
authorize @article
@article = @article.decorate
@user = @article.user
@rating_vote = RatingVote.where(article_id: @article.id, user_id: @user.id).first
@buffer_updates = BufferUpdate.where(composer_user_id: @user.id, article_id: @article.id)
@organizations = @user&.organizations
# TODO: fix this for multi orgs
@org_members = @organization.users.pluck(:name, :id) if @organization
end
def preview
authorize Article
begin
fixed_body_markdown = MarkdownFixer.fix_for_preview(params[:article_body])
parsed = FrontMatterParser::Parser.new(:md).call(fixed_body_markdown)
parsed_markdown = MarkdownParser.new(parsed.content, source: Article.new, user: current_user)
processed_html = parsed_markdown.finalize
rescue StandardError => e
@article = Article.new(body_markdown: params[:article_body])
@article.errors[:base] << ErrorMessageCleaner.new(e.message).clean
end
respond_to do |format|
if @article
format.json { render json: @article.errors, status: :unprocessable_entity }
else
format.json do
render json: {
processed_html: processed_html,
title: parsed["title"],
tags: (Article.new.tag_list.add(parsed["tags"], parser: ActsAsTaggableOn::TagParser) if parsed["tags"]),
cover_image: (ApplicationController.helpers.cloud_cover_url(parsed["cover_image"]) if parsed["cover_image"])
}
end
end
end
end
def create
authorize Article
@user = current_user
article = Articles::Creator.call(@user, article_params_json)
render json: if article.persisted?
{ id: article.id, current_state_path: article.decorate.current_state_path }.to_json
else
article.errors.to_json
end
end
def update
authorize @article
@user = @article.user || current_user
not_found if @article.user_id != @user.id && !@user.has_role?(:super_admin)
edited_at_date = if @article.user == current_user && @article.published
Time.current
else
@article.edited_at
end
updated = @article.update(article_params_json.merge(edited_at: edited_at_date))
handle_notifications(updated)
Webhook::DispatchEvent.call("article_updated", @article) if updated
respond_to do |format|
format.html do
# TODO: JSON should probably not be returned in the format.html section
if article_params_json[:archived] && @article.archived # just to get archived working
render json: @article.to_json(only: [:id], methods: [:current_state_path])
return
end
if params[:destination]
redirect_to(URI.parse(params[:destination]).path)
return
end
if params[:article][:video_thumbnail_url]
redirect_to("#{@article.path}/edit")
return
end
render json: { status: 200 }
end
format.json do
render json: if updated
@article.to_json(only: [:id], methods: [:current_state_path])
else
@article.errors.to_json
end
end
end
end
def delete_confirm
@article = current_user.articles.find_by(slug: params[:slug])
not_found unless @article
authorize @article
end
def destroy
authorize @article
Articles::Destroyer.call(@article)
respond_to do |format|
format.html { redirect_to "/dashboard", notice: "Article was successfully deleted." }
format.json { head :no_content }
end
end
def stats
authorize current_user, :pro_user?
authorize @article
@organization_id = @article.organization_id
end
private
def base_editor_assigments
@user = current_user
@version = @user.editor_version if @user
@organizations = @user&.organizations
@tag = Tag.find_by(name: params[:template])
@prefill = params[:prefill].to_s.gsub("\\n ", "\n").gsub("\\n", "\n")
set_user_approved_liquid_tags
end
def set_user_approved_liquid_tags
@user_approved_liquid_tags =
if @user
RESTRICTED_LIQUID_TAGS.filter_map do |liquid_tag|
liquid_tag if liquid_tag::VALID_ROLES.any? { |role| @user.has_role?(*Array(role)) }
end
else
[]
end
end
def handle_user_or_organization_feed
if (@user = User.find_by(username: params[:username]))
Honeycomb.add_field("articles_route", "user")
@articles = @articles.where(user_id: @user.id)
elsif (@user = Organization.find_by(slug: params[:username]))
Honeycomb.add_field("articles_route", "org")
@articles = @articles.where(organization_id: @user.id).includes(:user)
end
end
def handle_tag_feed
@tag = Tag.aliased_name(params[:tag])
return unless @tag
@articles = @articles.cached_tagged_with(@tag)
end
def set_article
owner = User.find_by(username: params[:username]) || Organization.find_by(slug: params[:username])
found_article = if params[:slug] && owner
owner.articles.find_by(slug: params[:slug])
else
Article.includes(:user).find(params[:id])
end
@article = found_article || not_found
Honeycomb.add_field("article_id", @article.id)
end
def article_params
params[:article][:published] = true if params[:submit_button] == "PUBLISH"
modified_params = policy(Article).permitted_attributes
modified_params << :user_id if org_admin_user_change_privilege
modified_params << :comment_template if current_user.has_role?(:admin)
params.require(:article).permit(modified_params)
end
# TODO: refactor all of this update logic into the Articles::Updater possibly,
# ideally there should only be one place to handle the update logic
def article_params_json
params.require(:article) # to trigger the correct exception in case `:article` is missing
params["article"].transform_keys!(&:underscore)
# handle series/collections
if params["article"]["series"].present?
collection = Collection.find_series(params["article"]["series"], @user)
params["article"]["collection_id"] = collection.id
elsif params["article"]["series"] == "" # reset collection?
params["article"]["collection_id"] = nil
end
allowed_params = if params["article"]["version"] == "v1"
%i[body_markdown]
else
%i[
title body_markdown main_image published description video_thumbnail_url
tag_list canonical_url series collection_id archived
]
end
# NOTE: the organization logic is still a little counter intuitive but this should
# fix the bug <https://github.com/thepracticaldev/dev.to/issues/2871>
if params["article"]["user_id"] && org_admin_user_change_privilege
allowed_params << :user_id
elsif params["article"]["organization_id"] && allowed_to_change_org_id?
# change the organization of the article only if explicitly asked to do so
allowed_params << :organization_id
end
params.require(:article).permit(allowed_params)
end
def handle_notifications(updated)
if updated && @article.published && @article.saved_changes["published"] == [false, true]
Notification.send_to_followers(@article, "Published")
elsif @article.saved_changes["published"] == [true, false]
Notification.remove_all_by_action_without_delay(notifiable_ids: @article.id, notifiable_type: "Article",
action: "Published")
if @article.comments.exists?
Notification.remove_all(notifiable_ids: @article.comments.ids,
notifiable_type: "Comment")
end
end
end
def allowed_to_change_org_id?
potential_user = @article&.user || current_user
potential_org_id = params["article"]["organization_id"].presence || @article&.organization_id
OrganizationMembership.exists?(user: potential_user, organization_id: potential_org_id) ||
current_user.any_admin?
end
def org_admin_user_change_privilege
params[:article][:user_id] &&
# if current_user is an org admin of the article's org
current_user.org_admin?(@article.organization_id) &&
# and if the author being changed to belongs to the article's org
OrganizationMembership.exists?(user_id: params[:article][:user_id], organization_id: @article.organization_id)
end
end