From ace7d9140f2629ed268673839e4825336111d569 Mon Sep 17 00:00:00 2001 From: nejdetkadir Date: Sat, 7 Jan 2023 00:17:47 +0300 Subject: [PATCH 1/3] feat: use blueprint instead of jbuilder --- Gemfile | 6 ++ Gemfile.lock | 4 ++ app/blueprints/api/shared/meta_blueprint.rb | 20 ++++++ .../doorkeeper/access_token_blueprint.rb | 27 ++++++++ .../doorkeeper/application_blueprint.rb | 19 +++++ app/blueprints/models/user_blueprint.rb | 15 ++++ .../passwords/send_instructions_contract.rb | 11 --- .../users/passwords/update_contract.rb | 16 ----- .../users/registrations/register_contract.rb | 12 ---- .../passwords/send_instructions.rb | 13 ++++ .../v1/users_contract/passwords/update.rb | 18 +++++ .../users_contract/registrations/register.rb | 14 ++++ .../v1/users/passwords_controller.rb | 4 +- .../v1/users/registrations_controller.rb | 8 ++- .../users/passwords/create_operation.rb | 23 ------- .../users/passwords/update_operation.rb | 23 ------- .../users/registrations/create_operation.rb | 24 ------- .../v1/users_operation/passwords/create.rb | 25 +++++++ .../v1/users_operation/passwords/update.rb | 25 +++++++ .../users_operation/registrations/create.rb | 26 +++++++ .../access_tokens/create.rb} | 14 +++- app/services/users/create_service.rb | 13 ---- app/services/users_service/create.rb | 23 +++++++ .../passwords/send_instructions.rb} | 4 +- .../passwords/update.rb} | 4 +- .../registrations/register.rb} | 11 ++- .../responses/v1/user/sign_up_response.rb | 61 ++++++++++++++-- app/views/shared/partials/_meta.json.jbuilder | 5 -- .../shared/partials/_pagination.json.jbuilder | 10 --- config/application.rb | 9 ++- config/initializers/blueprinter.rb | 7 ++ .../send_instructions_contract_test.rb | 22 ------ .../users/passwords/update_contract_test.rb | 38 ---------- .../registrations/register_contract_test.rb | 29 -------- .../v1/passwords/send_instructions_test.rb | 24 +++++++ test/contracts/v1/passwords/update_test.rb | 40 +++++++++++ .../registrations/register_test.rb | 31 +++++++++ .../v1/users/registrations_controller_test.rb | 3 +- .../users/passwords/create_operation_test.rb | 60 ---------------- .../users/passwords/update_operation_test.rb | 61 ---------------- .../registrations/create_operation_test.rb | 67 ------------------ .../users_operation/passwords/create_test.rb | 62 +++++++++++++++++ .../users_operation/passwords/update_test.rb | 63 +++++++++++++++++ .../registrations/create_test.rb | 69 +++++++++++++++++++ .../access_tokens/create_test.rb} | 8 +-- .../create_test.rb} | 8 +-- .../passwords/send_instructions_test.rb} | 8 +-- .../passwords/update_test.rb} | 8 +-- .../registrations/register_test.rb} | 19 ++--- 49 files changed, 648 insertions(+), 466 deletions(-) create mode 100644 app/blueprints/api/shared/meta_blueprint.rb create mode 100644 app/blueprints/models/doorkeeper/access_token_blueprint.rb create mode 100644 app/blueprints/models/doorkeeper/application_blueprint.rb create mode 100644 app/blueprints/models/user_blueprint.rb delete mode 100644 app/contracts/users/passwords/send_instructions_contract.rb delete mode 100644 app/contracts/users/passwords/update_contract.rb delete mode 100644 app/contracts/users/registrations/register_contract.rb create mode 100644 app/contracts/v1/users_contract/passwords/send_instructions.rb create mode 100644 app/contracts/v1/users_contract/passwords/update.rb create mode 100644 app/contracts/v1/users_contract/registrations/register.rb delete mode 100644 app/operations/users/passwords/create_operation.rb delete mode 100644 app/operations/users/passwords/update_operation.rb delete mode 100644 app/operations/users/registrations/create_operation.rb create mode 100644 app/operations/v1/users_operation/passwords/create.rb create mode 100644 app/operations/v1/users_operation/passwords/update.rb create mode 100644 app/operations/v1/users_operation/registrations/create.rb rename app/services/{doorkeeper/access_tokens/create_service.rb => doorkeeper_service/access_tokens/create.rb} (71%) delete mode 100644 app/services/users/create_service.rb create mode 100644 app/services/users_service/create.rb rename app/services/{users/passwords/send_instructions_service.rb => users_service/passwords/send_instructions.rb} (91%) rename app/services/{users/passwords/update_service.rb => users_service/passwords/update.rb} (92%) rename app/services/{users/registrations/register_service.rb => users_service/registrations/register.rb} (69%) delete mode 100644 app/views/shared/partials/_meta.json.jbuilder delete mode 100644 app/views/shared/partials/_pagination.json.jbuilder create mode 100644 config/initializers/blueprinter.rb delete mode 100644 test/contracts/users/passwords/send_instructions_contract_test.rb delete mode 100644 test/contracts/users/passwords/update_contract_test.rb delete mode 100644 test/contracts/users/registrations/register_contract_test.rb create mode 100644 test/contracts/v1/passwords/send_instructions_test.rb create mode 100644 test/contracts/v1/passwords/update_test.rb create mode 100644 test/contracts/v1/users_contract/registrations/register_test.rb delete mode 100644 test/operations/users/passwords/create_operation_test.rb delete mode 100644 test/operations/users/passwords/update_operation_test.rb delete mode 100644 test/operations/users/registrations/create_operation_test.rb create mode 100644 test/operations/v1/users_operation/passwords/create_test.rb create mode 100644 test/operations/v1/users_operation/passwords/update_test.rb create mode 100644 test/operations/v1/users_operation/registrations/create_test.rb rename test/services/{doorkeeper/access_tokens/create_service_test.rb => doorkeeper_service/access_tokens/create_test.rb} (79%) rename test/services/{users/create_service_test.rb => users_service/create_test.rb} (72%) rename test/services/{users/passwords/send_instructions_service_test.rb => users_service/passwords/send_instructions_test.rb} (78%) rename test/services/{users/passwords/update_service_test.rb => users_service/passwords/update_test.rb} (83%) rename test/services/{users/registrations/register_service_test.rb => users_service/registrations/register_test.rb} (70%) diff --git a/Gemfile b/Gemfile index 12b5d29..84ad4dd 100644 --- a/Gemfile +++ b/Gemfile @@ -124,3 +124,9 @@ gem 'sidekiq', '~> 6.4', '>= 6.4.1' # Object-based searching [https://github.com/activerecord-hackery/ransack] gem 'ransack', github: 'activerecord-hackery/ransack' + +# Blueprinter is a JSON Object Presenter for Ruby [https://github.com/procore/blueprinter] +gem 'blueprinter', '~> 0.25.3' + +# The fastest JSON parser and object serializer [https://github.com/ohler55/oj] +gem 'oj', '~> 3.13', '>= 3.13.23' diff --git a/Gemfile.lock b/Gemfile.lock index 47d3450..8a48eaf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -88,6 +88,7 @@ GEM ast (2.4.2) awesome_print (1.9.2) bcrypt (3.1.18) + blueprinter (0.25.3) bootsnap (1.13.0) msgpack (~> 1.2) builder (3.2.4) @@ -210,6 +211,7 @@ GEM nio4r (2.5.8) nokogiri (1.13.9-arm64-darwin) racc (~> 1.4) + oj (3.13.23) orm_adapter (0.5.0) parallel (1.22.1) parser (3.1.2.1) @@ -313,6 +315,7 @@ PLATFORMS DEPENDENCIES awesome_print + blueprinter (~> 0.25.3) bootsnap byebug (~> 11.1, >= 11.1.3) debug @@ -330,6 +333,7 @@ DEPENDENCIES letter_opener (~> 1.4, >= 1.4.1) minitest-focus mocha (~> 1.13) + oj (~> 3.13, >= 3.13.23) pg (~> 1.1) pry (~> 0.14.1) puma (~> 5.0) diff --git a/app/blueprints/api/shared/meta_blueprint.rb b/app/blueprints/api/shared/meta_blueprint.rb new file mode 100644 index 0000000..a6bae7e --- /dev/null +++ b/app/blueprints/api/shared/meta_blueprint.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Api + module Shared + class MetaBlueprint + def self.render(collection) + { + pagination: { + current: collection.current_page, + previous: collection.prev_page, + next: collection.next_page, + limit: collection.limit_value, + totalPages: collection.total_pages, + totalCount: collection.total_count + } + } + end + end + end +end diff --git a/app/blueprints/models/doorkeeper/access_token_blueprint.rb b/app/blueprints/models/doorkeeper/access_token_blueprint.rb new file mode 100644 index 0000000..7e38c93 --- /dev/null +++ b/app/blueprints/models/doorkeeper/access_token_blueprint.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Models + module Doorkeeper + class AccessTokenBlueprint < Blueprinter::Base + identifier :id + + view :standard do + fields :token, :refresh_token, :expires_in, :created_at, :revoked_at, :token_type + end + + view :with_resource_owner do + association :resource_owner, blueprint: Models::UserBlueprint, view: :standard + end + + view :with_application do + association :application, blueprint: Models::Doorkeeper::ApplicationBlueprint, view: :without_secrets + end + + view :with_resource_owner_and_application do + include_view :with_resource_owner + include_view :with_application + include_view :standard + end + end + end +end diff --git a/app/blueprints/models/doorkeeper/application_blueprint.rb b/app/blueprints/models/doorkeeper/application_blueprint.rb new file mode 100644 index 0000000..aac2a31 --- /dev/null +++ b/app/blueprints/models/doorkeeper/application_blueprint.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Models + module Doorkeeper + class ApplicationBlueprint < Blueprinter::Base + identifier :id + + view :standard do + fields :name, :uid, :secret, :redirect_uri, :scopes, :confidential, :created_at, :updated_at + end + + view :without_secrets do + include_view :standard + + excludes :uid, :secret, :redirect_uri, :confidential + end + end + end +end diff --git a/app/blueprints/models/user_blueprint.rb b/app/blueprints/models/user_blueprint.rb new file mode 100644 index 0000000..004f9c5 --- /dev/null +++ b/app/blueprints/models/user_blueprint.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Models + class UserBlueprint < Blueprinter::Base + identifier :id + + view :standard do + fields :email, :created_at, :updated_at + end + + view :with_devise_trackable do + fields :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip + end + end +end diff --git a/app/contracts/users/passwords/send_instructions_contract.rb b/app/contracts/users/passwords/send_instructions_contract.rb deleted file mode 100644 index c59ba72..0000000 --- a/app/contracts/users/passwords/send_instructions_contract.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Users - module Passwords - class SendInstructionsContract < ApplicationContract - params do - required(:email).filled(Types::Email) - end - end - end -end diff --git a/app/contracts/users/passwords/update_contract.rb b/app/contracts/users/passwords/update_contract.rb deleted file mode 100644 index 8dfbf55..0000000 --- a/app/contracts/users/passwords/update_contract.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Users - module Passwords - class UpdateContract < ApplicationContract - params do - required(:reset_password_token).filled(:string) - required(:password) { filled? & str? & min_size?(Devise.password_length.min) } - required(:password_confirmation) { filled? & str? & min_size?(Devise.password_length.min) } - end - - rule(:password).validate(:password_confirmation) - rule(:password_confirmation).validate(:password_confirmation) - end - end -end diff --git a/app/contracts/users/registrations/register_contract.rb b/app/contracts/users/registrations/register_contract.rb deleted file mode 100644 index 03269e3..0000000 --- a/app/contracts/users/registrations/register_contract.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Users - module Registrations - class RegisterContract < ApplicationContract - params do - required(:email).filled(Types::Email) - required(:password).filled(:str?, min_size?: Devise.password_length.min) - end - end - end -end diff --git a/app/contracts/v1/users_contract/passwords/send_instructions.rb b/app/contracts/v1/users_contract/passwords/send_instructions.rb new file mode 100644 index 0000000..72d9408 --- /dev/null +++ b/app/contracts/v1/users_contract/passwords/send_instructions.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module V1 + module UsersContract + module Passwords + class SendInstructions < ApplicationContract + params do + required(:email).filled(Types::Email) + end + end + end + end +end diff --git a/app/contracts/v1/users_contract/passwords/update.rb b/app/contracts/v1/users_contract/passwords/update.rb new file mode 100644 index 0000000..5b79b87 --- /dev/null +++ b/app/contracts/v1/users_contract/passwords/update.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module V1 + module UsersContract + module Passwords + class Update < ApplicationContract + params do + required(:reset_password_token).filled(:string) + required(:password) { filled? & str? & min_size?(Devise.password_length.min) } + required(:password_confirmation) { filled? & str? & min_size?(Devise.password_length.min) } + end + + rule(:password).validate(:password_confirmation) + rule(:password_confirmation).validate(:password_confirmation) + end + end + end +end diff --git a/app/contracts/v1/users_contract/registrations/register.rb b/app/contracts/v1/users_contract/registrations/register.rb new file mode 100644 index 0000000..36703a6 --- /dev/null +++ b/app/contracts/v1/users_contract/registrations/register.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module V1 + module UsersContract + module Registrations + class Register < ApplicationContract + params do + required(:email).filled(Types::Email) + required(:password).filled(:str?, min_size?: Devise.password_length.min) + end + end + end + end +end diff --git a/app/controllers/v1/users/passwords_controller.rb b/app/controllers/v1/users/passwords_controller.rb index 91788f5..81a0a44 100644 --- a/app/controllers/v1/users/passwords_controller.rb +++ b/app/controllers/v1/users/passwords_controller.rb @@ -6,7 +6,7 @@ class PasswordsController < ApplicationController include Doorkeeper::Authorize def create - operation = ::Users::Passwords::CreateOperation.new(params: password_params).call + operation = V1::UsersOperation::Passwords::Create.new(params: password_params).call if operation.success? render json: operation.success, status: :ok @@ -16,7 +16,7 @@ def create end def update - operation = ::Users::Passwords::UpdateOperation.new(params: password_params).call + operation = V1::UsersOperation::Passwords::Update.new(params: password_params).call if operation.success? render json: operation.success, status: :ok diff --git a/app/controllers/v1/users/registrations_controller.rb b/app/controllers/v1/users/registrations_controller.rb index 258d142..4858f8a 100644 --- a/app/controllers/v1/users/registrations_controller.rb +++ b/app/controllers/v1/users/registrations_controller.rb @@ -6,11 +6,13 @@ class RegistrationsController < ApplicationController include Doorkeeper::Authorize def create - operation = ::Users::Registrations::CreateOperation.new(params: registration_params, - doorkeeper_application: current_doorkeeper_application).call + operation = V1::UsersOperation::Registrations::Create.new(params: registration_params, + doorkeeper_application: current_doorkeeper_application).call if operation.success? - render json: operation.success, status: :created + render json: Models::Doorkeeper::AccessTokenBlueprint.render(operation.success, + view: :with_resource_owner_and_application), + status: :created else render json: operation.failure, status: :unprocessable_entity end diff --git a/app/operations/users/passwords/create_operation.rb b/app/operations/users/passwords/create_operation.rb deleted file mode 100644 index 803a4db..0000000 --- a/app/operations/users/passwords/create_operation.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Users - module Passwords - class CreateOperation < ApplicationOperation - option :params - option :contract, default: proc { Users::Passwords::SendInstructionsContract.new } - - def call - contract_params = yield validate(contract) - result = yield call_service(contract_params) - - Success(result) - end - - private - - def call_service(contract_params) - Users::Passwords::SendInstructionsService.new(params: contract_params).call - end - end - end -end diff --git a/app/operations/users/passwords/update_operation.rb b/app/operations/users/passwords/update_operation.rb deleted file mode 100644 index 7fb3832..0000000 --- a/app/operations/users/passwords/update_operation.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Users - module Passwords - class UpdateOperation < ApplicationOperation - option :params - option :contract, default: proc { Users::Passwords::UpdateContract.new } - - def call - contract_params = yield validate(contract) - result = yield call_service(contract_params) - - Success(result) - end - - private - - def call_service(contract_params) - Users::Passwords::UpdateService.new(params: contract_params).call - end - end - end -end diff --git a/app/operations/users/registrations/create_operation.rb b/app/operations/users/registrations/create_operation.rb deleted file mode 100644 index 3fa5d38..0000000 --- a/app/operations/users/registrations/create_operation.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Users - module Registrations - class CreateOperation < ApplicationOperation - option :params - option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application) - option :contract, default: proc { Users::Registrations::RegisterContract.new } - - def call - contract_params = yield validate(contract) - user = yield call_service(contract_params) - - Success(user) - end - - private - - def call_service(contract_params) - Users::Registrations::RegisterService.new(params: contract_params, doorkeeper_application:).call - end - end - end -end diff --git a/app/operations/v1/users_operation/passwords/create.rb b/app/operations/v1/users_operation/passwords/create.rb new file mode 100644 index 0000000..2018be5 --- /dev/null +++ b/app/operations/v1/users_operation/passwords/create.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module V1 + module UsersOperation + module Passwords + class Create < ApplicationOperation + option :params + option :contract, default: proc { V1::UsersContract::Passwords::SendInstructions.new } + + def call + contract_params = yield validate(contract) + result = yield call_service(contract_params) + + Success(result) + end + + private + + def call_service(contract_params) + UsersService::Passwords::SendInstructions.new(params: contract_params).call + end + end + end + end +end diff --git a/app/operations/v1/users_operation/passwords/update.rb b/app/operations/v1/users_operation/passwords/update.rb new file mode 100644 index 0000000..03c7ef7 --- /dev/null +++ b/app/operations/v1/users_operation/passwords/update.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module V1 + module UsersOperation + module Passwords + class Update < ApplicationOperation + option :params + option :contract, default: proc { V1::UsersContract::Passwords::Update.new } + + def call + contract_params = yield validate(contract) + result = yield call_service(contract_params) + + Success(result) + end + + private + + def call_service(contract_params) + UsersService::Passwords::Update.new(params: contract_params).call + end + end + end + end +end diff --git a/app/operations/v1/users_operation/registrations/create.rb b/app/operations/v1/users_operation/registrations/create.rb new file mode 100644 index 0000000..c73b84d --- /dev/null +++ b/app/operations/v1/users_operation/registrations/create.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module V1 + module UsersOperation + module Registrations + class Create < ApplicationOperation + option :params + option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application) + option :contract, default: proc { V1::UsersContract::Registrations::Register.new } + + def call + contract_params = yield validate(contract) + user = yield call_service(contract_params) + + Success(user) + end + + private + + def call_service(contract_params) + UsersService::Registrations::Register.new(params: contract_params, doorkeeper_application:).call + end + end + end + end +end diff --git a/app/services/doorkeeper/access_tokens/create_service.rb b/app/services/doorkeeper_service/access_tokens/create.rb similarity index 71% rename from app/services/doorkeeper/access_tokens/create_service.rb rename to app/services/doorkeeper_service/access_tokens/create.rb index 64513b0..3818b15 100644 --- a/app/services/doorkeeper/access_tokens/create_service.rb +++ b/app/services/doorkeeper_service/access_tokens/create.rb @@ -1,19 +1,27 @@ # frozen_string_literal: true -module Doorkeeper +module DoorkeeperService module AccessTokens - class CreateService < ApplicationService + class Create < ApplicationService option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application) option :user, type: Types.Instance(User) def call - access_token = yield create_resource(Doorkeeper::AccessToken) + access_token = yield create_access_token Success(access_token) end private + def create_access_token + access_token = Doorkeeper::AccessToken.new(params) + + return Success(access_token) if access_token.save + + resource_failure(access_token) + end + def params { resource_owner_id: user.id, diff --git a/app/services/users/create_service.rb b/app/services/users/create_service.rb deleted file mode 100644 index 5172351..0000000 --- a/app/services/users/create_service.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Users - class CreateService < ApplicationService - option :params, type: Types::Hash - - def call - user = yield create_resource(User) - - Success(user) - end - end -end diff --git a/app/services/users_service/create.rb b/app/services/users_service/create.rb new file mode 100644 index 0000000..6caaec6 --- /dev/null +++ b/app/services/users_service/create.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module UsersService + class Create < ApplicationService + option :params, type: Types::Hash + + def call + user = yield create_user + + Success(user) + end + + private + + def create_user + user = User.new(params) + + return Success(user) if user.save + + resource_failure(user) + end + end +end diff --git a/app/services/users/passwords/send_instructions_service.rb b/app/services/users_service/passwords/send_instructions.rb similarity index 91% rename from app/services/users/passwords/send_instructions_service.rb rename to app/services/users_service/passwords/send_instructions.rb index e847d8d..6e62ff2 100644 --- a/app/services/users/passwords/send_instructions_service.rb +++ b/app/services/users_service/passwords/send_instructions.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -module Users +module UsersService module Passwords - class SendInstructionsService < ApplicationService + class SendInstructions < ApplicationService option :params, type: Types::Hash def call diff --git a/app/services/users/passwords/update_service.rb b/app/services/users_service/passwords/update.rb similarity index 92% rename from app/services/users/passwords/update_service.rb rename to app/services/users_service/passwords/update.rb index 7528e79..9122db4 100644 --- a/app/services/users/passwords/update_service.rb +++ b/app/services/users_service/passwords/update.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -module Users +module UsersService module Passwords - class UpdateService < ApplicationService + class Update < ApplicationService option :params, type: Types::Hash def call diff --git a/app/services/users/registrations/register_service.rb b/app/services/users_service/registrations/register.rb similarity index 69% rename from app/services/users/registrations/register_service.rb rename to app/services/users_service/registrations/register.rb index 570bc1a..bf156d8 100644 --- a/app/services/users/registrations/register_service.rb +++ b/app/services/users_service/registrations/register.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -module Users +module UsersService module Registrations - class RegisterService < ApplicationService + class Register < ApplicationService include Supports::Doorkeeper::CustomRegisterResponse option :params, type: Types::Hash @@ -12,20 +12,19 @@ def call ActiveRecord::Base.transaction(requires_new: true) do user = yield create_user access_token = yield create_access_token(user) - response = body(user, access_token) - Success(response) + Success(access_token) end end private def create_user - Users::CreateService.new(params:).call + UsersService::Create.new(params:).call end def create_access_token(user) - Doorkeeper::AccessTokens::CreateService.new(user:, doorkeeper_application:).call + DoorkeeperService::AccessTokens::Create.new(user:, doorkeeper_application:).call end end end diff --git a/app/swagger_docs/responses/v1/user/sign_up_response.rb b/app/swagger_docs/responses/v1/user/sign_up_response.rb index 084ff78..b1e379c 100644 --- a/app/swagger_docs/responses/v1/user/sign_up_response.rb +++ b/app/swagger_docs/responses/v1/user/sign_up_response.rb @@ -11,9 +11,9 @@ class SignUpResponse key :type, :object key :required, %i[user access_token token_type expires_in refresh_token created_at] - property :user do + property :resource_owner do key :type, :object - key :required, %i[id type email] + key :required, %i[id email created_at updated_at] property :id do key :type, :string @@ -21,18 +21,54 @@ class SignUpResponse key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' end - property :type do + property :email do key :type, :string - key :example, 'User' + key :example, 'test@test.com' end - property :email do + property :created_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + + property :updated_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + end + + property :application do + key :type, :object + key :required, %i[id name created_at updated_at] + + property :id do key :type, :string - key :example, 'test@test.com' + key :format, :uuid + key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' + end + + property :name do + key :type, :string + key :example, 'Test App' + end + + property :scopes do + key :type, :string + key :example, 'public' + end + + property :created_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + + property :updated_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' end end - property :access_token do + property :token do key :type, :string key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' end @@ -56,6 +92,17 @@ class SignUpResponse key :type, :integer key :example, 1_661_719_307 end + + property :revoked_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + + property :id do + key :type, :string + key :format, :uuid + key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' + end end end end diff --git a/app/views/shared/partials/_meta.json.jbuilder b/app/views/shared/partials/_meta.json.jbuilder deleted file mode 100644 index 90c1c4f..0000000 --- a/app/views/shared/partials/_meta.json.jbuilder +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -json.meta do - json.partial! 'shared/partials/pagination', locals: { collection: } -end diff --git a/app/views/shared/partials/_pagination.json.jbuilder b/app/views/shared/partials/_pagination.json.jbuilder deleted file mode 100644 index 3e52c85..0000000 --- a/app/views/shared/partials/_pagination.json.jbuilder +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -json.pagination do - json.current collection.current_page - json.previous collection.prev_page - json.next collection.next_page - json.limit collection.limit_value - json.total_pages collection.total_pages - json.total_count collection.total_count -end diff --git a/config/application.rb b/config/application.rb index 61d0f0d..ab5fe89 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,7 +27,14 @@ class Application < Rails::Application # config.eager_load_paths << Rails.root.join("extras") # Require supports - Dir[Rails.root.join('lib/supports/**/*.rb')].each { |file| require file } + Dir[Rails.root.join('lib/supports/**/*.rb')].each do |file| + Rails.configuration.cache_classes ? require(file) : load(file) + end + + # Require decorators + Dir[Rails.root.join('lib/overrides/**/*._decorator.rb')].each do |file| + Rails.configuration.cache_classes ? require(file) : load(file) + end config.generators do |g| g.test_framework :test_unit, fixture: false diff --git a/config/initializers/blueprinter.rb b/config/initializers/blueprinter.rb new file mode 100644 index 0000000..b3cdeac --- /dev/null +++ b/config/initializers/blueprinter.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'oj' + +Blueprinter.configure do |config| + config.generator = Oj # default is JSON +end diff --git a/test/contracts/users/passwords/send_instructions_contract_test.rb b/test/contracts/users/passwords/send_instructions_contract_test.rb deleted file mode 100644 index ccb5c53..0000000 --- a/test/contracts/users/passwords/send_instructions_contract_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -module Users - module Passwords - class SendInstructionsContractTest < ActiveSupport::TestCase - CONTRACT = Users::Passwords::SendInstructionsContract.new - - def validate(payload = {}) - CONTRACT.call(payload) - end - - test 'validate#email' do - success?(validate({ email: 'test@test.com' }), :email, CONTRACT) - filled?(validate({ email: nil }), :email, CONTRACT) - filled?(validate({ email: '' }), :email, CONTRACT) - format?(validate({ email: 'invalid email' }), :email, CONTRACT) - end - end - end -end diff --git a/test/contracts/users/passwords/update_contract_test.rb b/test/contracts/users/passwords/update_contract_test.rb deleted file mode 100644 index 463ed76..0000000 --- a/test/contracts/users/passwords/update_contract_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -module Users - module Passwords - class UpdateContractTest < ActiveSupport::TestCase - CONTRACT = Users::Passwords::UpdateContract.new - - def validate(payload = {}) - CONTRACT.call(payload) - end - - test 'validate#reset_password_token' do - success?(validate({ reset_password_token: 'token' }), :reset_password_token, CONTRACT) - filled?(validate({ reset_password_token: nil }), :reset_password_token, CONTRACT) - filled?(validate({ reset_password_token: '' }), :reset_password_token, CONTRACT) - str?(validate({ reset_password_token: 1 }), :reset_password_token, CONTRACT) - end - - test 'validate#password' do - success?(validate({ password: '123456', password_confirmation: '123456' }), :password, CONTRACT) - filled?(validate({ password: nil }), :password, CONTRACT) - filled?(validate({ password: '' }), :password, CONTRACT) - min_size?(validate({ password: '12345' }), :password, 6, CONTRACT) - same_password?(validate({ password: '1234566', password_confirmation: '123456' }), :password, CONTRACT) - end - - test 'validate#password_confirmation' do - success?(validate({ password_confirmation: '123456', password: '123456' }), :password_confirmation, CONTRACT) - filled?(validate({ password_confirmation: nil }), :password_confirmation, CONTRACT) - filled?(validate({ password_confirmation: '' }), :password_confirmation, CONTRACT) - min_size?(validate({ password_confirmation: '12345' }), :password_confirmation, 6, CONTRACT) - same_password?(validate({ password_confirmation: '1234566', password: '123456' }), :password_confirmation, CONTRACT) - end - end - end -end diff --git a/test/contracts/users/registrations/register_contract_test.rb b/test/contracts/users/registrations/register_contract_test.rb deleted file mode 100644 index f64ee32..0000000 --- a/test/contracts/users/registrations/register_contract_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -module Users - module Registrations - class RegisterContractTest < ActiveSupport::TestCase - CONTRACT = Users::Registrations::RegisterContract.new - - def validate(payload = {}) - CONTRACT.call(payload) - end - - test 'validate#email' do - success?(validate({ email: 'test@test.com' }), :email, CONTRACT) - filled?(validate({ email: nil }), :email, CONTRACT) - filled?(validate({ email: '' }), :email, CONTRACT) - format?(validate({ email: 'invalid email' }), :email, CONTRACT) - end - - test 'validate#password' do - success?(validate({ password: '123456' }), :password, CONTRACT) - filled?(validate({ password: nil }), :password, CONTRACT) - filled?(validate({ password: '' }), :password, CONTRACT) - min_size?(validate({ password: '12345' }), :password, 6, CONTRACT) - end - end - end -end diff --git a/test/contracts/v1/passwords/send_instructions_test.rb b/test/contracts/v1/passwords/send_instructions_test.rb new file mode 100644 index 0000000..2a40937 --- /dev/null +++ b/test/contracts/v1/passwords/send_instructions_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'test_helper' + +module V1 + module UsersContract + module Passwords + class SendInstructionsTest < ActiveSupport::TestCase + CONTRACT = V1::UsersContract::Passwords::SendInstructions.new + + def validate(payload = {}) + CONTRACT.call(payload) + end + + test 'validate#email' do + success?(validate({ email: 'test@test.com' }), :email, CONTRACT) + filled?(validate({ email: nil }), :email, CONTRACT) + filled?(validate({ email: '' }), :email, CONTRACT) + format?(validate({ email: 'invalid email' }), :email, CONTRACT) + end + end + end + end +end diff --git a/test/contracts/v1/passwords/update_test.rb b/test/contracts/v1/passwords/update_test.rb new file mode 100644 index 0000000..fc9a078 --- /dev/null +++ b/test/contracts/v1/passwords/update_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'test_helper' + +module V1 + module UsersContract + module Passwords + class UpdateTest < ActiveSupport::TestCase + CONTRACT = V1::UsersContract::Passwords::Update.new + + def validate(payload = {}) + CONTRACT.call(payload) + end + + test 'validate#reset_password_token' do + success?(validate({ reset_password_token: 'token' }), :reset_password_token, CONTRACT) + filled?(validate({ reset_password_token: nil }), :reset_password_token, CONTRACT) + filled?(validate({ reset_password_token: '' }), :reset_password_token, CONTRACT) + str?(validate({ reset_password_token: 1 }), :reset_password_token, CONTRACT) + end + + test 'validate#password' do + success?(validate({ password: '123456', password_confirmation: '123456' }), :password, CONTRACT) + filled?(validate({ password: nil }), :password, CONTRACT) + filled?(validate({ password: '' }), :password, CONTRACT) + min_size?(validate({ password: '12345' }), :password, 6, CONTRACT) + same_password?(validate({ password: '1234566', password_confirmation: '123456' }), :password, CONTRACT) + end + + test 'validate#password_confirmation' do + success?(validate({ password_confirmation: '123456', password: '123456' }), :password_confirmation, CONTRACT) + filled?(validate({ password_confirmation: nil }), :password_confirmation, CONTRACT) + filled?(validate({ password_confirmation: '' }), :password_confirmation, CONTRACT) + min_size?(validate({ password_confirmation: '12345' }), :password_confirmation, 6, CONTRACT) + same_password?(validate({ password_confirmation: '1234566', password: '123456' }), :password_confirmation, CONTRACT) + end + end + end + end +end diff --git a/test/contracts/v1/users_contract/registrations/register_test.rb b/test/contracts/v1/users_contract/registrations/register_test.rb new file mode 100644 index 0000000..24e1f59 --- /dev/null +++ b/test/contracts/v1/users_contract/registrations/register_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'test_helper' + +module V1 + module UsersContract + module Registrations + class RegisterTest < ActiveSupport::TestCase + CONTRACT = V1::UsersContract::Registrations::Register.new + + def validate(payload = {}) + CONTRACT.call(payload) + end + + test 'validate#email' do + success?(validate({ email: 'test@test.com' }), :email, CONTRACT) + filled?(validate({ email: nil }), :email, CONTRACT) + filled?(validate({ email: '' }), :email, CONTRACT) + format?(validate({ email: 'invalid email' }), :email, CONTRACT) + end + + test 'validate#password' do + success?(validate({ password: '123456' }), :password, CONTRACT) + filled?(validate({ password: nil }), :password, CONTRACT) + filled?(validate({ password: '' }), :password, CONTRACT) + min_size?(validate({ password: '12345' }), :password, 6, CONTRACT) + end + end + end + end +end diff --git a/test/controllers/v1/users/registrations_controller_test.rb b/test/controllers/v1/users/registrations_controller_test.rb index 50cb325..ee279b8 100644 --- a/test/controllers/v1/users/registrations_controller_test.rb +++ b/test/controllers/v1/users/registrations_controller_test.rb @@ -23,7 +23,8 @@ def setup body = load_body(response) - assert_equal params[:email], body.user.email + assert_equal params[:email], body.resource_owner.email + assert_equal doorkeeper_application.id, body.application.id assert_response :created end diff --git a/test/operations/users/passwords/create_operation_test.rb b/test/operations/users/passwords/create_operation_test.rb deleted file mode 100644 index 2792057..0000000 --- a/test/operations/users/passwords/create_operation_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -module Users - module Passwords - class CreateOperationTest < ActiveSupport::TestCase - test 'should pass contract validation then calling the service' do - params_mock = mock - params_mock.expects(:to_h).returns(params_mock) - - contract_mock = mock - Users::Passwords::SendInstructionsContract.expects(:new).returns(contract_mock) - contract_mock.expects(:call).with(params_mock).returns(contract_mock) - contract_mock.expects(:success?).returns(true) - contract_mock.expects(:to_h).returns(contract_mock) - - service_mock = mock - Users::Passwords::SendInstructionsService.expects(:new).returns(service_mock) - service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true)) - - operation = Users::Passwords::CreateOperation.new(params: params_mock).call - - assert operation.success? - end - - test 'should return errors if something goes wrong while validating params' do - service_mock = mock - Users::Passwords::SendInstructionsService.expects(:new).returns(service_mock).never - - operation = Users::Passwords::CreateOperation.new(params: {}).call - - errors = contract_errors_parser(operation.failure) - - assert operation.failure? - assert_equal errors[:email], 'is missing' - end - - test 'should return errors if something goes wrong while executing service' do - params_mock = mock - params_mock.expects(:to_h).returns(params_mock) - - contract_mock = mock - Users::Passwords::SendInstructionsContract.expects(:new).returns(contract_mock) - contract_mock.expects(:call).with(params_mock).returns(contract_mock) - contract_mock.expects(:success?).returns(true) - contract_mock.expects(:to_h).returns(contract_mock) - - service_mock = mock - Users::Passwords::SendInstructionsService.expects(:new).returns(service_mock) - service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) - - operation = Users::Passwords::CreateOperation.new(params: params_mock).call - - assert operation.failure? - assert_equal :failed_because_of_me, operation.failure - end - end - end -end diff --git a/test/operations/users/passwords/update_operation_test.rb b/test/operations/users/passwords/update_operation_test.rb deleted file mode 100644 index b1c20c7..0000000 --- a/test/operations/users/passwords/update_operation_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -module Users - module Passwords - class UpdateOperationTest < ActiveSupport::TestCase - test 'should pass contract validation then calling the service' do - params_mock = mock - params_mock.expects(:to_h).returns(params_mock) - - contract_mock = mock - Users::Passwords::UpdateContract.expects(:new).returns(contract_mock) - contract_mock.expects(:call).with(params_mock).returns(contract_mock) - contract_mock.expects(:success?).returns(true) - contract_mock.expects(:to_h).returns(contract_mock) - - service_mock = mock - Users::Passwords::UpdateService.expects(:new).returns(service_mock) - service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true)) - - operation = Users::Passwords::UpdateOperation.new(params: params_mock).call - - assert operation.success? - end - - test 'should return errors if something goes wrong while validating params' do - service_mock = mock - Users::Passwords::UpdateService.expects(:new).returns(service_mock).never - - operation = Users::Passwords::UpdateOperation.new(params: {}).call - - errors = contract_errors_parser(operation.failure) - - assert operation.failure? - assert_equal errors[:password], 'is missing' - assert_equal errors[:password_confirmation], 'is missing' - end - - test 'should return errors if something goes wrong while executing service' do - params_mock = mock - params_mock.expects(:to_h).returns(params_mock) - - contract_mock = mock - Users::Passwords::UpdateContract.expects(:new).returns(contract_mock) - contract_mock.expects(:call).with(params_mock).returns(contract_mock) - contract_mock.expects(:success?).returns(true) - contract_mock.expects(:to_h).returns(contract_mock) - - service_mock = mock - Users::Passwords::UpdateService.expects(:new).returns(service_mock) - service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) - - operation = Users::Passwords::UpdateOperation.new(params: params_mock).call - - assert operation.failure? - assert_equal :failed_because_of_me, operation.failure - end - end - end -end diff --git a/test/operations/users/registrations/create_operation_test.rb b/test/operations/users/registrations/create_operation_test.rb deleted file mode 100644 index 40622ac..0000000 --- a/test/operations/users/registrations/create_operation_test.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -module Users - module Registrations - class CreateOperationTest < ActiveSupport::TestCase - attr_reader :doorkeeper_application - - def setup - @doorkeeper_application = create(:doorkeeper_application) - end - - test 'should pass contract validation then calling the service' do - params_mock = mock - params_mock.expects(:to_h).returns(params_mock) - - contract_mock = mock - Users::Registrations::RegisterContract.expects(:new).returns(contract_mock) - contract_mock.expects(:call).with(params_mock).returns(contract_mock) - contract_mock.expects(:success?).returns(true) - contract_mock.expects(:to_h).returns(contract_mock) - - service_mock = mock - Users::Registrations::RegisterService.expects(:new).returns(service_mock) - service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true)) - - operation = Users::Registrations::CreateOperation.new(params: params_mock, doorkeeper_application:).call - - assert operation.success? - end - - test 'should return errors if something goes wrong while validating params' do - service_mock = mock - Users::Registrations::RegisterService.expects(:new).returns(service_mock).never - - operation = Users::Registrations::CreateOperation.new(params: {}, doorkeeper_application:).call - - errors = contract_errors_parser(operation.failure) - - assert operation.failure? - assert_equal errors[:email], 'is missing' - assert_equal errors[:password], 'is missing' - end - - test 'should return errors if something goes wrong while executing service' do - params_mock = mock - params_mock.expects(:to_h).returns(params_mock) - - contract_mock = mock - Users::Registrations::RegisterContract.expects(:new).returns(contract_mock) - contract_mock.expects(:call).with(params_mock).returns(contract_mock) - contract_mock.expects(:success?).returns(true) - contract_mock.expects(:to_h).returns(contract_mock) - - service_mock = mock - Users::Registrations::RegisterService.expects(:new).returns(service_mock) - service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) - - operation = Users::Registrations::CreateOperation.new(params: params_mock, doorkeeper_application:).call - - assert operation.failure? - assert_equal :failed_because_of_me, operation.failure - end - end - end -end diff --git a/test/operations/v1/users_operation/passwords/create_test.rb b/test/operations/v1/users_operation/passwords/create_test.rb new file mode 100644 index 0000000..15228f2 --- /dev/null +++ b/test/operations/v1/users_operation/passwords/create_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'test_helper' + +module V1 + module UsersOperation + module Passwords + class CreateTest < ActiveSupport::TestCase + test 'should pass contract validation then calling the service' do + params_mock = mock + params_mock.expects(:to_h).returns(params_mock) + + contract_mock = mock + V1::UsersContract::Passwords::SendInstructions.expects(:new).returns(contract_mock) + contract_mock.expects(:call).with(params_mock).returns(contract_mock) + contract_mock.expects(:success?).returns(true) + contract_mock.expects(:to_h).returns(contract_mock) + + service_mock = mock + UsersService::Passwords::SendInstructions.expects(:new).returns(service_mock) + service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true)) + + operation = V1::UsersOperation::Passwords::Create.new(params: params_mock).call + + assert operation.success? + end + + test 'should return errors if something goes wrong while validating params' do + service_mock = mock + UsersService::Passwords::SendInstructions.expects(:new).returns(service_mock).never + + operation = V1::UsersOperation::Passwords::Create.new(params: {}).call + + errors = contract_errors_parser(operation.failure) + + assert operation.failure? + assert_equal errors[:email], 'is missing' + end + + test 'should return errors if something goes wrong while executing service' do + params_mock = mock + params_mock.expects(:to_h).returns(params_mock) + + contract_mock = mock + V1::UsersContract::Passwords::SendInstructions.expects(:new).returns(contract_mock) + contract_mock.expects(:call).with(params_mock).returns(contract_mock) + contract_mock.expects(:success?).returns(true) + contract_mock.expects(:to_h).returns(contract_mock) + + service_mock = mock + UsersService::Passwords::SendInstructions.expects(:new).returns(service_mock) + service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) + + operation = V1::UsersOperation::Passwords::Create.new(params: params_mock).call + + assert operation.failure? + assert_equal :failed_because_of_me, operation.failure + end + end + end + end +end diff --git a/test/operations/v1/users_operation/passwords/update_test.rb b/test/operations/v1/users_operation/passwords/update_test.rb new file mode 100644 index 0000000..fe8a692 --- /dev/null +++ b/test/operations/v1/users_operation/passwords/update_test.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'test_helper' + +module V1 + module UsersOperation + module Passwords + class UpdateTest < ActiveSupport::TestCase + test 'should pass contract validation then calling the service' do + params_mock = mock + params_mock.expects(:to_h).returns(params_mock) + + contract_mock = mock + V1::UsersContract::Passwords::Update.expects(:new).returns(contract_mock) + contract_mock.expects(:call).with(params_mock).returns(contract_mock) + contract_mock.expects(:success?).returns(true) + contract_mock.expects(:to_h).returns(contract_mock) + + service_mock = mock + UsersService::Passwords::Update.expects(:new).returns(service_mock) + service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true)) + + operation = V1::UsersOperation::Passwords::Update.new(params: params_mock).call + + assert operation.success? + end + + test 'should return errors if something goes wrong while validating params' do + service_mock = mock + UsersService::Passwords::Update.expects(:new).returns(service_mock).never + + operation = UsersOperation::Passwords::Update.new(params: {}).call + + errors = contract_errors_parser(operation.failure) + + assert operation.failure? + assert_equal errors[:password], 'is missing' + assert_equal errors[:password_confirmation], 'is missing' + end + + test 'should return errors if something goes wrong while executing service' do + params_mock = mock + params_mock.expects(:to_h).returns(params_mock) + + contract_mock = mock + V1::UsersContract::Passwords::Update.expects(:new).returns(contract_mock) + contract_mock.expects(:call).with(params_mock).returns(contract_mock) + contract_mock.expects(:success?).returns(true) + contract_mock.expects(:to_h).returns(contract_mock) + + service_mock = mock + UsersService::Passwords::Update.expects(:new).returns(service_mock) + service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) + + operation = V1::UsersOperation::Passwords::Update.new(params: params_mock).call + + assert operation.failure? + assert_equal :failed_because_of_me, operation.failure + end + end + end + end +end diff --git a/test/operations/v1/users_operation/registrations/create_test.rb b/test/operations/v1/users_operation/registrations/create_test.rb new file mode 100644 index 0000000..edc8f0e --- /dev/null +++ b/test/operations/v1/users_operation/registrations/create_test.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'test_helper' + +module V1 + module UsersOperation + module Registrations + class CreateTest < ActiveSupport::TestCase + attr_reader :doorkeeper_application + + def setup + @doorkeeper_application = create(:doorkeeper_application) + end + + test 'should pass contract validation then calling the service' do + params_mock = mock + params_mock.expects(:to_h).returns(params_mock) + + contract_mock = mock + V1::UsersContract::Registrations::Register.expects(:new).returns(contract_mock) + contract_mock.expects(:call).with(params_mock).returns(contract_mock) + contract_mock.expects(:success?).returns(true) + contract_mock.expects(:to_h).returns(contract_mock) + + service_mock = mock + UsersService::Registrations::Register.expects(:new).returns(service_mock) + service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true)) + + operation = V1::UsersOperation::Registrations::Create.new(params: params_mock, doorkeeper_application:).call + + assert operation.success? + end + + test 'should return errors if something goes wrong while validating params' do + service_mock = mock + UsersService::Registrations::Register.expects(:new).returns(service_mock).never + + operation = V1::UsersOperation::Registrations::Create.new(params: {}, doorkeeper_application:).call + + errors = contract_errors_parser(operation.failure) + + assert operation.failure? + assert_equal errors[:email], 'is missing' + assert_equal errors[:password], 'is missing' + end + + test 'should return errors if something goes wrong while executing service' do + params_mock = mock + params_mock.expects(:to_h).returns(params_mock) + + contract_mock = mock + V1::UsersContract::Registrations::Register.expects(:new).returns(contract_mock) + contract_mock.expects(:call).with(params_mock).returns(contract_mock) + contract_mock.expects(:success?).returns(true) + contract_mock.expects(:to_h).returns(contract_mock) + + service_mock = mock + UsersService::Registrations::Register.expects(:new).returns(service_mock) + service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) + + operation = V1::UsersOperation::Registrations::Create.new(params: params_mock, doorkeeper_application:).call + + assert operation.failure? + assert_equal :failed_because_of_me, operation.failure + end + end + end + end +end diff --git a/test/services/doorkeeper/access_tokens/create_service_test.rb b/test/services/doorkeeper_service/access_tokens/create_test.rb similarity index 79% rename from test/services/doorkeeper/access_tokens/create_service_test.rb rename to test/services/doorkeeper_service/access_tokens/create_test.rb index cf97f0c..98d5968 100644 --- a/test/services/doorkeeper/access_tokens/create_service_test.rb +++ b/test/services/doorkeeper_service/access_tokens/create_test.rb @@ -2,9 +2,9 @@ require 'test_helper' -module Doorkeeper +module DoorkeeperService module AccessTokens - class CreateServiceTest < ActiveSupport::TestCase + class CreateTest < ActiveSupport::TestCase attr_reader :doorkeeper_application, :user def setup @@ -14,7 +14,7 @@ def setup test 'should create doorkeeper access token' do assert_difference 'Doorkeeper::AccessToken.count', 1 do - service = Doorkeeper::AccessTokens::CreateService.new(doorkeeper_application:, user:).call + service = DoorkeeperService::AccessTokens::Create.new(doorkeeper_application:, user:).call assert service.success? end @@ -24,7 +24,7 @@ def setup Doorkeeper::AccessToken.any_instance.expects(:save).returns(false) assert_no_difference 'Doorkeeper::AccessToken.count' do - service = Doorkeeper::AccessTokens::CreateService.new(doorkeeper_application:, user:).call + service = DoorkeeperService::AccessTokens::Create.new(doorkeeper_application:, user:).call assert service.failure? end diff --git a/test/services/users/create_service_test.rb b/test/services/users_service/create_test.rb similarity index 72% rename from test/services/users/create_service_test.rb rename to test/services/users_service/create_test.rb index 86351c8..c96908f 100644 --- a/test/services/users/create_service_test.rb +++ b/test/services/users_service/create_test.rb @@ -2,8 +2,8 @@ require 'test_helper' -module Users - class CreateServiceTest < ActiveSupport::TestCase +module UsersService + class CreateTest < ActiveSupport::TestCase attr_reader :params def setup @@ -12,7 +12,7 @@ def setup test 'should create user' do assert_difference 'User.count', 1 do - service = Users::CreateService.new(params:).call + service = UsersService::Create.new(params:).call assert service.success? end @@ -22,7 +22,7 @@ def setup User.any_instance.expects(:save).returns(false) assert_no_difference 'User.count' do - service = Users::CreateService.new(params:).call + service = UsersService::Create.new(params:).call assert service.failure? end diff --git a/test/services/users/passwords/send_instructions_service_test.rb b/test/services/users_service/passwords/send_instructions_test.rb similarity index 78% rename from test/services/users/passwords/send_instructions_service_test.rb rename to test/services/users_service/passwords/send_instructions_test.rb index 9ccd547..b9974b7 100644 --- a/test/services/users/passwords/send_instructions_service_test.rb +++ b/test/services/users_service/passwords/send_instructions_test.rb @@ -2,9 +2,9 @@ require 'test_helper' -module Users +module UsersService module Passwords - class SendInstructionsServiceTest < ActiveSupport::TestCase + class SendInstructionsTest < ActiveSupport::TestCase attr_reader :user def setup @@ -15,7 +15,7 @@ def setup params = { email: user.email } assert_changes -> { user.updated_at } do - service = Users::Passwords::SendInstructionsService.new(params:).call + service = UsersService::Passwords::SendInstructions.new(params:).call user.reload @@ -28,7 +28,7 @@ def setup params = { email: 'test@development.com' } assert_no_changes -> { user.updated_at } do - service = Users::Passwords::SendInstructionsService.new(params:).call + service = UsersService::Passwords::SendInstructions.new(params:).call user.reload diff --git a/test/services/users/passwords/update_service_test.rb b/test/services/users_service/passwords/update_test.rb similarity index 83% rename from test/services/users/passwords/update_service_test.rb rename to test/services/users_service/passwords/update_test.rb index cd1a0ec..09f4edd 100644 --- a/test/services/users/passwords/update_service_test.rb +++ b/test/services/users_service/passwords/update_test.rb @@ -2,9 +2,9 @@ require 'test_helper' -module Users +module UsersService module Passwords - class UpdateServiceTest < ActiveSupport::TestCase + class UpdateTest < ActiveSupport::TestCase attr_reader :user def setup @@ -19,7 +19,7 @@ def setup password_confirmation: '123456' } assert_changes -> { user.updated_at } do - service = Users::Passwords::UpdateService.new(params:).call + service = UsersService::Passwords::Update.new(params:).call user.reload @@ -34,7 +34,7 @@ def setup password_confirmation: '123456' } assert_no_changes -> { user.updated_at } do - service = Users::Passwords::UpdateService.new(params:).call + service = UsersService::Passwords::Update.new(params:).call user.reload diff --git a/test/services/users/registrations/register_service_test.rb b/test/services/users_service/registrations/register_test.rb similarity index 70% rename from test/services/users/registrations/register_service_test.rb rename to test/services/users_service/registrations/register_test.rb index 9cef44c..d984421 100644 --- a/test/services/users/registrations/register_service_test.rb +++ b/test/services/users_service/registrations/register_test.rb @@ -2,9 +2,9 @@ require 'test_helper' -module Users +module UsersService module Registrations - class RegisterServiceTest < ActiveSupport::TestCase + class RegisterTest < ActiveSupport::TestCase attr_reader :params, :doorkeeper_application def setup @@ -14,22 +14,23 @@ def setup test 'should register user' do assert_difference(['User.count', 'Doorkeeper::AccessToken.count'], 1) do - service = Users::Registrations::RegisterService.new(params:, doorkeeper_application:).call + service = UsersService::Registrations::Register.new(params:, doorkeeper_application:).call - user = service.success[:user] + doorkeeper_token = service.success assert service.success? - assert_equal params[:email], user[:email] + assert_equal params[:email], doorkeeper_token.resource_owner.email + assert_equal doorkeeper_application.id, doorkeeper_token.application.id end end test 'should return error if something goes wrong while creating doorkeeper access token' do access_token_service_mock = mock - Doorkeeper::AccessTokens::CreateService.expects(:new).returns(access_token_service_mock) + DoorkeeperService::AccessTokens::Create.expects(:new).returns(access_token_service_mock) access_token_service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) assert_no_difference(['User.count', 'Doorkeeper::AccessToken.count']) do - service = Users::Registrations::RegisterService.new(params:, doorkeeper_application:).call + service = UsersService::Registrations::Register.new(params:, doorkeeper_application:).call assert service.failure? assert_equal :failed_because_of_me, service.failure @@ -38,11 +39,11 @@ def setup test 'should fail if user service returns failure' do user_service = mock - Users::CreateService.expects(:new).returns(user_service) + UsersService::Create.expects(:new).returns(user_service) user_service.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me)) assert_no_difference(['User.count', 'Doorkeeper::AccessToken.count']) do - service = Users::Registrations::RegisterService.new(params:, doorkeeper_application:).call + service = UsersService::Registrations::Register.new(params:, doorkeeper_application:).call assert service.failure? assert_equal :failed_because_of_me, service.failure From e122c2b5a7d637f717e2fd6316bd97ad467a84b7 Mon Sep 17 00:00:00 2001 From: nejdetkadir Date: Sat, 7 Jan 2023 18:39:54 +0300 Subject: [PATCH 2/3] refactor: pagination and sign in response --- app/blueprints/api/shared/meta_blueprint.rb | 6 +- .../doorkeeper/access_token_blueprint.rb | 2 + .../users_service/registrations/register.rb | 2 - .../responses/v1/user/sign_in_response.rb | 90 ++++++++++++++----- .../responses/v1/user/sign_up_response.rb | 15 ++-- config/application.rb | 4 +- config/initializers/doorkeeper.rb | 4 +- .../doorkeeper/error_response_decorator.rb} | 4 +- .../doorkeeper/token_response_decorator.rb | 12 +++ .../doorkeeper/custom_register_response.rb | 23 ----- .../doorkeeper/custom_token_response.rb | 22 ----- .../v1/users/tokens_controller_test.rb | 29 ++++-- test/supports/body_parser.rb | 2 +- 13 files changed, 124 insertions(+), 91 deletions(-) rename lib/{supports/doorkeeper/custom_error_response.rb => overrides/doorkeeper/error_response_decorator.rb} (93%) create mode 100644 lib/overrides/doorkeeper/token_response_decorator.rb delete mode 100644 lib/supports/doorkeeper/custom_register_response.rb delete mode 100644 lib/supports/doorkeeper/custom_token_response.rb diff --git a/app/blueprints/api/shared/meta_blueprint.rb b/app/blueprints/api/shared/meta_blueprint.rb index a6bae7e..e98f8ac 100644 --- a/app/blueprints/api/shared/meta_blueprint.rb +++ b/app/blueprints/api/shared/meta_blueprint.rb @@ -10,8 +10,10 @@ def self.render(collection) previous: collection.prev_page, next: collection.next_page, limit: collection.limit_value, - totalPages: collection.total_pages, - totalCount: collection.total_count + total_pages: collection.total_pages, + total_count: collection.total_count, + first_page: collection.first_page?, + last_page: collection.last_page? } } end diff --git a/app/blueprints/models/doorkeeper/access_token_blueprint.rb b/app/blueprints/models/doorkeeper/access_token_blueprint.rb index 7e38c93..14c6b0d 100644 --- a/app/blueprints/models/doorkeeper/access_token_blueprint.rb +++ b/app/blueprints/models/doorkeeper/access_token_blueprint.rb @@ -21,6 +21,8 @@ class AccessTokenBlueprint < Blueprinter::Base include_view :with_resource_owner include_view :with_application include_view :standard + + exclude :revoked_at end end end diff --git a/app/services/users_service/registrations/register.rb b/app/services/users_service/registrations/register.rb index bf156d8..baef87e 100644 --- a/app/services/users_service/registrations/register.rb +++ b/app/services/users_service/registrations/register.rb @@ -3,8 +3,6 @@ module UsersService module Registrations class Register < ApplicationService - include Supports::Doorkeeper::CustomRegisterResponse - option :params, type: Types::Hash option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application) diff --git a/app/swagger_docs/responses/v1/user/sign_in_response.rb b/app/swagger_docs/responses/v1/user/sign_in_response.rb index be27fc9..8dc66eb 100644 --- a/app/swagger_docs/responses/v1/user/sign_in_response.rb +++ b/app/swagger_docs/responses/v1/user/sign_in_response.rb @@ -9,9 +9,70 @@ class SignInResponse swagger_component do schema :UserSignInSuccessResponse do key :type, :object - key :required, %i[access_token token_type expires_in refresh_token created_at user] + key :required, %i[token token_type expires_in refresh_token created_at application resource_owner] - property :access_token do + property :resource_owner do + key :type, :object + key :required, %i[id email created_at updated_at] + + property :id do + key :type, :string + key :format, :uuid + key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' + end + + property :email do + key :type, :string + key :example, 'test@test.com' + end + + property :created_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + + property :updated_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + end + + property :application do + key :type, :object + key :required, %i[id name created_at updated_at] + + property :id do + key :type, :string + key :format, :uuid + key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' + end + + property :name do + key :type, :string + key :example, 'Test App' + end + + property :scopes do + key :type, :array + + items do + key :type, :string + key :example, 'public' + end + end + + property :created_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + + property :updated_at do + key :type, :datetime + key :example, '2021-11-21T12:00:00.000Z' + end + end + + property :token do key :type, :string key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' end @@ -33,28 +94,13 @@ class SignInResponse property :created_at do key :type, :integer - key :example, 1_661_719_659 + key :example, 1_661_719_307 end - property :user do - key :type, :object - key :required, %i[id type email] - - property :id do - key :type, :string - key :format, :uuid - key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' - end - - property :type do - key :type, :string - key :example, 'User' - end - - property :email do - key :type, :string - key :example, 'test@test.com' - end + property :id do + key :type, :string + key :format, :uuid + key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8' end end end diff --git a/app/swagger_docs/responses/v1/user/sign_up_response.rb b/app/swagger_docs/responses/v1/user/sign_up_response.rb index b1e379c..580d1af 100644 --- a/app/swagger_docs/responses/v1/user/sign_up_response.rb +++ b/app/swagger_docs/responses/v1/user/sign_up_response.rb @@ -9,7 +9,7 @@ class SignUpResponse swagger_component do schema :UserSignUpSuccessResponse do key :type, :object - key :required, %i[user access_token token_type expires_in refresh_token created_at] + key :required, %i[token token_type expires_in refresh_token created_at application resource_owner] property :resource_owner do key :type, :object @@ -53,8 +53,12 @@ class SignUpResponse end property :scopes do - key :type, :string - key :example, 'public' + key :type, :array + + items do + key :type, :string + key :example, 'public' + end end property :created_at do @@ -93,11 +97,6 @@ class SignUpResponse key :example, 1_661_719_307 end - property :revoked_at do - key :type, :datetime - key :example, '2021-11-21T12:00:00.000Z' - end - property :id do key :type, :string key :format, :uuid diff --git a/config/application.rb b/config/application.rb index ab5fe89..a96edbf 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,8 +31,8 @@ class Application < Rails::Application Rails.configuration.cache_classes ? require(file) : load(file) end - # Require decorators - Dir[Rails.root.join('lib/overrides/**/*._decorator.rb')].each do |file| + # Require overrides + Dir[Rails.root.join('lib/overrides/**/*.rb')].each do |file| Rails.configuration.cache_classes ? require(file) : load(file) end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 5adf74b..b68da96 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -490,5 +490,5 @@ # realm "Doorkeeper" end -Doorkeeper::OAuth::TokenResponse.prepend Supports::Doorkeeper::CustomTokenResponse -Doorkeeper::OAuth::ErrorResponse.prepend Supports::Doorkeeper::CustomErrorResponse +Doorkeeper::OAuth::TokenResponse.prepend Overrides::Doorkeeper::TokenResponseDecorator +Doorkeeper::OAuth::ErrorResponse.prepend Overrides::Doorkeeper::ErrorResponseDecorator diff --git a/lib/supports/doorkeeper/custom_error_response.rb b/lib/overrides/doorkeeper/error_response_decorator.rb similarity index 93% rename from lib/supports/doorkeeper/custom_error_response.rb rename to lib/overrides/doorkeeper/error_response_decorator.rb index 859c255..eedb47e 100644 --- a/lib/supports/doorkeeper/custom_error_response.rb +++ b/lib/overrides/doorkeeper/error_response_decorator.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true # https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/error_response.rb -module Supports +module Overrides module Doorkeeper - module CustomErrorResponse + module ErrorResponseDecorator def body { errors: custom_errors diff --git a/lib/overrides/doorkeeper/token_response_decorator.rb b/lib/overrides/doorkeeper/token_response_decorator.rb new file mode 100644 index 0000000..1bebdee --- /dev/null +++ b/lib/overrides/doorkeeper/token_response_decorator.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/token_response.rb +module Overrides + module Doorkeeper + module TokenResponseDecorator + def body + Models::Doorkeeper::AccessTokenBlueprint.render_as_hash(@token, view: :with_resource_owner_and_application) + end + end + end +end diff --git a/lib/supports/doorkeeper/custom_register_response.rb b/lib/supports/doorkeeper/custom_register_response.rb deleted file mode 100644 index 7113439..0000000 --- a/lib/supports/doorkeeper/custom_register_response.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -# https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/token_response.rb -module Supports - module Doorkeeper - module CustomRegisterResponse - def body(user, access_token, token_type = 'Bearer') - { - user: { - id: user.id, - type: user.class.name, - email: user.email - }, - access_token: access_token.token, - token_type:, - expires_in: access_token.expires_in, - refresh_token: access_token.refresh_token, - created_at: access_token.created_at.to_time.to_i - } - end - end - end -end diff --git a/lib/supports/doorkeeper/custom_token_response.rb b/lib/supports/doorkeeper/custom_token_response.rb deleted file mode 100644 index e256dd6..0000000 --- a/lib/supports/doorkeeper/custom_token_response.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -# https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/token_response.rb -module Supports - module Doorkeeper - module CustomTokenResponse - def body - current_user = @token.resource_owner - - additional_data = { - user: { - id: current_user.id, - type: current_user.class.name, - email: current_user.email - } - } - - super.merge(additional_data) - end - end - end -end diff --git a/test/controllers/v1/users/tokens_controller_test.rb b/test/controllers/v1/users/tokens_controller_test.rb index e15a2c6..90a4998 100644 --- a/test/controllers/v1/users/tokens_controller_test.rb +++ b/test/controllers/v1/users/tokens_controller_test.rb @@ -22,14 +22,22 @@ def setup body = load_body(response) - assert_respond_to body, :access_token + assert_respond_to body, :token assert_respond_to body, :refresh_token assert_respond_to body, :token_type assert_respond_to body, :expires_in assert_respond_to body, :created_at - assert_respond_to body.user, :email - assert_respond_to body.user, :type - assert_respond_to body.user, :id + assert_respond_to body.resource_owner, :id + assert_respond_to body.resource_owner, :email + assert_equal body.resource_owner.email, user.email + assert_respond_to body.resource_owner, :created_at + assert_respond_to body.resource_owner, :updated_at + assert_respond_to body.application, :id + assert_equal body.application.id, doorkeeper_application.id + assert_respond_to body.application, :name + assert_respond_to body.application, :scopes + assert_respond_to body.application, :created_at + assert_respond_to body.application, :updated_at assert_response :success end @@ -88,11 +96,22 @@ def setup body = load_body(response) - assert_respond_to body, :access_token + assert_respond_to body, :token assert_respond_to body, :refresh_token assert_respond_to body, :token_type assert_respond_to body, :expires_in assert_respond_to body, :created_at + assert_respond_to body.resource_owner, :id + assert_respond_to body.resource_owner, :email + assert_equal body.resource_owner.email, user.email + assert_respond_to body.resource_owner, :created_at + assert_respond_to body.resource_owner, :updated_at + assert_respond_to body.application, :id + assert_equal body.application.id, doorkeeper_application.id + assert_respond_to body.application, :name + assert_respond_to body.application, :scopes + assert_respond_to body.application, :created_at + assert_respond_to body.application, :updated_at assert_response :success end diff --git a/test/supports/body_parser.rb b/test/supports/body_parser.rb index b89d6ef..5cb498f 100644 --- a/test/supports/body_parser.rb +++ b/test/supports/body_parser.rb @@ -22,7 +22,7 @@ def parse_meta(response) def pagination_present?(response) pagination = parse_meta(response).pagination - expected_keys = %i[current previous next limit total_pages total_count] + expected_keys = %i[current previous next limit total_pages total_count first_page last_page] expected_keys == pagination.keys end From a7a5c8df89e87eab26ca9eeacb129538dcfa184b Mon Sep 17 00:00:00 2001 From: nejdetkadir Date: Sat, 7 Jan 2023 18:44:19 +0300 Subject: [PATCH 3/3] fix: rubocop offenses --- app/swagger_docs/responses/v1/user/sign_in_response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/swagger_docs/responses/v1/user/sign_in_response.rb b/app/swagger_docs/responses/v1/user/sign_in_response.rb index 8dc66eb..657cd50 100644 --- a/app/swagger_docs/responses/v1/user/sign_in_response.rb +++ b/app/swagger_docs/responses/v1/user/sign_in_response.rb @@ -54,7 +54,7 @@ class SignInResponse property :scopes do key :type, :array - + items do key :type, :string key :example, 'public'