diff --git a/app/jobs/pretalx/import_job.rb b/app/jobs/pretalx/import_job.rb index db4f80125b02f3e7b3667f6420f16a75d2784133..d0f1ee377195e125aca40a03bed96191f4f3bc91 100644 --- a/app/jobs/pretalx/import_job.rb +++ b/app/jobs/pretalx/import_job.rb @@ -8,6 +8,41 @@ module Pretalx queue_as :default include ActionView::Helpers + # Class method to return required data fields + def self.required_data_fields + [ "schedule_url", "filedrop_url", "engelsystem_url", "heartbeat_url" ] + end + + # Class method to return field metadata + def self.field_metadata + { + "schedule_url" => { + title: "Schedule URL", + description: "URL to the Pretalx schedule data in JSON format", + placeholder: "https://pretalx.com/api/events/<EVENT>/schedules/latest/", + required: true + }, + "filedrop_url" => { + title: "Filedrop URL", + description: "URL to the filedrop service for speaker slides and materials", + placeholder: "https://filedrop.example.com/api/", + required: false + }, + "engelsystem_url" => { + title: "Engelsystem URL", + description: "URL to the Engelsystem API for volunteer management", + placeholder: "https://engelsystem.example.com/api/", + required: false + }, + "heartbeat_url" => { + title: "Heartbeat URL", + description: "URL to ping when the import is complete", + placeholder: "https://monitoring.example.com/ping/<ID>", + required: false + } + } + end + def import_schedule(conference) response = HTTParty.get(conference.schedule_url) response.success? or return Rails.logger.error "Failed to fetch schedule from #{conference.schedule_url}" @@ -62,7 +97,10 @@ module Pretalx end end session.recorded = !session_data.fetch("do_not_record", false) - update_filedrop_data(session, filedrop_index[session.ref_id], conference.filedrop_url) if filedrop_index[session.ref_id] + if filedrop_index[session.ref_id] + update_filedrop_data(session, filedrop_index[session.ref_id], + conference.filedrop_url) + end session.save! end end @@ -97,6 +135,7 @@ module Pretalx shifts_at_time.each do |shift| next unless session.stage.name == shift.dig("location", "name") + session.engelsystem_id = shift["id"] session.engelsystem_url = shift["url"] session.save @@ -129,13 +168,13 @@ module Pretalx }, headers: { "Accept" => "application/json" }, timeout: 30 - ) + ) data = JSON.parse(response.body) rescue StandardError => e Rails.logger.warn("Filedrop response for #{session.ref_id} failed: #{e.message}") return {} end - if !data["talks"].is_a?(Array) + unless data["talks"].is_a?(Array) Rails.logger.warn("Filedrop index was incomplete") return {} end @@ -172,7 +211,8 @@ module Pretalx # Add or update files filedrop_data["files"]&.each do |file_data| - session.filedrop_files.find_or_initialize_by(name: file_data["name"], checksum: file_data["meta"]["hash"]).tap do |file| + session.filedrop_files.find_or_initialize_by(name: file_data["name"], + checksum: file_data["meta"]["hash"]).tap do |file| file.size = file_data["meta"]["size"] file.orig_created = parse_datetime_or_nil(session.conference, file_data["meta"]["created"]) if file_data["url"].blank? diff --git a/app/jobs/republica_2023_or_later/import_job.rb b/app/jobs/republica_2023_or_later/import_job.rb index 1e697488a3c15f277f360fc7fd0cf60db159df1c..b4846734ddee94d08ccb24d2679030317943acc2 100644 --- a/app/jobs/republica_2023_or_later/import_job.rb +++ b/app/jobs/republica_2023_or_later/import_job.rb @@ -1,4 +1,5 @@ require "httparty" +require "httparty" module Republica2023OrLater class ImportJob < ApplicationJob @@ -67,7 +68,9 @@ module Republica2023OrLater session.starts_at = session_data["datetime_start"] session.ends_at = session_data["datetime_end"] session.url = "https://re-publica.com#{session_data['path']}" - session.speakers = session_data["speaker_uid"].map { |speaker_uid| conference.speakers.find_by!(ref_id: speaker_uid) } + session.speakers = session_data["speaker_uid"].map do |speaker_uid| + conference.speakers.find_by!(ref_id: speaker_uid) + end session.save! end end diff --git a/app/models/filedrop_file.rb b/app/models/filedrop_file.rb index af8f3dc1ca7c822b6bc1dc6b9a3aac162c368521..400955baa9432262562da55a7eb458509f11a82b 100644 --- a/app/models/filedrop_file.rb +++ b/app/models/filedrop_file.rb @@ -1,6 +1,7 @@ class FiledropFile < ApplicationRecord belongs_to :session - validates :checksum, presence: true, format: { with: /\A[0-9a-fA-F]+\z/, message: "only allows hexadecimal characters" } + validates :checksum, presence: true, + format: { with: /\A[0-9a-fA-F]+\z/, message: "only allows hexadecimal characters" } def sanitize_filename(filename) filename.gsub(/[^\w\s.-]/, "_") @@ -9,11 +10,11 @@ class FiledropFile < ApplicationRecord def safe_download_path(download_dir, filename) sanitized_filename = sanitize_filename(filename) output_path = File.join(download_dir, sanitized_filename) - if File.expand_path(output_path).start_with?(File.expand_path(download_dir)) - output_path - else + unless File.expand_path(output_path).start_with?(File.expand_path(download_dir)) raise "Invalid filename, potential directory traversal detected!" end + + output_path end def download(url) diff --git a/app/models/session.rb b/app/models/session.rb index d11923d09d79a531d9f8a6f3c6328310e5ce27d7..8577ddc627e894c698b7be8e674e6ef455083029 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -64,8 +64,9 @@ class Session < ApplicationRecord def notify_if_changed return if new_record? - if saved_changes.present? - ActiveSupport::Notifications.instrument("session.updated", record: self, changes: saved_changes) - end + + return unless saved_changes.present? + + ActiveSupport::Notifications.instrument("session.updated", record: self, changes: saved_changes) end end diff --git a/app/models/speaker.rb b/app/models/speaker.rb index de7b9b8141172aa967e8e55a30bf0b3765eadb4a..2e1d8ff60134b0bc374aabbcba2b26608cf1d4c5 100644 --- a/app/models/speaker.rb +++ b/app/models/speaker.rb @@ -15,8 +15,9 @@ class Speaker < ApplicationRecord def notify_if_changed return if new_record? - if saved_changes.present? - ActiveSupport::Notifications.instrument("speaker.updated", record: self, changes: saved_changes) - end + + return unless saved_changes.present? + + ActiveSupport::Notifications.instrument("speaker.updated", record: self, changes: saved_changes) end end diff --git a/app/subscribers/assignment_audit_subscriber.rb b/app/subscribers/assignment_audit_subscriber.rb index 4f01df40a92031c16569048877d8ac51ae423efc..4922d51793f9ee5efb0d1ee3d0862ce719573552 100644 --- a/app/subscribers/assignment_audit_subscriber.rb +++ b/app/subscribers/assignment_audit_subscriber.rb @@ -2,7 +2,7 @@ class AssignmentAuditSubscriber def self.subscribe ActiveSupport::Notifications.subscribe(/\Aassignment\..*/) do |*args| event = ActiveSupport::Notifications::Event.new(*args) - new.handle_event(event) # Call the instance method + new.handle_event(event) # Call the instance method end end diff --git a/app/subscribers/revision_subscriber.rb b/app/subscribers/revision_subscriber.rb index 92bb52c32f5c73331347929fd294b0ce78d2146f..e109b5320443bcb8a68d2afcd1fe693bd260204f 100644 --- a/app/subscribers/revision_subscriber.rb +++ b/app/subscribers/revision_subscriber.rb @@ -2,7 +2,7 @@ class RevisionSubscriber def self.subscribe ActiveSupport::Notifications.subscribe(/\A(?:session|speaker)\.updated/) do |*args| event = ActiveSupport::Notifications::Event.new(*args) - new.handle_event(event) # Call the instance method + new.handle_event(event) # Call the instance method end end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 8834e7c0ec49e621cc8c0eb9cbc3cf81569270e5..1289c8d41e086d148408915f5841ebf883aedaef 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,84 +1,58 @@ <!DOCTYPE html> -<html class="md:scroll-pt-16"> +<html> <head> - <title>ReScheduled</title> - <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"> + <title>re:scheduled</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> - <%= stylesheet_link_tag "application" %> + <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> - - <script type="text/javascript"> - function applyDarkmode() { - const userTheme = document.body.dataset.darkmode; - document.documentElement.classList.toggle( - 'dark', - userTheme === 'dark' || ((userTheme === 'auto') && window.matchMedia('(prefers-color-scheme: dark)').matches) - ); - } - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - mediaQuery.addEventListener('change', applyDarkmode); - </script> </head> - <body <%= tag.attributes(body_data_attributes) %> class="dark:bg-gray-900 dark:text-slate-300"> - <script type="text/javascript"> - applyDarkmode(); // Required to prevent flashing on load - </script> - <nav class="bg-slate-100 dark:bg-zinc-700 text-gray-700 dark:text-slate-300 shadow w-full relative md:fixed z-50" data-controller="navigation"> - <div class="relative bg-slate-100 dark:bg-zinc-700 z-50 container mx-auto px-4 py-4 flex justify-between items-center"> - <div class="flex items-center space-x-4"> - <div class="text-xl font-bold text-black dark:text-white"><%= link_to 're:scheduled', '/', class: "!no-underline" %></div> - <div class="hidden md:flex space-x-4"> - <%= link_to 'Conferences', conferences_path, class: 'hover:text-gray-900 dark:hover:text-slate-200' %> - <%= link_to 'Assignments', assignments_path, class: 'hover:text-gray-900 dark:hover:text-slate-200' %> + <body class="bg-gray-100 dark:bg-gray-900 min-h-screen"> + <header class="bg-white dark:bg-gray-800 shadow"> + <div class="container mx-auto px-4 py-4 flex justify-between items-center"> + <div class="flex items-center"> + <h1 class="text-xl font-bold text-gray-900 dark:text-white"> + <%= link_to "re:scheduled", root_path %> + </h1> </div> + <nav class="flex items-center space-x-4"> + <% if user_signed_in? %> + <div class="flex items-center mr-4"> + <div class="w-8 h-8 rounded-full flex items-center justify-center mr-2" style="background-color: <%= current_user.avatar_color %>; color: <%= current_user.text_color %>"> + <%= current_user.initials %> + </div> + <span class="text-gray-700 dark:text-gray-300"><%= current_user.name %></span> + </div> + <% if current_user.has_role?("events_admin") %> + <%= link_to "Admin", admin_conferences_path, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %> + <% end %> + <%= link_to "Conferences", conferences_path, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %> + <%= link_to "Assignments", assignments_path, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %> + <%= link_to "Sign Out", destroy_user_session_path, method: :delete, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %> + <% else %> + <%= link_to "Sign In", new_user_session_path, class: "text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white" %> + <% end %> + </nav> </div> - <div class="hidden md:flex items-center space-x-4 ml-0 lg:ml-8"> - <% if user_signed_in? %> - <span class="-mr-2 hidden lg:inline">logged in as</span> - <%= render partial: 'application/user_avatar', locals: { user: current_user } %> - <%= link_to '<span class="hidden lg:inline">My </span>Profile'.html_safe, edit_user_registration_path, class: 'hover:text-gray-900 dark:hover:text-slate-200', aria_label: "My Profile" %> - <%= link_to 'My Assignments', user_assignments_path(current_user), class: 'hover:text-gray-900 dark:hover:text-slate-200' %> - <%= link_to 'Logout', destroy_user_session_path, data: { turbo_method: :delete }, class: 'hover:text-gray-900 dark:hover:text-slate-200' %> - <% else %> - <span class="px-2">not logged in</span> - <%= link_to 'Login', new_user_session_path, class: 'hover:text-gray-900 dark:hover:text-slate-200' %> - <%= link_to 'Sign Up', new_user_registration_path, class: 'hover:text-gray-900 dark:hover:text-slate-200' %> - <% end %> - </div> - <div class="md:hidden"> - <button id="menu-button" class="text-gray-700 dark:text-slate-400 hover:text-gray-900 dark:hover:text-white focus:outline-none" data-action="click->navigation#toggleMenu"> - <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> - </svg> - </button> - </div> - </div> - <div id="mobile-menu" class="absolute top-18 bg-slate-100 dark:bg-zinc-700 z-20 container mx-auto hidden md:hidden space-y-4 px-8 pb-4 shadow" data-navigation-target="mobileMenu"> - <%= link_to 'Conferences', conferences_path, class: 'block hover:text-gray-900 dark:hover:text-white' %> - <%= link_to 'Assignments', assignments_path, class: 'block hover:text-gray-900 dark:hover:text-white' %> - <hr> - <% if user_signed_in? %> - <div>logged in as <%= render partial: 'application/user_avatar', locals: { user: current_user } %></div> - <%= link_to 'My Profile', edit_user_registration_path, class: 'block hover:text-gray-900 dark:hover:text-white' %> - <%= link_to 'My Assignments', user_assignments_path(current_user), class: 'block hover:text-gray-900 dark:hover:text-white' %> - <%= link_to 'Logout', destroy_user_session_path, data: { turbo_method: :delete }, class: 'block hover:text-gray-900 dark:hover:text-white' %> - <% else %> - <div>not logged in</div> - <%= link_to 'Login', new_user_session_path, class: 'block hover:text-gray-900 dark:hover:text-white' %> - <%= link_to 'Sign Up', new_user_registration_path, class: 'block hover:text-gray-900 dark:hover:text-white ' %> - <% end %> - </div> - </nav> + </header> - - - - <%= render partial: 'shared/flash' %> - <main class="container mx-auto mt-8 px-5 flex pb-4 md:mt-24"> + <main> + <% if notice %> + <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4 mx-4 mt-4" role="alert"> + <span class="block sm:inline"><%= notice %></span> + </div> + <% end %> + + <% if alert %> + <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4 mx-4 mt-4" role="alert"> + <span class="block sm:inline"><%= alert %></span> + </div> + <% end %> + <%= yield %> </main> </body> diff --git a/config/environments/development.rb b/config/environments/development.rb index 29d5eb505925938002993dd5ebac1c0683d7d778..3de265f4c41dcac63b1941d115a66da63e9ddb25 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -75,6 +75,8 @@ Rails.application.configure do config.action_controller.raise_on_missing_callback_actions = true config.telegram_default_target = ENV["TELEGRAM_DEFAULT_TARGET"] || "2192297" + + config.solid_queue.logger = ActiveSupport::Logger.new(STDOUT) end Rails.application.routes.default_url_options[:host] = "127.0.0.1"