Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
f33cc0aafd |
1 changed files with 217 additions and 0 deletions
217
commands/sync.rb
Normal file
217
commands/sync.rb
Normal file
|
@ -0,0 +1,217 @@
|
|||
require 'highline/import'
|
||||
require 'yaml'
|
||||
require 'net/http'
|
||||
require 'json'
|
||||
|
||||
usage 'sync'
|
||||
aliases :s
|
||||
summary 'Sync events with DSA'
|
||||
description 'Sync future events with the DSA panel.'
|
||||
option :k, :key, 'API key for DSA', argument: :required
|
||||
|
||||
def bold_say(str)
|
||||
say "<%= color %(#{str}), :bold %>"
|
||||
end
|
||||
|
||||
def bold_ask(str, *args)
|
||||
res = ask "<%= color %(#{str}), :bold %>", *args
|
||||
puts
|
||||
res
|
||||
end
|
||||
|
||||
DSA_API = "https://localhost:8080/api/activiteiten"
|
||||
|
||||
# Inspired by https://github.com/nanoc/nanoc/blob/main/nanoc-cli/lib/nanoc/cli/commands/shell.rb
|
||||
class SyncRunner < Nanoc::CLI::Commands::Shell
|
||||
|
||||
def run
|
||||
@site = load_site
|
||||
Nanoc::Core::Compiler.new_for(@site).run_until_preprocessed
|
||||
items = env[:items]
|
||||
# Add about ~50 hours
|
||||
cut_off = DateTime.now + Rational('2.08333')
|
||||
local_events = filter_items(items, cut_off)
|
||||
|
||||
api_key = "Zrl0JRxxKJHelIn5IRubA-GZiPw"
|
||||
|
||||
puts "Found #{local_events.length} local events"
|
||||
|
||||
# Construct a connection to the server, which will be kept open.
|
||||
uri = URI(DSA_API)
|
||||
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
|
||||
|
||||
# Get remote events from DSA.
|
||||
remote_events = get_server_events(http, api_key, cut_off)["page"]["entries"]
|
||||
.find_all { |e| e["advertise"] }
|
||||
.find_all { |e| e["sync_data"] }
|
||||
|
||||
puts "Found #{local_events.length} remote events"
|
||||
|
||||
do_sync(http, local_events, remote_events, api_key)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_sync(http, local_events, remote_events, api_key)
|
||||
# Contains local events we want to add.
|
||||
to_add = []
|
||||
# Contains events we want to potentially update.
|
||||
to_update = []
|
||||
|
||||
# Calculate the identifier for each local event.
|
||||
# We use the map (academic year) + file name without extension.
|
||||
# This is necessarily unique.
|
||||
local_events.each { |local_event|
|
||||
path = Pathname(local_event[:filename])
|
||||
academic_year = path.dirname.basename
|
||||
filename = path.basename(".*")
|
||||
identifier = "#{academic_year}-#{filename}"
|
||||
|
||||
# Check if we have a remote event with the same identifier
|
||||
remote = remote_events.find { |e| e["sync_data"] == identifier }
|
||||
|
||||
if remote == nil
|
||||
to_add.append([identifier, local_event])
|
||||
else
|
||||
to_update.append([identifier, remote, local_event])
|
||||
end
|
||||
|
||||
remote_events.delete(remote)
|
||||
}
|
||||
|
||||
puts "#{to_update.length} existing events will be updated"
|
||||
puts "#{to_add.length} new events will be added to remote"
|
||||
puts "#{remote_events.length} remote events will be deleted"
|
||||
|
||||
# Add new events.
|
||||
to_add.each { |new_event|
|
||||
add_event(http, api_key, *new_event)
|
||||
}
|
||||
|
||||
to_update.each { |existing_event|
|
||||
update_event(http, api_key, *existing_event)
|
||||
}
|
||||
|
||||
remote_events.each { |old_event|
|
||||
delete(http, api_key, old_event)
|
||||
}
|
||||
end
|
||||
|
||||
def local_to_params(local_event, identifier)
|
||||
puts local_event[:time].iso8601
|
||||
{
|
||||
"activity" => {
|
||||
"title" => local_event[:title],
|
||||
"description" => local_event[:description],
|
||||
"location" => local_event[:location],
|
||||
"address" => local_event[:locationlink],
|
||||
"advertise" => true,
|
||||
"association" => "zeus",
|
||||
"sync_data" => identifier,
|
||||
"start_time" => local_event[:time].iso8601,
|
||||
"end_time" => local_event[:end].iso8601,
|
||||
"infolink" => "https://zeus.ugent.be#{local_event.path}"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_event(http, api_key, identifier, remote_event, local_event)
|
||||
params = local_to_params(local_event, identifier)
|
||||
|
||||
uri = URI("#{DSA_API}/#{remote_event["id"]}")
|
||||
|
||||
req = Net::HTTP::Put.new(uri)
|
||||
req['Authorization'] = api_key
|
||||
req['Content-Type'] = 'application/json'
|
||||
req.body = params.to_json
|
||||
|
||||
response = http.request(req)
|
||||
|
||||
case response
|
||||
when Net::HTTPSuccess
|
||||
puts "Updated event #{remote_event["id"]}"
|
||||
when Net::HTTPUnauthorized
|
||||
raise "#{response.message}: username and password set and correct?"
|
||||
when Net::HTTPServerError
|
||||
raise "#{response.message}: try again later?"
|
||||
else
|
||||
raise response.message
|
||||
end
|
||||
end
|
||||
|
||||
def add_event(http, api_key, identifier, local_event)
|
||||
params = local_to_params(local_event, identifier)
|
||||
|
||||
uri = URI(DSA_API)
|
||||
|
||||
req = Net::HTTP::Post.new(uri)
|
||||
req['Authorization'] = api_key
|
||||
req['Content-Type'] = 'application/json'
|
||||
req.body = params.to_json
|
||||
response = http.request(req)
|
||||
|
||||
case response
|
||||
when Net::HTTPSuccess
|
||||
puts "Added event #{identifier}"
|
||||
when Net::HTTPUnauthorized
|
||||
raise "#{response.message}: username and password set and correct?"
|
||||
when Net::HTTPServerError
|
||||
raise "#{response.message}: try again later?"
|
||||
else
|
||||
print response.body
|
||||
raise response.message
|
||||
end
|
||||
end
|
||||
|
||||
def delete(http, api_key, remote)
|
||||
uri = URI("#{DSA_API}/#{remote["id"]}")
|
||||
|
||||
req = Net::HTTP::Delete.new(uri)
|
||||
req['Authorization'] = api_key
|
||||
|
||||
response = http.request(req)
|
||||
|
||||
case response
|
||||
when Net::HTTPSuccess
|
||||
puts "Delete event #{remote["id"]}"
|
||||
when Net::HTTPUnauthorized
|
||||
raise "#{response.message}: username and password set and correct?"
|
||||
when Net::HTTPServerError
|
||||
raise "#{response.message}: try again later?"
|
||||
else
|
||||
raise response.message
|
||||
end
|
||||
end
|
||||
|
||||
def filter_items(items, cut_off)
|
||||
# Must be in the future
|
||||
# Must have sync_id
|
||||
items.find_all('/events/*/*.md')
|
||||
.find_all { |event| event[:time] > cut_off }
|
||||
.find_all { |event| event[:exclude_from_sync] != true }
|
||||
end
|
||||
|
||||
def get_server_events(http, api_key, cut_off)
|
||||
uri = URI(DSA_API)
|
||||
params = {:start_time => cut_off.iso8601, :association => "zeus"}
|
||||
uri.query = URI.encode_www_form(params)
|
||||
|
||||
request = Net::HTTP::Get.new(uri)
|
||||
request['Authorization'] = api_key
|
||||
response = http.request(request)
|
||||
|
||||
case response
|
||||
when Net::HTTPSuccess
|
||||
JSON.parse response.body
|
||||
when Net::HTTPUnauthorized
|
||||
raise "#{response.message}: username and password set and correct?"
|
||||
when Net::HTTPServerError
|
||||
raise "#{response.message}: try again later?"
|
||||
else
|
||||
raise response.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
runner SyncRunner
|
Loading…
Reference in a new issue