maestrano-connector-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/DEVELOPER.md +10 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +222 -0
- data/LICENSE +21 -0
- data/README.md +138 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/app/controllers/maestrano/account/group_users_controller.rb +20 -0
- data/app/controllers/maestrano/account/groups_controller.rb +22 -0
- data/app/controllers/maestrano/application_controller.rb +7 -0
- data/app/controllers/maestrano/auth/saml_controller.rb +31 -0
- data/app/controllers/maestrano/sessions_controller.rb +15 -0
- data/app/helpers/maestrano/connector/rails/session_helper.rb +17 -0
- data/app/jobs/maestrano/connector/rails/synchronization_job.rb +49 -0
- data/app/models/maestrano/connector/rails/complex_entity.rb +175 -0
- data/app/models/maestrano/connector/rails/concerns/entity.rb +231 -0
- data/app/models/maestrano/connector/rails/concerns/external.rb +13 -0
- data/app/models/maestrano/connector/rails/connector_logger.rb +9 -0
- data/app/models/maestrano/connector/rails/entity.rb +5 -0
- data/app/models/maestrano/connector/rails/external.rb +5 -0
- data/app/models/maestrano/connector/rails/id_map.rb +6 -0
- data/app/models/maestrano/connector/rails/organization.rb +60 -0
- data/app/models/maestrano/connector/rails/sub_entity_base.rb +32 -0
- data/app/models/maestrano/connector/rails/synchronization.rb +38 -0
- data/app/models/maestrano/connector/rails/user.rb +25 -0
- data/app/models/maestrano/connector/rails/user_organization_rel.rb +9 -0
- data/bin/rails +12 -0
- data/config/routes.rb +28 -0
- data/db/migrate/20151122162100_create_maestrano_connector_rails_users.rb +16 -0
- data/db/migrate/20151122162414_create_maestrano_connector_rails_organizations.rb +21 -0
- data/db/migrate/20151122162613_create_maestrano_connector_rails_user_organization_rels.rb +12 -0
- data/db/migrate/20151122163325_create_maestrano_connector_rails_synchronizations.rb +13 -0
- data/db/migrate/20151122163449_create_maestrano_connector_rails_id_maps.rb +18 -0
- data/lib/generators/connector/USAGE +2 -0
- data/lib/generators/connector/complex_entity_generator.rb +19 -0
- data/lib/generators/connector/install_generator.rb +70 -0
- data/lib/generators/connector/templates/admin_controller.rb +48 -0
- data/lib/generators/connector/templates/admin_index.html.erb +51 -0
- data/lib/generators/connector/templates/complex_entity_example/contact.rb +22 -0
- data/lib/generators/connector/templates/complex_entity_example/contact_and_lead.rb +56 -0
- data/lib/generators/connector/templates/complex_entity_example/contact_mapper.rb +17 -0
- data/lib/generators/connector/templates/complex_entity_example/lead.rb +22 -0
- data/lib/generators/connector/templates/complex_entity_example/lead_mapper.rb +15 -0
- data/lib/generators/connector/templates/complex_entity_example/person.rb +23 -0
- data/lib/generators/connector/templates/contact_and_lead.rb +55 -0
- data/lib/generators/connector/templates/entity.rb +48 -0
- data/lib/generators/connector/templates/example_entity.rb +26 -0
- data/lib/generators/connector/templates/external.rb +19 -0
- data/lib/generators/connector/templates/home_controller.rb +17 -0
- data/lib/generators/connector/templates/home_index.html.erb +42 -0
- data/lib/generators/connector/templates/oauth_controller.rb +45 -0
- data/lib/maestrano-connector-rails.rb +1 -0
- data/lib/maestrano/connector/rails.rb +13 -0
- data/maestrano-connector-rails.gemspec +197 -0
- data/maestrano.png +0 -0
- data/spec/dummy/README.md +1 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/admin_controller.rb +48 -0
- data/spec/dummy/app/controllers/application_controller.rb +8 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/home_controller.rb +17 -0
- data/spec/dummy/app/controllers/oauth_controller.rb +45 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/entities/example_entitiy.rb +26 -0
- data/spec/dummy/app/models/maestrano/connector/rails/entity.rb +10 -0
- data/spec/dummy/app/models/maestrano/connector/rails/external.rb +11 -0
- data/spec/dummy/app/views/admin/index.html.erb +51 -0
- data/spec/dummy/app/views/home/index.html.erb +36 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/maestrano.rb +135 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20151209134445_create_maestrano_connector_rails_users.maestrano_connector_rails.rb +17 -0
- data/spec/dummy/db/migrate/20151209134446_create_maestrano_connector_rails_organizations.maestrano_connector_rails.rb +22 -0
- data/spec/dummy/db/migrate/20151209134447_create_maestrano_connector_rails_user_organization_rels.maestrano_connector_rails.rb +13 -0
- data/spec/dummy/db/migrate/20151209134448_create_maestrano_connector_rails_synchronizations.maestrano_connector_rails.rb +14 -0
- data/spec/dummy/db/migrate/20151209134449_create_maestrano_connector_rails_id_maps.maestrano_connector_rails.rb +19 -0
- data/spec/dummy/db/schema.rb +83 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories.rb +33 -0
- data/spec/jobs/syncrhonization_job_spec.rb +53 -0
- data/spec/models/complex_entity_spec.rb +353 -0
- data/spec/models/connector_logger_spec.rb +21 -0
- data/spec/models/entity_spec.rb +457 -0
- data/spec/models/external_spec.rb +15 -0
- data/spec/models/id_map_spec.rb +12 -0
- data/spec/models/organizaztion_spec.rb +105 -0
- data/spec/models/sub_entity_base_spec.rb +51 -0
- data/spec/models/synchronization_spec.rb +75 -0
- data/spec/models/user_organization_rel_spec.rb +14 -0
- data/spec/models/user_spec.rb +15 -0
- data/spec/spec_helper.rb +19 -0
- metadata +327 -0
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Maestrano::Account::GroupUsersController < Maestrano::Rails::WebHookController
|
2
|
+
|
3
|
+
# DELETE /maestrano/account/groups/cld-1/users/usr-1
|
4
|
+
# DELETE /maestrano/account/groups/cld-1/users/usr-1/tenant
|
5
|
+
# Remove a user from a group
|
6
|
+
def destroy
|
7
|
+
# Set the right uid based on Maestrano.param('sso.creation_mode')
|
8
|
+
user_uid = Maestrano.mask_user(params[:id], params[:group_id])
|
9
|
+
group_uid = params[:group_id]
|
10
|
+
|
11
|
+
# Get the entities
|
12
|
+
user = User.find_by_provider_and_uid_and_tenant('maestrano', user_uid, params[:tenant])
|
13
|
+
organization = Organization.find_by_provider_and_uid_and_tenant('maestrano', group_uid, params[:tenant])
|
14
|
+
|
15
|
+
# Remove the user from the organization
|
16
|
+
organization.remove_member(user)
|
17
|
+
|
18
|
+
render json: {success: true}
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Maestrano::Account::GroupsController < Maestrano::Rails::WebHookController
|
2
|
+
|
3
|
+
# DELETE /maestrano/account/groups/cld-1
|
4
|
+
# DELETE /maestrano/account/groups/cld-1/tenant
|
5
|
+
# Delete an entire group
|
6
|
+
def destroy
|
7
|
+
# id
|
8
|
+
group_uid = params[:id]
|
9
|
+
|
10
|
+
# Get entity
|
11
|
+
organization = Organization.find_by_provider_and_uid_and_tenant('maestrano', group_uid, params[:tenant])
|
12
|
+
|
13
|
+
# Delete all relations
|
14
|
+
organization.user_company_rels.delete_all
|
15
|
+
|
16
|
+
# Delete the organization
|
17
|
+
organization.destroy
|
18
|
+
|
19
|
+
# Respond
|
20
|
+
render json: {success: true}
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Maestrano::Auth::SamlController < Maestrano::Rails::SamlBaseController
|
2
|
+
#== POST '/maestrano/auth/saml/consume'
|
3
|
+
# Final phase of the Single Sign-On handshake. Find or create
|
4
|
+
# the required resources (user and group) and sign the user
|
5
|
+
# in
|
6
|
+
#
|
7
|
+
# This action is left to you to customize based on your application
|
8
|
+
# requirements. Below is presented a potential way of writing
|
9
|
+
# the action.
|
10
|
+
#
|
11
|
+
# Assuming you have enabled maestrano on a user model
|
12
|
+
# called 'User' and a group model called 'Organization'
|
13
|
+
# the action could be written the following way
|
14
|
+
def consume
|
15
|
+
params[:tenant] ||= 'default'
|
16
|
+
user = Maestrano::Connector::Rails::User.find_or_create_for_maestrano(user_auth_hash, params[:tenant])
|
17
|
+
organization = Maestrano::Connector::Rails::Organization.find_or_create_for_maestrano(group_auth_hash, params[:tenant])
|
18
|
+
if user && organization
|
19
|
+
unless organization.member?(user)
|
20
|
+
organization.add_member(user)
|
21
|
+
end
|
22
|
+
|
23
|
+
session[:tenant] = params[:tenant]
|
24
|
+
session[:uid] = user.uid
|
25
|
+
session[:org_uid] = organization.uid
|
26
|
+
session[:"role_#{organization.uid}"] = user_group_rel_hash[:role]
|
27
|
+
end
|
28
|
+
|
29
|
+
redirect_to main_app.root_path
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Maestrano
|
2
|
+
class SessionsController < ApplicationController
|
3
|
+
# Logout
|
4
|
+
def destroy
|
5
|
+
session.delete(:uid)
|
6
|
+
session.delete(:"role_#{session[:org_uid]}")
|
7
|
+
session.delete(:org_uid)
|
8
|
+
session.delete(:tenant)
|
9
|
+
session.delete(:current_user_id)
|
10
|
+
@current_user = nil
|
11
|
+
|
12
|
+
redirect_to root_url
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Maestrano::Connector::Rails
|
2
|
+
module SessionHelper
|
3
|
+
|
4
|
+
def is_admin?(user, organization)
|
5
|
+
organization.member?(user) && session[:"role_#{organization.uid}"] && ['Admin', 'Super Admin'].include?(session[:"role_#{organization.uid}"])
|
6
|
+
end
|
7
|
+
|
8
|
+
def current_organization
|
9
|
+
Organization.find_by(uid: session[:org_uid], tenant: session[:tenant])
|
10
|
+
end
|
11
|
+
|
12
|
+
def current_user
|
13
|
+
@current_user ||= User.find_by(uid: session[:uid], tenant: session[:tenant])
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Maestrano::Connector::Rails
|
2
|
+
class SynchronizationJob < Struct.new(:organization, :opts)
|
3
|
+
|
4
|
+
# Supported options:
|
5
|
+
# * :forced => true synchronization has been triggered manually (for logging purposes only)
|
6
|
+
# * :only_entities => [person, tasks_list]
|
7
|
+
# * :full_sync => true synchronization is performed without date filtering
|
8
|
+
# * :connec_preemption => true|false : preemption is always|never given to connec in case of conflict (if not set, the most recently updated entity is kept)
|
9
|
+
def perform
|
10
|
+
ConnectorLogger.log('info', organization, "Start synchronization, opts=#{opts}")
|
11
|
+
current_synchronization = Synchronization.create_running(organization)
|
12
|
+
|
13
|
+
begin
|
14
|
+
last_synchronization = Synchronization.where(organization_id: organization.id, status: 'SUCCESS', partial: false).order(updated_at: :desc).first
|
15
|
+
connec_client = Maestrano::Connec::Client.new(organization.uid)
|
16
|
+
external_client = External.get_client(organization)
|
17
|
+
|
18
|
+
if opts[:only_entities]
|
19
|
+
ConnectorLogger.log('info', organization, "Synchronization is partial and will synchronize only #{opts[:only_entities].join(' ')}")
|
20
|
+
# The synchronization is marked as partial and will not be considered as the last-synchronization for the next sync
|
21
|
+
current_synchronization.set_partial
|
22
|
+
opts[:only_entities].each do |entity|
|
23
|
+
sync_entity(entity, organization, connec_client, external_client, last_synchronization, opts)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
organization.synchronized_entities.select{|k, v| v}.keys.each do |entity|
|
27
|
+
sync_entity(entity.to_s, organization, connec_client, external_client, last_synchronization, opts)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
ConnectorLogger.log('info', organization, "Finished synchronization, organization=#{organization.uid}, status=success")
|
32
|
+
current_synchronization.set_success
|
33
|
+
rescue => e
|
34
|
+
ConnectorLogger.log('info', organization, "Finished synchronization, organization=#{organization.uid}, status=error, message=#{e.message} backtrace=#{e.backtrace.join("\n\t")}")
|
35
|
+
current_synchronization.set_error(e.message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sync_entity(entity, organization, connec_client, external_client, last_synchronization, opts)
|
40
|
+
entity_instance = "Entities::#{entity.titleize.split.join}".constantize.new
|
41
|
+
|
42
|
+
external_entities = entity_instance.get_external_entities(external_client, last_synchronization, organization, opts)
|
43
|
+
connec_entities = entity_instance.get_connec_entities(connec_client, last_synchronization, organization, opts)
|
44
|
+
entity_instance.consolidate_and_map_data(connec_entities, external_entities, organization, opts)
|
45
|
+
entity_instance.push_entities_to_external(external_client, connec_entities, organization)
|
46
|
+
entity_instance.push_entities_to_connec(connec_client, external_entities, organization)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Maestrano::Connector::Rails
|
2
|
+
class ComplexEntity
|
3
|
+
|
4
|
+
@@external_name = External.external_name
|
5
|
+
|
6
|
+
# -------------------------------------------------------------
|
7
|
+
# Complex specific methods
|
8
|
+
# Those methods needs to be implemented in each complex entity
|
9
|
+
# -------------------------------------------------------------
|
10
|
+
def connec_entities_names
|
11
|
+
raise "Not implemented"
|
12
|
+
end
|
13
|
+
|
14
|
+
def external_entities_names
|
15
|
+
raise "Not implemented"
|
16
|
+
end
|
17
|
+
|
18
|
+
# input : {
|
19
|
+
# connec_entities_names[0]: [unmapped_connec_entitiy1, unmapped_connec_entitiy2],
|
20
|
+
# connec_entities_names[1]: [unmapped_connec_entitiy3, unmapped_connec_entitiy4]
|
21
|
+
# }
|
22
|
+
# output : {
|
23
|
+
# connec_entities_names[0]: {
|
24
|
+
# external_entities_names[0]: [unmapped_connec_entitiy1, unmapped_connec_entitiy2]
|
25
|
+
# },
|
26
|
+
# connec_entities_names[1]: {
|
27
|
+
# external_entities_names[0]: [unmapped_connec_entitiy3],
|
28
|
+
# external_entities_names[1]: [unmapped_connec_entitiy4]
|
29
|
+
# }
|
30
|
+
# }
|
31
|
+
def connec_model_to_external_model!(connec_hash_of_entities)
|
32
|
+
raise "Not implemented"
|
33
|
+
end
|
34
|
+
|
35
|
+
# input : {
|
36
|
+
# external_entities_names[0]: [unmapped_external_entity1}, unmapped_external_entity2],
|
37
|
+
# external_entities_names[1]: [unmapped_external_entity3}, unmapped_external_entity4]
|
38
|
+
# }
|
39
|
+
# output : {
|
40
|
+
# external_entities_names[0]: {
|
41
|
+
# connec_entities_names[0]: [unmapped_external_entity1],
|
42
|
+
# connec_entities_names[1]: [unmapped_external_entity2]
|
43
|
+
# },
|
44
|
+
# external_entities_names[1]: {
|
45
|
+
# connec_entities_names[0]: [unmapped_external_entity3, unmapped_external_entity4]
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
def external_model_to_connec_model!(external_hash_of_entities)
|
49
|
+
raise "Not implemented"
|
50
|
+
end
|
51
|
+
|
52
|
+
# -------------------------------------------------------------
|
53
|
+
# General methods
|
54
|
+
# -------------------------------------------------------------
|
55
|
+
def map_to_external_with_idmap(entity, organization, connec_entity_name, external_entity_name, sub_entity_instance)
|
56
|
+
idmap = IdMap.find_by(connec_id: entity['id'], connec_entity: connec_entity_name.downcase, external_entity: external_entity_name.downcase, organization_id: organization.id)
|
57
|
+
|
58
|
+
if idmap && idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
|
59
|
+
ConnectorLogger.log('info', organization, "Discard Connec! #{connec_entity_name} : #{entity}")
|
60
|
+
nil
|
61
|
+
else
|
62
|
+
{entity: sub_entity_instance.map_to(external_entity_name, entity, organization), idmap: idmap || IdMap.create(connec_id: entity['id'], connec_entity: connec_entity_name.downcase, external_entity: external_entity_name.downcase, organization_id: organization.id)}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# -------------------------------------------------------------
|
67
|
+
# Entity equivalent methods
|
68
|
+
# -------------------------------------------------------------
|
69
|
+
def get_connec_entities(client, last_synchronization, organization, opts={})
|
70
|
+
entities = ActiveSupport::HashWithIndifferentAccess.new
|
71
|
+
|
72
|
+
self.connec_entities_names.each do |connec_entity_name|
|
73
|
+
sub_entity_instance = "Entities::SubEntities::#{connec_entity_name.titleize.split.join}".constantize.new
|
74
|
+
entities[connec_entity_name] = sub_entity_instance.get_connec_entities(client, last_synchronization, organization, opts)
|
75
|
+
end
|
76
|
+
entities
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_external_entities(client, last_synchronization, organization, opts={})
|
80
|
+
entities = ActiveSupport::HashWithIndifferentAccess.new
|
81
|
+
|
82
|
+
self.external_entities_names.each do |external_entity_name|
|
83
|
+
sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
|
84
|
+
entities[external_entity_name] = sub_entity_instance.get_external_entities(client, last_synchronization, organization, opts)
|
85
|
+
end
|
86
|
+
entities
|
87
|
+
end
|
88
|
+
|
89
|
+
def consolidate_and_map_data(connec_entities, external_entities, organization, opts)
|
90
|
+
external_model_to_connec_model!(external_entities)
|
91
|
+
connec_model_to_external_model!(connec_entities)
|
92
|
+
|
93
|
+
external_entities.each do |external_entity_name, entities_in_connec_model|
|
94
|
+
entities_in_connec_model.each do |connec_entity_name, entities|
|
95
|
+
sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
|
96
|
+
|
97
|
+
entities.map!{|entity|
|
98
|
+
idmap = IdMap.find_by(external_id: sub_entity_instance.get_id_from_external_entity_hash(entity), external_entity: external_entity_name.downcase, connec_entity: connec_entity_name.downcase, organization_id: organization.id)
|
99
|
+
|
100
|
+
# No idmap: creating one, nothing else to do
|
101
|
+
unless idmap
|
102
|
+
next {entity: sub_entity_instance.map_to(connec_entity_name, entity, organization), idmap: IdMap.create(external_id: sub_entity_instance.get_id_from_external_entity_hash(entity), external_entity: external_entity_name.downcase, connec_entity: connec_entity_name.downcase, organization_id: organization.id)}
|
103
|
+
end
|
104
|
+
|
105
|
+
# Entity has not been modified since its last push to connec!
|
106
|
+
if idmap.last_push_to_connec && idmap.last_push_to_connec > sub_entity_instance.get_last_update_date_from_external_entity_hash(entity)
|
107
|
+
ConnectorLogger.log('info', organization, "Discard #{@@external_name} #{external_entity_name} : #{entity}")
|
108
|
+
next nil
|
109
|
+
end
|
110
|
+
|
111
|
+
equivalent_connec_entities = connec_entities[connec_entity_name][external_entity_name] || []
|
112
|
+
# Check for conflict with entities from connec!
|
113
|
+
if idmap.connec_id && connec_entity = equivalent_connec_entities.detect{|connec_entity| connec_entity['id'] == idmap.connec_id}
|
114
|
+
# We keep the most recently updated entity
|
115
|
+
if !opts[:connec_preemption].nil?
|
116
|
+
keep_external = !opts[:connec_preemption]
|
117
|
+
else
|
118
|
+
keep_external = connec_entity['updated_at'] < sub_entity_instance.get_last_update_date_from_external_entity_hash(entity)
|
119
|
+
end
|
120
|
+
|
121
|
+
if keep_external
|
122
|
+
ConnectorLogger.log('info', organization, "Conflict between #{@@external_name} #{external_entity_name} #{entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from #{@@external_name} kept")
|
123
|
+
equivalent_connec_entities.delete(connec_entity)
|
124
|
+
{entity: sub_entity_instance.map_to(connec_entity_name, entity, organization), idmap: idmap}
|
125
|
+
else
|
126
|
+
ConnectorLogger.log('info', organization, "Conflict between #{@@external_name} #{external_entity_name} #{entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from Connec! kept")
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
|
130
|
+
else
|
131
|
+
{entity: sub_entity_instance.map_to(connec_entity_name, entity, organization), idmap: idmap}
|
132
|
+
end
|
133
|
+
}.compact!
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
connec_entities.each do |connec_entity_name, entities_in_external_model|
|
138
|
+
entities_in_external_model.each do |external_entity_name, entities|
|
139
|
+
sub_entity_instance = "Entities::SubEntities::#{connec_entity_name.titleize.split.join}".constantize.new
|
140
|
+
entities.map!{|entity|
|
141
|
+
self.map_to_external_with_idmap(entity, organization, connec_entity_name, external_entity_name, sub_entity_instance)
|
142
|
+
}.compact!
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# input : {
|
148
|
+
# external_entities_names[0]: {
|
149
|
+
# connec_entities_names[0]: [mapped_external_entity1],
|
150
|
+
# connec_entities_names[1]: [mapped_external_entity2]
|
151
|
+
# },
|
152
|
+
# external_entities_names[1]: {
|
153
|
+
# connec_entities_names[0]: [mapped_external_entity3, mapped_external_entity4]
|
154
|
+
# }
|
155
|
+
# }
|
156
|
+
def push_entities_to_connec(connec_client, mapped_external_entities_with_idmaps, organization)
|
157
|
+
mapped_external_entities_with_idmaps.each do |external_entity_name, entities_in_connec_model|
|
158
|
+
sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
|
159
|
+
entities_in_connec_model.each do |connec_entity_name, mapped_entities_with_idmaps|
|
160
|
+
sub_entity_instance.push_entities_to_connec_to(connec_client, mapped_entities_with_idmaps, connec_entity_name, organization)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def push_entities_to_external(external_client, mapped_connec_entities_with_idmaps, organization)
|
167
|
+
mapped_connec_entities_with_idmaps.each do |connec_entity_name, entities_in_external_model|
|
168
|
+
sub_entity_instance = "Entities::SubEntities::#{connec_entity_name.titleize.split.join}".constantize.new
|
169
|
+
entities_in_external_model.each do |external_entity_name, mapped_entities_with_idmaps|
|
170
|
+
sub_entity_instance.push_entities_to_external_to(external_client, mapped_entities_with_idmaps, external_entity_name, organization)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module Maestrano::Connector::Rails::Concerns::Entity
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
# Return an array of all the entities that the connector can synchronize
|
6
|
+
# If you add new entities, you need to generate
|
7
|
+
# a migration to add them to existing organizations
|
8
|
+
def entities_list
|
9
|
+
raise "Not implemented"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
@@external_name = Maestrano::Connector::Rails::External.external_name
|
14
|
+
|
15
|
+
# ----------------------------------------------
|
16
|
+
# Mapper methods
|
17
|
+
# ----------------------------------------------
|
18
|
+
# Map a Connec! entity to the external format
|
19
|
+
def map_to_external(entity, organization)
|
20
|
+
self.mapper_class.normalize(entity)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Map an external entity to Connec! format
|
24
|
+
def map_to_connec(entity, organization)
|
25
|
+
self.mapper_class.denormalize(entity)
|
26
|
+
end
|
27
|
+
|
28
|
+
# ----------------------------------------------
|
29
|
+
# Connec! methods
|
30
|
+
# ----------------------------------------------
|
31
|
+
def get_connec_entities(client, last_synchronization, organization, opts={})
|
32
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching Connec! #{self.connec_entity_name}")
|
33
|
+
|
34
|
+
entities = []
|
35
|
+
|
36
|
+
# Fetch first page
|
37
|
+
if last_synchronization.blank? || opts[:full_sync]
|
38
|
+
response = client.get("/#{self.connec_entity_name.downcase.pluralize}")
|
39
|
+
else
|
40
|
+
query_param = URI.encode("$filter=updated_at gt '#{last_synchronization.updated_at.iso8601}'")
|
41
|
+
response = client.get("/#{self.connec_entity_name.downcase.pluralize}?#{query_param}")
|
42
|
+
end
|
43
|
+
raise "No data received from Connec! when trying to fetch #{self.connec_entity_name.pluralize}" unless response
|
44
|
+
|
45
|
+
response_hash = JSON.parse(response.body)
|
46
|
+
if response_hash["#{self.connec_entity_name.downcase.pluralize}"]
|
47
|
+
entities << response_hash["#{self.connec_entity_name.downcase.pluralize}"]
|
48
|
+
else
|
49
|
+
raise "Received unrecognized Connec! data when trying to fetch #{self.connec_entity_name.pluralize}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Fetch subsequent pages
|
53
|
+
while response_hash['pagination'] && response_hash['pagination']['next']
|
54
|
+
# ugly way to convert https://api-connec/api/v2/group_id/organizations?next_page_params to /organizations?next_page_params
|
55
|
+
next_page = response_hash['pagination']['next'].gsub(/^(.*)\/#{self.connec_entity_name.downcase.pluralize}/, self.connec_entity_name.downcase.pluralize)
|
56
|
+
response = client.get(next_page)
|
57
|
+
|
58
|
+
raise "No data received from Connec! when trying to fetch subsequent page of #{self.connec_entity_name.pluralize}" unless response
|
59
|
+
response_hash = JSON.parse(response.body)
|
60
|
+
if response_hash["#{self.connec_entity_name.downcase.pluralize}"]
|
61
|
+
entities << response_hash["#{self.connec_entity_name.downcase.pluralize}"]
|
62
|
+
else
|
63
|
+
raise "Received unrecognized Connec! data when trying to fetch subsequent page of #{self.connec_entity_name.pluralize}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
entities = entities.flatten
|
68
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received data: Source=Connec!, Entity=#{self.connec_entity_name}, Data=#{entities}")
|
69
|
+
entities
|
70
|
+
end
|
71
|
+
|
72
|
+
def push_entities_to_connec(connec_client, mapped_external_entities_with_idmaps, organization)
|
73
|
+
self.push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, self.connec_entity_name, organization)
|
74
|
+
end
|
75
|
+
|
76
|
+
def push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, connec_entity_name, organization)
|
77
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending #{@@external_name} #{self.external_entity_name.pluralize} to Connec! #{connec_entity_name.pluralize}")
|
78
|
+
mapped_external_entities_with_idmaps.each do |mapped_external_entity_with_idmap|
|
79
|
+
external_entity = mapped_external_entity_with_idmap[:entity]
|
80
|
+
idmap = mapped_external_entity_with_idmap[:idmap]
|
81
|
+
|
82
|
+
if idmap.connec_id.blank?
|
83
|
+
connec_entity = self.create_entity_to_connec(connec_client, external_entity, connec_entity_name, organization)
|
84
|
+
idmap.update_attributes(connec_id: connec_entity['id'], connec_entity: connec_entity_name.downcase, last_push_to_connec: Time.now)
|
85
|
+
else
|
86
|
+
connec_entity = self.update_entity_to_connec(connec_client, external_entity, idmap.connec_id, connec_entity_name, organization)
|
87
|
+
idmap.update_attributes(last_push_to_connec: Time.now)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_entity_to_connec(connec_client, mapped_external_entity, connec_entity_name, organization)
|
93
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending create #{connec_entity_name}: #{mapped_external_entity} to Connec!")
|
94
|
+
response = connec_client.post("/#{connec_entity_name.downcase.pluralize}", { "#{connec_entity_name.downcase.pluralize}".to_sym => mapped_external_entity })
|
95
|
+
raise "No response received from Connec! when trying to create a #{self.connec_entity_name}" unless response
|
96
|
+
JSON.parse(response.body)["#{connec_entity_name.downcase.pluralize}"]
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_entity_to_connec(connec_client, mapped_external_entity, connec_id, connec_entity_name, organization)
|
100
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending update #{connec_entity_name}: #{mapped_external_entity} to Connec!")
|
101
|
+
response = connec_client.put("/#{connec_entity_name.downcase.pluralize}/#{connec_id}", { "#{connec_entity_name.downcase.pluralize}".to_sym => mapped_external_entity })
|
102
|
+
raise "No response received from Connec! when trying to update a #{self.connec_entity_name}" unless response
|
103
|
+
end
|
104
|
+
|
105
|
+
def map_to_external_with_idmap(entity, organization)
|
106
|
+
idmap = Maestrano::Connector::Rails::IdMap.find_by(connec_id: entity['id'], connec_entity: self.connec_entity_name.downcase, organization_id: organization.id)
|
107
|
+
|
108
|
+
if idmap && idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
|
109
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{self.connec_entity_name} : #{entity}")
|
110
|
+
nil
|
111
|
+
else
|
112
|
+
{entity: self.map_to_external(entity, organization), idmap: idmap || Maestrano::Connector::Rails::IdMap.create(connec_id: entity['id'], connec_entity: self.connec_entity_name.downcase, organization_id: organization.id)}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# ----------------------------------------------
|
117
|
+
# External methods
|
118
|
+
# ----------------------------------------------
|
119
|
+
def get_external_entities(client, last_synchronization, organization, opts={})
|
120
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching #{@@external_name} #{self.external_entity_name.pluralize}")
|
121
|
+
raise "Not implemented"
|
122
|
+
end
|
123
|
+
|
124
|
+
def push_entities_to_external(external_client, mapped_connec_entities_with_idmaps, organization)
|
125
|
+
push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, self.external_entity_name, organization)
|
126
|
+
end
|
127
|
+
|
128
|
+
def push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, external_entity_name, organization)
|
129
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending Connec! #{self.connec_entity_name.pluralize} to #{@@external_name} #{external_entity_name.pluralize}")
|
130
|
+
mapped_connec_entities_with_idmaps.each do |mapped_connec_entity_with_idmap|
|
131
|
+
self.push_entity_to_external(external_client, mapped_connec_entity_with_idmap, external_entity_name, organization)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def push_entity_to_external(external_client, mapped_connec_entity_with_idmap, external_entity_name, organization)
|
136
|
+
idmap = mapped_connec_entity_with_idmap[:idmap]
|
137
|
+
connec_entity = mapped_connec_entity_with_idmap[:entity]
|
138
|
+
|
139
|
+
if idmap.external_id.blank?
|
140
|
+
external_id = self.create_entity_to_external(external_client, connec_entity, external_entity_name, organization)
|
141
|
+
idmap.update_attributes(external_id: external_id, external_entity: external_entity_name.downcase, last_push_to_external: Time.now)
|
142
|
+
else
|
143
|
+
self.update_entity_to_external(external_client, connec_entity, idmap.external_id, external_entity_name, organization)
|
144
|
+
idmap.update_attributes(last_push_to_external: Time.now)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_entity_to_external(client, mapped_connec_entity, external_entity_name, organization)
|
149
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending create #{external_entity_name}: #{mapped_connec_entity} to #{@@external_name}")
|
150
|
+
raise "Not implemented"
|
151
|
+
end
|
152
|
+
|
153
|
+
def update_entity_to_external(client, mapped_connec_entity, external_id, external_entity_name, organization)
|
154
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending update #{external_entity_name} (id=#{external_id}): #{mapped_connec_entity} to #{@@external_name}")
|
155
|
+
raise "Not implemented"
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_id_from_external_entity_hash(entity)
|
159
|
+
raise "Not implemented"
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_last_update_date_from_external_entity_hash(entity)
|
163
|
+
raise "Not implemented"
|
164
|
+
end
|
165
|
+
|
166
|
+
# ----------------------------------------------
|
167
|
+
# General methods
|
168
|
+
# ----------------------------------------------
|
169
|
+
# * Discards entities that do not need to be pushed because they have not been updated since their last push
|
170
|
+
# * Discards entities from one of the two source in case of conflict
|
171
|
+
# * Maps not discarded entities and associates them with their idmap, or create one if there isn't any
|
172
|
+
def consolidate_and_map_data(connec_entities, external_entities, organization, opts={})
|
173
|
+
external_entities.map!{|entity|
|
174
|
+
idmap = Maestrano::Connector::Rails::IdMap.find_by(external_id: self.get_id_from_external_entity_hash(entity), external_entity: self.external_entity_name.downcase, organization_id: organization.id)
|
175
|
+
|
176
|
+
# No idmap: creating one, nothing else to do
|
177
|
+
unless idmap
|
178
|
+
next {entity: self.map_to_connec(entity, organization), idmap: Maestrano::Connector::Rails::IdMap.create(external_id: self.get_id_from_external_entity_hash(entity), external_entity: self.external_entity_name.downcase, organization_id: organization.id)}
|
179
|
+
end
|
180
|
+
|
181
|
+
# Entity has not been modified since its last push to connec!
|
182
|
+
if idmap.last_push_to_connec && idmap.last_push_to_connec > self.get_last_update_date_from_external_entity_hash(entity)
|
183
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard #{@@external_name} #{self.external_entity_name} : #{entity}")
|
184
|
+
next nil
|
185
|
+
end
|
186
|
+
|
187
|
+
# Check for conflict with entities from connec!
|
188
|
+
if idmap.connec_id && connec_entity = connec_entities.detect{|connec_entity| connec_entity['id'] == idmap.connec_id}
|
189
|
+
# We keep the most recently updated entity
|
190
|
+
if !opts[:connec_preemption].nil?
|
191
|
+
keep_external = !opts[:connec_preemption]
|
192
|
+
else
|
193
|
+
keep_external = connec_entity['updated_at'] < self.get_last_update_date_from_external_entity_hash(entity)
|
194
|
+
end
|
195
|
+
|
196
|
+
if keep_external
|
197
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{@@external_name} #{self.external_entity_name} #{entity} and Connec! #{self.connec_entity_name} #{connec_entity}. Entity from #{@@external_name} kept")
|
198
|
+
connec_entities.delete(connec_entity)
|
199
|
+
{entity: self.map_to_connec(entity, organization), idmap: idmap}
|
200
|
+
else
|
201
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{@@external_name} #{self.external_entity_name} #{entity} and Connec! #{self.connec_entity_name} #{connec_entity}. Entity from Connec! kept")
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
|
205
|
+
else
|
206
|
+
{entity: self.map_to_connec(entity, organization), idmap: idmap}
|
207
|
+
end
|
208
|
+
}.compact!
|
209
|
+
|
210
|
+
connec_entities.map!{|entity|
|
211
|
+
self.map_to_external_with_idmap(entity, organization)
|
212
|
+
}.compact!
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
# ----------------------------------------------
|
217
|
+
# Entity specific methods
|
218
|
+
# Those methods need to be define in each entity
|
219
|
+
# ----------------------------------------------
|
220
|
+
def connec_entity_name
|
221
|
+
raise "Not implemented"
|
222
|
+
end
|
223
|
+
|
224
|
+
def external_entity_name
|
225
|
+
raise "Not implemented"
|
226
|
+
end
|
227
|
+
|
228
|
+
def mapper_class
|
229
|
+
raise "Not implemented"
|
230
|
+
end
|
231
|
+
end
|