feat: Platform API improvements (#2900)
- Platform APIs to add and update custom attributes to users - Platform APIs to delete accounts - Platform APIs to delete users
This commit is contained in:
parent
a60a33679f
commit
ad83d1bb71
18 changed files with 160 additions and 12 deletions
|
@ -16,7 +16,7 @@ class Platform::Api::V1::AccountsController < PlatformController
|
|||
end
|
||||
|
||||
def destroy
|
||||
# TODO: obfusicate account
|
||||
DeleteObjectJob.perform_later(@resource)
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -19,21 +19,33 @@ class Platform::Api::V1::UsersController < PlatformController
|
|||
def show; end
|
||||
|
||||
def update
|
||||
@resource.update!(user_params)
|
||||
@resource.assign_attributes(user_update_params)
|
||||
@resource.save!
|
||||
end
|
||||
|
||||
def destroy
|
||||
# TODO: obfusicate user
|
||||
DeleteObjectJob.perform_later(@resource)
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_custom_attributes
|
||||
return @resource.custom_attributes.merge(user_params[:custom_attributes]) if user_params[:custom_attributes]
|
||||
|
||||
@resource.custom_attributes
|
||||
end
|
||||
|
||||
def user_update_params
|
||||
# we want the merged custom attributes not the original one
|
||||
user_params.except(:custom_attributes).merge({ custom_attributes: user_custom_attributes })
|
||||
end
|
||||
|
||||
def set_resource
|
||||
@resource = User.find(params[:id])
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.permit(:name, :email, :password)
|
||||
params.permit(:name, :email, :password, custom_attributes: {})
|
||||
end
|
||||
end
|
||||
|
|
7
app/jobs/delete_object_job.rb
Normal file
7
app/jobs/delete_object_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DeleteObjectJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(object)
|
||||
object.destroy!
|
||||
end
|
||||
end
|
|
@ -9,6 +9,7 @@
|
|||
# confirmed_at :datetime
|
||||
# current_sign_in_at :datetime
|
||||
# current_sign_in_ip :string
|
||||
# custom_attributes :jsonb
|
||||
# display_name :string
|
||||
# email :string
|
||||
# encrypted_password :string default(""), not null
|
||||
|
@ -112,6 +113,7 @@ class User < ApplicationRecord
|
|||
self[:display_name].presence || name
|
||||
end
|
||||
|
||||
# Used internally for Chatwoot in Chatwoot
|
||||
def hmac_identifier
|
||||
hmac_key = GlobalConfig.get('CHATWOOT_INBOX_HMAC_KEY')['CHATWOOT_INBOX_HMAC_KEY']
|
||||
return OpenSSL::HMAC.hexdigest('sha256', hmac_key, email) if hmac_key.present?
|
||||
|
|
|
@ -4,6 +4,7 @@ json.confirmed resource.confirmed?
|
|||
json.email resource.email
|
||||
json.available_name resource.available_name
|
||||
json.id resource.id
|
||||
json.custom_attributes resource.custom_attributes if resource.custom_attributes.present?
|
||||
json.name resource.name
|
||||
json.role resource.role
|
||||
json.thumbnail resource.avatar_url
|
||||
|
|
|
@ -12,6 +12,7 @@ json.inviter_id resource.active_account_user&.inviter_id
|
|||
json.name resource.name
|
||||
json.provider resource.provider
|
||||
json.pubsub_token resource.pubsub_token
|
||||
json.custom_attributes resource.custom_attributes if resource.custom_attributes.present?
|
||||
json.role resource.active_account_user&.role
|
||||
json.ui_settings resource.ui_settings
|
||||
json.uid resource.uid
|
||||
|
|
|
@ -10,6 +10,7 @@ json.id resource.id
|
|||
json.name resource.name
|
||||
json.provider resource.provider
|
||||
json.pubsub_token resource.pubsub_token
|
||||
json.custom_attributes resource.custom_attributes if resource.custom_attributes.present?
|
||||
json.role resource.active_account_user&.role
|
||||
json.ui_settings resource.ui_settings
|
||||
json.uid resource.uid
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddCustomAttributesToUser < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :users, :custom_attributes, :jsonb, default: {}
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2021_08_24_152852) do
|
||||
ActiveRecord::Schema.define(version: 2021_08_27_120929) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
|
@ -659,6 +659,7 @@ ActiveRecord::Schema.define(version: 2021_08_24_152852) do
|
|||
t.string "pubsub_token"
|
||||
t.integer "availability", default: 0
|
||||
t.jsonb "ui_settings", default: {}
|
||||
t.jsonb "custom_attributes", default: {}
|
||||
t.index ["email"], name: "index_users_on_email"
|
||||
t.index ["pubsub_token"], name: "index_users_on_pubsub_token", unique: true
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
|
|
|
@ -2,7 +2,7 @@ require 'rails_helper'
|
|||
|
||||
RSpec.describe 'Agents API', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:admin) { create(:user, custom_attributes: { test: 'test' }, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
|
||||
describe 'GET /api/v1/accounts/{account.id}/agents' do
|
||||
|
@ -25,6 +25,18 @@ RSpec.describe 'Agents API', type: :request do
|
|||
expect(response).to have_http_status(:success)
|
||||
expect(JSON.parse(response.body).size).to eq(account.users.count)
|
||||
end
|
||||
|
||||
it 'returns custom fields on agents if present' do
|
||||
agent.update(custom_attributes: { test: 'test' })
|
||||
|
||||
get "/api/v1/accounts/#{account.id}/agents",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = JSON.parse(response.body)
|
||||
expect(data.first['custom_attributes']['test']).to eq('test')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ RSpec.describe 'Profile API', type: :request do
|
|||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:agent) { create(:user, account: account, custom_attributes: { test: 'test' }, role: :agent) }
|
||||
|
||||
it 'returns current user information' do
|
||||
get '/api/v1/profile',
|
||||
|
@ -25,6 +25,7 @@ RSpec.describe 'Profile API', type: :request do
|
|||
expect(json_response['id']).to eq(agent.id)
|
||||
expect(json_response['email']).to eq(agent.email)
|
||||
expect(json_response['access_token']).to eq(agent.access_token.token)
|
||||
expect(json_response['custom_attributes']['test']).to eq('test')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -104,4 +104,38 @@ RSpec.describe 'Platform Accounts API', type: :request do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /platform/api/v1/accounts/{account_id}' do
|
||||
context 'when it is an unauthenticated platform app' do
|
||||
it 'returns unauthorized' do
|
||||
delete "/platform/api/v1/accounts/#{account.id}"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an invalid platform app token' do
|
||||
it 'returns unauthorized' do
|
||||
delete "/platform/api/v1/accounts/#{account.id}", headers: { api_access_token: 'invalid' }, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated platform app' do
|
||||
let(:platform_app) { create(:platform_app) }
|
||||
|
||||
it 'returns unauthorized when its not a permissible object' do
|
||||
delete "/platform/api/v1/accounts/#{account.id}", headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'destroys the object' do
|
||||
create(:platform_app_permissible, platform_app: platform_app, permissible: account)
|
||||
expect(DeleteObjectJob).to receive(:perform_later).with(account).once
|
||||
delete "/platform/api/v1/accounts/#{account.id}",
|
||||
headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Platform Users API', type: :request do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:user) { create(:user, custom_attributes: { test: 'test' }) }
|
||||
|
||||
describe 'GET /platform/api/v1/users/{user_id}' do
|
||||
context 'when it is an unauthenticated platform app' do
|
||||
|
@ -35,6 +35,7 @@ RSpec.describe 'Platform Users API', type: :request do
|
|||
expect(response).to have_http_status(:success)
|
||||
data = JSON.parse(response.body)
|
||||
expect(data['email']).to eq(user.email)
|
||||
expect(data['custom_attributes']['test']).to eq('test')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -94,12 +95,14 @@ RSpec.describe 'Platform Users API', type: :request do
|
|||
let(:platform_app) { create(:platform_app) }
|
||||
|
||||
it 'creates a new user and permissible for the user' do
|
||||
post '/platform/api/v1/users/', params: { name: 'test', email: 'test@test.com', password: 'Password1!' },
|
||||
post '/platform/api/v1/users/', params: { name: 'test', email: 'test@test.com', password: 'Password1!',
|
||||
custom_attributes: { test: 'test_create' } },
|
||||
headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = JSON.parse(response.body)
|
||||
expect(data['email']).to eq('test@test.com')
|
||||
expect(data['custom_attributes']['test']).to eq('test_create')
|
||||
expect(platform_app.platform_app_permissibles.first.permissible_id).to eq data['id']
|
||||
end
|
||||
|
||||
|
@ -142,12 +145,46 @@ RSpec.describe 'Platform Users API', type: :request do
|
|||
|
||||
it 'updates the user' do
|
||||
create(:platform_app_permissible, platform_app: platform_app, permissible: user)
|
||||
patch "/platform/api/v1/users/#{user.id}", params: { name: 'test123' },
|
||||
patch "/platform/api/v1/users/#{user.id}", params: { name: 'test123', custom_attributes: { test: 'test_update' } },
|
||||
headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = JSON.parse(response.body)
|
||||
expect(data['name']).to eq('test123')
|
||||
expect(data['custom_attributes']['test']).to eq('test_update')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /platform/api/v1/users/{user_id}' do
|
||||
context 'when it is an unauthenticated platform app' do
|
||||
it 'returns unauthorized' do
|
||||
delete "/platform/api/v1/users/#{user.id}"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an invalid platform app token' do
|
||||
it 'returns unauthorized' do
|
||||
delete "/platform/api/v1/users/#{user.id}", headers: { api_access_token: 'invalid' }, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated platform app' do
|
||||
let(:platform_app) { create(:platform_app) }
|
||||
|
||||
it 'returns unauthorized when its not a permissible object' do
|
||||
delete "/platform/api/v1/users/#{user.id}", headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'deletes the user' do
|
||||
create(:platform_app_permissible, platform_app: platform_app, permissible: user)
|
||||
expect(DeleteObjectJob).to receive(:perform_later).with(user).once
|
||||
delete "/platform/api/v1/users/#{user.id}",
|
||||
headers: { api_access_token: platform_app.access_token.token }, as: :json
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
20
spec/jobs/delete_object_job_spec.rb
Normal file
20
spec/jobs/delete_object_job_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe DeleteObjectJob, type: :job do
|
||||
subject(:job) { described_class.perform_later(account) }
|
||||
|
||||
let(:account) { create(:account) }
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { job }.to have_enqueued_job(described_class)
|
||||
.with(account)
|
||||
.on_queue('default')
|
||||
end
|
||||
|
||||
context 'when an object is passed to the job' do
|
||||
it 'is deleted' do
|
||||
described_class.perform_now(account)
|
||||
expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,4 +9,7 @@ properties:
|
|||
password:
|
||||
type: string
|
||||
description: Password must contain uppercase, lowercase letters, number and a special character
|
||||
custom_attributes:
|
||||
type: object
|
||||
description: Custom attributes you want to associate with the user
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ properties:
|
|||
enum: ['agent', 'administrator']
|
||||
confirmed:
|
||||
type: boolean
|
||||
custom_attributes:
|
||||
type: object
|
||||
description: Available for users who are created through platform APIs and has custom attributes associated.
|
||||
accounts:
|
||||
type: array
|
||||
items:
|
||||
|
|
|
@ -64,7 +64,7 @@ x-tagGroups:
|
|||
- Teams
|
||||
- Custom Filter
|
||||
- Reports
|
||||
- name: Public
|
||||
- name: Client
|
||||
tags:
|
||||
- Contacts API
|
||||
- Conversations API
|
||||
|
|
|
@ -3085,6 +3085,10 @@
|
|||
"confirmed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"custom_attributes": {
|
||||
"type": "object",
|
||||
"description": "Available for users who are created through platform APIs and has custom attributes associated."
|
||||
},
|
||||
"accounts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -3489,6 +3493,10 @@
|
|||
"password": {
|
||||
"type": "string",
|
||||
"description": "Password must contain uppercase, lowercase letters, number and a special character"
|
||||
},
|
||||
"custom_attributes": {
|
||||
"type": "object",
|
||||
"description": "Custom attributes you want to associate with the user"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4173,7 +4181,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "Public",
|
||||
"name": "Client",
|
||||
"tags": [
|
||||
"Contacts API",
|
||||
"Conversations API",
|
||||
|
|
Loading…
Reference in a new issue