nexo 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/helpers/nexo/controller_helper.rb +10 -0
- data/app/jobs/nexo/api_clients.rb +25 -0
- data/app/jobs/nexo/base_job.rb +8 -0
- data/app/jobs/nexo/delete_remote_resource_job.rb +10 -0
- data/app/jobs/nexo/folder_sync_job.rb +25 -0
- data/app/jobs/nexo/sync_element_job.rb +91 -0
- data/app/jobs/nexo/synchronizable_changed_job.rb +25 -0
- data/app/jobs/nexo/update_remote_resource_job.rb +56 -0
- data/app/lib/nexo/active_record_google_token_store.rb +43 -0
- data/app/lib/nexo/api_client/api_response.rb +4 -0
- data/app/lib/nexo/api_client/calendar_service.rb +9 -0
- data/app/lib/nexo/api_client/google_auth_service.rb +89 -0
- data/app/lib/nexo/api_client/google_calendar_service.rb +113 -0
- data/app/lib/nexo/api_client/google_dummy_calendar_service.rb +31 -0
- data/app/lib/nexo/errors.rb +28 -0
- data/app/lib/nexo/event_receiver.rb +45 -0
- data/app/lib/nexo/folder_service.rb +67 -0
- data/app/lib/nexo/policy_service.rb +40 -0
- data/app/lib/nexo/service_builder.rb +30 -0
- data/app/models/concerns/nexo/calendar_event.rb +56 -0
- data/app/models/concerns/nexo/folder_policy.rb +24 -0
- data/app/models/concerns/nexo/synchronizable.rb +88 -0
- data/app/models/nexo/application_record.rb +5 -0
- data/app/models/nexo/client.rb +49 -0
- data/app/models/nexo/element.rb +74 -0
- data/app/models/nexo/element_version.rb +38 -0
- data/app/models/nexo/folder.rb +43 -0
- data/app/models/nexo/integration.rb +60 -0
- data/app/models/nexo/token.rb +29 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20250505192315_create_nexo_clients.rb +13 -0
- data/db/migrate/20250505195429_create_nexo_integrations.rb +15 -0
- data/db/migrate/20250506125057_create_nexo_tokens.rb +12 -0
- data/db/migrate/20250512025423_create_nexo_folders.rb +13 -0
- data/db/migrate/20250512025950_create_nexo_elements.rb +16 -0
- data/db/migrate/20250512030530_create_nexo_element_versions.rb +13 -0
- data/db/migrate/20250519210346_create_good_jobs.rb +104 -0
- data/db/seeds.rb +3 -0
- data/lib/nexo/engine.rb +24 -0
- data/lib/nexo/version.rb +5 -0
- data/lib/nexo.rb +6 -0
- metadata +144 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Nexo
|
2
|
+
# This callbacks must be called when synchronizables change locally, must not
|
3
|
+
# be called when the system is notified of an external element change
|
4
|
+
class EventReceiver
|
5
|
+
def synchronizable_created(synchronizable)
|
6
|
+
validate_synchronizable_state!(synchronizable)
|
7
|
+
|
8
|
+
synchronizable.initialize_values!
|
9
|
+
|
10
|
+
SynchronizableChangedJob.perform_later(synchronizable)
|
11
|
+
end
|
12
|
+
|
13
|
+
def synchronizable_updated(synchronizable)
|
14
|
+
if synchronizable.change_is_significative_to_sequence?
|
15
|
+
synchronizable.increment_sequence!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Even if sequence remains the same the synchronizable may be removed
|
19
|
+
# from some Folder, so we have to always enqueue the job
|
20
|
+
SynchronizableChangedJob.perform_later(synchronizable)
|
21
|
+
end
|
22
|
+
|
23
|
+
def synchronizable_destroyed(synchronizable)
|
24
|
+
folder_service.destroy_elements(synchronizable, :synchronizable_destroyed)
|
25
|
+
end
|
26
|
+
|
27
|
+
def folder_policy_changed(folder_policy)
|
28
|
+
FolderSyncJob.perform_later(folder_policy.folder)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# :nocov: mocked
|
34
|
+
def folder_service
|
35
|
+
@folder_service ||= FolderService.new
|
36
|
+
end
|
37
|
+
# :nocov:
|
38
|
+
|
39
|
+
def validate_synchronizable_state!(synchronizable)
|
40
|
+
unless synchronizable.sequence.nil?
|
41
|
+
raise Errors::InvalidSynchronizableState, "sequence is present: #{synchronizable.sequence}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Nexo
|
2
|
+
# Handles the composition of folders
|
3
|
+
#
|
4
|
+
# Responsabilities:
|
5
|
+
# - Creation of Element's
|
6
|
+
# - Flagging Element's for deletion
|
7
|
+
# - Triggering the SyncElementJob
|
8
|
+
class FolderService
|
9
|
+
def find_element_and_sync(folder, synchronizable)
|
10
|
+
# TODO: handle conflicted synchronizable
|
11
|
+
element = find_element(folder, synchronizable)
|
12
|
+
|
13
|
+
if element.present?
|
14
|
+
sync_element(element)
|
15
|
+
else
|
16
|
+
create_and_sync_element(folder, synchronizable)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy_elements(synchronizable, reason)
|
21
|
+
synchronizable.nexo_elements.each do |element|
|
22
|
+
element.flag_for_deletion!(reason)
|
23
|
+
|
24
|
+
SyncElementJob.perform_later(element)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def find_element(folder, synchronizable)
|
31
|
+
folder.find_element(synchronizable:)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_and_sync_element(folder, synchronizable)
|
35
|
+
must_be_included = folder.policy_match?(synchronizable)
|
36
|
+
|
37
|
+
if must_be_included
|
38
|
+
element = Element.create!(
|
39
|
+
synchronizable:,
|
40
|
+
folder:
|
41
|
+
)
|
42
|
+
|
43
|
+
if synchronizable.sequence.nil?
|
44
|
+
# TODO: whats the use of having this and also in EventReceiver?
|
45
|
+
synchronizable.initialize_values!
|
46
|
+
end
|
47
|
+
|
48
|
+
SyncElementJob.perform_later(element)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def sync_element(element)
|
53
|
+
synchronizable = element.synchronizable
|
54
|
+
|
55
|
+
if synchronizable.conflicted?
|
56
|
+
raise Nexo::Errors::ElementConflicted, element
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check if Synchronizable still must be included in folder
|
60
|
+
if !element.policy_still_match?
|
61
|
+
element.flag_for_deletion!(:no_longer_included_in_folder)
|
62
|
+
end
|
63
|
+
|
64
|
+
SyncElementJob.perform_later(element)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Nexo
|
2
|
+
class PolicyService
|
3
|
+
def initialize
|
4
|
+
@finders = []
|
5
|
+
end
|
6
|
+
|
7
|
+
@instance = new
|
8
|
+
|
9
|
+
private_class_method :new
|
10
|
+
|
11
|
+
def self.instance
|
12
|
+
@instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_folder_policy_finder(&block)
|
16
|
+
@finders << block
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :finders
|
20
|
+
|
21
|
+
def match?(folder, synchronizable)
|
22
|
+
policies = policies_for(folder)
|
23
|
+
matching_policies = policies.select { |policy| policy.match?(synchronizable) }
|
24
|
+
if matching_policies.any?
|
25
|
+
aplicable_policy = matching_policies.sort_by { |policy| policy.priority }.last
|
26
|
+
aplicable_policy.sync_policy == :include
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def policies_for(folder)
|
33
|
+
aux = @finders.map do |finder|
|
34
|
+
finder.call(folder)
|
35
|
+
end
|
36
|
+
|
37
|
+
aux.flatten.compact
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Nexo
|
2
|
+
# Centralizes the creation of API service objects
|
3
|
+
class ServiceBuilder
|
4
|
+
@instance = new
|
5
|
+
|
6
|
+
private_class_method :new
|
7
|
+
|
8
|
+
def self.instance
|
9
|
+
@instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_protocol_service(folder)
|
13
|
+
service_klass_name = "#{folder.integration.client.service}_#{folder.protocol}_service".camelcase
|
14
|
+
build_service(service_klass_name, folder.integration)
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_auth_service(integration)
|
18
|
+
service_klass_name = "#{integration.client.service}_auth_service".camelcase
|
19
|
+
build_service(service_klass_name, integration)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_service(klass_name, integration)
|
25
|
+
service_klass = Nexo.const_get(klass_name)
|
26
|
+
|
27
|
+
service_klass.new(integration)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Nexo
|
2
|
+
module CalendarEvent
|
3
|
+
include Synchronizable
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
define_protocol(:nexo_calendar_event, %i[
|
8
|
+
date_from
|
9
|
+
date_to
|
10
|
+
time_from
|
11
|
+
time_to
|
12
|
+
summary
|
13
|
+
description
|
14
|
+
])
|
15
|
+
|
16
|
+
def change_is_significative_to_sequence?
|
17
|
+
previous_changes.keys.map(&:to_sym).intersection(protocol_methods).any?
|
18
|
+
end
|
19
|
+
|
20
|
+
def datetime_from
|
21
|
+
build_date_time(date_from, time_from)
|
22
|
+
end
|
23
|
+
|
24
|
+
def datetime_to
|
25
|
+
build_date_time(date_to, time_to)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @param [Date] date
|
31
|
+
# @param [Time] time
|
32
|
+
#
|
33
|
+
# @return [Date, DateTime]
|
34
|
+
def build_date_time(date, time = nil)
|
35
|
+
if time.present?
|
36
|
+
DateTime.new(
|
37
|
+
date.year,
|
38
|
+
date.month,
|
39
|
+
date.day,
|
40
|
+
time.hour,
|
41
|
+
time.min,
|
42
|
+
time.sec,
|
43
|
+
time.zone
|
44
|
+
)
|
45
|
+
else
|
46
|
+
date
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# TODO: refactor https://api.rubyonrails.org/classes/ActiveSupport/Concern.html
|
52
|
+
included do
|
53
|
+
include Nexo::Synchronizable::Associations
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Nexo
|
2
|
+
module FolderPolicy
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
# include ActiveModel::Model
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
# TODO: add belongs_to :folder ?
|
8
|
+
|
9
|
+
# validates_inclusion_of :sync_policy, in: %w( include exclude )
|
10
|
+
# TODO!: check if this is being used
|
11
|
+
included do
|
12
|
+
validates :sync_policy, inclusion: { in: %w[ bla ] }
|
13
|
+
validates :priority, numericality: true
|
14
|
+
end
|
15
|
+
|
16
|
+
def match?(synchronizable)
|
17
|
+
# :nocov: borderline
|
18
|
+
raise "must be implemented in subclass"
|
19
|
+
# :nocov:
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :priority, :sync_policy
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Nexo
|
2
|
+
module Synchronizable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def protocol_methods
|
6
|
+
# uuid (deprecated)
|
7
|
+
# hay un problema con generar un uuid por synchronizable y es que si se
|
8
|
+
# inserta en un google calendar y luego se elimina, luego ya no se
|
9
|
+
# puede volver a insertar con el mismo uuid, sería mejor que el id se
|
10
|
+
# genere automáticamente por google y guardarlo en Element
|
11
|
+
%i[
|
12
|
+
sequence
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def define_protocol(name, methods)
|
18
|
+
define_method(:protocols) do
|
19
|
+
if defined? super
|
20
|
+
# :nocov: TODO, not yet implemented
|
21
|
+
super << name
|
22
|
+
# :nocov:
|
23
|
+
else
|
24
|
+
[ name ]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method(:protocol_methods) do
|
29
|
+
if defined? super
|
30
|
+
[ super(), methods ].flatten
|
31
|
+
else
|
32
|
+
# :nocov: borderline
|
33
|
+
raise "protocol_methods should be defined"
|
34
|
+
# :nocov:
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(method_name, *, &)
|
41
|
+
if method_name.in?(protocol_methods)
|
42
|
+
raise Errors::InterfaceMethodNotImplemented, method_name
|
43
|
+
end
|
44
|
+
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
# ----------------------------------------------------
|
49
|
+
|
50
|
+
module Associations
|
51
|
+
extend ActiveSupport::Concern
|
52
|
+
included do
|
53
|
+
has_many :nexo_elements, class_name: "Nexo::Element", as: :synchronizable
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def conflicted?
|
58
|
+
nexo_elements.conflicted.any?
|
59
|
+
end
|
60
|
+
|
61
|
+
# :nocov: TODO, not yet implemented
|
62
|
+
def update_from!(element_version)
|
63
|
+
transaction do
|
64
|
+
# TODO: parse the element_version.payload
|
65
|
+
# and set the Synchronizable fields according to the Folder#protocol
|
66
|
+
|
67
|
+
new_sequence = increment_sequence!
|
68
|
+
element_version.update_sequence!(new_sequence)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# :nocov:
|
72
|
+
|
73
|
+
def initialize_values!
|
74
|
+
update!(sequence: 0)
|
75
|
+
end
|
76
|
+
|
77
|
+
def increment_sequence!
|
78
|
+
# This operation is performed directly in the database without the need
|
79
|
+
# to read the current value first. So, it is atomic at a database level,
|
80
|
+
# though the in-memory object consitency is subject to race conditions.
|
81
|
+
#
|
82
|
+
# increment(:sequence), without the bang, is not atomic
|
83
|
+
#
|
84
|
+
# https://api.rubyonrails.org/v7.2/classes/ActiveRecord/Persistence.html#method-i-increment-21
|
85
|
+
increment!(:sequence)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: nexo_clients
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# service :integer
|
7
|
+
# secret :string
|
8
|
+
# tcp_status :integer
|
9
|
+
# brand_name :integer
|
10
|
+
# user_integrations_allowed :boolean
|
11
|
+
# created_at :datetime not null
|
12
|
+
# updated_at :datetime not null
|
13
|
+
#
|
14
|
+
require "google-apis-calendar_v3"
|
15
|
+
require "google-apis-oauth2_v2"
|
16
|
+
|
17
|
+
module Nexo
|
18
|
+
AVAILABLE_SCOPES = {
|
19
|
+
google: {
|
20
|
+
auth_email: Google::Apis::Oauth2V2::AUTH_USERINFO_EMAIL,
|
21
|
+
auth_calendar_app_created: Google::Apis::CalendarV3::AUTH_CALENDAR_APP_CREATED,
|
22
|
+
auth_calendar_calendarlist: Google::Apis::CalendarV3::AUTH_CALENDAR_CALENDARLIST
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
class Client < ApplicationRecord
|
27
|
+
encrypts :secret
|
28
|
+
|
29
|
+
enum :service, google: 0
|
30
|
+
enum :tcp_status, authorized: 0, disabled: 1, expired: 2
|
31
|
+
|
32
|
+
validates :service, :user_integrations_allowed,
|
33
|
+
:tcp_status, :secret, presence: true
|
34
|
+
|
35
|
+
serialize :secret, coder: JSON
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
service.titleize
|
39
|
+
end
|
40
|
+
|
41
|
+
def available_scopes_for_select
|
42
|
+
service_scopes.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
def service_scopes
|
46
|
+
AVAILABLE_SCOPES[service.to_sym]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: nexo_elements
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# folder_id :integer not null
|
7
|
+
# synchronizable_id :integer not null
|
8
|
+
# synchronizable_type :string not null
|
9
|
+
# uuid :string
|
10
|
+
# flag_deletion :boolean not null
|
11
|
+
# deletion_reason :integer
|
12
|
+
# conflicted :boolean default(FALSE), not null
|
13
|
+
# discarded_at :datetime
|
14
|
+
# created_at :datetime not null
|
15
|
+
# updated_at :datetime not null
|
16
|
+
#
|
17
|
+
module Nexo
|
18
|
+
class Element < ApplicationRecord
|
19
|
+
belongs_to :folder, class_name: "Nexo::Folder"
|
20
|
+
belongs_to :synchronizable, polymorphic: true
|
21
|
+
has_many :element_versions, dependent: :destroy, class_name: "Nexo::ElementVersion"
|
22
|
+
|
23
|
+
after_initialize do
|
24
|
+
# TODO: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
|
25
|
+
self.flag_deletion = false if flag_deletion.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
scope :kept, -> { where(discarded_at: nil) }
|
29
|
+
|
30
|
+
enum :deletion_reason, no_longer_included_in_folder: 0, synchronizable_destroyed: 1
|
31
|
+
|
32
|
+
scope :conflicted, -> { where(conflicted: true) }
|
33
|
+
|
34
|
+
def policy_still_match?
|
35
|
+
folder.policy_match?(synchronizable)
|
36
|
+
end
|
37
|
+
|
38
|
+
def last_synced_sequence
|
39
|
+
element_versions.pluck(:sequence).max || -1
|
40
|
+
end
|
41
|
+
|
42
|
+
def external_unsynced_change?
|
43
|
+
last_external_unsynced_version.present?
|
44
|
+
end
|
45
|
+
|
46
|
+
def last_external_unsynced_version
|
47
|
+
element_versions.where(sequence: nil).order(created_at: :desc).first
|
48
|
+
end
|
49
|
+
|
50
|
+
def flag_for_deletion!(deletion_reason)
|
51
|
+
update!(flag_deletion: true, deletion_reason:)
|
52
|
+
end
|
53
|
+
|
54
|
+
def flagged_for_deletion?
|
55
|
+
flag_deletion?
|
56
|
+
end
|
57
|
+
|
58
|
+
# :nocov: TODO, not yet being called
|
59
|
+
def flag_as_conflicted!
|
60
|
+
update!(conflicted: true)
|
61
|
+
|
62
|
+
# TODO: log "Conflicted Element: #{element.to_gid}"
|
63
|
+
end
|
64
|
+
# :nocov:
|
65
|
+
|
66
|
+
def discarded?
|
67
|
+
discarded_at.present?
|
68
|
+
end
|
69
|
+
|
70
|
+
def discard!
|
71
|
+
update!(discarded_at: Time.current)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: nexo_element_versions
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# element_id :integer not null
|
7
|
+
# payload :string
|
8
|
+
# etag :string
|
9
|
+
# sequence :integer
|
10
|
+
# origin :integer not null
|
11
|
+
# created_at :datetime not null
|
12
|
+
# updated_at :datetime not null
|
13
|
+
#
|
14
|
+
module Nexo
|
15
|
+
# sequence
|
16
|
+
#
|
17
|
+
# cuando es null significa que el origin es external que debe ser
|
18
|
+
# sincronizado. si está presente, significa que fue sinzronizado
|
19
|
+
#
|
20
|
+
# etag
|
21
|
+
#
|
22
|
+
# id de versión remota, para evitar pisar datos remotos aún no fetcheados y
|
23
|
+
# para no volver a traer datos si no hubo ningún cambio desde la última vez
|
24
|
+
# que se fetchearon
|
25
|
+
#
|
26
|
+
# payload
|
27
|
+
#
|
28
|
+
# raw data from API
|
29
|
+
class ElementVersion < ApplicationRecord
|
30
|
+
belongs_to :element, class_name: "Nexo::Element"
|
31
|
+
|
32
|
+
enum :origin, internal: 0, external: 1
|
33
|
+
|
34
|
+
serialize :payload, coder: JSON
|
35
|
+
|
36
|
+
validates :payload, :etag, :origin, presence: true
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: nexo_folders
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# integration_id :integer not null
|
7
|
+
# protocol :integer not null
|
8
|
+
# external_identifier :string
|
9
|
+
# name :string
|
10
|
+
# description :string
|
11
|
+
# created_at :datetime not null
|
12
|
+
# updated_at :datetime not null
|
13
|
+
#
|
14
|
+
module Nexo
|
15
|
+
class Folder < ApplicationRecord
|
16
|
+
belongs_to :integration, class_name: "Nexo::Integration"
|
17
|
+
has_many :elements, class_name: "Nexo::Element"
|
18
|
+
|
19
|
+
enum :protocol, calendar: 0, dummy_calendar: 1
|
20
|
+
|
21
|
+
validates :protocol, :name, presence: true
|
22
|
+
|
23
|
+
# TODO!: find better name
|
24
|
+
# maybe policy_applies?
|
25
|
+
def policy_match?(synchronizable)
|
26
|
+
PolicyService.instance.match?(self, synchronizable)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_element(synchronizable:)
|
30
|
+
ary = elements.where(synchronizable:, discarded_at: nil).to_a
|
31
|
+
|
32
|
+
if ary.count > 1
|
33
|
+
raise Errors::MoreThanOneElementInFolderForSynchronizable
|
34
|
+
end
|
35
|
+
|
36
|
+
ary.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def time_zone
|
40
|
+
Rails.application.config.time_zone
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: nexo_integrations
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# user_id :integer not null
|
7
|
+
# client_id :integer not null
|
8
|
+
# name :string
|
9
|
+
# scope :string
|
10
|
+
# expires_at :datetime
|
11
|
+
# discarded_at :datetime
|
12
|
+
# created_at :datetime not null
|
13
|
+
# updated_at :datetime not null
|
14
|
+
#
|
15
|
+
module Nexo
|
16
|
+
class Integration < ApplicationRecord
|
17
|
+
include Discard::Model if defined? Discard::Model
|
18
|
+
include Hashid::Rails if defined? Hashid::Rails
|
19
|
+
|
20
|
+
serialize :scope, coder: JSON
|
21
|
+
belongs_to :user
|
22
|
+
belongs_to :client, class_name: "Nexo::Client"
|
23
|
+
has_many :tokens, class_name: "Nexo::Token"
|
24
|
+
|
25
|
+
validates :scope, presence: true
|
26
|
+
|
27
|
+
def external_api_scope
|
28
|
+
if scope.blank?
|
29
|
+
# :nocov: borderline
|
30
|
+
raise "scope must be present"
|
31
|
+
# :nocov:
|
32
|
+
end
|
33
|
+
|
34
|
+
scope.map { |permission| client.service_scopes[permission.to_sym] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def expires_in
|
38
|
+
return unless credentials.present?
|
39
|
+
|
40
|
+
(credentials.expires_at - Time.current).to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def token_status
|
44
|
+
if credentials.present?
|
45
|
+
if credentials.expires_at > Time.current
|
46
|
+
:active_token
|
47
|
+
else
|
48
|
+
:expired_token
|
49
|
+
end
|
50
|
+
else
|
51
|
+
:no_token
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def credentials
|
56
|
+
service = ServiceBuilder.instance.build_auth_service(self)
|
57
|
+
@credentials ||= service.get_credentials
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: nexo_tokens
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# integration_id :integer not null
|
7
|
+
# secret :string
|
8
|
+
# tpt_status :integer not null
|
9
|
+
# environment :string not null
|
10
|
+
# created_at :datetime not null
|
11
|
+
# updated_at :datetime not null
|
12
|
+
#
|
13
|
+
module Nexo
|
14
|
+
class Token < ApplicationRecord
|
15
|
+
belongs_to :integration, class_name: "Nexo::Integration"
|
16
|
+
|
17
|
+
after_initialize do
|
18
|
+
# TODO: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
|
19
|
+
self.tpt_status = :active if tpt_status.nil?
|
20
|
+
self.environment = Rails.env if environment.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
encrypts :secret
|
24
|
+
|
25
|
+
enum :tpt_status, active: 0, revoked: 1, expired: 2
|
26
|
+
|
27
|
+
validates :secret, :tpt_status, :environment, presence: true
|
28
|
+
end
|
29
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateNexoClients < ActiveRecord::Migration[7.2]
|
2
|
+
def change
|
3
|
+
create_table :nexo_clients do |t|
|
4
|
+
t.integer :service
|
5
|
+
t.string :secret
|
6
|
+
t.integer :tcp_status
|
7
|
+
t.integer :brand_name
|
8
|
+
t.boolean :user_integrations_allowed
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateNexoIntegrations < ActiveRecord::Migration[7.2]
|
2
|
+
def change
|
3
|
+
create_table :nexo_integrations do |t|
|
4
|
+
t.references :user, null: false, foreign_key: true
|
5
|
+
t.references :client, null: false, foreign_key: { to_table: :nexo_clients }
|
6
|
+
t.string :name
|
7
|
+
t.string :scope
|
8
|
+
t.datetime :expires_at
|
9
|
+
|
10
|
+
t.datetime :discarded_at
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|