nexo 0.1.4 → 0.1.6
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 +11 -0
- data/app/controllers/nexo/element_versions_controller.rb +28 -0
- data/app/controllers/nexo/elements_controller.rb +64 -0
- data/app/controllers/nexo/folders_controller.rb +41 -0
- data/app/controllers/nexo/nexo_controller.rb +5 -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 +11 -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 +58 -24
- data/app/lib/nexo/active_record_google_token_store.rb +3 -3
- data/app/lib/nexo/api_client/google_auth_service.rb +11 -6
- data/app/lib/nexo/api_client/google_calendar_service.rb +167 -32
- data/app/lib/nexo/api_client/google_calendar_sync_service.rb +84 -0
- data/app/lib/nexo/element_service.rb +236 -0
- data/app/lib/nexo/errors.rb +6 -4
- 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 +33 -2
- data/app/models/concerns/nexo/synchronizable.rb +25 -9
- data/app/models/nexo/client.rb +9 -10
- data/app/models/nexo/element.rb +22 -39
- data/app/models/nexo/element_version.rb +12 -2
- data/app/models/nexo/folder.rb +29 -17
- data/app/models/nexo/integration.rb +4 -3
- data/app/models/nexo/token.rb +6 -4
- data/app/views/layouts/nexo.html.erb +42 -0
- data/app/views/nexo/element_versions/show.html.erb +16 -0
- data/app/views/nexo/elements/index.html.erb +32 -0
- data/app/views/nexo/elements/show.html.erb +56 -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 +25 -0
- data/config/spring.rb +1 -0
- data/db/migrate/20250505192315_create_nexo_clients.rb +1 -1
- data/db/migrate/20250506125057_create_nexo_tokens.rb +1 -1
- data/db/migrate/20250512025950_create_nexo_elements.rb +2 -2
- data/db/migrate/20250604124821_element_sync_status.rb +11 -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 +5 -4
- data/lib/nexo/engine.rb +22 -5
- data/lib/nexo/version.rb +1 -1
- metadata +38 -4
- data/app/jobs/nexo/sync_element_job.rb +0 -93
- data/app/models/concerns/nexo/folder_policy.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aca3e3e57c524de464408274ab6309fbea580931420c9da736556d7997ed6bd6
|
4
|
+
data.tar.gz: 770c4033d4dc280ad32471800e59a6d6d5d5af7a150897d52e5fa69de043b393
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 299fe496fcaa3041699cf37fa39e6d897005a4d7a5343c912694ff72cec18a91d19d317c91898c6cd1d55aeba606967298f1ce73ea40008cc14efa4acd62d807
|
7
|
+
data.tar.gz: 63057165522974406b55ec157a46e1c664d17b81e341e7dd04a551e93266e94bc38a0501297d3151f4a0636ad7d8c01e1cd2f739a7bc37cc340f37f53b435d8b
|
data/README.md
CHANGED
@@ -26,6 +26,17 @@ Configure good_job
|
|
26
26
|
max_threads:
|
27
27
|
queues:
|
28
28
|
|
29
|
+
## Unhandled cases / TODO's
|
30
|
+
|
31
|
+
- Restore locally discarded Events. It creates a new Google Calendar event?
|
32
|
+
|
33
|
+
- Recurring google events
|
34
|
+
|
35
|
+
- IMPORTANTE: loggear las exceptions de los jobs
|
36
|
+
|
37
|
+
- Llevar mucho control de los jobs y las exceptions que puedan surgir como
|
38
|
+
ActiveRecord::PreparedStatementCacheExpired
|
39
|
+
|
29
40
|
## Contributing
|
30
41
|
Contribution directions go here.
|
31
42
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# :nocov: TODO
|
2
|
+
module Nexo
|
3
|
+
class ElementVersionsController < NexoController
|
4
|
+
def show
|
5
|
+
@element_version = ElementVersion.find(params[:id])
|
6
|
+
end
|
7
|
+
|
8
|
+
def sync
|
9
|
+
@element_version = ElementVersion.find(params[:id])
|
10
|
+
|
11
|
+
case params[:operation]
|
12
|
+
when "import"
|
13
|
+
ImportRemoteElementVersion.new.perform(@element_version)
|
14
|
+
notice = "Imported"
|
15
|
+
when "update_remote"
|
16
|
+
UpdateRemoteResourceJob.perform_later(@element_version)
|
17
|
+
notice = "enqueued UpdateRemoteResourceJob"
|
18
|
+
else
|
19
|
+
raise "unkown action"
|
20
|
+
end
|
21
|
+
|
22
|
+
redirect_to @element_version, notice:
|
23
|
+
rescue StandardError => e
|
24
|
+
redirect_to @element_version, alert: e.message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# :nocov:
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# :nocov: TODO
|
2
|
+
module Nexo
|
3
|
+
class ElementsController < NexoController
|
4
|
+
before_action except: :index do
|
5
|
+
@element = Element.find(params[:id])
|
6
|
+
end
|
7
|
+
|
8
|
+
def index
|
9
|
+
@elements =
|
10
|
+
Element.includes(:synchronizable).order(id: :desc)
|
11
|
+
.page(params[:page]).per(params[:page_size] || 10)
|
12
|
+
|
13
|
+
if params[:not_synced]
|
14
|
+
@elements = @elements.where.not(ne_status: :synced)
|
15
|
+
end
|
16
|
+
if params[:without_synchronizable]
|
17
|
+
@elements = @elements.where(synchronizable_id: nil)
|
18
|
+
end
|
19
|
+
I18n.locale = :en
|
20
|
+
end
|
21
|
+
|
22
|
+
def show
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_status
|
26
|
+
ElementService.new(element: @element).update_ne_status!
|
27
|
+
|
28
|
+
redirect_to @element, notice: "Updated status"
|
29
|
+
end
|
30
|
+
|
31
|
+
def modify_local
|
32
|
+
unless Rails.env.local?
|
33
|
+
redirect_to @element, alert: "Available only on local env"
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
event = @element.synchronizable
|
38
|
+
event.date_from = event.date_from + 1.day
|
39
|
+
event.date_to = event.date_to + 1.day
|
40
|
+
event.save!
|
41
|
+
|
42
|
+
EventReceiver.new.synchronizable_updated(event)
|
43
|
+
|
44
|
+
redirect_to @element, notice: "Modified"
|
45
|
+
rescue StandardError => e
|
46
|
+
redirect_to @element, alert: e.message
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch_remote
|
50
|
+
FetchRemoteResourceJob.perform_later(@element)
|
51
|
+
|
52
|
+
redirect_to @element, notice: "Enqueued FetchRemoteResourceJob"
|
53
|
+
end
|
54
|
+
|
55
|
+
def resolve_conflict
|
56
|
+
ElementService.new(element: @element).resolve_conflict!
|
57
|
+
|
58
|
+
redirect_to @element, notice: "Conflict solved"
|
59
|
+
rescue StandardError => e
|
60
|
+
redirect_to @element, alert: e.message
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# :nocov:
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# :nocov: TODO
|
2
|
+
module Nexo
|
3
|
+
class FoldersController < NexoController
|
4
|
+
before_action except: :index do
|
5
|
+
@folder = Folder.find(params[:id])
|
6
|
+
end
|
7
|
+
|
8
|
+
def index
|
9
|
+
page = params[:page].to_i || 0
|
10
|
+
page_size = 100
|
11
|
+
@folders = Folder.offset(page * page_size).limit(page_size).order(id: :desc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def show
|
15
|
+
end
|
16
|
+
|
17
|
+
def check_status
|
18
|
+
FolderCheckStatusJob.perform_later(@folder)
|
19
|
+
|
20
|
+
redirect_to @folder, notice: "Checking status"
|
21
|
+
end
|
22
|
+
|
23
|
+
def sync
|
24
|
+
FolderDownloadJob.perform_later(@folder, "full_or_incremental_sync")
|
25
|
+
|
26
|
+
redirect_to @folder, notice: "Enqueued sync"
|
27
|
+
end
|
28
|
+
|
29
|
+
def full_sync
|
30
|
+
FolderDownloadJob.perform_later(@folder, "full_sync")
|
31
|
+
|
32
|
+
redirect_to @folder, notice: "Enqueued full sync"
|
33
|
+
end
|
34
|
+
|
35
|
+
def incremental_sync
|
36
|
+
FolderDownloadJob.perform_later(@folder, "incremental_sync")
|
37
|
+
|
38
|
+
redirect_to @folder, notice: "Enqueued incremental sync"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -3,29 +3,31 @@ module Nexo
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
|
6
|
+
queue_as :nexo_api_clients
|
7
7
|
|
8
|
-
|
8
|
+
# :nocov: tricky
|
9
|
+
if defined? GoodJob
|
10
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
good_job_control_concurrency_with(
|
14
|
-
perform_limit: 1,
|
12
|
+
# https://github.com/bensheldon/good_job?tab=readme-ov-file#optimize-queues-threads-and-processes
|
13
|
+
good_job_control_concurrency_with(
|
14
|
+
perform_limit: 1,
|
15
15
|
|
16
|
-
|
16
|
+
perform_throttle: (Nexo.api_jobs_throttle || [ 100, 5.minute ]),
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
key: -> { "#{queue_name}" }
|
19
|
+
)
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
retry_on(
|
22
|
+
GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError,
|
23
|
+
attempts: Float::INFINITY,
|
24
|
+
wait: :polynomially_longer,
|
25
|
+
jitter: 0.99,
|
26
|
+
# wait: ->(executions) { ((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2 }
|
27
|
+
# wait: ->(executions) { ((executions**3) + (Kernel.rand * (executions**3) * 0.5)) + 2 }
|
28
|
+
)
|
29
|
+
end
|
30
|
+
# :nocov:
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
data/app/jobs/nexo/base_job.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module Nexo
|
2
2
|
class BaseJob < ActiveJob::Base
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
# Like ActiveJob does with Rails.logger, we do with Nexo.logger
|
4
|
+
# push job's name and id tags
|
5
|
+
around_perform do |job, block|
|
6
|
+
tags = job.class.name, job.job_id
|
7
|
+
Nexo.plain_logger.tagged(*tags, &block)
|
6
8
|
end
|
7
9
|
end
|
8
10
|
end
|
@@ -1,10 +1,20 @@
|
|
1
1
|
module Nexo
|
2
|
+
# - Removing/discarding Element's
|
2
3
|
class DeleteRemoteResourceJob < BaseJob
|
3
4
|
include ApiClients
|
4
5
|
|
5
6
|
def perform(element)
|
7
|
+
if element.discarded?
|
8
|
+
raise "element already discarded"
|
9
|
+
end
|
10
|
+
|
11
|
+
unless element.flagged_for_removal?
|
12
|
+
raise "element not flagged for removal"
|
13
|
+
end
|
14
|
+
|
6
15
|
ServiceBuilder.instance.build_protocol_service(element.folder).remove(element)
|
7
|
-
|
16
|
+
|
17
|
+
ElementService.new(element:).discard!
|
8
18
|
end
|
9
19
|
end
|
10
20
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Nexo
|
2
|
+
# Always must be executed asynchronously to ensure the concurrency limit applies
|
3
|
+
#
|
4
|
+
# Responsabilities:
|
5
|
+
# - Creating a ElementVersion on external incoming change
|
6
|
+
# - Updating a Synchronizable for that external change
|
7
|
+
#
|
8
|
+
# @raise ActiveRecord::RecordNotUnique
|
9
|
+
class FetchRemoteResourceJob < BaseJob
|
10
|
+
include ApiClients
|
11
|
+
|
12
|
+
attr_reader :element
|
13
|
+
|
14
|
+
def perform(element)
|
15
|
+
@element = element
|
16
|
+
|
17
|
+
remote_service = ServiceBuilder.instance.build_protocol_service(element.folder)
|
18
|
+
|
19
|
+
response = remote_service.get_event(element)
|
20
|
+
# TODO!: handle calendar change
|
21
|
+
|
22
|
+
handle_response(element, response)
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_response(element, response)
|
26
|
+
# TODO!: refactor y que no tenga que repetir esta asignación
|
27
|
+
# extraer a algún Service?
|
28
|
+
@element = element
|
29
|
+
|
30
|
+
if response.present? && element.element_versions.where(etag: response.etag).empty?
|
31
|
+
Nexo.logger.debug { "Fetched new element version from remote server" }
|
32
|
+
Nexo.logger.debug { response.payload }
|
33
|
+
|
34
|
+
element_version = save_element_version(response)
|
35
|
+
|
36
|
+
if element.folder.sync_external_changes?
|
37
|
+
ImportRemoteElementVersion.new.perform(element_version)
|
38
|
+
else
|
39
|
+
Nexo.logger.info("Element version ignored_by_sync_direction")
|
40
|
+
end
|
41
|
+
else
|
42
|
+
Nexo.logger.debug { "No new version fetched from remote server" }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def save_element_version(service_response)
|
49
|
+
nev_status =
|
50
|
+
element.folder.sync_external_changes? ? :pending_sync : :ignored_by_sync_direction
|
51
|
+
|
52
|
+
ElementService.new(element:).create_element_version!(
|
53
|
+
origin: :external,
|
54
|
+
etag: service_response.etag,
|
55
|
+
payload: service_response.payload,
|
56
|
+
sequence: nil,
|
57
|
+
nev_status:
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Nexo
|
2
|
+
class FolderCheckStatusJob < BaseJob
|
3
|
+
def perform(folder)
|
4
|
+
calendar = GoogleCalendarService.new(folder.integration).get_calendar(folder)
|
5
|
+
folder.update(nf_status: :ok)
|
6
|
+
rescue StandardError => e
|
7
|
+
folder.update(nf_status: :not_found)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Nexo
|
2
2
|
class FolderDestroyJob < BaseJob
|
3
|
+
# TODO!: limit concurrency
|
3
4
|
def perform(folder)
|
4
5
|
if folder.external_identifier.present?
|
5
6
|
protocol_service = ServiceBuilder.instance.build_protocol_service(folder)
|
6
7
|
response = protocol_service.remove_calendar(folder)
|
7
8
|
else
|
8
|
-
|
9
|
+
Nexo.logger.info("Folder doesn't have external_identifier: #{folder.to_gid}")
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Nexo
|
2
|
+
class FolderDownloadJob < BaseJob
|
3
|
+
def perform(folder, type)
|
4
|
+
if type == "full_sync"
|
5
|
+
GoogleCalendarSyncService.new(folder.integration).full_sync!(folder)
|
6
|
+
elsif type == "incremental_sync"
|
7
|
+
GoogleCalendarSyncService.new(folder.integration).incremental_sync!(folder)
|
8
|
+
elsif type == "full_or_incremental_sync"
|
9
|
+
GoogleCalendarSyncService.new(folder.integration).full_or_incremental_sync!(folder)
|
10
|
+
else
|
11
|
+
raise "unknown sync type"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Nexo
|
2
2
|
class FolderSyncJob < BaseJob
|
3
|
+
# TODO!: limit concurrency
|
3
4
|
def perform(folder)
|
4
5
|
protocol_service = ServiceBuilder.instance.build_protocol_service(folder)
|
5
6
|
if folder.external_identifier.blank?
|
@@ -10,18 +11,23 @@ module Nexo
|
|
10
11
|
end
|
11
12
|
|
12
13
|
policies = PolicyService.instance.policies_for(folder)
|
14
|
+
Nexo.logger.debug { "Found #{policies.length} policies" }
|
13
15
|
# flat_map should be equivalent to:
|
14
16
|
# policies.map(&:synchronizable_queries).flatten(1)
|
15
17
|
queries = policies.flat_map(&:synchronizable_queries)
|
18
|
+
Nexo.logger.debug { "Found #{queries.length} queries" }
|
16
19
|
|
17
20
|
GoodJob::Bulk.enqueue do
|
18
21
|
queries.each do |query|
|
19
22
|
# TODO: avoid calling more than once per synchronizable
|
20
23
|
query.find_each do |synchronizable|
|
24
|
+
Nexo.logger.debug { "Processing synchronizable: #{synchronizable}" }
|
25
|
+
|
21
26
|
folder_service.find_element_and_sync(folder, synchronizable)
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
30
|
+
Nexo.logger.debug { "Finished processing queries" }
|
25
31
|
end
|
26
32
|
|
27
33
|
private
|
@@ -1,15 +1,30 @@
|
|
1
1
|
module Nexo
|
2
2
|
class SynchronizableChangedJob < BaseJob
|
3
|
-
|
3
|
+
# :nocov: tricky
|
4
|
+
if defined? GoodJob
|
5
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
6
|
+
|
7
|
+
good_job_control_concurrency_with(
|
8
|
+
perform_limit: 1,
|
9
|
+
key: -> { arguments.first.to_gid.to_s }
|
10
|
+
)
|
11
|
+
end
|
12
|
+
# :nocov:
|
4
13
|
|
5
14
|
# TODO: check
|
6
15
|
# https://github.com/rails/solid_queue?tab=readme-ov-file#jobs-and-transactional-integrity
|
7
16
|
#
|
8
17
|
# TODO: handle exceptions
|
9
18
|
|
10
|
-
|
19
|
+
# @raise [ActiveRecord::PreparedStatementCacheExpired]
|
20
|
+
def perform(synchronizable, excluded_folders: [])
|
11
21
|
# Maybe restrict this query to a more specific scope
|
12
22
|
scope = Folder.kept
|
23
|
+
if excluded_folders.any?
|
24
|
+
scope = scope.where.not(id: excluded_folders)
|
25
|
+
end
|
26
|
+
Nexo.logger.debug("Processing #{scope.count} folders")
|
27
|
+
|
13
28
|
# TODO: test
|
14
29
|
GoodJob::Bulk.enqueue do
|
15
30
|
scope.each do |folder|
|
@@ -2,34 +2,74 @@ module Nexo
|
|
2
2
|
class UpdateRemoteResourceJob < BaseJob
|
3
3
|
include ApiClients
|
4
4
|
|
5
|
-
|
5
|
+
# https://github.com/rails/solid_queue?tab=readme-ov-file#jobs-and-transactional-integrity
|
6
|
+
self.enqueue_after_transaction_commit = true
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
attr_reader :element, :element_version
|
9
|
+
|
10
|
+
# @raise Google::Apis::ClientError
|
11
|
+
# @raise [ActiveRecord::RecordNotUnique] on ElementVersion update
|
12
|
+
# @raise [Errno::ENETUNREACH]
|
13
|
+
# - (Network is unreachable - Network is unreachable - connect(2) for "www.googleapis.com"
|
14
|
+
# @raise [Signet::AuthorizationError]
|
15
|
+
# - Unexpected error: #<Faraday::ConnectionFailed
|
16
|
+
# wrapped=#<Socket::ResolutionError: Failed to open TCP connection to
|
17
|
+
# oauth2.googleapis.com:443 (getaddrinfo: Name or service not known)>>"
|
18
|
+
def perform(element_version)
|
19
|
+
@element_version = element_version
|
20
|
+
@element = element_version.element
|
9
21
|
|
10
22
|
validate_element_state!
|
11
23
|
|
12
24
|
remote_service = ServiceBuilder.instance.build_protocol_service(element.folder)
|
13
25
|
|
14
26
|
response =
|
15
|
-
if element.
|
27
|
+
if element.uuid.present?
|
16
28
|
remote_service.update(element)
|
17
29
|
else
|
18
|
-
remote_service.insert(element
|
19
|
-
element.
|
30
|
+
remote_service.insert(element).tap do |response|
|
31
|
+
ElementService.new(element:).update_element!(uuid: response.id)
|
20
32
|
end
|
21
33
|
end
|
22
34
|
|
23
|
-
|
35
|
+
ElementService.new(element_version:).update_element_version!(
|
36
|
+
nev_status: :synced,
|
37
|
+
etag: response.etag,
|
38
|
+
payload: response.payload
|
39
|
+
)
|
40
|
+
rescue Errors::ConflictingRemoteElementChange => e
|
41
|
+
Nexo.logger.warn <<~STR
|
42
|
+
ConflictingRemoteElementChange for #{element.to_gid}. \
|
43
|
+
Enqueuing FetchRemoteResourceJob, which should lead to conflicted element
|
44
|
+
STR
|
45
|
+
FetchRemoteResourceJob.perform_later(element)
|
24
46
|
end
|
25
47
|
|
26
48
|
private
|
27
49
|
|
28
50
|
def validate_element_state!
|
51
|
+
if element.element_versions.where(nev_status: :synced)
|
52
|
+
.where("sequence > ?", element_version.sequence).any?
|
53
|
+
|
54
|
+
ElementService.new(element_version:).update_element_version!(nev_status: :superseded)
|
55
|
+
|
56
|
+
raise Errors::Error, "version superseded"
|
57
|
+
end
|
58
|
+
|
59
|
+
unless element.folder.sync_internal_changes?
|
60
|
+
raise Errors::Error, "sync direction excludes internal changes"
|
61
|
+
end
|
62
|
+
|
29
63
|
if element.synchronizable.blank?
|
30
64
|
raise Errors::SynchronizableNotFound
|
31
65
|
end
|
32
66
|
|
67
|
+
element.synchronizable.validate_synchronizable!
|
68
|
+
|
69
|
+
if element.discarded?
|
70
|
+
raise Errors::ElementDiscarded
|
71
|
+
end
|
72
|
+
|
33
73
|
if element.synchronizable.respond_to?(:discarded?) && element.synchronizable.discarded?
|
34
74
|
raise Errors::SynchronizableDiscarded
|
35
75
|
end
|
@@ -39,30 +79,24 @@ module Nexo
|
|
39
79
|
end
|
40
80
|
|
41
81
|
if element.synchronizable.conflicted?
|
42
|
-
raise Errors::
|
82
|
+
raise Errors::UpdateRemoteVersionFailed, "synchronizable conflicted"
|
43
83
|
end
|
44
84
|
|
45
|
-
|
46
|
-
raise Errors::
|
85
|
+
unless element.pending_local_sync?
|
86
|
+
raise Errors::Error, "invalid ne_status: #{element.ne_status}"
|
47
87
|
end
|
48
88
|
|
49
|
-
|
50
|
-
|
89
|
+
if !element_version.internal?
|
90
|
+
raise Errors::Error, "invalid ElementVersion: must be internal"
|
91
|
+
end
|
51
92
|
|
52
|
-
|
53
|
-
raise Errors::
|
93
|
+
if element_version.etag.present?
|
94
|
+
raise Errors::Error, "invalid ElementVersion: etag must be blank"
|
54
95
|
end
|
55
|
-
end
|
56
96
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
element:,
|
61
|
-
origin: :internal,
|
62
|
-
etag: service_response.etag,
|
63
|
-
payload: service_response.payload,
|
64
|
-
sequence: element.synchronizable.sequence,
|
65
|
-
)
|
97
|
+
unless element_version.pending_sync?
|
98
|
+
raise Errors::Error, "invalid ElementVersion: must be pending_sync"
|
99
|
+
end
|
66
100
|
end
|
67
101
|
end
|
68
102
|
end
|
@@ -17,7 +17,7 @@ module Nexo
|
|
17
17
|
def store(integration, token)
|
18
18
|
ActiveRecord::Base.transaction do
|
19
19
|
# Maybe these should be destroyed
|
20
|
-
integration.tokens.active.update_all(
|
20
|
+
integration.tokens.active.update_all(nt_status: :expired)
|
21
21
|
|
22
22
|
Token.create!(integration:, secret: token)
|
23
23
|
end
|
@@ -28,7 +28,7 @@ module Nexo
|
|
28
28
|
token = find_by_id(id)
|
29
29
|
|
30
30
|
if token.present?
|
31
|
-
token.update!(
|
31
|
+
token.update!(nt_status: :revoked)
|
32
32
|
else
|
33
33
|
# TODO: pg_warn("Couldn't find token for revocation: #{id}")
|
34
34
|
end
|
@@ -37,7 +37,7 @@ module Nexo
|
|
37
37
|
private
|
38
38
|
|
39
39
|
def find_by_id(id)
|
40
|
-
Token.where(environment: Rails.env, integration: id,
|
40
|
+
Token.where(environment: Rails.env, integration: id, nt_status: :active).last
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -53,21 +53,26 @@ module Nexo
|
|
53
53
|
# Guarda el Token
|
54
54
|
# Si el client tiene más permisos que los que el user solicitó
|
55
55
|
def get_credentials(request = nil)
|
56
|
+
# :nocov: tricky
|
56
57
|
if request.present? && request.session["code_verifier"].present?
|
57
|
-
# :nocov: tricky
|
58
58
|
authorizer.code_verifier = request.session["code_verifier"]
|
59
|
-
# :nocov:
|
60
59
|
end
|
61
|
-
|
60
|
+
# :nocov:
|
61
|
+
|
62
|
+
authorizer.get_credentials(@integration, request).tap do |credentials|
|
63
|
+
if credentials.nil? && request.present? && !request.session["code_verifier"].present?
|
64
|
+
Nexo.logger.warn("Request has no code_verifier")
|
65
|
+
end
|
66
|
+
end
|
62
67
|
rescue Signet::AuthorizationError
|
63
68
|
# TODO: log
|
64
69
|
end
|
65
70
|
|
66
|
-
def get_authorization_url(request)
|
71
|
+
def get_authorization_url(request, login_hint: nil)
|
67
72
|
request.session["code_verifier"] ||= Google::Auth::WebUserAuthorizer.generate_code_verifier
|
68
73
|
authorizer.code_verifier = request.session["code_verifier"]
|
69
|
-
authorizer.get_authorization_url(request:)
|
70
|
-
|
74
|
+
# authorizer.get_authorization_url(request:)
|
75
|
+
authorizer.get_authorization_url(request:, login_hint:)
|
71
76
|
end
|
72
77
|
|
73
78
|
private
|