Skip to content

Commit

Permalink
Adding NBP push connector
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathis-Z committed Jul 27, 2024
1 parent 7d513fe commit dcb0451
Show file tree
Hide file tree
Showing 28 changed files with 1,095 additions and 6 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ gem 'json_schemer'
gem 'js-routes'
gem 'kramdown'
gem 'kramdown-parser-gfm'
gem 'loofah'
gem 'nested_form_fields'
gem 'net-http'
gem 'net-imap', require: false
Expand All @@ -30,6 +31,7 @@ gem 'proformaxml', '~> 1.4.0'
gem 'puma'
gem 'rails', '~> 7.1.3'
gem 'rails_admin'
gem 'rails-html-sanitizer'
gem 'rails-i18n'
gem 'ransack'
gem 'rqrcode'
Expand All @@ -39,6 +41,7 @@ gem 'sassc-rails'
gem 'shakapacker', '8.0.1'
gem 'simple_form'
gem 'slim-rails'
gem 'solid_queue'
gem 'sprockets-rails'
gem 'terser'
gem 'turbolinks'
Expand Down
15 changes: 15 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ GEM
docile (1.4.0)
drb (2.2.1)
erubi (1.13.0)
et-orbi (1.2.11)
tzinfo
event_stream_parser (1.0.0)
execjs (2.9.1)
factory_bot (6.4.6)
Expand All @@ -169,6 +171,9 @@ GEM
faraday-net_http (3.1.0)
net-http
ffi (1.17.0)
fugit (1.11.0)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
glob (0.4.1)
globalid (1.2.1)
activesupport (>= 6.1)
Expand Down Expand Up @@ -326,6 +331,7 @@ GEM
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
raabro (1.4.0)
racc (1.8.0)
rack (3.1.7)
rack-mini-profiler (3.3.1)
Expand Down Expand Up @@ -521,6 +527,12 @@ GEM
slim_lint (0.27.0)
rubocop (>= 1.0, < 2.0)
slim (>= 3.0, < 6.0)
solid_queue (0.3.3)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11.0)
railties (>= 7.1)
sorted_set (1.0.3)
rbtree
set (~> 1.0)
Expand Down Expand Up @@ -607,6 +619,7 @@ DEPENDENCIES
kramdown-parser-gfm
letter_opener
listen
loofah
mnemosyne-ruby
nested_form_fields
net-http
Expand All @@ -627,6 +640,7 @@ DEPENDENCIES
rack-mini-profiler
rails (~> 7.1.3)
rails-controller-testing
rails-html-sanitizer
rails-i18n
rails_admin
ransack
Expand All @@ -653,6 +667,7 @@ DEPENDENCIES
simplecov
slim-rails
slim_lint
solid_queue
sprockets-rails
stackprof
terser
Expand Down
11 changes: 11 additions & 0 deletions app/jobs/application_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class ApplicationJob < ActiveJob::Base
include ActiveRecordLogging

# Automatically retry jobs that encountered a deadlock
retry_on ActiveRecord::Deadlocked

# Most jobs are safe to ignore if the underlying records are no longer available
discard_on ActiveJob::DeserializationError
end
20 changes: 20 additions & 0 deletions app/jobs/concerns/active_record_logging.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# This module is used to log ActiveRecord queries performed in jobs.
module ActiveRecordLogging
extend ActiveSupport::Concern

included do
around_perform do |_job, block|
# With our current Solid Queue setup, there is a difference between both logger:
# - *ActiveRecord::Base.logger*: This logger is used for SQL queries and, normally, writes to the log file only.
# - *Rails.logger*: The regular logger, which writes to the log file and the console.
# For the duration of the job, we want to write the SQL queries to the Rails logger, so they show up in the console.
# See config/solid_queue_logging.rb for more information.
previous_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = Rails.logger
block.call
ActiveRecord::Base.logger = previous_logger
end
end
end
22 changes: 22 additions & 0 deletions app/jobs/nbp_sync_all_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class NbpSyncAllJob < ApplicationJob
def perform
uuids = Set[]

# First, add all uploaded UUIDs.
# This allows us to delete the ones that are still present remote but no longer in the local database.
Nbp::PushConnector.instance.process_uploaded_task_uuids do |uuid|
uuids.add(uuid)
end

# Then, add all local UUIDs.
# This allows us to upload tasks missing remote (and remove private tasks not yet removed).
Task.select(:id, :uuid).find_each {|task| uuids.add(task.uuid) }

# Finally, schedule a full sync for each UUID identified.
uuids.each do |uuid|
NbpSyncJob.perform_later uuid
end
end
end
18 changes: 18 additions & 0 deletions app/jobs/nbp_sync_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

class NbpSyncJob < ApplicationJob
retry_on Faraday::Error, Nbp::PushConnector::ServerError, wait: :polynomially_longer, attempts: 5

def perform(uuid)
task = Task.find_by(uuid:)

if task.present? && task.access_level_public?
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') {|xml| LomService::ExportLom.call(task:, xml:) }
Nbp::PushConnector.instance.push_lom!(builder.to_xml)
Rails.logger.debug { "Task ##{task.id} \"#{task}\" pushed to NBP" }
else
Nbp::PushConnector.instance.delete_task!(uuid)
Rails.logger.debug { "Task with UUID #{uuid} deleted from NBP" }
end
end
end
11 changes: 10 additions & 1 deletion app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
class Task < ApplicationRecord
acts_as_taggable_on :state

before_validation :lowercase_language
after_commit :sync_metadata_with_nbp, if: -> { Nbp::PushConnector.enabled? }

validates :title, presence: true

validates :uuid, uniqueness: true

before_validation :lowercase_language
validates :language, format: {with: /\A[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*\z/, message: :not_de_or_us}
validate :primary_language_tag_in_iso639?

Expand Down Expand Up @@ -102,6 +104,13 @@ def self.ransackable_associations(_auth_object = nil)
%w[labels]
end

def sync_metadata_with_nbp
if access_level_public? || saved_change_to_access_level?
NbpSyncJob.perform_later uuid
NbpSyncJob.perform_later uuid_previously_was if saved_change_to_uuid? && access_level_previously_was == 'public'
end
end

# This method creates a duplicate while leaving permissions and ownership unchanged
def duplicate
dup.tap do |task|
Expand Down
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@ class Application < Rails::Application

# Fix invalid Content-Type header for incoming requests made by edu-sharing.
config.middleware.insert_before 0, Middleware::EduSharingContentType

# Configure some defaults for the Solid Queue Supervisor
require_relative 'solid_queue_defaults'
end
end
4 changes: 4 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false

# Use a real queuing backend for Active Job (and separate queues per environment).
config.active_job.queue_adapter = :solid_queue
config.active_job.queue_name_prefix = 'codeharbor_development'

config.action_mailer.perform_caching = false

# Print deprecation notices to the Rails logger.
Expand Down
4 changes: 2 additions & 2 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
# config.cache_store = :mem_cache_store

# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "codeharbor_production"
config.active_job.queue_adapter = :solid_queue
config.active_job.queue_name_prefix = 'codeharbor_production'

config.action_mailer.perform_caching = false

Expand Down
4 changes: 4 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test

# Use a real queuing backend for Active Job (and separate queues per environment).
config.active_job.queue_adapter = :solid_queue
config.active_job.queue_name_prefix = 'codeharbor_test'

config.action_mailer.perform_caching = false

# Tell Action Mailer not to deliver emails to the real world.
Expand Down
1 change: 1 addition & 0 deletions config/schedule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@

every 1.day, at: '3:00 am' do
rake 'import_cache_files:cleanup'
runner 'SolidQueue::Job.clear_finished_in_batches'
end
11 changes: 11 additions & 0 deletions config/settings/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ omniauth:
private_key: ~
oai_pmh:
admin_mail: admin@example.org
nbp:
push_connector:
enable: true
client_id: testing_client_id
client_secret: testing_client_secret
token_path: 'https://test.provider/token'
api_host: 'https://test.api.host'
source:
organization: test_organization
name: CodeHarbor
slug: CoHaP2
open_ai:
access_token: ~ # Add a valid API key from https://platform.openai.com/api-keys
model: gpt-3.5-turbo
25 changes: 25 additions & 0 deletions config/solid_queue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
processes: 1
polling_interval: 0.1

development:
<<: *default

test:
<<: *default

production:
<<: *default
dispatchers:
- polling_interval: 1
batch_size: 500
recurring_tasks:
nbp_sync_all_job:
class: NbpSyncAllJob
schedule: "0 3 * * *"
39 changes: 39 additions & 0 deletions config/solid_queue_defaults.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

# This file must be loaded before other initializers due to the logging configuration.
Rails.application.configure do
# On shutdown, jobs Solid Queue will wait the specified timeout before forcefully shutting down.
# Any job not finished by then will be picked up again after a restart.
config.solid_queue.shutdown_timeout = 10.seconds
# Remove *successful* jobs from the database after 30 days
config.solid_queue.clear_finished_jobs_after = 30.days
config.solid_queue.supervisor_pidfile = Rails.root.join('tmp/pids/solid_queue_supervisor.pid')

# For Solid Queue, we want to hide regular SQL queries from the console, but still log them to a separate file.
# For the normal webserver, this dedicated setup is neither needed nor desired.
next unless Rake.application.top_level_tasks.to_s.include?('solid_queue:')

# Specify that all logs should be written to the specified log file
file_name = "#{Rails.env}.solid_queue.log"
config.paths.add 'log', with: "log/#{file_name}"

# Send all logs regarding SQL queries to the log file.
# This will include all queries performed by Solid Queue including periodic job checks.
log_file = ActiveSupport::Logger.new(Rails.root.join('log', file_name))
config.active_record.logger = ActiveSupport::BroadcastLogger.new(log_file)

config.after_initialize do
# Create a new logger that will write to the console
console = ActiveSupport::Logger.new($stdout)
console.level = Rails.logger.level
# Enable this line to have the same log format as Rails.logger
# It will include the job name, the job ID for each line
# console.formatter = Rails.logger.formatter

ActiveSupport.on_load :solid_queue_record do
# Once SolidQueue is loaded, we can broadcast its logs to the console, too.
# Due to the initialization order, this will effectively start logging once SolidQueue is about to start.
Rails.logger.broadcast_to console
end
end
end
Loading

0 comments on commit dcb0451

Please sign in to comment.