nexo 0.1.6 → 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 +9 -3
- data/app/controllers/nexo/element_versions_controller.rb +0 -2
- data/app/controllers/nexo/elements_controller.rb +10 -4
- data/app/controllers/nexo/nexo_controller.rb +7 -1
- data/app/jobs/nexo/delete_remote_resource_job.rb +19 -7
- data/app/jobs/nexo/update_remote_resource_job.rb +0 -4
- data/app/lib/nexo/api_client/google_calendar_service.rb +2 -1
- data/app/lib/nexo/api_client/google_calendar_sync_service.rb +6 -2
- data/app/lib/nexo/element_service.rb +15 -2
- data/app/lib/nexo/errors.rb +0 -1
- data/app/models/concerns/nexo/calendar_event.rb +6 -0
- data/app/models/concerns/nexo/synchronizable.rb +6 -3
- data/app/models/nexo/element.rb +7 -1
- data/app/models/nexo/element_version.rb +6 -1
- data/app/views/layouts/error.html.erb +8 -0
- data/app/views/layouts/nexo.html.erb +0 -2
- data/app/views/nexo/elements/index.html.erb +10 -2
- data/app/views/nexo/elements/show.html.erb +5 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20250604124821_element_sync_status.rb +4 -2
- data/lib/nexo/engine.rb +4 -0
- data/lib/nexo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a18eacef8af04c17f7ce29a9d0101696ebc42f9337f7773cb1b7418a653f57e4
|
4
|
+
data.tar.gz: 5a7664dc27f2f2d9143e8bb4db675289f7c2d4ff063ef5603cac59a183e3a9b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a16900fc806a754bb90cef361babb57e770e3322596909bf50d1312eabab836b9131941784c5d773ce2d00698e0db087de93bf41db7743d12596004a8b6c0782
|
7
|
+
data.tar.gz: 6bd7ef667816a643303898202e5ea7de7404ddf539120243ddb5b4c3d6bdcf86f5fab35d49cd2c19b740097fb7bd981f199336094abce06b8b3ddfada5a2e657
|
data/README.md
CHANGED
@@ -28,15 +28,21 @@ Configure good_job
|
|
28
28
|
|
29
29
|
## Unhandled cases / TODO's
|
30
30
|
|
31
|
-
-
|
31
|
+
- Buscar la manera de wrappear los jobs en un with_tenant
|
32
|
+
cuáles jobs?
|
33
|
+
los que se disparan desde Google webhooks
|
34
|
+
aunque quizá la posta es qeu esas routes estén scopeadas dentro del tid
|
35
|
+
- Testear el "no longer included in folder"
|
36
|
+
o sea, modificar una rule y ver qué ande
|
32
37
|
|
38
|
+
- Restore locally discarded Events. It creates a new Google Calendar event?
|
33
39
|
- Recurring google events
|
34
|
-
|
35
40
|
- IMPORTANTE: loggear las exceptions de los jobs
|
36
|
-
|
37
41
|
- Llevar mucho control de los jobs y las exceptions que puedan surgir como
|
38
42
|
ActiveRecord::PreparedStatementCacheExpired
|
39
43
|
|
44
|
+
|
45
|
+
|
40
46
|
## Contributing
|
41
47
|
Contribution directions go here.
|
42
48
|
|
@@ -42,8 +42,6 @@ module Nexo
|
|
42
42
|
EventReceiver.new.synchronizable_updated(event)
|
43
43
|
|
44
44
|
redirect_to @element, notice: "Modified"
|
45
|
-
rescue StandardError => e
|
46
|
-
redirect_to @element, alert: e.message
|
47
45
|
end
|
48
46
|
|
49
47
|
def fetch_remote
|
@@ -56,8 +54,16 @@ module Nexo
|
|
56
54
|
ElementService.new(element: @element).resolve_conflict!
|
57
55
|
|
58
56
|
redirect_to @element, notice: "Conflict solved"
|
59
|
-
|
60
|
-
|
57
|
+
end
|
58
|
+
|
59
|
+
def modify
|
60
|
+
case params[:operation]
|
61
|
+
when "delete"
|
62
|
+
DeleteRemoteResourceJob.perform_later(@element)
|
63
|
+
redirect_to @element, notice: "Enqueued DeleteRemoteResourceJob"
|
64
|
+
else
|
65
|
+
redirect_to @element, alert: "Unknown action"
|
66
|
+
end
|
61
67
|
end
|
62
68
|
end
|
63
69
|
end
|
@@ -3,18 +3,30 @@ module Nexo
|
|
3
3
|
class DeleteRemoteResourceJob < BaseJob
|
4
4
|
include ApiClients
|
5
5
|
|
6
|
-
|
7
|
-
if element.discarded?
|
8
|
-
raise "element already discarded"
|
9
|
-
end
|
6
|
+
retry_on(Errors::ConflictingRemoteElementChange, attempts: 2)
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
# Once discarded, an Element is obsolete and cant be restored because even
|
9
|
+
# though some APIs may allow it, others may not.
|
10
|
+
def perform(element)
|
11
|
+
raise "element already discarded" if element.discarded?
|
14
12
|
|
15
13
|
ServiceBuilder.instance.build_protocol_service(element.folder).remove(element)
|
16
14
|
|
17
15
|
ElementService.new(element:).discard!
|
16
|
+
|
17
|
+
# Fetch the removed/cancelled version
|
18
|
+
FetchRemoteResourceJob.perform_now(element)
|
19
|
+
rescue Errors::ConflictingRemoteElementChange => e
|
20
|
+
Nexo.logger.warn <<~STR
|
21
|
+
ConflictingRemoteElementChange for #{element.to_gid}. \
|
22
|
+
|
23
|
+
Performing now FetchRemoteResourceJob, so next attempt should succeed
|
24
|
+
STR
|
25
|
+
|
26
|
+
# NOTE: maybe this should be performed always directly in the first attempt?
|
27
|
+
FetchRemoteResourceJob.perform_now(element)
|
28
|
+
|
29
|
+
raise e
|
18
30
|
end
|
19
31
|
end
|
20
32
|
end
|
@@ -70,10 +70,6 @@ module Nexo
|
|
70
70
|
raise Errors::ElementDiscarded
|
71
71
|
end
|
72
72
|
|
73
|
-
if element.synchronizable.respond_to?(:discarded?) && element.synchronizable.discarded?
|
74
|
-
raise Errors::SynchronizableDiscarded
|
75
|
-
end
|
76
|
-
|
77
73
|
if element.folder.discarded?
|
78
74
|
raise Errors::FolderDiscarded
|
79
75
|
end
|
@@ -53,7 +53,7 @@ module Nexo
|
|
53
53
|
# sidebranch
|
54
54
|
# TODO!: validate uuid presence
|
55
55
|
|
56
|
-
# TODO: try with cancelled
|
56
|
+
# TODO: try with cancelled / maybe its the same
|
57
57
|
client.delete_event(element.folder.external_identifier, element.uuid, options: ifmatch_options(element))
|
58
58
|
ApiResponse.new(payload: nil, status: :ok, etag: nil)
|
59
59
|
rescue Google::Apis::ClientError => e
|
@@ -211,6 +211,7 @@ module Nexo
|
|
211
211
|
summary: calendar_event.summary,
|
212
212
|
description: calendar_event.description,
|
213
213
|
transparency: calendar_event.transparency,
|
214
|
+
status: calendar_event.nce_status,
|
214
215
|
# sequence: calendar_event.sequence
|
215
216
|
)
|
216
217
|
|
@@ -8,7 +8,8 @@ module Nexo
|
|
8
8
|
def incremental_sync!(folder)
|
9
9
|
raise Nexo::Errors::Error, "no sync token" if folder.google_next_sync_token.blank?
|
10
10
|
|
11
|
-
Nexo.logger.
|
11
|
+
Nexo.logger.info("performing incremental sync")
|
12
|
+
|
12
13
|
sync!(folder, sync_token: folder.google_next_sync_token)
|
13
14
|
rescue Google::Apis::ClientError => e
|
14
15
|
if e.message.match /fullSyncRequired/
|
@@ -51,11 +52,14 @@ module Nexo
|
|
51
52
|
events.items.each do |event|
|
52
53
|
response = ApiResponse.new(payload: event.to_h, etag: event.etag, id: event.id)
|
53
54
|
|
54
|
-
element = Element.where(uuid: response.id).first
|
55
|
+
element = Element.kept.where(uuid: response.id).first
|
56
|
+
|
55
57
|
if element.present?
|
56
58
|
Nexo.logger.debug("Element found for event")
|
57
59
|
|
58
60
|
FetchRemoteResourceJob.new.handle_response(element, response)
|
61
|
+
elsif event.status == "cancelled"
|
62
|
+
Nexo.logger.debug("Skipping cancelled event")
|
59
63
|
else
|
60
64
|
element = ElementService.new.create_element_for_remote_resource!(folder, response)
|
61
65
|
|
@@ -26,13 +26,19 @@ module Nexo
|
|
26
26
|
|
27
27
|
def discard!
|
28
28
|
element.update!(discarded_at: Time.current)
|
29
|
+
_update_ne_status!
|
29
30
|
end
|
30
31
|
|
32
|
+
# The reason of `flagged_for_removal` is that there is a time gap between
|
33
|
+
# the the user action of removing the element and the actual API call that
|
34
|
+
# deletes the remote element. At some point is necesary to know if the
|
35
|
+
# element is being deleted, like when fetching a remote version previous to
|
36
|
+
# the actual delete API call.
|
31
37
|
def flag_for_removal!(removal_reason)
|
32
38
|
Nexo.logger.debug("Flagging an element for removal")
|
33
39
|
|
34
|
-
# TODO!: the reason for this? just monitoring?
|
35
40
|
element.update!(flagged_for_removal: true, removal_reason:)
|
41
|
+
_update_ne_status!
|
36
42
|
end
|
37
43
|
|
38
44
|
# @raise ActiveRecord::RecordNotUnique
|
@@ -45,7 +51,12 @@ module Nexo
|
|
45
51
|
|
46
52
|
# and set the Synchronizable fields according to the Folder#nexo_protocol
|
47
53
|
synchronizable = element_version.element.synchronizable
|
48
|
-
if
|
54
|
+
if element.flagged_for_removal?
|
55
|
+
Nexo.logger.info("Element flagged for removal")
|
56
|
+
ElementService.new(element_version:).update_element_version!(
|
57
|
+
nev_status: :ignored_by_deletion
|
58
|
+
)
|
59
|
+
elsif synchronizable.present?
|
49
60
|
synchronizable.update_from_fields!(fields)
|
50
61
|
|
51
62
|
# synchronizable could have been destroyed
|
@@ -226,6 +237,8 @@ module Nexo
|
|
226
237
|
:pending_external_sync
|
227
238
|
elsif local_change
|
228
239
|
:pending_local_sync
|
240
|
+
elsif element.flagged_for_removal? && element.discarded_at.nil?
|
241
|
+
:pending_remote_delete
|
229
242
|
else
|
230
243
|
:synced
|
231
244
|
end
|
data/app/lib/nexo/errors.rb
CHANGED
@@ -12,7 +12,6 @@ module Nexo
|
|
12
12
|
class MoreThanOneElementInFolderForSynchronizable < Error; end
|
13
13
|
class InvalidFolderState < Error; end
|
14
14
|
class FolderDiscarded < Error; end
|
15
|
-
class SynchronizableDiscarded < Error; end
|
16
15
|
|
17
16
|
# on ControllerHelper
|
18
17
|
class InvalidParamsError < Error; 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?
|
@@ -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
|
]
|
@@ -42,9 +46,8 @@ module Nexo
|
|
42
46
|
fields = service.fields_from_payload(payload)
|
43
47
|
|
44
48
|
synchronizable = new
|
45
|
-
attributes = synchronizable.translate_fields(fields)
|
49
|
+
attributes = synchronizable.translate_fields(fields, for_create: true, folder:)
|
46
50
|
synchronizable.assign_attributes(attributes)
|
47
|
-
synchronizable.sequence = payload["sequence"]
|
48
51
|
synchronizable.save!
|
49
52
|
|
50
53
|
synchronizable
|
@@ -81,7 +84,7 @@ module Nexo
|
|
81
84
|
raise "must be implemented in subclass"
|
82
85
|
end
|
83
86
|
|
84
|
-
def translate_fields(fields)
|
87
|
+
def translate_fields(fields, for_create: false, folder: nil)
|
85
88
|
raise "must be implemented in subclass"
|
86
89
|
end
|
87
90
|
# :nocov:
|
data/app/models/nexo/element.rb
CHANGED
@@ -32,7 +32,13 @@ module Nexo
|
|
32
32
|
scope :kept, -> { where(discarded_at: nil) }
|
33
33
|
|
34
34
|
enum :removal_reason, no_longer_included_in_folder: 0, synchronizable_destroyed: 1
|
35
|
-
|
35
|
+
|
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
|
36
42
|
|
37
43
|
scope :conflicted, -> { where(ne_status: :conflicted) }
|
38
44
|
|
@@ -39,10 +39,15 @@ module Nexo
|
|
39
39
|
synced: 1,
|
40
40
|
ignored_in_conflict: 2,
|
41
41
|
superseded: 3,
|
42
|
-
ignored_by_sync_direction: 4
|
42
|
+
ignored_by_sync_direction: 4,
|
43
|
+
ignored_by_deletion: 5
|
43
44
|
|
44
45
|
serialize :payload, coder: JSON
|
45
46
|
|
46
47
|
validates :origin, presence: true
|
48
|
+
|
49
|
+
def remote_status
|
50
|
+
payload["status"] if payload.is_a?(Hash)
|
51
|
+
end
|
47
52
|
end
|
48
53
|
end
|
@@ -5,22 +5,30 @@
|
|
5
5
|
<table>
|
6
6
|
<tr>
|
7
7
|
<th></th>
|
8
|
+
<th>ID</th>
|
8
9
|
<th>User</th>
|
9
10
|
<th>Folder</th>
|
10
11
|
<th>Synchronizable</th>
|
11
12
|
<th>Status</th>
|
12
13
|
<th>Updated at</th>
|
13
14
|
<th>Created at</th>
|
15
|
+
<th>Remote status</th>
|
16
|
+
<th>Flagged for removal?</th>
|
17
|
+
<th>Discarded at</th>
|
14
18
|
</tr>
|
15
19
|
<% @elements.each do |element| %>
|
16
20
|
<tr>
|
17
21
|
<td><%= link_to "Show", element_path(element) %></td>
|
22
|
+
<td><%= element.id %></td>
|
18
23
|
<td><%= element.folder.integration.user %></td>
|
19
24
|
<td><%= element.folder %></td>
|
20
|
-
<td><%= "#{element.synchronizable_type}: #{element.synchronizable}" %></td>
|
25
|
+
<td><%= truncate "#{element.synchronizable_type}: #{element.synchronizable}", length: 50 %></td>
|
21
26
|
<td><%= element.ne_status %></td>
|
22
27
|
<td><%= element.updated_at %></td>
|
23
28
|
<td><%= element.created_at %></td>
|
29
|
+
<td><%= element.last_remote_version&.remote_status %></td>
|
30
|
+
<td><%= element.flagged_for_removal %></td>
|
31
|
+
<td><%= element.discarded_at %></td>
|
24
32
|
</tr>
|
25
33
|
<% end %>
|
26
34
|
</table>
|
@@ -28,5 +36,5 @@
|
|
28
36
|
<div class="paginator">
|
29
37
|
<%= page_entries_info @elements %>
|
30
38
|
<%= paginate @elements %>
|
39
|
+
<%#= paginate @elements, theme: 'kaminari' %>
|
31
40
|
</div>
|
32
|
-
|
@@ -17,6 +17,10 @@
|
|
17
17
|
<%= form_with(url: update_status_element_path(@element)) do |f| %>
|
18
18
|
<%= f.submit "Update status" %>
|
19
19
|
<% end %>
|
20
|
+
|
21
|
+
<%= form_with(url: modify_element_path(@element)) do |f| %>
|
22
|
+
<%= f.button(:operation, value: "delete") { "Delete" } %>
|
23
|
+
<% end %>
|
20
24
|
<br>
|
21
25
|
|
22
26
|
<h4>Element</h4>
|
@@ -47,7 +51,7 @@
|
|
47
51
|
<td><%= version.origin %></td>
|
48
52
|
<td><%= version.nev_status %></td>
|
49
53
|
<td><%= version.sequence %></td>
|
50
|
-
<td><%= version.
|
54
|
+
<td><%= version.remote_status %></td>
|
51
55
|
<td><%= version.etag %></td>
|
52
56
|
<td><%= version.created_at %></td>
|
53
57
|
<td><%= version.updated_at %></td>
|
data/config/routes.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
class ElementSyncStatus < ActiveRecord::Migration[7.2]
|
2
2
|
def change
|
3
|
-
add_column :nexo_elements, :ne_status, :integer
|
3
|
+
add_column :nexo_elements, :ne_status, :integer
|
4
|
+
change_column_null :nexo_elements, :ne_status, false, -1
|
4
5
|
remove_column :nexo_elements, :conflicted, :boolean, default: false, null: false
|
5
6
|
|
6
|
-
add_column :nexo_element_versions, :nev_status, :integer
|
7
|
+
add_column :nexo_element_versions, :nev_status, :integer
|
8
|
+
change_column_null :nexo_element_versions, :nev_status, false, -1
|
7
9
|
|
8
10
|
add_index :nexo_element_versions, [:element_id, :sequence], unique: true, where: "sequence IS NOT NULL"
|
9
11
|
add_index :nexo_element_versions, [:element_id, :etag], unique: true, where: "etag IS NOT NULL"
|
data/lib/nexo/engine.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "googleauth"
|
2
2
|
require "google-apis-oauth2_v2"
|
3
3
|
require "google-apis-calendar_v3"
|
4
|
+
require "kaminari"
|
4
5
|
|
5
6
|
module Nexo
|
6
7
|
def self.folder_rules
|
@@ -20,6 +21,9 @@ module Nexo
|
|
20
21
|
end
|
21
22
|
|
22
23
|
mattr_accessor :api_jobs_throttle
|
24
|
+
mattr_accessor :admin_controller_parent_class
|
25
|
+
|
26
|
+
self.admin_controller_parent_class = ActionController::Base
|
23
27
|
|
24
28
|
# @!visibility private
|
25
29
|
class Engine < ::Rails::Engine
|
data/lib/nexo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nexo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martín Rosso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -128,6 +128,7 @@ files:
|
|
128
128
|
- app/models/nexo/folder.rb
|
129
129
|
- app/models/nexo/integration.rb
|
130
130
|
- app/models/nexo/token.rb
|
131
|
+
- app/views/layouts/error.html.erb
|
131
132
|
- app/views/layouts/nexo.html.erb
|
132
133
|
- app/views/nexo/element_versions/show.html.erb
|
133
134
|
- app/views/nexo/elements/index.html.erb
|