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.
- checksums.yaml +4 -4
- data/app/controllers/audiences/contexts_controller.rb +38 -2
- data/app/controllers/audiences/scim_proxy_controller.rb +5 -10
- data/app/models/audiences/context.rb +24 -4
- data/app/models/audiences/context_users.rb +32 -0
- data/app/models/audiences/criterion.rb +20 -0
- data/app/models/audiences/criterion_users.rb +27 -0
- data/app/models/audiences/external_user.rb +21 -0
- data/app/models/audiences/membership.rb +8 -0
- data/app/models/audiences/users_search.rb +33 -0
- data/app/models/concerns/audiences/membership_group.rb +14 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20230725194934_create_audiences_contexts.rb +2 -1
- data/db/migrate/20231114210454_add_extra_users_to_audiences_context.rb +7 -0
- data/db/migrate/20231114210633_remove_criteria_from_audiences_context.rb +7 -0
- data/db/migrate/20231114215843_create_audiences_criterions.rb +12 -0
- data/db/migrate/20231115204932_add_users_to_audiences_criterion.rb +8 -0
- data/db/migrate/20231130165945_create_audiences_external_users.rb +13 -0
- data/db/migrate/20231130172718_create_audiences_memberships.rb +12 -0
- data/db/migrate/20231205140046_remove_serialized_users_from_criterion.rb +7 -0
- data/docs/CHANGELOG.md +3 -0
- data/docs/README.md +29 -14
- data/lib/audiences/notifications.rb +36 -0
- data/lib/audiences/scim/client.rb +29 -0
- data/lib/audiences/scim/resources_query.rb +56 -0
- data/lib/audiences/scim.rb +19 -0
- data/lib/audiences/version.rb +1 -1
- data/lib/audiences.rb +29 -1
- data/lib/tasks/audiences_tasks.rake +1 -0
- metadata +25 -9
- data/app/views/layouts/audiences/application.html.erb +0 -15
- data/lib/audiences/scim_proxy.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d908257a2da48f7c59f74138a5e92c160682d929826267aad9095f99510f2f3c
|
4
|
+
data.tar.gz: 313ff13a58a5e64827e4e52cf36b9f214913d9ce0aeb64f23962246552853f87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
private
|
6
|
+
resources = Audiences::Scim.resources(
|
7
|
+
type: params[:scim_path].to_sym,
|
8
|
+
filter: params[:filter]
|
9
|
+
)
|
14
10
|
|
15
|
-
|
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
|
-
|
8
|
-
|
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
|
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,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,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,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
|
data/docs/CHANGELOG.md
CHANGED
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
|
-
###
|
16
|
+
### Configuring the SCIM backend
|
17
17
|
|
18
|
-
|
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
|
-
|
20
|
+
I.e.:
|
21
21
|
|
22
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
41
|
+
or scheduling an AcitiveJob:
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
data/lib/audiences/version.rb
CHANGED
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.
|
59
|
+
::Audiences::Context.for(owner).tap(&block)
|
32
60
|
end
|
33
61
|
end
|
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
|
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:
|
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/
|
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/
|
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/
|
68
|
+
homepage: https://github.com/powerhome/audiences
|
53
69
|
licenses:
|
54
70
|
- MIT
|
55
71
|
metadata:
|
56
|
-
homepage_uri: https://github.com/powerhome/
|
57
|
-
source_code_uri: https://github.com/powerhome/
|
58
|
-
changelog_uri: https://github.com/powerhome/
|
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.
|
91
|
+
rubygems_version: 3.5.9
|
76
92
|
signing_key:
|
77
93
|
specification_version: 4
|
78
94
|
summary: Audiences system
|
data/lib/audiences/scim_proxy.rb
DELETED
@@ -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
|