audiences 0.1.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/audiences/contexts_controller.rb +38 -2
  3. data/app/controllers/audiences/scim_proxy_controller.rb +5 -10
  4. data/app/models/audiences/context.rb +24 -4
  5. data/app/models/audiences/context_users.rb +32 -0
  6. data/app/models/audiences/criterion.rb +20 -0
  7. data/app/models/audiences/criterion_users.rb +27 -0
  8. data/app/models/audiences/external_user.rb +21 -0
  9. data/app/models/audiences/membership.rb +8 -0
  10. data/app/models/audiences/users_search.rb +33 -0
  11. data/app/models/concerns/audiences/membership_group.rb +14 -0
  12. data/config/routes.rb +2 -0
  13. data/db/migrate/20230725194934_create_audiences_contexts.rb +2 -1
  14. data/db/migrate/20231114210454_add_extra_users_to_audiences_context.rb +7 -0
  15. data/db/migrate/20231114210633_remove_criteria_from_audiences_context.rb +7 -0
  16. data/db/migrate/20231114215843_create_audiences_criterions.rb +12 -0
  17. data/db/migrate/20231115204932_add_users_to_audiences_criterion.rb +8 -0
  18. data/db/migrate/20231130165945_create_audiences_external_users.rb +13 -0
  19. data/db/migrate/20231130172718_create_audiences_memberships.rb +12 -0
  20. data/db/migrate/20231205140046_remove_serialized_users_from_criterion.rb +7 -0
  21. data/docs/CHANGELOG.md +3 -0
  22. data/docs/README.md +29 -14
  23. data/lib/audiences/notifications.rb +36 -0
  24. data/lib/audiences/scim/client.rb +29 -0
  25. data/lib/audiences/scim/resources_query.rb +56 -0
  26. data/lib/audiences/scim.rb +19 -0
  27. data/lib/audiences/version.rb +1 -1
  28. data/lib/audiences.rb +29 -1
  29. data/lib/tasks/audiences_tasks.rake +1 -0
  30. metadata +25 -9
  31. data/app/views/layouts/audiences/application.html.erb +0 -15
  32. data/lib/audiences/scim_proxy.rb +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 301017a77754bc9639d80ae7cf2a5260c1a6811e9bec5c49daefaca3b9833220
4
- data.tar.gz: bd65ad4e4baf6577420716c58b75921700cd866d43514cf7c18978d564b0d5e7
3
+ metadata.gz: d908257a2da48f7c59f74138a5e92c160682d929826267aad9095f99510f2f3c
4
+ data.tar.gz: 313ff13a58a5e64827e4e52cf36b9f214913d9ce0aeb64f23962246552853f87
5
5
  SHA512:
6
- metadata.gz: 0af804e8e6e477a2248b80339721897e3dd48ba27aa34f3524dbef8e5fd0cf09de9feaee0093fa2208030b7a194385e2313157a20f64cc3b536e719ac35d1b5d
7
- data.tar.gz: '08b631ec00c43818f1537ee222df54e196df0f6cb918e6cd45eb23c552a70b9d953a3a25c8774bf3d842360be406cdee4d9ffd89e54d33bd0760d8182348b637'
6
+ metadata.gz: 2849064c0ded8cca2a083b3f4aca9d8cbcf1ea6ab10102c96f6de76ffedd23439cbbe2fe2e8743988e5cb97cd375c0ccff69988376361fc5c305e3e75878e250
7
+ data.tar.gz: c0af5d5b298135abb4d362f277f2eb7f69855346be5ec262c223c42c71647c33f113b91902b3323191ede6e5787c87a26355b7c1b7387cc154171cd030196b55
@@ -3,13 +3,49 @@
3
3
  module Audiences
4
4
  class ContextsController < ApplicationController
5
5
  def show
6
- render json: current_context.as_json(only: %w[match_all], methods: %w[key criteria])
6
+ render_context Audiences.load(params.require(:key))
7
+ end
8
+
9
+ def update
10
+ render_context Audiences.update(params.require(:key), **context_params)
11
+ end
12
+
13
+ def users
14
+ users = (current_criterion || current_context).users
15
+ search = UsersSearch.new(scope: users,
16
+ query: params[:search],
17
+ limit: params[:limit],
18
+ offset: params[:offset])
19
+
20
+ render json: search
7
21
  end
8
22
 
9
23
  private
10
24
 
11
25
  def current_context
12
- @current_context ||= Audiences.load(params[:key])
26
+ @current_context ||= Audiences.load(params.require(:key))
27
+ end
28
+
29
+ def current_criterion
30
+ return unless params[:criterion_id]
31
+
32
+ @current_criterion ||= current_context.criteria.find(params[:criterion_id])
33
+ end
34
+
35
+ def render_context(context)
36
+ render json: context.as_json(
37
+ only: %i[match_all extra_users],
38
+ methods: %i[count],
39
+ include: { criteria: { only: %i[id groups], methods: %i[count] } }
40
+ )
41
+ end
42
+
43
+ def context_params
44
+ params.permit(
45
+ :match_all,
46
+ criteria: [groups: {}],
47
+ extra_users: [:id, :displayName, { photos: %i[type value] }]
48
+ ).to_h.symbolize_keys
13
49
  end
14
50
  end
15
51
  end
@@ -1,19 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "audiences/scim_proxy"
4
-
5
3
  module Audiences
6
4
  class ScimProxyController < ApplicationController
7
5
  def get
8
- status, body = ScimProxy.get(params[:scim_path], scim_params)
9
-
10
- render body: body, status: status, content_type: "application/json"
11
- end
12
-
13
- private
6
+ resources = Audiences::Scim.resources(
7
+ type: params[:scim_path].to_sym,
8
+ filter: params[:filter]
9
+ )
14
10
 
15
- def scim_params
16
- params.except(:scim_path, :controller, :action).permit!.to_h
11
+ render json: resources
17
12
  end
18
13
  end
19
14
  end
@@ -1,15 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Audiences
4
+ # Represents a context where the group of users (audience) is relevant.
5
+ # It includes the current matching users and the criteria to match these
6
+ # users (#criteria, #match_all, #extra_users).
7
+ #
4
8
  class Context < ApplicationRecord
9
+ include ::Audiences::MembershipGroup
10
+
5
11
  belongs_to :owner, polymorphic: true
12
+ has_many :criteria, class_name: "Audiences::Criterion",
13
+ autosave: true,
14
+ dependent: :destroy
15
+
16
+ before_save if: :match_all do
17
+ self.criteria = []
18
+ self.extra_users = []
19
+ end
6
20
 
7
- def key
8
- Audiences.sign(owner)
21
+ # Finds or creates a context for the given owner
22
+ #
23
+ # @private
24
+ # @return [Audiences::Context]
25
+ def self.for(owner)
26
+ where(owner: owner).first_or_create!
9
27
  end
10
28
 
11
- def criteria
12
- []
29
+ def refresh_users!
30
+ criteria.each(&:refresh_users!)
31
+ update!(users: ContextUsers.new(self).to_a)
32
+ Notifications.publish(self)
13
33
  end
14
34
  end
15
35
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ # @private
5
+ class ContextUsers
6
+ include Enumerable
7
+
8
+ def initialize(context)
9
+ @context = context
10
+ end
11
+
12
+ def each(...)
13
+ if @context.match_all
14
+ all_users.each(...)
15
+ else
16
+ matching_users.each(...)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def all_users
23
+ users = Scim.resources(type: :Users)
24
+ ExternalUser.wrap(users.all)
25
+ end
26
+
27
+ def matching_users
28
+ extras = ExternalUser.wrap(@context.extra_users)
29
+ [*extras, *@context.criteria.flat_map(&:users)].uniq
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ class Criterion < ApplicationRecord
5
+ include ::Audiences::MembershipGroup
6
+
7
+ belongs_to :context, class_name: "Audiences::Context"
8
+
9
+ def self.map(criteria)
10
+ Array(criteria).map { new(_1) }
11
+ end
12
+
13
+ def refresh_users!
14
+ update!(
15
+ users: CriterionUsers.new(groups || {}).to_a,
16
+ refreshed_at: Time.current
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ # @private
5
+ class CriterionUsers
6
+ include Enumerable
7
+
8
+ def initialize(groups)
9
+ @groups = groups
10
+ end
11
+
12
+ def each(...)
13
+ @groups.values
14
+ .map { |groups| groups_users(groups.pluck("id")) }
15
+ .reduce(&:&)
16
+ &.each(...)
17
+ end
18
+
19
+ private
20
+
21
+ def groups_users(group_ids)
22
+ filter = group_ids.map { "groups.value eq #{_1}" }.join(" OR ")
23
+ users = Audiences::Scim.resources(type: :Users, filter: filter)
24
+ ExternalUser.wrap(users.all)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ class ExternalUser < ApplicationRecord
5
+ has_many :memberships
6
+
7
+ def self.wrap(resources)
8
+ return [] unless resources&.any?
9
+
10
+ attrs = resources.map do |data|
11
+ { user_id: data["id"], data: data, created_at: Time.current, updated_at: Time.current }
12
+ end
13
+ upsert_all(attrs, unique_by: :user_id) # rubocop:disable Rails/SkipsModelValidations
14
+ where(user_id: attrs.pluck(:user_id))
15
+ end
16
+
17
+ def as_json(*)
18
+ data.as_json
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ class Membership < ApplicationRecord
5
+ belongs_to :external_user
6
+ belongs_to :group, polymorphic: true
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ class UsersSearch
5
+ DEFAULT_LIMIT = 20
6
+
7
+ def initialize(query:, limit: nil, offset: 0, scope: ExternalUser)
8
+ @scope = scope
9
+ @query = query
10
+ @limit = limit || DEFAULT_LIMIT
11
+ @offset = offset
12
+ end
13
+
14
+ def as_json(*)
15
+ {
16
+ users: users,
17
+ count: count,
18
+ }
19
+ end
20
+
21
+ delegate :count, to: :result
22
+
23
+ def users
24
+ @users ||= result.limit(@limit).offset(@offset)
25
+ end
26
+
27
+ private
28
+
29
+ def result
30
+ @result ||= @scope.where("data LIKE ?", "%#{@query}%")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ module MembershipGroup
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :memberships, as: :group, dependent: :delete_all
9
+ has_many :users, through: :memberships, source: :external_user, dependent: :delete_all
10
+
11
+ delegate :count, to: :users
12
+ end
13
+ end
14
+ end
data/config/routes.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  Audiences::Engine.routes.draw do
4
4
  get "/scim(/*scim_path)" => "scim_proxy#get", as: :scim_proxy
5
5
  get "/:key" => "contexts#show", as: :signed_context
6
+ get "/:key/users(/:criterion_id)" => "contexts#users", as: :users
7
+ put "/:key" => "contexts#update"
6
8
  end
7
9
 
8
10
  Rails.application.routes.draw do
@@ -5,8 +5,9 @@ class CreateAudiencesContexts < ActiveRecord::Migration[6.0]
5
5
  create_table :audiences_contexts do |t|
6
6
  t.references :owner, polymorphic: true, null: false, index: { unique: true }
7
7
  t.boolean :match_all, default: false, null: false
8
+ t.json :criteria
8
9
 
9
- t.timestamps
10
+ t.timestamps precision: 0
10
11
  end
11
12
  end
12
13
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddExtraUsersToAudiencesContext < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column :audiences_contexts, :extra_users, :json
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RemoveCriteriaFromAudiencesContext < ActiveRecord::Migration[6.0]
4
+ def change
5
+ remove_column :audiences_contexts, :criteria, :json
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateAudiencesCriterions < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :audiences_criterions do |t|
6
+ t.json :groups
7
+ t.references :context, null: false, foreign_key: false
8
+
9
+ t.timestamps precision: 0
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddUsersToAudiencesCriterion < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column :audiences_criterions, :users, :json
6
+ add_column :audiences_criterions, :refreshed_at, :datetime
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateAudiencesExternalUsers < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :audiences_external_users do |t|
6
+ t.string :user_id, null: false
7
+ t.json :data
8
+
9
+ t.timestamps precision: 0
10
+ t.index :user_id, unique: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateAudiencesMemberships < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :audiences_memberships do |t|
6
+ t.references :external_user, null: false, foreign_key: false
7
+ t.references :group, null: false, foreign_key: false, polymorphic: true
8
+
9
+ t.timestamps precision: 0
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RemoveSerializedUsersFromCriterion < ActiveRecord::Migration[6.0]
4
+ def change
5
+ remove_column :audiences_criterions, :users, :json
6
+ end
7
+ end
data/docs/CHANGELOG.md CHANGED
@@ -0,0 +1,3 @@
1
+ # Version 1.0 (2024-04-25)
2
+
3
+ * Inaugural release of Audiences
data/docs/README.md CHANGED
@@ -13,30 +13,45 @@ That can be done with a unobstrusive JS renderer like react-rails, or a custom o
13
13
  - The context URI: `audience_context_url(owner)` helper
14
14
  - The SCIM endpoint: `audience_scim_proxy_url` helper if using the [proxy](#configuring-the-scim-proxy), or the SCIM endpoint.
15
15
 
16
- ### Listening to audience changes
16
+ ### Configuring the SCIM backend
17
17
 
18
- **TBD**
18
+ The Audience::Scim should point to the real SCIM endpoint. The service allows you to configure the endpoint and the credentials/headers:
19
19
 
20
- ### Configuring the SCIM proxy
20
+ I.e.:
21
21
 
22
- The Audience::ScimProxy should point to the real SCIM endpoint. The proxy allows you to configure the endpoint and the credentials/headers:
22
+ ```ruby
23
+ Audiences::Scim.client = Audiences::Scim::Client.new(
24
+ uri: ENV.fetch("SCIM_V2_API"),
25
+ headers: { "Authorization" => "Bearer #{ENV.fetch('SCIM_V2_TOKEN')}" }
26
+ )
27
+ ```
23
28
 
24
- I.e.:
29
+ ### Listening to audience changes
30
+
31
+ The goal of audiences is to allow the app to keep up with a mutable group of people. To allow that, `Audiences` includes the `Audiences::Notifications` module, to allow the hosting app to subscribe to audiences related to a certain owner type, and react to that through a block:
25
32
 
26
33
  ```ruby
27
- # frozen_string_literal: true
34
+ Rails.application.config.to_prepare do
35
+ Audiences::Notifications.subscribe Team do |context|
36
+ team.update_memberships(context.users)
37
+ end
38
+ end
39
+ ```
28
40
 
29
- require "audiences/scim_proxy"
41
+ or scheduling an AcitiveJob:
30
42
 
31
- Audiences::ScimProxy.config = {
32
- uri: "http://super-secret-scim.com/scim/v2/",
33
- headers: {
34
- "Authorization": "Beaer very-secret"
35
- }
36
- debug: $stdout,
37
- }
43
+ ```ruby
44
+ Rails.application.config.to_prepare do
45
+ Audiences::Notifications.subscribe Group, job: UpdateGroupMembershipsJob
46
+ Audiences::Notifications.subscribe Team, job: UpdateTeamMembershipsJob.set(queue: "low")
47
+ end
38
48
  ```
39
49
 
50
+ You can find a working example in our dummy app:
51
+
52
+ - [initializer](../spec/dummy/config/initializers/audiences.rb)
53
+ - [job class](../spec/dummy/app/jobs/update_memberships_job.rb)
54
+
40
55
  ## Installation
41
56
 
42
57
  Add this line to your application's Gemfile:
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Audiences
4
+ # Handles notification of audience context changes. The notifications handled
5
+ # by this module are related to the membership composition of a context.
6
+ #
7
+ # For instance, when a user leaves a group, the app will be notified of changes
8
+ # to the members of that context so it can react to that. When the audience
9
+ # configuration of a context changes, a notification will also be published
10
+ # through `Audiences::Notifications`.
11
+ #
12
+ module Notifications
13
+ mattr_reader :subscriptions, default: {}
14
+
15
+ module_function
16
+
17
+ # Subscribes to audience changes to a specific owner type, either with a
18
+ # background job or a callable block
19
+ #
20
+ # @param owner_type [Class] the type of owners handled by the job or block
21
+ # @param job [Class<ActiveJob::Base>] job that will respond to audience changes
22
+ # @yield block that will handle the audience change if a job is not given
23
+ #
24
+ def subscribe(owner_type, job: nil, &cbk)
25
+ subscriptions[owner_type] = job&.method(:perform_later) || cbk
26
+ end
27
+
28
+ # Notifies that a given audience context was changed
29
+ #
30
+ # @param context [Audiences::Context] updated context
31
+ #
32
+ def publish(context)
33
+ subscriptions[context.owner.class]&.call(context)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module Audiences
8
+ module Scim
9
+ class Client
10
+ def initialize(uri:, headers: {})
11
+ @uri = uri
12
+ @headers = headers
13
+ end
14
+
15
+ def perform_request(method:, path:, query: {})
16
+ uri = URI.join(@uri, path.to_s)
17
+ uri.query = URI.encode_www_form(query)
18
+ request = ::Net::HTTP.const_get(method).new(uri, @headers)
19
+
20
+ http = ::Net::HTTP.new(uri.host, uri.port)
21
+ http.use_ssl = uri.scheme == "https"
22
+
23
+ response = http.request(request)
24
+
25
+ JSON.parse(response.body)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ module Audiences
8
+ module Scim
9
+ class ResourcesQuery
10
+ include Enumerable
11
+
12
+ attr_reader :query_options
13
+
14
+ def initialize(client, resource_type:, **query_options)
15
+ @client = client
16
+ @resource_type = resource_type
17
+ @query_options = query_options
18
+ end
19
+
20
+ def all
21
+ to_enum(:each, all: true)
22
+ end
23
+
24
+ def each(all: false, &block)
25
+ resources.each(&block)
26
+ next_page&.each(all: true, &block) if all
27
+ end
28
+
29
+ def resources
30
+ @resources ||= response.fetch("Resources", [])
31
+ end
32
+
33
+ def next_page?
34
+ start_index = response.fetch("startIndex", 1)
35
+ per_page = response["itemsPerPage"].to_i
36
+ total_results = response["totalResults"].to_i
37
+
38
+ start_index + per_page <= total_results
39
+ end
40
+
41
+ def next_page
42
+ return unless next_page?
43
+
44
+ current_page = @query_options.fetch(:page, 1)
45
+ ResourcesQuery.new(@client, resource_type: @resource_type, **@query_options,
46
+ page: current_page + 1)
47
+ end
48
+
49
+ private
50
+
51
+ def response
52
+ @response ||= @client.perform_request(path: @resource_type, method: :Get, query: @query_options)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "scim/client"
4
+ require_relative "scim/resources_query"
5
+
6
+ module Audiences
7
+ module Scim
8
+ mattr_accessor :client
9
+ mattr_accessor :defaults, default: Hash.new(attributes: "id,displayName")
10
+
11
+ module_function
12
+
13
+ def resources(type:, client: Scim.client, **options)
14
+ options = defaults.fetch(type, {}).merge(options)
15
+
16
+ ResourcesQuery.new(client, resource_type: type, **options)
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Audiences
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.1"
5
5
  end
data/lib/audiences.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "audiences/version"
4
+ require "audiences/scim"
5
+ require "audiences/notifications"
4
6
 
5
7
  # Audiences system
6
8
  # Audiences pushes notifications to your rails app when a
@@ -27,7 +29,33 @@ module_function
27
29
  # @return Audience::Context
28
30
  #
29
31
  def load(key)
32
+ locate_context(key, &:readonly!)
33
+ end
34
+
35
+ # Updates the given context
36
+ #
37
+ # Params might contain:
38
+ #
39
+ # match_all: Boolean
40
+ # criteria: Array<{ <group_type>: Array<Integer> }>
41
+ #
42
+ # @param token [String] a signed token (see #sign)
43
+ # @param params [Hash] the updated params
44
+ # @return Audience::Context
45
+ #
46
+ def update(key, criteria: [], **attrs)
47
+ locate_context(key) do |context|
48
+ context.update!(
49
+ criteria: ::Audiences::Criterion.map(criteria),
50
+ **attrs
51
+ )
52
+ context.refresh_users!
53
+ context.readonly!
54
+ end
55
+ end
56
+
57
+ private_class_method def locate_context(key, &block)
30
58
  owner = GlobalID::Locator.locate_signed(key, for: GID_RESOURCE)
31
- ::Audiences::Context.where(owner: owner).first_or_create!.tap(&:readonly!)
59
+ ::Audiences::Context.for(owner).tap(&block)
32
60
  end
33
61
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # desc "Explaining what the task does"
3
4
  # task :audiences do
4
5
  # # Task goes here
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: audiences
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Palhares
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-16 00:00:00.000000000 Z
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -39,23 +39,39 @@ files:
39
39
  - app/jobs/audiences/application_job.rb
40
40
  - app/models/audiences/application_record.rb
41
41
  - app/models/audiences/context.rb
42
- - app/views/layouts/audiences/application.html.erb
42
+ - app/models/audiences/context_users.rb
43
+ - app/models/audiences/criterion.rb
44
+ - app/models/audiences/criterion_users.rb
45
+ - app/models/audiences/external_user.rb
46
+ - app/models/audiences/membership.rb
47
+ - app/models/audiences/users_search.rb
48
+ - app/models/concerns/audiences/membership_group.rb
43
49
  - config/routes.rb
44
50
  - db/migrate/20230725194934_create_audiences_contexts.rb
51
+ - db/migrate/20231114210454_add_extra_users_to_audiences_context.rb
52
+ - db/migrate/20231114210633_remove_criteria_from_audiences_context.rb
53
+ - db/migrate/20231114215843_create_audiences_criterions.rb
54
+ - db/migrate/20231115204932_add_users_to_audiences_criterion.rb
55
+ - db/migrate/20231130165945_create_audiences_external_users.rb
56
+ - db/migrate/20231130172718_create_audiences_memberships.rb
57
+ - db/migrate/20231205140046_remove_serialized_users_from_criterion.rb
45
58
  - docs/CHANGELOG.md
46
59
  - docs/README.md
47
60
  - lib/audiences.rb
48
61
  - lib/audiences/engine.rb
49
- - lib/audiences/scim_proxy.rb
62
+ - lib/audiences/notifications.rb
63
+ - lib/audiences/scim.rb
64
+ - lib/audiences/scim/client.rb
65
+ - lib/audiences/scim/resources_query.rb
50
66
  - lib/audiences/version.rb
51
67
  - lib/tasks/audiences_tasks.rake
52
- homepage: https://github.com/powerhome/power-tools
68
+ homepage: https://github.com/powerhome/audiences
53
69
  licenses:
54
70
  - MIT
55
71
  metadata:
56
- homepage_uri: https://github.com/powerhome/power-tools
57
- source_code_uri: https://github.com/powerhome/power-tools
58
- changelog_uri: https://github.com/powerhome/power-tools/blob/main/packages/audiences/docs/CHANGELOG.md
72
+ homepage_uri: https://github.com/powerhome/audiences
73
+ source_code_uri: https://github.com/powerhome/audiences
74
+ changelog_uri: https://github.com/powerhome/audiences/blob/main/packages/audiences/docs/CHANGELOG.md
59
75
  rubygems_mfa_required: 'true'
60
76
  post_install_message:
61
77
  rdoc_options: []
@@ -72,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
88
  - !ruby/object:Gem::Version
73
89
  version: '0'
74
90
  requirements: []
75
- rubygems_version: 3.4.10
91
+ rubygems_version: 3.5.9
76
92
  signing_key:
77
93
  specification_version: 4
78
94
  summary: Audiences system
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Audiences</title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
7
-
8
- <%= stylesheet_link_tag "audiences/application", media: "all" %>
9
- </head>
10
- <body>
11
-
12
- <%= yield %>
13
-
14
- </body>
15
- </html>
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "uri"
5
-
6
- module Audiences
7
- module ScimProxy
8
- mattr_accessor :config do
9
- {}
10
- end
11
-
12
- module_function
13
-
14
- def get(path, query)
15
- response = perform_request(path: path, method: :Get, query: query)
16
-
17
- [response.code, response.body]
18
- end
19
-
20
- private_class_method def perform_request(method:, path:, query: {})
21
- uri = URI.join(config[:uri], path)
22
- uri.query = URI.encode_www_form(query)
23
- request = ::Net::HTTP.const_get(method).new(uri, config[:headers])
24
-
25
- http = ::Net::HTTP.new(uri.host, uri.port)
26
- http.use_ssl = uri.scheme == "https"
27
-
28
- http.request(request)
29
- end
30
- end
31
- end