Commit 2a9401dd authored by Sean McGivern's avatar Sean McGivern

Use DSL style for feature categories in users API

This isn't possible for custom attributes, as they're a mixin, but
everywhere else can use the DSL style.

This also means a slight tweak to the validation in WithFeatureCategory:
previously we didn't allow duplicates at all, but now we allow
duplicates with the same category. That's because the API supports
different HTTP methods to the same path, but those methods will always
be the same category. Omitting the feature category from the duplicates
would look odd.
parent 753a3da0
...@@ -8,39 +8,7 @@ module API ...@@ -8,39 +8,7 @@ module API
allow_access_with_scope :read_user, if: -> (request) { request.get? } allow_access_with_scope :read_user, if: -> (request) { request.get? }
feature_category :users, [ feature_category :users, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key']
'/users/:id/custom_attributes',
'/users/:id/custom_attributes/:key',
'/users/:id/emails',
'/users/:id/emails/:email_id',
'/users/:user_id/memberships',
'/user',
'/user/emails',
'/user/emails/:email_id',
'/user/activities',
'/user/status'
]
feature_category :authentication_and_authorization, [
'/users/:id/identities/:provider',
'/users/:id/keys',
'/users/:user_id/keys',
'/users/:id/keys/:key_id',
'/users/:id/gpg_keys',
'/users/:id/gpg_keys/:key_id',
'/users/:id/gpg_keys/:key_id/revoke',
'/users/:id/activate',
'/users/:id/deactivate',
'/users/:id/block',
'/users/:id/unblock',
'/users/:user_id/impersonation_tokens',
'/users/:user_id/impersonation_tokens/:impersonation_token_id',
'/user/keys',
'/user/keys/:key_id',
'/user/gpg_keys',
'/user/gpg_keys/:key_id',
'/user/gpg_keys/:key_id/revoke'
]
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
include CustomAttributesEndpoints include CustomAttributesEndpoints
...@@ -204,7 +172,7 @@ module API ...@@ -204,7 +172,7 @@ module API
optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set' optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set'
use :optional_attributes use :optional_attributes
end end
post do post feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
params = declared_params(include_missing: false) params = declared_params(include_missing: false)
...@@ -238,7 +206,7 @@ module API ...@@ -238,7 +206,7 @@ module API
use :optional_attributes use :optional_attributes
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
put ":id" do put ":id", feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params.delete(:id)) user = User.find_by(id: params.delete(:id))
...@@ -279,7 +247,7 @@ module API ...@@ -279,7 +247,7 @@ module API
requires :provider, type: String, desc: 'The external provider' requires :provider, type: String, desc: 'The external provider'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ":id/identities/:provider" do delete ":id/identities/:provider", feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
...@@ -302,7 +270,7 @@ module API ...@@ -302,7 +270,7 @@ module API
optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ":id/keys" do post ":id/keys", feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params.delete(:id)) user = User.find_by(id: params.delete(:id))
...@@ -325,7 +293,7 @@ module API ...@@ -325,7 +293,7 @@ module API
requires :user_id, type: String, desc: 'The ID or username of the user' requires :user_id, type: String, desc: 'The ID or username of the user'
use :pagination use :pagination
end end
get ':user_id/keys', requirements: API::USER_REQUIREMENTS do get ':user_id/keys', requirements: API::USER_REQUIREMENTS, feature_category: :authentication_and_authorization do
user = find_user(params[:user_id]) user = find_user(params[:user_id])
not_found!('User') unless user && can?(current_user, :read_user, user) not_found!('User') unless user && can?(current_user, :read_user, user)
...@@ -341,7 +309,7 @@ module API ...@@ -341,7 +309,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key' requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ':id/keys/:key_id' do delete ':id/keys/:key_id', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
...@@ -366,7 +334,7 @@ module API ...@@ -366,7 +334,7 @@ module API
requires :key, type: String, desc: 'The new GPG key' requires :key, type: String, desc: 'The new GPG key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ':id/gpg_keys' do post ':id/gpg_keys', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params.delete(:id)) user = User.find_by(id: params.delete(:id))
...@@ -391,7 +359,7 @@ module API ...@@ -391,7 +359,7 @@ module API
use :pagination use :pagination
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ':id/gpg_keys' do get ':id/gpg_keys', feature_category: :authentication_and_authorization do
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -408,7 +376,7 @@ module API ...@@ -408,7 +376,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key' requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ':id/gpg_keys/:key_id' do get ':id/gpg_keys/:key_id', feature_category: :authentication_and_authorization do
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -427,7 +395,7 @@ module API ...@@ -427,7 +395,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key' requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ':id/gpg_keys/:key_id' do delete ':id/gpg_keys/:key_id', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
...@@ -451,7 +419,7 @@ module API ...@@ -451,7 +419,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key' requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ':id/gpg_keys/:key_id/revoke' do post ':id/gpg_keys/:key_id/revoke', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
...@@ -474,7 +442,7 @@ module API ...@@ -474,7 +442,7 @@ module API
optional :skip_confirmation, type: Boolean, desc: 'Skip confirmation of email and assume it is verified' optional :skip_confirmation, type: Boolean, desc: 'Skip confirmation of email and assume it is verified'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ":id/emails" do post ":id/emails", feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params.delete(:id)) user = User.find_by(id: params.delete(:id))
...@@ -498,7 +466,7 @@ module API ...@@ -498,7 +466,7 @@ module API
use :pagination use :pagination
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ':id/emails' do get ':id/emails', feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -515,7 +483,7 @@ module API ...@@ -515,7 +483,7 @@ module API
requires :email_id, type: Integer, desc: 'The ID of the email' requires :email_id, type: Integer, desc: 'The ID of the email'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ':id/emails/:email_id' do delete ':id/emails/:email_id', feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -537,7 +505,7 @@ module API ...@@ -537,7 +505,7 @@ module API
optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions" optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ":id" do delete ":id", feature_category: :users do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/20757') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/20757')
authenticated_as_admin! authenticated_as_admin!
...@@ -557,7 +525,7 @@ module API ...@@ -557,7 +525,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user' requires :id, type: Integer, desc: 'The ID of the user'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ':id/activate' do post ':id/activate', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
...@@ -572,7 +540,7 @@ module API ...@@ -572,7 +540,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user' requires :id, type: Integer, desc: 'The ID of the user'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ':id/deactivate' do post ':id/deactivate', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -598,7 +566,7 @@ module API ...@@ -598,7 +566,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user' requires :id, type: Integer, desc: 'The ID of the user'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ':id/block' do post ':id/block', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -623,7 +591,7 @@ module API ...@@ -623,7 +591,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user' requires :id, type: Integer, desc: 'The ID of the user'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ':id/unblock' do post ':id/unblock', feature_category: :authentication_and_authorization do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
...@@ -646,7 +614,7 @@ module API ...@@ -646,7 +614,7 @@ module API
optional :type, type: String, values: %w[Project Namespace] optional :type, type: String, values: %w[Project Namespace]
use :pagination use :pagination
end end
get ":user_id/memberships" do get ":user_id/memberships", feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
user = find_user_by_id(params) user = find_user_by_id(params)
...@@ -690,7 +658,9 @@ module API ...@@ -690,7 +658,9 @@ module API
use :pagination use :pagination
optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens' optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens'
end end
get { present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken } get feature_category :authentication_and_authorization do
present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken
end
desc 'Create a impersonation token. Available only for admins.' do desc 'Create a impersonation token. Available only for admins.' do
detail 'This feature was introduced in GitLab 9.0' detail 'This feature was introduced in GitLab 9.0'
...@@ -701,7 +671,7 @@ module API ...@@ -701,7 +671,7 @@ module API
optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the impersonation token' optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the impersonation token'
optional :scopes, type: Array, desc: 'The array of scopes of the impersonation token' optional :scopes, type: Array, desc: 'The array of scopes of the impersonation token'
end end
post do post feature_category: :authentication_and_authorization do
impersonation_token = finder.build(declared_params(include_missing: false)) impersonation_token = finder.build(declared_params(include_missing: false))
if impersonation_token.save if impersonation_token.save
...@@ -718,7 +688,7 @@ module API ...@@ -718,7 +688,7 @@ module API
params do params do
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token' requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end end
get ':impersonation_token_id' do get ':impersonation_token_id', feature_category: :authentication_and_authorization do
present find_impersonation_token, with: Entities::ImpersonationToken present find_impersonation_token, with: Entities::ImpersonationToken
end end
...@@ -728,7 +698,7 @@ module API ...@@ -728,7 +698,7 @@ module API
params do params do
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token' requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end end
delete ':impersonation_token_id' do delete ':impersonation_token_id', feature_category: :authentication_and_authorization do
token = find_impersonation_token token = find_impersonation_token
destroy_conditionally!(token) do destroy_conditionally!(token) do
...@@ -750,7 +720,7 @@ module API ...@@ -750,7 +720,7 @@ module API
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
success Entities::UserPublic success Entities::UserPublic
end end
get do get feature_category: :users do
entity = entity =
if current_user.admin? if current_user.admin?
Entities::UserWithAdmin Entities::UserWithAdmin
...@@ -768,7 +738,7 @@ module API ...@@ -768,7 +738,7 @@ module API
params do params do
use :pagination use :pagination
end end
get "keys" do get "keys", feature_category: :authentication_and_authorization do
keys = current_user.keys.preload_users keys = current_user.keys.preload_users
present paginate(keys), with: Entities::SSHKey present paginate(keys), with: Entities::SSHKey
...@@ -781,7 +751,7 @@ module API ...@@ -781,7 +751,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key' requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get "keys/:key_id" do get "keys/:key_id", feature_category: :authentication_and_authorization do
key = current_user.keys.find_by(id: params[:key_id]) key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key not_found!('Key') unless key
...@@ -797,7 +767,7 @@ module API ...@@ -797,7 +767,7 @@ module API
requires :title, type: String, desc: 'The title of the new SSH key' requires :title, type: String, desc: 'The title of the new SSH key'
optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'
end end
post "keys" do post "keys", feature_category: :authentication_and_authorization do
key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false)).execute key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false)).execute
if key.persisted? if key.persisted?
...@@ -814,7 +784,7 @@ module API ...@@ -814,7 +784,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key' requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete "keys/:key_id" do delete "keys/:key_id", feature_category: :authentication_and_authorization do
key = current_user.keys.find_by(id: params[:key_id]) key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key not_found!('Key') unless key
...@@ -832,7 +802,7 @@ module API ...@@ -832,7 +802,7 @@ module API
params do params do
use :pagination use :pagination
end end
get 'gpg_keys' do get 'gpg_keys', feature_category: :authentication_and_authorization do
present paginate(current_user.gpg_keys), with: Entities::GpgKey present paginate(current_user.gpg_keys), with: Entities::GpgKey
end end
...@@ -844,7 +814,7 @@ module API ...@@ -844,7 +814,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key' requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get 'gpg_keys/:key_id' do get 'gpg_keys/:key_id', feature_category: :authentication_and_authorization do
key = current_user.gpg_keys.find_by(id: params[:key_id]) key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key not_found!('GPG Key') unless key
...@@ -859,7 +829,7 @@ module API ...@@ -859,7 +829,7 @@ module API
params do params do
requires :key, type: String, desc: 'The new GPG key' requires :key, type: String, desc: 'The new GPG key'
end end
post 'gpg_keys' do post 'gpg_keys', feature_category: :authentication_and_authorization do
key = ::GpgKeys::CreateService.new(current_user, declared_params(include_missing: false)).execute key = ::GpgKeys::CreateService.new(current_user, declared_params(include_missing: false)).execute
if key.persisted? if key.persisted?
...@@ -876,7 +846,7 @@ module API ...@@ -876,7 +846,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the GPG key' requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post 'gpg_keys/:key_id/revoke' do post 'gpg_keys/:key_id/revoke', feature_category: :authentication_and_authorization do
key = current_user.gpg_keys.find_by(id: params[:key_id]) key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key not_found!('GPG Key') unless key
...@@ -892,7 +862,7 @@ module API ...@@ -892,7 +862,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the SSH key' requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete 'gpg_keys/:key_id' do delete 'gpg_keys/:key_id', feature_category: :authentication_and_authorization do
key = current_user.gpg_keys.find_by(id: params[:key_id]) key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key not_found!('GPG Key') unless key
...@@ -909,7 +879,7 @@ module API ...@@ -909,7 +879,7 @@ module API
params do params do
use :pagination use :pagination
end end
get "emails" do get "emails", feature_category: :users do
present paginate(current_user.emails), with: Entities::Email present paginate(current_user.emails), with: Entities::Email
end end
...@@ -920,7 +890,7 @@ module API ...@@ -920,7 +890,7 @@ module API
requires :email_id, type: Integer, desc: 'The ID of the email' requires :email_id, type: Integer, desc: 'The ID of the email'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get "emails/:email_id" do get "emails/:email_id", feature_category: :users do
email = current_user.emails.find_by(id: params[:email_id]) email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email not_found!('Email') unless email
...@@ -934,7 +904,7 @@ module API ...@@ -934,7 +904,7 @@ module API
params do params do
requires :email, type: String, desc: 'The new email' requires :email, type: String, desc: 'The new email'
end end
post "emails" do post "emails", feature_category: :users do
email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute
if email.errors.blank? if email.errors.blank?
...@@ -949,7 +919,7 @@ module API ...@@ -949,7 +919,7 @@ module API
requires :email_id, type: Integer, desc: 'The ID of the email' requires :email_id, type: Integer, desc: 'The ID of the email'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete "emails/:email_id" do delete "emails/:email_id", feature_category: :users do
email = current_user.emails.find_by(id: params[:email_id]) email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email not_found!('Email') unless email
...@@ -965,7 +935,7 @@ module API ...@@ -965,7 +935,7 @@ module API
use :pagination use :pagination
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get "activities" do get "activities", feature_category: :users do
authenticated_as_admin! authenticated_as_admin!
activities = User activities = User
...@@ -983,7 +953,7 @@ module API ...@@ -983,7 +953,7 @@ module API
optional :emoji, type: String, desc: "The emoji to set on the status" optional :emoji, type: String, desc: "The emoji to set on the status"
optional :message, type: String, desc: "The status message to set" optional :message, type: String, desc: "The status message to set"
end end
put "status" do put "status", feature_category: :users do
forbidden! unless can?(current_user, :update_user_status, current_user) forbidden! unless can?(current_user, :update_user_status, current_user)
if ::Users::SetStatusService.new(current_user, declared_params).execute if ::Users::SetStatusService.new(current_user, declared_params).execute
...@@ -996,7 +966,7 @@ module API ...@@ -996,7 +966,7 @@ module API
desc 'get the status of the current user' do desc 'get the status of the current user' do
success Entities::UserStatus success Entities::UserStatus
end end
get 'status' do get 'status', feature_category: :users do
present current_user.status || {}, with: Entities::UserStatus present current_user.status || {}, with: Entities::UserStatus
end end
end end
......
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
def validate_config!(config) def validate_config!(config)
empty = config.find { |_, actions| actions.empty? } empty = config.find { |_, actions| actions.empty? }
duplicate_actions = config.values.flatten.group_by(&:itself).select { |_, v| v.count > 1 }.keys duplicate_actions = config.values.map(&:uniq).flatten.group_by(&:itself).select { |_, v| v.count > 1 }.keys
if config.length > 1 && empty if config.length > 1 && empty
raise ArgumentError, "#{empty.first} is defined for all actions, but other categories are set" raise ArgumentError, "#{empty.first} is defined for all actions, but other categories are set"
......
...@@ -56,5 +56,14 @@ RSpec.describe Gitlab::WithFeatureCategory do ...@@ -56,5 +56,14 @@ RSpec.describe Gitlab::WithFeatureCategory do
end end
end.to raise_error(ArgumentError, "Actions have multiple feature categories: world") end.to raise_error(ArgumentError, "Actions have multiple feature categories: world")
end end
it "does not raise an error when multiple calls define the same action and feature category" do
expect do
Class.new(base_controller) do
feature_category :hello, [:world]
feature_category :hello, ["world"]
end
end.not_to raise_error
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment