audiences 0.1.0 → 1.0.1

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 (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