Initial commit

This commit is contained in:
Yaser
2026-08-13 19:56:46 +03:30
commit de1d57a67b
474 changed files with 43185 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
# frozen_string_literal: true
module API::V2
module Management
class APIKeys < Grape::API
resource :api_keys do
desc 'Create an api key for service account' do
@settings[:scope] = :write_apikeys
success API::V2::Entities::APIKey
end
params do
requires :algorithm,
type: String,
allow_blank: false,
desc: 'API key algorithm'
requires :uid,
type: String,
allow_blank: false,
desc: 'User UID or Service Account UID'
optional :scopes,
type: String,
allow_blank: false,
desc: 'Comma separated scopes'
end
post do
if params[:uid].start_with?(Barong::App.config.uid_prefix)
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_user
key_holder = User.find_by(uid: params[:uid])
error!({ error: 'user_doesnt_exist' }, 422) unless key_holder
elsif params[:uid].start_with?(ServiceAccount::UID_PREFIX)
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_sa
key_holder = ServiceAccount.find_by(uid: params[:uid])
error!({ error: 'service_account_doesnt_exist' }, 422) unless key_holder
else
error!({ error: 'uid_prefix_doesnt_exist'}, 422)
end
declared_params = declared(params, include_missing: false)
.except(:uid, :sa_uid, :scopes)
.merge(scope: params[:scopes]&.split(','))
.merge(secret: SecureRandom.hex(16))
api_key = key_holder.api_keys.new(declared_params)
APIKey.transaction do
raise ActiveRecord::Rollback unless api_key.save
rescue Vault::VaultError
api_key.errors.add(:api_key, 'could_not_save_secret')
raise ActiveRecord::Rollback
end
code_error!(api_key.errors.details, 422) if api_key.errors.any?
present api_key, with: API::V2::Entities::APIKey
end
desc 'Updates an api key for service account' do
@settings[:scope] = :write_apikeys
success API::V2::Entities::APIKey
end
params do
requires :kid,
type: String,
allow_blank: false,
desc: 'API key kid'
requires :uid,
type: String,
allow_blank: false,
desc: 'Service Account UID'
optional :scopes,
type: String,
allow_blank: false,
desc: 'Comma separated scopes'
optional :state,
type: String,
allow_blank: false,
desc: 'State of API Key. "active" state means key is active and can be used for auth'
end
post '/update' do
if params[:uid].start_with?(Barong::App.config.uid_prefix)
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_user
key_holder = User.find_by(uid: params[:uid])
error!({ error: 'user_doesnt_exist' }, 422) unless key_holder
elsif params[:uid].start_with?(ServiceAccount::UID_PREFIX)
error!({ error: 'disabled_management_endpoint' }, 422) unless Barong::App.config.mgn_api_keys_sa
key_holder = ServiceAccount.find_by(uid: params[:uid])
error!({ error: 'service_account_doesnt_exist' }, 422) unless key_holder
else
error!({ error: 'uid_prefix_doesnt_exist'}, 422)
end
declared_params = declared(params, include_missing: false)
.except(:uid, :scopes)
.merge(scope: params[:scopes]&.split(','))
api_key = key_holder.api_keys.find_by(kid: params[:kid])
error!({ error: 'api_key_doesnt_exist' }, 422) unless api_key
code_error!(api_key.errors.details, 422) unless api_key.update(declared_params)
present api_key, with: API::V2::Entities::APIKey, except: [:secret]
end
end
end
end
end

View File

@@ -0,0 +1,58 @@
module API::V2
module Management
class Base < Grape::API
PREFIX = '/management'
do_not_route_options!
rescue_from(API::V2::Management::Exceptions::Base) { |e| error!(e.message, e.status, e.headers) }
rescue_from(Grape::Exceptions::ValidationErrors) { |e| error!(e.message, 422) }
rescue_from(ActiveRecord::RecordNotFound) { error!('Record is not found', 404) }
# Known Vault Error from TOTPService.with_human_error
rescue_from(TOTPService::Error) do |error|
error!(error.message, 422)
end
use API::V2::Management::JWTAuthenticationMiddleware
mount API::V2::Management::Labels
mount API::V2::Management::Users
mount API::V2::Management::Profiles
mount API::V2::Management::Phones
mount API::V2::Management::Tools
mount API::V2::Management::Otp
mount API::V2::Management::Documents
mount API::V2::Management::ServiceAccounts
mount API::V2::Management::APIKeys
add_swagger_documentation base_path: File.join(API::Base::PREFIX, API::V2::Base::API_VERSION, 'barong', PREFIX),
info: {
title: 'Barong',
description: 'Management API for barong OAuth server'
},
mount_path: '/swagger',
security_definitions: {
'SecurityScope': {
description: 'JWT should have signature keychains',
type: 'basic',
name: 'Authorization'
}
},
models: [
API::V2::Entities::Label,
API::V2::Entities::APIKey,
API::V2::Entities::UserWithFullInfo,
API::V2::Entities::User,
API::V2::Management::Entities::Profile,
API::V2::Management::Entities::Phone,
API::V2::Management::Entities::Document,
API::V2::Management::Entities::UserWithProfile,
API::V2::Management::Entities::UserWithKYC,
API::V2::Management::Entities::APIKey,
],
api_version: API::V2::Base::API_VERSION,
doc_version: Barong::Application::GIT_TAG,
add_base_path: true
end
end
end

View File

@@ -0,0 +1,70 @@
# frozen_string_literal: true
module API::V2
module Management
# Documents server-to-server API
class Documents < Grape::API
desc 'Documents related routes'
resource :documents do
helpers do
def parse_file_data(upload, name, ext)
decoded_file = Base64.strict_decode64(upload)
file = Tempfile.new([name, ext])
file.binmode
file.write decoded_file
return file
end
end
desc 'Push documents to barong DB' do
@settings[:scope] = :write_documents
end
params do
requires :uid, type: String, allow_blank: false, desc: 'User uid'
requires :doc_type,
type: String,
allow_blank: false,
desc: 'Document type'
requires :doc_number,
type: String,
allow_blank: false,
desc: 'Document number'
requires :filename,
type: String,
allow_blank: false,
desc: 'Document name'
requires :file_ext,
type: String,
allow_blank: false,
desc: 'Document file extension'
requires :upload,
type: String,
desc: 'Base64 encoded document'
optional :doc_expire,
type: { value: Date, message: 'management.documents.expire_not_a_date' },
allow_blank: true,
desc: 'Document expiration date'
optional :update_labels,
type: { value: Boolean, message: 'management.documents.update_labels_invalid' },
default: true,
desc: 'If set to false, user label will not be created/updated'
optional :metadata,
type: String,
desc: 'Any additional key: value pairs in json string format'
end
post do
user = User.find_by(uid: params[:uid])
error!(errors: ['user doesnt exist']) unless user
file = parse_file_data(params[:upload], params[:filename], params[:file_ext])
doc = user.documents.new(declared(params).except(:upload, :uid, :filename, :file_ext).merge(upload: file))
error!(doc.errors.full_messages.to_sentence, 422) unless doc.save
status 201
end
end
end
end
end

View File

@@ -0,0 +1,12 @@
# frozen_string_literal: true
module API::V2::Management
module Entities
class Document < API::V2::Entities::Document
expose :doc_number,
documentation: {
type: 'String', desc: 'Document number: AB123123 type'
}
end
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module API::V2::Management
module Entities
class Phone < API::V2::Entities::Phone
expose :number,
documentation: {
type: 'String',
desc: 'Phone Number'
}
end
end
end

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
module API::V2::Management
module Entities
class Profile < API::V2::Entities::Profile
expose :last_name,
documentation: {
type: 'String',
desc: 'Last name'
}
expose :dob,
documentation: {
type: 'Date',
desc: 'Birth date'
}
end
end
end

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
module API::V2::Management
module Entities
class UserWithKYC < API::V2::Entities::UserWithKYC
expose :profiles, using: Entities::Profile
expose :phones, using: Entities::Phone
expose :documents, using: Entities::Document
end
end
end

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
module API::V2::Management
module Entities
class UserWithProfile < API::V2::Entities::UserWithProfile
expose :profiles, using: Entities::Profile
end
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module API::V2
module Management
module Exceptions
class Authentication < Base
def status
@options.fetch(:status, 401)
end
end
end
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
module API::V2
module Management
module Exceptions
class Base < StandardError
def initialize(message:, **options)
@options = options
super(message)
end
def headers
@options.fetch(:headers, {})
end
def status
@options.fetch(:status)
end
end
end
end
end

View File

@@ -0,0 +1,93 @@
# frozen_string_literal: true
require 'stringio'
module API::V2
module Management
class JWTAuthenticationMiddleware < Grape::Middleware::Base
extend Memoist
mattr_accessor :security_configuration
def before
return if request.path == '/api/v2/management/swagger'
check_request_method!
check_query_parameters!
check_content_type!
payload = check_jwt!(jwt)
env['rack.input'] = StringIO.new(payload.fetch(:data, {}).to_json)
end
private
def request
Grape::Request.new(env)
end
memoize :request
def jwt
JSON.parse(request.body.read)
rescue StandardError => e
raise Exceptions::Authentication, \
message: 'Couldn\'t parse JWT.',
debug_message: e.inspect,
status: 400
end
memoize :jwt
def check_request_method!
return if request.post? || request.put?
raise Exceptions::Authentication, \
message: 'Only POST and PUT verbs are allowed.',
status: 405
end
def check_query_parameters!
return if request.GET.empty?
raise Exceptions::Authentication, \
message: 'Query parameters are not allowed.',
status: 400
end
def check_content_type!
return if request.content_type == 'application/json'
raise Exceptions::Authentication, \
message: 'Only JSON body is accepted.',
status: 400
end
def check_jwt!(jwt)
begin
scope = security_configuration.fetch(:scopes).fetch(security_scope)
keychain = security_configuration
.fetch(:keychain)
.slice(*scope.fetch(:permitted_signers))
.each_with_object({}) { |(k, v), memo| memo[k] = v.fetch(:value) }
result = JWT::Multisig.verify_jwt(jwt, keychain, security_configuration.fetch(:jwt, {}))
rescue StandardError => e
Rails.logger.error "ManagementAPI check_jwt error: #{e.inspect}"
raise Exceptions::Authentication, \
message: 'Failed to verify JWT.',
debug_message: e.inspect,
status: 401
end
unless (scope.fetch(:mandatory_signers) - result[:verified]).empty?
raise Exceptions::Authentication, \
message: 'Not enough signatures for the action.',
status: 401
end
result[:payload]
end
def security_scope
request.env['api.endpoint'].options.fetch(:route_options).fetch(:scope)
end
end
end
end

View File

@@ -0,0 +1,132 @@
# frozen_string_literal: true
module API::V2
module Management
# Labels-related API
class Labels < Grape::API
helpers ::API::V2::NamedParams
helpers do
def user
@user ||= User.find_by!(uid: params[:user_uid])
end
def permitted_search_params(params)
params.slice(:key, :value, :from, :to, :range, :scope)
end
end
desc 'Label related routes'
resource :labels do
desc 'Get all labels assigned to users' do
@settings[:scope] = :read_users
success API::V2::Entities::User
end
params do
requires :key, type: String, allow_blank: false, desc: 'Label key.'
optional :value, type: String, allow_blank: false, desc: 'Label value.'
optional :scope, type: String, allow_blank: false, desc: 'Label scope.'
optional :extended,
type: { value: Boolean, message: 'Non boolean extended value' },
default: false,
desc: 'When true endpoint returns full information about users'
optional :range,
type: String,
values: { value: ->(p) { %w[created updated].include?(p) }, message: 'Invalid range' },
default: 'created'
use :pagination_filters
end
post '/filter/users' do
entity = params[:extended] ? API::V2::Entities::UserWithProfile : API::V2::Entities::User
users = API::V2::Queries::UserWithLabelFilter.new(User.all).call(permitted_search_params(params))
present paginate(users), with: entity
status 200
end
desc 'Get user collection filtered on label attributes' do
@settings[:scope] = :read_labels
success API::V2::Entities::AdminLabelView
end
params do
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
end
post '/list' do
present user.labels, with: API::V2::Entities::AdminLabelView
end
desc "Create a label with 'private' scope and assigns to users" do
@settings[:scope] = :write_labels
success API::V2::Entities::Label
end
params do
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
requires :key, type: String, allow_blank: false, desc: 'Label key.'
requires :value, type: String, allow_blank: false, desc: 'Label value.'
optional :description, type: String, allow_blank: false, desc: 'Label desc.'
end
post do
label = user.labels.create(key: params[:key],
value: params[:value],
description: params[:description],
scope: 'private')
if label.errors.any?
error!(label.errors.as_json(full_messages: true), 422)
end
present label, with: API::V2::Entities::Label
end
desc "Update a label with 'private' scope" do
@settings[:scope] = :write_labels
success API::V2::Entities::Label
end
params do
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
requires :key, type: String, allow_blank: false, desc: 'Label key.'
requires :value, type: String, allow_blank: false, desc: 'Label value.'
optional :description, type: String, allow_blank: false, desc: 'Label desc.'
optional :replace, type: Boolean, default: true, desc: 'When true label will be created if not exist'
end
put do
label = user.labels.find_by(key: params[:key], scope: 'private')
if label.nil?
if params[:replace]
label = Label.create(
user_id: user.id,
key: params[:key],
value: params[:value],
description: params[:description],
scope: params[:scope] || 'private'
)
else
error!({ error: 'label doesnt exist' }, 404)
end
else
label.update({ value: params[:value], description: params[:description] }.compact)
end
error!(label.errors.as_json(full_messages: true), 422) if label.errors.any?
present label, with: API::V2::Entities::Label
end
desc "Delete a label with 'private' scope" do
@settings[:scope] = :write_labels
end
params do
requires :user_uid, type: String, allow_blank: false, desc: 'User uid'
requires :key, type: String, allow_blank: false, desc: 'Label key.'
end
post '/delete' do
user.labels.find_by!(key: params[:key], scope: 'private').destroy
status 204
end
end
end
end
end

View File

@@ -0,0 +1,40 @@
# frozen_string_literal: true
module API
module V2
module Management
class Otp < Grape::API
helpers do
def sign_request(jwt)
JWT::Multisig.add_jws(jwt, :barong, Barong::App.config.keystore.private_key, 'RS256')
rescue StandardError => e
error!("JWT is invalid by the reason \"#{e.message}\"", 422)
end
end
desc 'OTP related routes'
resource :otp do
desc 'Sign request with barong signature' do
@settings[:scope] = :otp_sign
end
params do
requires :user_uid, type: String, allow_blank: false, desc: 'Account UID'
requires :otp_code, type: String, allow_blank: false, desc: 'Code from Google Authenticator'
requires :jwt, type: Hash, allow_blank: false, desc: 'RFC 7516 jwt with applogic signature'
end
post '/sign' do
declared_params = declared(params)
user = User.active.find_by!(uid: declared_params[:user_uid])
error!('Account has not enabled 2FA', 422) unless user.otp
unless TOTPService.validate?(user.uid, declared_params[:otp_code])
error!('OTP code is invalid', 422)
end
sign_request(declared_params[:jwt])
end
end
end
end
end
end

View File

@@ -0,0 +1,74 @@
# frozen_string_literal: true
module API::V2
module Management
class Phones < Grape::API
helpers do
def validate_phone!(phone_number)
error!('management.phone.invalid_num', 400) unless Phone.valid?(phone_number)
error!('management.phone.number_exist', 400) if Phone.verified.find_by_number(phone_number)
end
end
desc 'Phones related routes'
resource :phones do
desc 'Get user phone numbers' do
@settings[:scope] = :read_phones
success API::V2::Management::Entities::Phone
end
params do
requires :uid, type: String, desc: 'User uid', allow_blank: false
end
post '/get' do
user = User.find_by(uid: params[:uid])
error!('user.doesnt_exist', 422) unless user
present user.phones, with: API::V2::Management::Entities::Phone
end
desc 'Create phone number for user' do
@settings[:scope] = :write_phones
success API::V2::Management::Entities::Phone
end
params do
requires :uid, type: String, desc: 'User uid', allow_blank: false
requires :number, type: String, desc: 'User phone number', allow_blank: false
end
post do
user = User.find_by(uid: params[:uid])
error!('user.doesnt_exist', 422) unless user
phone_number = Phone.international(params[:number])
validate_phone!(phone_number)
error!('management.phone.exists', 400) if user.phones.find_by_number(phone_number)
phone = user.phones.create(number: params[:number], validated_at: Time.now)
error!(phone.errors.full_messages, 422) if phone.errors.any?
present phone, with: API::V2::Management::Entities::Phone
end
desc 'Delete phone number for user' do
@settings[:scope] = :write_phones
success API::V2::Management::Entities::Phone
end
params do
requires :uid, type: String, desc: 'User uid', allow_blank: false
requires :number, type: String, desc: 'User phone number', allow_blank: false
end
post '/delete' do
user = User.find_by(uid: params[:uid])
error!('user.doesnt_exist', 422) unless user
phone_number = Phone.international(params[:number])
phone = user.phones.find_by_number(phone_number) if phone_number.present?
error!('management.phone.doesnt_exists', 422) unless phone
present phone.destroy, with: API::V2::Management::Entities::Phone
end
end
end
end
end

View File

@@ -0,0 +1,47 @@
# frozen_string_literal: true
module API::V2
module Management
# Profiles server-to-server API
class Profiles < Grape::API
desc 'Profiles related routes'
resource :profiles do
helpers do
def profile_param_keys
%w[first_name last_name dob address
postcode city country state metadata].freeze
end
end
desc 'Imports a profile for user' do
@settings[:scope] = :write_users
success API::V2::Management::Entities::UserWithProfile
end
params do
requires :uid, type: String, desc: 'User Uid', allow_blank: false
optional :first_name, type: String, desc: 'First Name', allow_blank: false
optional :last_name, type: String, desc: 'Last Name', allow_blank: false
optional :dob, type: Date, desc: 'Birth date', allow_blank: false
optional :address, type: String, desc: 'Address', allow_blank: false
optional :postcode, type: String, desc: 'Postcode', allow_blank: false
optional :city, type: String, desc: 'City', allow_blank: false
optional :country, type: String, desc: 'Country', allow_blank: false
optional :state, type: String, desc: 'State', allow_blank: false
optional :metadata, type: String, desc: 'Metadata', allow_blank: false
end
post do
user = User.find_by(uid: params[:uid])
error! 'user.doesnt_exist', 422 unless user
profile_params = params.slice(*profile_param_keys)
profile = Profile.new(profile_params.merge(user_id: user.id))
error!(profile.errors.full_messages, 422) unless profile.save
present user, with: API::V2::Management::Entities::UserWithProfile
end
end
end
end
end

View File

@@ -0,0 +1,113 @@
# frozen_string_literal: true
module API::V2
module Management
# ServiceAccounts server-to-server API
class ServiceAccounts < Grape::API
helpers ::API::V2::NamedParams
desc 'ServiceAccounts related routes'
resource :service_accounts do
desc 'Get specific service_account information' do
@settings[:scope] = :read_service_accounts
success API::V2::Entities::ServiceAccounts
end
params do
optional :uid, type: String, allow_blank: false, desc: 'service_account uid'
optional :email, type: String, allow_blank: false, desc: 'service_account email'
exactly_one_of :uid, :email
end
post '/get' do
declared_params = declared(params, include_missing: false)
service_acc = ServiceAccount.find_by!(declared_params)
error!('Service account doesnt exist', 422) unless service_acc
present service_acc, with: API::V2::Entities::ServiceAccounts
status 200
end
desc 'Get service_accounts as a paginated collection' do
@settings[:scope] = :read_service_accounts
success API::V2::Entities::ServiceAccounts
end
params do
use :pagination_filters
optional :owner_uid, type: String, allow_blank: false, desc: 'owner uid'
optional :owner_email, type: String, allow_blank: false, desc: 'owner email'
end
post '/list' do
owner = User.find_by(uid: params[:owner_uid]) || User.find_by(email: params[:owner_email]) if params[:owner_uid] || params[:owner_email]
service_accs = owner ? owner.service_accounts : ServiceAccount.all
service_accs.tap { |q| present paginate(q), with: API::V2::Entities::ServiceAccounts }
status 200
end
desc 'Create service_account' do
@settings[:scope] = :write_service_accounts
success API::V2::Entities::ServiceAccounts
end
params do
requires :service_account_role, type: String, allow_blank: false, desc: 'service_account role'
optional :owner_uid, type: String, allow_blank: false, desc: 'owner uid'
optional :service_account_uid, type: String, allow_blank: false, desc: 'service_account uid'
optional :service_account_email, type: String, allow_blank: false, desc: 'service_account email'
end
post '/create' do
owner = User.find_by(uid: params[:owner_uid])
error!('User doesnt exist', 422) unless owner
s_params = { email: params[:service_account_email], uid: params[:service_account_uid], role: params[:service_account_role] }.compact
service_acc = ServiceAccount.new(s_params.merge(user: owner))
error!(service_acc.errors.full_messages, 422) unless service_acc.save
present service_acc, with: API::V2::Entities::ServiceAccounts
status 201
end
desc 'Update service_account' do
@settings[:scope] = :write_service_accounts
success API::V2::Entities::ServiceAccounts
end
params do
requires :uid, type: String, allow_blank: false, desc: 'service_account uid'
optional :owner_uid, type: String, allow_blank: false, desc: 'service_account owner uid'
end
post '/update' do
service_acc = ServiceAccount.find_by(uid: params[:uid])
error!('Service account doesnt exist', 422) unless service_acc
owner = User.find_by(uid: params[:owner_uid])
s_params = { owner_id: owner&.id }.compact
code_error!(service_acc.errors.details, 422) unless service_acc.update(s_params)
present service_acc, with: API::V2::Entities::ServiceAccounts
end
desc 'Delete specific service_account' do
@settings[:scope] = :write_service_accounts
success API::V2::Entities::ServiceAccounts
end
params do
requires :uid, type: String, allow_blank: false, desc: 'service_account uid'
end
post '/delete' do
declared_params = declared(params, include_missing: false)
service_acc = ServiceAccount.find_by!(declared_params)
error!('Service account doesnt exist', 422) unless service_acc
unless service_acc.update(state: 'disabled')
code_error!(service_acc.errors.details, 422)
end
present service_acc, with: API::V2::Entities::ServiceAccounts
status 200
end
end
end
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
module API::V2
module Management
class Tools < Grape::API
desc 'Returns server time in seconds since Unix epoch.' do
@settings[:scope] = :tools
end
post '/timestamp' do
body timestamp: Time.now.to_i
status 200
end
end
end
end

View File

@@ -0,0 +1,168 @@
# frozen_string_literal: true
module API::V2
module Management
class Users < Grape::API
helpers ::API::V2::NamedParams
helpers do
def profile_param_keys
%w[first_name last_name dob address
postcode city country state].freeze
end
def create_user(user_params)
user = User.new(user_params)
user.send :assign_uid
user.save(validate: false)
error!(user.errors.full_messages3, 422) unless user.persisted?
user
end
def all_profile_fields?(params)
profile_param_keys.all? { |key| params[key].present? }
end
def create_phone(user:, number:)
return if number.blank?
phone = user.phones.create(number: number)
error!(phone.errors.full_messages, 422) unless phone.persisted?
phone.update(validated_at: Time.current)
end
end
desc 'Users related routes'
resource :users do
desc 'Get users and profile information' do
@settings[:scope] = :read_users
success API::V2::Management::Entities::UserWithKYC
end
params do
optional :uid, type: String, allow_blank: false, desc: 'User uid'
optional :email, type: String, allow_blank: false, desc: 'User email'
optional :phone_num, type: String, allow_blank: false, desc: 'User phone number'
exactly_one_of :uid, :email, :phone_num
end
post '/get' do
declared_params = declared(params, include_missing: false)
if declared_params.key?(:phone_num)
user = Phone.find_by_number!(declared_params[:phone_num]).user
present user, with: API::V2::Management::Entities::UserWithKYC
return status 201
end
user = User.find_by!(declared_params)
present user, with: API::V2::Management::Entities::UserWithKYC
end
desc 'Returns array of users as collection',
security: [{ "BearerToken": [] }],
failure: [
{ code: 401, message: 'Invalid bearer token' }
] do
@settings[:scope] = :read_users
success API::V2::Entities::User
end
params do
optional :extended,
type: { value: Boolean, message: 'Non boolean extended value' },
default: false,
desc: 'When true endpoint returns full information about users'
optional :range,
type: String,
values: { value: -> (p){ %w[created updated].include?(p) }, message: 'Non positive page' },
default: 'created'
use :timeperiod_filters
use :pagination_filters
end
post '/list' do
entity = params[:extended] ? API::V2::Management::Entities::UserWithProfile : API::V2::Entities::User
users = API::V2::Queries::UserFilter.new(User.all).call(params)
users.tap { |q| present paginate(q), with: entity }
status 200
end
desc 'Creates new user' do
@settings[:scope] = :write_users
success API::V2::Management::Entities::UserWithProfile
end
params do
requires :email, type: String, desc: 'User Email', allow_blank: false
requires :password, type: String, desc: 'User Password', allow_blank: false
optional :referral_uid, type: String, desc: 'Referral uid', allow_blank: false
end
post do
referral = User.find_by_uid(params[:referral_uid]).id if params[:referral_uid]
user = User.create({ email: params[:email], password: params[:password], referral_id: referral }.compact )
error!(user.errors.full_messages, 422) unless user.persisted?
present user, with: API::V2::Management::Entities::UserWithProfile
end
desc 'Updates role and data fields of existing user' do
@settings[:scope] = :write_users
success API::V2::Management::Entities::UserWithProfile
end
params do
requires :uid, type: String, desc: 'User Uid', allow_blank: false
optional :role, type: String, desc: 'User Role', allow_blank: false
optional :data, type: String, desc: 'Any additional key:value pairs in json format', allow_blank: false
at_least_one_of :role, :data
end
post '/update' do
user = User.find_by_uid(params[:uid])
error! 'user.doesnt_exist', 422 unless user
u_params = { data: params[:data], role: params[:role] }.compact
error!(user.errors.full_messages, 422) unless user.update(u_params)
present user, with: API::V2::Management::Entities::UserWithProfile
end
desc 'Imports an existing user' do
@settings[:scope] = :write_users
success API::V2::Management::Entities::UserWithProfile
end
params do
requires :email, type: String,
desc: 'User Email',
allow_blank: false
requires :password_digest, type: String,
desc: 'User Password Hash',
allow_blank: false
optional :referral_uid, type: String, desc: 'Referral uid', allow_blank: false
optional :phone, type: String, allow_blank: false, desc: 'Phone'
optional :first_name, type: String, allow_blank: false, desc: 'First Name'
optional :last_name, type: String, allow_blank: false, desc: 'Last Name'
optional :dob, type: Date, desc: 'Birth date', allow_blank: false
optional :address, type: String, allow_blank: false, desc: 'Address'
optional :postcode, type: String, allow_blank: false, desc: 'Postcode'
optional :city, type: String, allow_blank: false, desc: 'City'
optional :country, type: String, allow_blank: false, desc: 'Country'
optional :state, type: String, allow_blank: false, desc: 'State'
end
post '/import' do
if User.find_by(email: params[:email]).present?
error! 'User already exists by this email', 422
end
referral = User.find_by_uid(params[:referral_uid]).id if params[:referral_uid]
user = create_user({
email: params[:email],
password_digest: params[:password_digest],
referral_id: referral
}.compact)
create_phone(user: user, number: params[:phone])
profile_params = params.slice(*profile_param_keys)
profile = Profile.new(profile_params.merge(user_id: user.id))
error!(profile.errors.full_messages, 422) unless profile.save
present user, with: API::V2::Management::Entities::UserWithProfile
end
end
end
end
end