224 lines
6.1 KiB
Ruby
224 lines
6.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Finder for retrieving snippets that a user can see, optionally scoped to a
|
|
# project or snippets author.
|
|
#
|
|
# Basic usage:
|
|
#
|
|
# user = User.find(1)
|
|
#
|
|
# SnippetsFinder.new(user).execute
|
|
#
|
|
# To limit the snippets to a specific project, supply the `project:` option:
|
|
#
|
|
# user = User.find(1)
|
|
# project = Project.find(1)
|
|
#
|
|
# SnippetsFinder.new(user, project: project).execute
|
|
#
|
|
# Limiting snippets to an author can be done by supplying the `author:` option:
|
|
#
|
|
# user = User.find(1)
|
|
# project = Project.find(1)
|
|
#
|
|
# SnippetsFinder.new(user, author: user).execute
|
|
#
|
|
# To filter snippets using a specific visibility level, you can provide the
|
|
# `scope:` option:
|
|
#
|
|
# user = User.find(1)
|
|
# project = Project.find(1)
|
|
#
|
|
# SnippetsFinder.new(user, author: user, scope: :are_public).execute
|
|
#
|
|
# Valid `scope:` values are:
|
|
#
|
|
# * `:are_private`
|
|
# * `:are_internal`
|
|
# * `:are_public`
|
|
#
|
|
# Any other value will be ignored.
|
|
class SnippetsFinder < UnionFinder
|
|
include FinderMethods
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
attr_accessor :current_user, :params
|
|
delegate :explore, :only_personal, :only_project, :scope, :sort, to: :params
|
|
|
|
def initialize(current_user = nil, params = {})
|
|
@current_user = current_user
|
|
@params = OpenStruct.new(params)
|
|
|
|
if project && author
|
|
raise(
|
|
ArgumentError,
|
|
'Filtering by both an author and a project is not supported, ' \
|
|
'as this finder is not optimised for this use case'
|
|
)
|
|
end
|
|
end
|
|
|
|
def execute
|
|
# The snippet query can be expensive, therefore if the
|
|
# author or project params have been passed and they don't
|
|
# exist, or if a Project has been passed and has snippets
|
|
# disabled, it's better to return
|
|
return Snippet.none if author.nil? && params[:author].present?
|
|
return Snippet.none if project.nil? && params[:project].present?
|
|
return Snippet.none if project && !project.feature_available?(:snippets, current_user)
|
|
|
|
items = init_collection
|
|
items = by_ids(items)
|
|
items = items.with_optional_visibility(visibility_from_scope)
|
|
|
|
items.order_by(sort_param)
|
|
end
|
|
|
|
private
|
|
|
|
def init_collection
|
|
if explore
|
|
snippets_for_explore
|
|
elsif only_personal
|
|
personal_snippets
|
|
elsif project
|
|
snippets_for_a_single_project
|
|
else
|
|
snippets_for_personal_and_multiple_projects
|
|
end
|
|
end
|
|
|
|
# Produces a query that retrieves snippets for the Explore page
|
|
#
|
|
# We only show personal snippets here because this page is meant for
|
|
# discovery, and project snippets are of limited interest here.
|
|
def snippets_for_explore
|
|
Snippet.public_to_user(current_user).only_personal_snippets
|
|
end
|
|
|
|
# Produces a query that retrieves snippets from multiple projects.
|
|
#
|
|
# The resulting query will, depending on the user's permissions, include the
|
|
# following collections of snippets:
|
|
#
|
|
# 1. Snippets that don't belong to any project.
|
|
# 2. Snippets of projects that are visible to the current user (e.g. snippets
|
|
# in public projects).
|
|
# 3. Snippets of projects that the current user is a member of.
|
|
#
|
|
# Each collection is constructed in isolation, allowing for greater control
|
|
# over the resulting SQL query.
|
|
def snippets_for_personal_and_multiple_projects
|
|
queries = []
|
|
queries << personal_snippets unless only_project
|
|
|
|
if Ability.allowed?(current_user, :read_cross_project)
|
|
queries << snippets_of_visible_projects
|
|
queries << snippets_of_authorized_projects if current_user
|
|
end
|
|
|
|
prepared_union(queries)
|
|
end
|
|
|
|
def snippets_for_a_single_project
|
|
Snippet.for_project_with_user(project, current_user)
|
|
end
|
|
|
|
def personal_snippets
|
|
snippets_for_author_or_visible_to_user.only_personal_snippets
|
|
end
|
|
|
|
# Returns the snippets that the current user (logged in or not) can view.
|
|
def snippets_of_visible_projects
|
|
snippets_for_author_or_visible_to_user
|
|
.only_include_projects_visible_to(current_user)
|
|
.only_include_projects_with_snippets_enabled
|
|
end
|
|
|
|
# Returns the snippets that the currently logged in user has access to by
|
|
# being a member of the project the snippets belong to.
|
|
#
|
|
# This method requires that `current_user` returns a `User` instead of `nil`,
|
|
# and is optimised for this specific scenario.
|
|
def snippets_of_authorized_projects
|
|
base = author ? author.snippets : Snippet.all
|
|
|
|
base
|
|
.only_include_projects_with_snippets_enabled(include_private: true)
|
|
.only_include_authorized_projects(current_user)
|
|
end
|
|
|
|
def snippets_for_author_or_visible_to_user
|
|
if author
|
|
snippets_for_author
|
|
elsif current_user
|
|
Snippet.visible_to_or_authored_by(current_user)
|
|
else
|
|
Snippet.public_to_user
|
|
end
|
|
end
|
|
|
|
def snippets_for_author
|
|
base = author.snippets
|
|
|
|
if author == current_user
|
|
# If the current user is also the author of all snippets, then we can
|
|
# include private snippets.
|
|
base
|
|
else
|
|
base.public_to_user(current_user)
|
|
end
|
|
end
|
|
|
|
def visibility_from_scope
|
|
case scope.to_s
|
|
when 'are_private'
|
|
Snippet::PRIVATE
|
|
when 'are_internal'
|
|
Snippet::INTERNAL
|
|
when 'are_public'
|
|
Snippet::PUBLIC
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def by_ids(items)
|
|
return items unless params[:ids].present?
|
|
|
|
items.id_in(params[:ids])
|
|
end
|
|
|
|
def author
|
|
strong_memoize(:author) do
|
|
next unless params[:author].present?
|
|
|
|
params[:author].is_a?(User) ? params[:author] : User.find_by_id(params[:author])
|
|
end
|
|
end
|
|
|
|
def project
|
|
strong_memoize(:project) do
|
|
next unless params[:project].present?
|
|
|
|
params[:project].is_a?(Project) ? params[:project] : Project.find_by_id(params[:project])
|
|
end
|
|
end
|
|
|
|
def sort_param
|
|
sort.presence || 'id_desc'
|
|
end
|
|
|
|
def prepared_union(queries)
|
|
return Snippet.none if queries.empty?
|
|
return queries.first if queries.length == 1
|
|
|
|
# The queries are going to be part of a global `where`
|
|
# therefore we only need to retrieve the `id` column
|
|
# which will speed the query
|
|
queries.map! { |rel| rel.select(:id) }
|
|
Snippet.id_in(find_union(queries, Snippet))
|
|
end
|
|
end
|
|
|
|
SnippetsFinder.prepend_if_ee('EE::SnippetsFinder')
|