cnfs-cognito 0.0.1.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +24 -0
  5. data/app/controllers/chown_requests_controller.rb +4 -0
  6. data/app/controllers/chown_results_controller.rb +4 -0
  7. data/app/controllers/cognito/application_controller.rb +6 -0
  8. data/app/controllers/endpoints_controller.rb +4 -0
  9. data/app/controllers/identifiers_controller.rb +4 -0
  10. data/app/controllers/login_controller.rb +45 -0
  11. data/app/controllers/metabase_cards_controller.rb +4 -0
  12. data/app/controllers/metabase_token_controller.rb +46 -0
  13. data/app/controllers/pools_controller.rb +21 -0
  14. data/app/controllers/users_controller.rb +4 -0
  15. data/app/models/chown_request.rb +13 -0
  16. data/app/models/chown_result.rb +14 -0
  17. data/app/models/cognito/application_record.rb +7 -0
  18. data/app/models/endpoint.rb +6 -0
  19. data/app/models/identifier.rb +5 -0
  20. data/app/models/metabase_card.rb +6 -0
  21. data/app/models/platform_event_consumer.rb +8 -0
  22. data/app/models/pool.rb +6 -0
  23. data/app/models/tenant.rb +5 -0
  24. data/app/models/user.rb +50 -0
  25. data/app/models/user_pool.rb +6 -0
  26. data/app/operations/chown_request_process.rb +27 -0
  27. data/app/operations/pool_create.rb +65 -0
  28. data/app/operations/segments/age.rb +65 -0
  29. data/app/operations/segments/birthday.rb +49 -0
  30. data/app/operations/segments/except_ids.rb +28 -0
  31. data/app/operations/segments/gender.rb +35 -0
  32. data/app/operations/segments_apply.rb +40 -0
  33. data/app/policies/chown_request_policy.rb +4 -0
  34. data/app/policies/chown_result_policy.rb +4 -0
  35. data/app/policies/cognito/application_policy.rb +6 -0
  36. data/app/policies/endpoint_policy.rb +4 -0
  37. data/app/policies/identifier_policy.rb +4 -0
  38. data/app/policies/metabase_card_policy.rb +4 -0
  39. data/app/policies/pool_policy.rb +4 -0
  40. data/app/policies/tenant_policy.rb +5 -0
  41. data/app/policies/user_policy.rb +20 -0
  42. data/app/resources/chown_request_resource.rb +5 -0
  43. data/app/resources/chown_result_resource.rb +8 -0
  44. data/app/resources/cognito/application_resource.rb +7 -0
  45. data/app/resources/endpoint_resource.rb +49 -0
  46. data/app/resources/identifier_resource.rb +6 -0
  47. data/app/resources/metabase_card_resource.rb +5 -0
  48. data/app/resources/pool_resource.rb +30 -0
  49. data/app/resources/user_resource.rb +27 -0
  50. data/app/services/metabase/config.rb +16 -0
  51. data/app/services/metabase/token_generator.rb +36 -0
  52. data/config/environment.rb +0 -0
  53. data/config/routes.rb +14 -0
  54. data/config/sidekiq.yml +3 -0
  55. data/config/spring.rb +3 -0
  56. data/db/migrate/20190310013542_create_endpoints.rb +13 -0
  57. data/db/migrate/20190317072927_create_pools.rb +12 -0
  58. data/db/migrate/20190317073055_create_users.rb +18 -0
  59. data/db/migrate/20190317073139_create_identifiers.rb +14 -0
  60. data/db/migrate/20190317073328_create_user_pools.rb +12 -0
  61. data/db/migrate/20191105100356_create_chown_requests.rb +9 -0
  62. data/db/migrate/20191106023315_create_chown_results.rb +12 -0
  63. data/db/migrate/20191126011832_create_metabase_cards.rb +11 -0
  64. data/db/migrate/20191129055605_add_birthday_to_user.rb +5 -0
  65. data/db/migrate/20191205023159_add_gender_to_user.rb +5 -0
  66. data/db/migrate/20191210050613_add_system_generated_flag_to_pools.rb +5 -0
  67. data/db/seeds/development/data.seeds.rb +0 -0
  68. data/db/seeds/development/tenants.seeds.rb +7 -0
  69. data/db/seeds/development/users.seeds.rb +29 -0
  70. data/lib/ros/cognito.rb +16 -0
  71. data/lib/ros/cognito/engine.rb +53 -0
  72. data/lib/ros/cognito/version.rb +7 -0
  73. data/lib/tasks/ros/cognito_tasks.rake +16 -0
  74. metadata +162 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2380c60fb607f73af8375a2c5d68f28ce9760e423bfdc52143931342696c29d
4
+ data.tar.gz: 1af8136e3a110ec001a98437257acdb3be0a2cadda59befa99f95992ccf6f609
5
+ SHA512:
6
+ metadata.gz: ee571566b474f16df473fe00029d019a29e30b0da691524199aec6c84db9b50b29c460402636838c7e59415bde774827adce192810c253c5c4aa887b5a6d715e
7
+ data.tar.gz: e1375e02d3f5f77b01eac33b6ecc89c34df931c9bc504dda3fe117dbe4ca5b3e9189f42b842031f5940d888c18cf1879ca36fe6379aa8ec844666e0081658564
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Robert Roach
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Ros::Cognito
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'ros-cognito'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install ros-cognito
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Ros::Cognito'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownRequestsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownResultsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cognito
4
+ class ApplicationController < ::ApplicationController
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EndpointsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IdentifiersController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LoginController < ApplicationController
4
+ # TODO: Move payload to LoginController Spec
5
+ # {
6
+ # "data": {
7
+ # "type": "login",
8
+ # "attributes": {
9
+ # "primary_identifier": "f92a8503-7b33-42b0-939d-5f948d5422ac"
10
+ # }
11
+ # }
12
+ # }
13
+
14
+ def create
15
+ # Get the user from the payload
16
+ json = request.body.read
17
+ # TODO: use controller concern jsonapi_params
18
+ pi = JSON.parse(json)['data']['attributes']['primary_identifier']
19
+
20
+ unless (user = User.find_by(primary_identifier: pi))
21
+ # TODO: Create a controller concern method and use that to render
22
+ # It should take the status (401), code (:unauthorized) and the title as params
23
+ # title param if nil defaults to code.to_s.capitalize
24
+ render(status: :unauthorized, json: { errors: [{ status: '401', code: :unauthorized, title: 'Unauthorized' }] })
25
+ return
26
+ end
27
+
28
+ # Add the 'sub_cognito' claim to the JWT and set the header
29
+ current_jwt.add_claims('sub_cognito' => user.to_urn)
30
+ current_jwt.add_claims('act_cognito' => user)
31
+
32
+ # Render some body back to the client
33
+ # TODO: Use controller concern serialize_resource to render
34
+ render(status: :ok, json: { 'data': [{
35
+ 'type': 'login',
36
+ 'id': user.id,
37
+ 'attributes': {
38
+ 'jwt': current_jwt.encode
39
+ },
40
+ 'links': {
41
+ 'self': 'http://example.com/things/1'
42
+ }
43
+ }] })
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetabaseCardsController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetabaseTokenController < Cognito::ApplicationController
4
+ DEFAULT_TYPE = 'question'
5
+
6
+ before_action :identify_resource, only: :show
7
+
8
+ def show
9
+ tokenizer = Metabase::TokenGenerator.new(payload: payload, expiry: params[:expiry])
10
+ if tokenizer.valid?
11
+ render json: { token: tokenizer.token }, root: :data
12
+ else
13
+ render json: { errors: tokenizer.errors.messages }, status: :unprocessable_entity
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def payload
20
+ type = params.delete(:type) || DEFAULT_TYPE
21
+
22
+ {
23
+ resource: { type => params[:id].to_i },
24
+ params: payload_params
25
+ }
26
+ end
27
+
28
+ def payload_params
29
+ options = { tenant: Apartment::Tenant.current }
30
+ return options if params[:params].blank?
31
+
32
+ options.merge(params[:params].to_unsafe_h.deep_symbolize_keys)
33
+ end
34
+
35
+ def identify_resource
36
+ return params if /^\d+$/.match?(params[:id])
37
+
38
+ card = MetabaseCard.find_by(identifier: params[:id])
39
+ if card.nil?
40
+ render json: { errors: 'Card not found' }, status: :not_found
41
+ return
42
+ end
43
+
44
+ params[:id] = card.card_id
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PoolsController < Cognito::ApplicationController
4
+ def create
5
+ res = PoolCreate.call(params: create_params, user: context[:user])
6
+ if res.success?
7
+ render json: json_resource(resource_class: PoolResource, record: res.model, context: context), status: :created
8
+ else
9
+ resource = ApplicationResource.new(res, nil)
10
+ handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def create_params
17
+ # TODO: Think of the proper pattern for this
18
+ creatable_fields = PoolResource.creatable_fields(context) + [:base_pool_id, { segments: {} }]
19
+ jsonapi_params.permit(creatable_fields)
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UsersController < Cognito::ApplicationController
4
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownRequest < Cognito::ApplicationRecord
4
+ has_many :chown_results
5
+
6
+ after_commit :create_chown_results, on: :create
7
+
8
+ private
9
+
10
+ def create_chown_results
11
+ ChownRequestProcess.call(id: id, from_ids: from_ids, to_id: to_id)
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownResult < Cognito::ApplicationRecord
4
+ belongs_to :chown_request
5
+
6
+ after_commit :spawn_chown_jobs, on: :create
7
+
8
+ private
9
+
10
+ def spawn_chown_jobs
11
+ Ros::ChownJob.set(queue: "#{service_name}_default")
12
+ .perform_later(from_id: from_id, to_id: to_id, chown_result_id: id)
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cognito
4
+ class ApplicationRecord < ::ApplicationRecord
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Endpoint < Cognito::ApplicationRecord
4
+ belongs_to_resource :target, polymorphic: true
5
+ # api_belongs_to :user, class_name: 'Ros::IAM::User'
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Identifier < Cognito::ApplicationRecord
4
+ belongs_to :user
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MetabaseCard < Cognito::ApplicationRecord
4
+ validates :card_id, presence: true, uniqueness: true
5
+ validates :identifier, presence: true, uniqueness: true
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PlatformEventConsumer
4
+ # Includes cross service event consumer functionality, e.g. 'process_document'
5
+ # include Ros::PlatformEventConsumerConcern
6
+
7
+ # Here include event consumer functionality specific to this service
8
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pool < Cognito::ApplicationRecord
4
+ has_many :user_pools
5
+ has_many :users, through: :user_pools
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tenant < Cognito::ApplicationRecord
4
+ include Ros::TenantConcern
5
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User < Cognito::ApplicationRecord
4
+ attribute :anonymous, :boolean, default: false
5
+
6
+ enum gender: { male: 'm', female: 'f', other: 'o' }
7
+
8
+ has_many :user_pools
9
+ has_many :pools, through: :user_pools
10
+
11
+ scope :birth_day, ->(date) { where("TO_CHAR(birthday, 'DD-MM') = TO_CHAR(DATE(?), 'DD-MM')", date) }
12
+ scope :birth_month, ->(date) { where("TO_CHAR(birthday, 'MM') = TO_CHAR(DATE(?), 'MM')", date) }
13
+
14
+ def self.reset
15
+ UserPool.delete_all
16
+ Pool.delete_all
17
+ User.delete_all
18
+ end
19
+
20
+ def self.file_fingerprint_attributes
21
+ column_names + %i[pool_name]
22
+ end
23
+
24
+ def self.load_document(file_name, column_map = nil, create = false)
25
+ processed_count = 0
26
+ column_map ||= default_headers
27
+ column_map = column_map.invert.symbolize_keys.invert
28
+ CSV.foreach(file_name, headers: true, header_converters: ->(name) { column_map[name] }) do |row|
29
+ if create
30
+ pool = Pool.find_or_create_by(name: row[:pool_name])
31
+ # row[:phone_number] = "+#{row[:phone_number]}"
32
+ row = row.to_h.except(:pool_name)
33
+ user = User.find_or_create_by(primary_identifier: row[:primary_identifier])
34
+ user.update(row.slice(:title, :last_name, :phone_number, :birthday, :gender))
35
+ processed_count += 1
36
+ pool.users << user
37
+ else
38
+ Rails.logger.debug "title: #{row[:title]} phone_number: #{row[:phone_number]} " \
39
+ "last_name: #{row[:last_name]} id: #{row[:primary_identifier]} pool: #{row[:pool_name]}"
40
+ end
41
+ end
42
+ processed_count
43
+ end
44
+
45
+ def self.default_headers
46
+ { 'Salutation' => :title, 'Last Name' => :last_name, 'Mobile' => :phone_number,
47
+ 'Unique Number' => :primary_identifier, 'Campaign Code' => :pool_name,
48
+ 'Birthday' => :birthday, 'Gender' => :gender }
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserPool < ApplicationRecord
4
+ belongs_to :user
5
+ belongs_to :pool
6
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChownRequestProcess < Ros::ActivityBase
4
+ # - {id} ChownRequest id
5
+ # - {from_ids} list of user ids to merge
6
+ # - {to_id} Final user id to receive all the data
7
+
8
+ # TODO: Needs improvement
9
+ # - Ensure that user id is confirmed while all the other users are not
10
+ # confirmed
11
+ # - Which permissions should this require?
12
+ # - For now, requesting user (identified via token), has to match the
13
+ # id passed in the params
14
+
15
+ step :create_chown_results
16
+
17
+ private
18
+
19
+ def create_chown_results(_ctx, id:, from_ids:, to_id:, **)
20
+ from_ids.each do |from_id|
21
+ %w[game instant-outcome voucher-service outcome].each do |service|
22
+ ChownResult.create(chown_request_id: id, service_name: service,
23
+ from_id: from_id, to_id: to_id, status: 'pending')
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PoolCreate < Ros::ActivityBase
4
+ step :check_permission
5
+ failed :not_permitted, Output(:success) => End(:failure)
6
+ step :should_be_segmented?, Output(:failure) => Id(:create_regular_pool), Output(:success) => Id(:find_base_pool)
7
+ step :create_regular_pool, Output(:success) => End(:success), Output(:failure) => End(:failure)
8
+
9
+ step :find_base_pool
10
+ failed :base_pool_not_found, Output(:success) => End(:failure)
11
+ step :apply_segment
12
+ failed :fail_to_apply_segment, Output(:success) => End(:failure)
13
+ step :create_pool
14
+ failed :pool_not_created, Output(:success) => End(:failure)
15
+ step :add_users_to_pool
16
+
17
+ def check_permission(_ctx, user:, **)
18
+ PoolPolicy.new(user, Pool.new).create?
19
+ end
20
+
21
+ def not_permitted(_ctx, errors:, **)
22
+ errors.add(:user, 'not permitted to create a pool')
23
+ end
24
+
25
+ def should_be_segmented?(_ctx, params:, **)
26
+ params.key?(:base_pool_id) && params.key?(:segments)
27
+ end
28
+
29
+ def create_regular_pool(ctx, params:, **)
30
+ ctx[:model] = Pool.create(params)
31
+ end
32
+
33
+ def find_base_pool(ctx, params:, **)
34
+ ctx[:base_pool] = Pool.find(params[:base_pool_id])
35
+ end
36
+
37
+ def base_pool_not_found(_ctx, errors:, params:, **)
38
+ errors.add(:base_pool, "Can't find base pool with id: #{params[:base_pool_id]}")
39
+ end
40
+
41
+ def apply_segment(ctx, base_pool:, params:, **)
42
+ segment_result = SegmentsApply.call(users: base_pool.users, segments: params[:segments])
43
+ return false unless segment_result.success?
44
+
45
+ ctx[:users] = segment_result.model
46
+ end
47
+
48
+ def fail_to_apply_segment(_ctx, errors:, **)
49
+ errors.add(:users, 'Failed to apply segment')
50
+ end
51
+
52
+ def create_pool(ctx, **)
53
+ ctx[:model] = Pool.create(name: "temporary_pool_#{SecureRandom.hex}", system_generated: true)
54
+ end
55
+
56
+ def pool_not_created(_ctx, errors:, **)
57
+ errors.add(:pool, "Can't create temporary pool")
58
+ end
59
+
60
+ def add_users_to_pool(_ctx, model:, users:, **)
61
+ return true if users.empty?
62
+
63
+ model.users << users
64
+ end
65
+ end