nexo 0.1.5 → 0.1.7
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/README.md +17 -0
- data/app/controllers/nexo/element_versions_controller.rb +26 -0
- data/app/controllers/nexo/elements_controller.rb +70 -0
- data/app/controllers/nexo/folders_controller.rb +41 -0
- data/app/controllers/nexo/nexo_controller.rb +11 -0
- data/app/jobs/nexo/api_clients.rb +20 -18
- data/app/jobs/nexo/base_job.rb +5 -3
- data/app/jobs/nexo/delete_remote_resource_job.rb +23 -1
- data/app/jobs/nexo/fetch_remote_resource_job.rb +61 -0
- data/app/jobs/nexo/folder_check_status_job.rb +10 -0
- data/app/jobs/nexo/folder_destroy_job.rb +2 -1
- data/app/jobs/nexo/folder_download_job.rb +15 -0
- data/app/jobs/nexo/folder_sync_job.rb +6 -0
- data/app/jobs/nexo/synchronizable_changed_job.rb +17 -2
- data/app/jobs/nexo/update_remote_resource_job.rb +56 -26
- data/app/lib/nexo/api_client/google_auth_service.rb +9 -8
- data/app/lib/nexo/api_client/google_calendar_service.rb +162 -38
- data/app/lib/nexo/api_client/google_calendar_sync_service.rb +88 -0
- data/app/lib/nexo/element_service.rb +249 -0
- data/app/lib/nexo/errors.rb +6 -5
- data/app/lib/nexo/event_receiver.rb +4 -0
- data/app/lib/nexo/folder_service.rb +55 -28
- data/app/lib/nexo/import_remote_element_version.rb +75 -0
- data/app/lib/nexo/policy_service.rb +27 -8
- data/app/models/concerns/nexo/calendar_event.rb +39 -2
- data/app/models/concerns/nexo/synchronizable.rb +28 -9
- data/app/models/nexo/element.rb +23 -34
- data/app/models/nexo/element_version.rb +17 -2
- data/app/models/nexo/folder.rb +29 -17
- data/app/models/nexo/integration.rb +0 -3
- data/app/models/nexo/token.rb +2 -0
- data/app/views/layouts/error.html.erb +8 -0
- data/app/views/layouts/nexo.html.erb +40 -0
- data/app/views/nexo/element_versions/show.html.erb +16 -0
- data/app/views/nexo/elements/index.html.erb +40 -0
- data/app/views/nexo/elements/show.html.erb +60 -0
- data/app/views/nexo/folders/index.html.erb +22 -0
- data/app/views/nexo/folders/show.html.erb +22 -0
- data/config/environment.rb +1 -0
- data/config/routes.rb +26 -0
- data/config/spring.rb +1 -0
- data/db/migrate/20250604124821_element_sync_status.rb +13 -0
- data/db/migrate/20250612002919_google_sync_tokens.rb +5 -0
- data/db/migrate/20250623132502_folder_sync_direction.rb +8 -0
- data/db/migrate/20250718012839_synchronizable_nullable.rb +6 -0
- data/db/seeds.rb +3 -2
- data/lib/nexo/engine.rb +26 -5
- data/lib/nexo/version.rb +1 -1
- metadata +39 -4
- data/app/jobs/nexo/sync_element_job.rb +0 -93
- data/app/models/concerns/nexo/folder_policy.rb +0 -24
@@ -3,60 +3,87 @@ module Nexo
|
|
3
3
|
#
|
4
4
|
# Responsabilities:
|
5
5
|
# - Creation of Element's
|
6
|
-
# -
|
7
|
-
# -
|
6
|
+
# - Creation of ElementVersion on local changes
|
7
|
+
# - Flagging Element's for removal
|
8
|
+
# - Enqueues UpdateRemoteResourceJob, DeleteRemoteResourceJob
|
8
9
|
class FolderService
|
10
|
+
# @raise [ActiveRecord::RecordNotUnique] on ElementVersion creation
|
9
11
|
def find_element_and_sync(folder, synchronizable)
|
10
|
-
|
11
|
-
|
12
|
+
validate_synchronizable!(synchronizable)
|
13
|
+
|
14
|
+
element = folder.find_element(synchronizable:)
|
12
15
|
|
13
16
|
if element.present?
|
17
|
+
Nexo.logger.debug { "Element found" }
|
18
|
+
validate_element_state!(element)
|
14
19
|
sync_element(element)
|
15
20
|
else
|
21
|
+
Nexo.logger.debug { "Element not found" }
|
16
22
|
create_and_sync_element(folder, synchronizable)
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
|
-
def destroy_elements(synchronizable, reason)
|
21
|
-
|
22
|
-
|
26
|
+
def destroy_elements(synchronizable, reason, exclude_elements: [])
|
27
|
+
Nexo.logger.debug("Destroying elements for synchronizable")
|
28
|
+
|
29
|
+
scope = synchronizable.nexo_elements
|
30
|
+
|
31
|
+
if exclude_elements.any?
|
32
|
+
Nexo.logger.debug("Excluding elements: #{exclude_elements}")
|
23
33
|
|
24
|
-
|
34
|
+
scope = scope.where.not(id: exclude_elements)
|
25
35
|
end
|
26
|
-
end
|
27
36
|
|
28
|
-
|
37
|
+
scope.each do |element|
|
38
|
+
unless element.folder.sync_internal_changes?
|
39
|
+
Nexo.logger.debug("Folder dont syncs internal changes, skipping")
|
40
|
+
next
|
41
|
+
end
|
29
42
|
|
30
|
-
|
31
|
-
|
43
|
+
ElementService.new(element:).flag_for_removal!(reason)
|
44
|
+
|
45
|
+
DeleteRemoteResourceJob.perform_later(element)
|
46
|
+
end
|
32
47
|
end
|
33
48
|
|
34
|
-
|
35
|
-
must_be_included = folder.policy_match?(synchronizable)
|
49
|
+
private
|
36
50
|
|
37
|
-
|
38
|
-
|
39
|
-
synchronizable:,
|
40
|
-
folder:
|
41
|
-
)
|
51
|
+
def validate_synchronizable!(synchronizable)
|
52
|
+
synchronizable.validate_synchronizable!
|
42
53
|
|
43
|
-
|
54
|
+
if synchronizable.sequence.nil?
|
55
|
+
raise Errors::SynchronizableSequenceIsNull
|
44
56
|
end
|
45
57
|
end
|
46
58
|
|
59
|
+
def validate_element_state!(element)
|
60
|
+
# unless element.synced?
|
61
|
+
# raise Errors::Error, <<~STR
|
62
|
+
# element ne_status is invalid: #{element.ne_status} \
|
63
|
+
# (should be synced)
|
64
|
+
# STR
|
65
|
+
# end
|
66
|
+
end
|
67
|
+
|
47
68
|
def sync_element(element)
|
48
|
-
|
69
|
+
if element.policy_still_applies?
|
70
|
+
ElementService.new(element:).create_internal_version_if_none!
|
71
|
+
else
|
72
|
+
Nexo.logger.debug("Flagging for removal and enqueuing DeleteRemoteResourceJob")
|
73
|
+
ElementService.new(element:).flag_for_removal!(:no_longer_included_in_folder)
|
49
74
|
|
50
|
-
|
51
|
-
raise Nexo::Errors::ElementConflicted, element
|
75
|
+
DeleteRemoteResourceJob.perform_later(element)
|
52
76
|
end
|
77
|
+
end
|
53
78
|
|
54
|
-
|
55
|
-
|
56
|
-
element.flag_for_deletion!(:no_longer_included_in_folder)
|
57
|
-
end
|
79
|
+
def create_and_sync_element(folder, synchronizable)
|
80
|
+
must_be_included = folder.policy_applies?(synchronizable)
|
58
81
|
|
59
|
-
|
82
|
+
if must_be_included
|
83
|
+
ElementService.new.create_element_for!(folder, synchronizable)
|
84
|
+
else
|
85
|
+
Nexo.logger.debug { "Policy not applies, skipping creation" }
|
86
|
+
end
|
60
87
|
end
|
61
88
|
end
|
62
89
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Nexo
|
2
|
+
class ImportRemoteElementVersion
|
3
|
+
class VersionSuperseded < Errors::Error; end
|
4
|
+
class ImportRemoteVersionFailed < Errors::Error; end
|
5
|
+
|
6
|
+
def perform(element_version)
|
7
|
+
validate_element_state!(element_version)
|
8
|
+
|
9
|
+
ElementService.new(element_version:).update_synchronizable!
|
10
|
+
rescue ImportRemoteVersionFailed => e
|
11
|
+
Nexo.logger.warn(e.inspect)
|
12
|
+
rescue VersionSuperseded
|
13
|
+
Nexo.logger.info("ImportRemoteElementVersion: version superseded")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def validate_element_state!(element_version)
|
19
|
+
element = element_version.element
|
20
|
+
|
21
|
+
raise Nexo::Errors::Error, "element version must be external" if element_version.internal?
|
22
|
+
raise Nexo::Errors::Error, "etag must be present" if element_version.etag.blank?
|
23
|
+
|
24
|
+
unless element_version.nev_status == "pending_sync"
|
25
|
+
raise Nexo::Errors::Error, "nev_status invalid, should be pending_sync"
|
26
|
+
end
|
27
|
+
|
28
|
+
# NOTE: this is actually very coupled to the way Google manages etag as a
|
29
|
+
# sequential number, if a new protocol or service is added in the future
|
30
|
+
# and it doesnt manage it that way, it would have to be delegated to the
|
31
|
+
# protocol service
|
32
|
+
if element.element_versions.where(nev_status: :synced)
|
33
|
+
.where("etag > ?", element_version.etag).any?
|
34
|
+
|
35
|
+
ElementService.new(element_version:).update_element_version!(nev_status: :superseded)
|
36
|
+
|
37
|
+
raise VersionSuperseded
|
38
|
+
end
|
39
|
+
|
40
|
+
if element.conflicted?
|
41
|
+
raise ImportRemoteVersionFailed, "element conflicted"
|
42
|
+
end
|
43
|
+
|
44
|
+
# if element.synchronizable.blank?
|
45
|
+
# # TODO!: this could be that an external element was restored. i.e.:
|
46
|
+
# # google calendar event cancelled and restored.
|
47
|
+
# # this should be handled in some way, maybe configurable per folder
|
48
|
+
# # options are:
|
49
|
+
# # - ignore the element
|
50
|
+
# # - create synchronizable as if it were new
|
51
|
+
# # - discard/undiscard, for this the synchronizable should have been
|
52
|
+
# # deleted
|
53
|
+
# raise ImportRemoteVersionFailed, "synchronizable not found"
|
54
|
+
# end
|
55
|
+
|
56
|
+
# :nocov: borderline
|
57
|
+
if element.discarded?
|
58
|
+
# TODO!: this could be that an external element was restored. i.e.:
|
59
|
+
# Event excluded from folder and then restored from Google Calendar
|
60
|
+
raise ImportRemoteVersionFailed, "element discarded"
|
61
|
+
end
|
62
|
+
|
63
|
+
if element.synchronizable.present?
|
64
|
+
if element.synchronizable.conflicted?
|
65
|
+
raise ImportRemoteVersionFailed, "synchronizable conflicted"
|
66
|
+
end
|
67
|
+
|
68
|
+
if element.synchronizable.sequence.nil?
|
69
|
+
raise ImportRemoteVersionFailed, "synchronizable sequence is null"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# :nocov:
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
module Nexo
|
2
2
|
class PolicyService
|
3
|
-
def
|
3
|
+
def clear_finders!
|
4
4
|
@finders = []
|
5
5
|
end
|
6
6
|
|
7
|
+
def initialize
|
8
|
+
clear_finders!
|
9
|
+
end
|
10
|
+
|
7
11
|
@instance = new
|
8
12
|
|
9
13
|
private_class_method :new
|
@@ -12,29 +16,44 @@ module Nexo
|
|
12
16
|
@instance
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
19
|
+
def register_folder_rule_finder(&block)
|
16
20
|
@finders << block
|
17
21
|
end
|
18
22
|
|
19
23
|
attr_reader :finders
|
20
24
|
|
21
|
-
def
|
25
|
+
def applies?(folder, synchronizable)
|
22
26
|
policies = policies_for(folder)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
aplicable_policy.
|
27
|
+
applied_policies = policies.select { |policy| policy.applies?(synchronizable) }
|
28
|
+
# :nocov: TODO
|
29
|
+
if applied_policies.any?
|
30
|
+
aplicable_policy = applied_policies.sort_by { |policy| policy.priority }.last
|
31
|
+
logger.debug { "Aplicable policy: #{aplicable_policy.inspect}" }
|
32
|
+
logger.debug { "sync_policy: #{aplicable_policy.sync_policy}" }
|
33
|
+
aplicable_policy.sync_policy.to_s == "include"
|
27
34
|
else
|
35
|
+
logger.debug { "No applicable policies" }
|
28
36
|
false
|
29
37
|
end
|
38
|
+
# :nocov:
|
30
39
|
end
|
31
40
|
|
32
41
|
def policies_for(folder)
|
42
|
+
logger.debug { "Found #{@finders.length} finders" }
|
43
|
+
|
33
44
|
aux = @finders.map do |finder|
|
34
45
|
finder.call(folder)
|
35
46
|
end
|
36
47
|
|
37
|
-
aux.flatten.compact
|
48
|
+
aux.flatten.compact.tap do |ret|
|
49
|
+
logger.debug { "Found #{ret.length} policies" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def logger
|
56
|
+
logger = Nexo.logger
|
38
57
|
end
|
39
58
|
end
|
40
59
|
end
|
@@ -4,6 +4,11 @@ module Nexo
|
|
4
4
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
# nce_status (Nexo Calendar Event Status)
|
8
|
+
#
|
9
|
+
# - confirmed
|
10
|
+
# - tentative (unused)
|
11
|
+
# - cancelled (deleted)
|
7
12
|
define_protocol(:nexo_calendar_event, %i[
|
8
13
|
date_from
|
9
14
|
date_to
|
@@ -11,6 +16,7 @@ module Nexo
|
|
11
16
|
time_to
|
12
17
|
summary
|
13
18
|
description
|
19
|
+
nce_status
|
14
20
|
])
|
15
21
|
|
16
22
|
def change_is_significative_to_sequence?
|
@@ -33,6 +39,38 @@ module Nexo
|
|
33
39
|
build_date_time(date_to, time_to)
|
34
40
|
end
|
35
41
|
|
42
|
+
def all_day?
|
43
|
+
time_from.blank? && time_to.blank?
|
44
|
+
end
|
45
|
+
|
46
|
+
# transparent: non blocking
|
47
|
+
# opaque: blocking
|
48
|
+
def transparency
|
49
|
+
if all_day?
|
50
|
+
"transparent"
|
51
|
+
else
|
52
|
+
"opaque"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_synchronizable!
|
57
|
+
unless datetime_from.present?
|
58
|
+
raise Errors::SynchronizableInvalid, "datetime_from is nil"
|
59
|
+
end
|
60
|
+
|
61
|
+
unless datetime_to.present?
|
62
|
+
raise Errors::SynchronizableInvalid, "datetime_to is nil"
|
63
|
+
end
|
64
|
+
|
65
|
+
unless datetime_from != datetime_to
|
66
|
+
raise Errors::SynchronizableInvalid, "datetime_from and datetime_to are equal"
|
67
|
+
end
|
68
|
+
|
69
|
+
unless summary.present?
|
70
|
+
raise Errors::SynchronizableInvalid, "summary is nil"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
36
74
|
private
|
37
75
|
|
38
76
|
# @param [Date] date
|
@@ -40,7 +78,7 @@ module Nexo
|
|
40
78
|
#
|
41
79
|
# @return [Date, DateTime]
|
42
80
|
def build_date_time(date, time = nil)
|
43
|
-
if time.present?
|
81
|
+
if time.present? && date.present?
|
44
82
|
DateTime.new(
|
45
83
|
date.year,
|
46
84
|
date.month,
|
@@ -55,7 +93,6 @@ module Nexo
|
|
55
93
|
end
|
56
94
|
end
|
57
95
|
|
58
|
-
|
59
96
|
# TODO: refactor https://api.rubyonrails.org/classes/ActiveSupport/Concern.html
|
60
97
|
included do
|
61
98
|
include Nexo::Synchronizable::Associations
|
@@ -8,6 +8,10 @@ module Nexo
|
|
8
8
|
# inserta en un google calendar y luego se elimina, luego ya no se
|
9
9
|
# puede volver a insertar con el mismo uuid, sería mejor que el id se
|
10
10
|
# genere automáticamente por google y guardarlo en Element
|
11
|
+
#
|
12
|
+
# sequence
|
13
|
+
# autoincremental integer, starting at 0. its recommended that
|
14
|
+
# the attribute is always initialized with 0 value on creation
|
11
15
|
%i[
|
12
16
|
sequence
|
13
17
|
]
|
@@ -35,6 +39,19 @@ module Nexo
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
end
|
42
|
+
|
43
|
+
def create_from_payload!(folder, payload)
|
44
|
+
Nexo.logger.debug("Synchronizable#create_from_payload!")
|
45
|
+
service = Nexo::ServiceBuilder.instance.build_protocol_service(folder)
|
46
|
+
fields = service.fields_from_payload(payload)
|
47
|
+
|
48
|
+
synchronizable = new
|
49
|
+
attributes = synchronizable.translate_fields(fields, for_create: true, folder:)
|
50
|
+
synchronizable.assign_attributes(attributes)
|
51
|
+
synchronizable.save!
|
52
|
+
|
53
|
+
synchronizable
|
54
|
+
end
|
38
55
|
end
|
39
56
|
|
40
57
|
def method_missing(method_name, *, &)
|
@@ -58,21 +75,23 @@ module Nexo
|
|
58
75
|
nexo_elements.conflicted.any?
|
59
76
|
end
|
60
77
|
|
61
|
-
# :nocov:
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
# and set the Synchronizable fields according to the Folder#nexo_protocol
|
78
|
+
# :nocov: borderline
|
79
|
+
def validate_synchronizable!
|
80
|
+
raise "must be implemented in subclass"
|
81
|
+
end
|
66
82
|
|
67
|
-
|
68
|
-
|
69
|
-
|
83
|
+
def update_from_fields!(fields)
|
84
|
+
raise "must be implemented in subclass"
|
85
|
+
end
|
86
|
+
|
87
|
+
def translate_fields(fields, for_create: false, folder: nil)
|
88
|
+
raise "must be implemented in subclass"
|
70
89
|
end
|
71
90
|
# :nocov:
|
72
91
|
|
73
92
|
def increment_sequence!
|
74
93
|
if sequence.nil?
|
75
|
-
|
94
|
+
Nexo.logger.warn("Synchronizable sequence is nil on increment_sequence!: #{self.to_gid}")
|
76
95
|
end
|
77
96
|
|
78
97
|
# This operation is performed directly in the database without the need
|
data/app/models/nexo/element.rb
CHANGED
@@ -4,20 +4,24 @@
|
|
4
4
|
#
|
5
5
|
# id :bigint not null, primary key
|
6
6
|
# folder_id :bigint not null
|
7
|
-
# synchronizable_id :integer
|
8
|
-
# synchronizable_type :string
|
7
|
+
# synchronizable_id :integer
|
8
|
+
# synchronizable_type :string
|
9
9
|
# uuid :string
|
10
10
|
# flagged_for_removal :boolean not null
|
11
11
|
# removal_reason :integer
|
12
|
-
# conflicted :boolean default(FALSE), not null
|
13
12
|
# discarded_at :datetime
|
14
13
|
# created_at :datetime not null
|
15
14
|
# updated_at :datetime not null
|
15
|
+
# ne_status :integer not null
|
16
16
|
#
|
17
17
|
module Nexo
|
18
18
|
class Element < ApplicationRecord
|
19
19
|
belongs_to :folder, class_name: "Nexo::Folder"
|
20
|
-
|
20
|
+
|
21
|
+
# when blank it's a brand new remote element that needs a synchronizable to
|
22
|
+
# be created
|
23
|
+
belongs_to :synchronizable, polymorphic: true, optional: true
|
24
|
+
|
21
25
|
has_many :element_versions, dependent: :destroy, class_name: "Nexo::ElementVersion"
|
22
26
|
|
23
27
|
after_initialize do
|
@@ -29,46 +33,31 @@ module Nexo
|
|
29
33
|
|
30
34
|
enum :removal_reason, no_longer_included_in_folder: 0, synchronizable_destroyed: 1
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
36
|
+
enum :ne_status,
|
37
|
+
synced: 0,
|
38
|
+
pending_external_sync: 1,
|
39
|
+
pending_local_sync: 2,
|
40
|
+
conflicted: 3,
|
41
|
+
pending_remote_delete: 4
|
45
42
|
|
46
|
-
|
47
|
-
element_versions.where(sequence: nil).order(created_at: :desc).first
|
48
|
-
end
|
43
|
+
scope :conflicted, -> { where(ne_status: :conflicted) }
|
49
44
|
|
50
|
-
def
|
51
|
-
|
45
|
+
def policy_still_applies?
|
46
|
+
# :nocov: TODO
|
47
|
+
folder.policy_applies?(synchronizable)
|
48
|
+
# :nocov:
|
52
49
|
end
|
53
50
|
|
54
|
-
def
|
55
|
-
|
51
|
+
def etag
|
52
|
+
last_remote_version&.etag
|
56
53
|
end
|
57
54
|
|
58
|
-
|
59
|
-
|
60
|
-
update!(conflicted: true)
|
61
|
-
|
62
|
-
# TODO: log "Conflicted Element: #{element.to_gid}"
|
55
|
+
def last_remote_version
|
56
|
+
element_versions.where.not(etag: nil).order(:etag).last
|
63
57
|
end
|
64
|
-
# :nocov:
|
65
58
|
|
66
59
|
def discarded?
|
67
60
|
discarded_at.present?
|
68
61
|
end
|
69
|
-
|
70
|
-
def discard!
|
71
|
-
update!(discarded_at: Time.current)
|
72
|
-
end
|
73
62
|
end
|
74
63
|
end
|
@@ -10,12 +10,16 @@
|
|
10
10
|
# origin :integer not null
|
11
11
|
# created_at :datetime not null
|
12
12
|
# updated_at :datetime not null
|
13
|
+
# nev_status :integer not null
|
13
14
|
#
|
14
15
|
module Nexo
|
15
16
|
# sequence
|
16
17
|
#
|
17
18
|
# cuando es null significa que el origin es external que debe ser
|
18
|
-
# sincronizado. si está presente, significa que fue
|
19
|
+
# sincronizado. si está presente, significa que fue sincronizado
|
20
|
+
# incremental correlativa. puede ser discontinua si se generan updates
|
21
|
+
# a una frecuencia alta
|
22
|
+
# TODO!: rename to "version"
|
19
23
|
#
|
20
24
|
# etag
|
21
25
|
#
|
@@ -30,9 +34,20 @@ module Nexo
|
|
30
34
|
belongs_to :element, class_name: "Nexo::Element"
|
31
35
|
|
32
36
|
enum :origin, internal: 0, external: 1
|
37
|
+
enum :nev_status,
|
38
|
+
pending_sync: 0,
|
39
|
+
synced: 1,
|
40
|
+
ignored_in_conflict: 2,
|
41
|
+
superseded: 3,
|
42
|
+
ignored_by_sync_direction: 4,
|
43
|
+
ignored_by_deletion: 5
|
33
44
|
|
34
45
|
serialize :payload, coder: JSON
|
35
46
|
|
36
|
-
validates :
|
47
|
+
validates :origin, presence: true
|
48
|
+
|
49
|
+
def remote_status
|
50
|
+
payload["status"] if payload.is_a?(Hash)
|
51
|
+
end
|
37
52
|
end
|
38
53
|
end
|
data/app/models/nexo/folder.rb
CHANGED
@@ -2,37 +2,45 @@
|
|
2
2
|
#
|
3
3
|
# Table name: nexo_folders
|
4
4
|
#
|
5
|
-
# id
|
6
|
-
# integration_id
|
7
|
-
# nexo_protocol
|
8
|
-
# external_identifier
|
9
|
-
# name
|
10
|
-
# description
|
11
|
-
# discarded_at
|
12
|
-
# created_at
|
13
|
-
# updated_at
|
5
|
+
# id :bigint not null, primary key
|
6
|
+
# integration_id :bigint not null
|
7
|
+
# nexo_protocol :integer not null
|
8
|
+
# external_identifier :string
|
9
|
+
# name :string
|
10
|
+
# description :string
|
11
|
+
# discarded_at :datetime
|
12
|
+
# created_at :datetime not null
|
13
|
+
# updated_at :datetime not null
|
14
|
+
# google_next_sync_token :string
|
15
|
+
# sync_direction :integer not null
|
16
|
+
# nf_status :integer default("initial"), not null
|
14
17
|
#
|
15
18
|
module Nexo
|
16
19
|
class Folder < ApplicationRecord
|
17
20
|
belongs_to :integration, class_name: "Nexo::Integration"
|
18
21
|
has_many :elements, class_name: "Nexo::Element"
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
enum :nexo_protocol, calendar: 0, dummy_calendar: 1
|
24
|
+
enum :sync_direction, sync_in_out: 0, sync_out_in: 1, sync_bidirectional: 2
|
25
|
+
enum :nf_status, initial: 0, ok: 1, not_found: 2
|
26
|
+
|
27
|
+
def sync_external_changes?
|
28
|
+
sync_out_in? || sync_bidirectional?
|
29
|
+
end
|
30
|
+
|
31
|
+
def sync_internal_changes?
|
32
|
+
sync_in_out? || sync_bidirectional?
|
24
33
|
end
|
25
34
|
|
26
35
|
scope :kept, -> { where(discarded_at: nil) }
|
27
36
|
|
28
37
|
validates :nexo_protocol, :name, presence: true
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
def policy_match?(synchronizable)
|
33
|
-
PolicyService.instance.match?(self, synchronizable)
|
39
|
+
def policy_applies?(synchronizable)
|
40
|
+
PolicyService.instance.applies?(self, synchronizable)
|
34
41
|
end
|
35
42
|
|
43
|
+
# TODO: use find_sole_by
|
36
44
|
def find_element(synchronizable:)
|
37
45
|
ary = elements.where(synchronizable:, discarded_at: nil).to_a
|
38
46
|
|
@@ -54,5 +62,9 @@ module Nexo
|
|
54
62
|
def discard!
|
55
63
|
update!(discarded_at: Time.current)
|
56
64
|
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
name.presence || super
|
68
|
+
end
|
57
69
|
end
|
58
70
|
end
|
@@ -14,9 +14,6 @@
|
|
14
14
|
#
|
15
15
|
module Nexo
|
16
16
|
class Integration < ApplicationRecord
|
17
|
-
include Discard::Model if defined? Discard::Model
|
18
|
-
include Hashid::Rails if defined? Hashid::Rails
|
19
|
-
|
20
17
|
serialize :scope, coder: JSON
|
21
18
|
belongs_to :user
|
22
19
|
belongs_to :client, class_name: "Nexo::Client"
|
data/app/models/nexo/token.rb
CHANGED
@@ -14,6 +14,8 @@ module Nexo
|
|
14
14
|
class Token < ApplicationRecord
|
15
15
|
belongs_to :integration, class_name: "Nexo::Integration"
|
16
16
|
|
17
|
+
scope :active, -> { where(nt_status: :active) }
|
18
|
+
|
17
19
|
after_initialize do
|
18
20
|
# TODO: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
|
19
21
|
self.nt_status = :active if nt_status.nil?
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= content_for(:title) || "Nexo" %></title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
<%= csp_meta_tag %>
|
9
|
+
|
10
|
+
<style>
|
11
|
+
table th {
|
12
|
+
font-weight: bold;
|
13
|
+
}
|
14
|
+
table th, table td {
|
15
|
+
border: 1px solid lightgrey;
|
16
|
+
padding: 0.4em;
|
17
|
+
}
|
18
|
+
</style>
|
19
|
+
</head>
|
20
|
+
|
21
|
+
<body>
|
22
|
+
<h1><%= link_to "NEXO", root_path %></h1>
|
23
|
+
<% flash.each do |flash_type, message| %>
|
24
|
+
<% if flash_type == "alert" %>
|
25
|
+
<div style="color: red">
|
26
|
+
<% else %>
|
27
|
+
<div style="color: blue">
|
28
|
+
<% end %>
|
29
|
+
<%= flash_type %>:
|
30
|
+
<%= message %>
|
31
|
+
</div>
|
32
|
+
<% end %>
|
33
|
+
<%= link_to "Folders", folders_path %>
|
34
|
+
<%= link_to "Elements", elements_path %>
|
35
|
+
|
|
36
|
+
<%= link_to "Home", main_app.root_path %>
|
37
|
+
<hr>
|
38
|
+
<%= yield %>
|
39
|
+
</body>
|
40
|
+
</html>
|