From 7d04fddeb952d4480282929c5f080831a7069318 Mon Sep 17 00:00:00 2001 From: Teal Bauer <git@teal.is> Date: Sun, 26 May 2024 17:15:58 +0200 Subject: [PATCH] add model versioning log and crono --- Gemfile | 4 +++- Gemfile.lock | 6 ++++++ .../telegram_group_chat_notification_job.rb | 4 ++-- app/models/assignment.rb | 13 ++++++++++++ app/models/model_version.rb | 2 ++ app/models/notification_channel.rb | 1 + app/models/user.rb | 8 +++++++ .../assignment_audit_subscriber.rb | 15 +++++++++++++ app/subscribers/telegram_bot_subscriber.rb | 14 ++++++------- config/cronotab.rb | 17 +++++++++++++++ config/initializers/subscribers.rb | 5 +++-- config/routes.rb | 2 ++ .../20240526145439_create_model_versions.rb | 11 ++++++++++ ...40526145815_add_action_to_model_version.rb | 5 +++++ .../20240526151339_create_crono_jobs.rb | 12 +++++++++++ db/schema.rb | 21 ++++++++++++++++++- test/fixtures/model_versions.yml | 11 ++++++++++ test/models/model_version_test.rb | 7 +++++++ 18 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 app/models/model_version.rb create mode 100644 app/subscribers/assignment_audit_subscriber.rb create mode 100644 config/cronotab.rb create mode 100644 db/migrate/20240526145439_create_model_versions.rb create mode 100644 db/migrate/20240526145815_add_action_to_model_version.rb create mode 100644 db/migrate/20240526151339_create_crono_jobs.rb create mode 100644 test/fixtures/model_versions.yml create mode 100644 test/models/model_version_test.rb diff --git a/Gemfile b/Gemfile index fc463c2..1df1a54 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem "redis", ">= 4.0.1" # gem "kredis" # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" +gem "bcrypt", "~> 3.1.7" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ windows jruby ] @@ -69,3 +69,5 @@ gem "importmap-rails", "~> 2.0" gem "icalendar", "~> 2.10" gem "telegram-bot-ruby", "~> 2.0" + +gem "crono", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index 8a92be5..e9f8ce2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,6 +78,7 @@ GEM addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) base64 (0.2.0) + bcrypt (3.1.20) bigdecimal (3.1.7) bindex (0.8.1) bootsnap (1.18.3) @@ -95,6 +96,9 @@ GEM concurrent-ruby (1.2.3) connection_pool (2.4.1) crass (1.0.6) + crono (2.0.1) + rails (>= 5.2.8) + sprockets-rails date (3.3.4) debug (1.9.2) irb (~> 1.10) @@ -315,8 +319,10 @@ PLATFORMS x86_64-linux DEPENDENCIES + bcrypt (~> 3.1.7) bootsnap capybara + crono (~> 2.0) debug hotwire-rails (~> 0.1.3) httparty diff --git a/app/jobs/telegram_group_chat_notification_job.rb b/app/jobs/telegram_group_chat_notification_job.rb index ae907df..ea10213 100644 --- a/app/jobs/telegram_group_chat_notification_job.rb +++ b/app/jobs/telegram_group_chat_notification_job.rb @@ -3,11 +3,11 @@ require 'telegram/bot' class TelegramGroupChatNotificationJob < NotificationJob queue_as :notifications - def perform(*args) + def perform(**args) channel = NotificationChannel.find_by(name: 'telegram_group_chat') token = channel.data['token'] Telegram::Bot::Client.run(token) do |bot| - bot.api.send_message(chat_id: args[:target], text: content) + bot.api.send_message(chat_id: args[:target], text: args[:text]) end end end diff --git a/app/models/assignment.rb b/app/models/assignment.rb index 3426f22..20d75f3 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -6,6 +6,9 @@ class Assignment < ApplicationRecord validate :no_overlapping_assignments + after_create_commit :notify_assignment_created + after_destroy_commit :notify_assignment_destroyed + private def no_overlapping_assignments @@ -25,4 +28,14 @@ class Assignment < ApplicationRecord errors.add(:base, "This assignment overlaps with another assignment for this user.") end end + + private + + def notify_assignment_created + ActiveSupport::Notifications.instrument("assignment.created", record: self) + end + + def notify_assignment_destroyed + ActiveSupport::Notifications.instrument("assignment.destroyed", record: self) + end end diff --git a/app/models/model_version.rb b/app/models/model_version.rb new file mode 100644 index 0000000..10660b8 --- /dev/null +++ b/app/models/model_version.rb @@ -0,0 +1,2 @@ +class ModelVersion < ApplicationRecord +end diff --git a/app/models/notification_channel.rb b/app/models/notification_channel.rb index fd234e2..074e0c6 100644 --- a/app/models/notification_channel.rb +++ b/app/models/notification_channel.rb @@ -1,2 +1,3 @@ class NotificationChannel < ApplicationRecord + serialize :data, coder: JSON end diff --git a/app/models/user.rb b/app/models/user.rb index 8e3dc5a..f343dde 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,9 +1,17 @@ class User < ApplicationRecord has_many :assignments + + has_secure_password + + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true validates :email, uniqueness: { case_sensitive: false, message: "already in use" } after_initialize :set_avatar_color + def has_password? + !password_digest.nil? + end + def text_color r, g, b = avatar_color.delete_prefix('#').chars.each_slice(2).map { |hex| hex.join.to_i(16) } diff --git a/app/subscribers/assignment_audit_subscriber.rb b/app/subscribers/assignment_audit_subscriber.rb new file mode 100644 index 0000000..4bdb724 --- /dev/null +++ b/app/subscribers/assignment_audit_subscriber.rb @@ -0,0 +1,15 @@ +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 + end + end + + def handle_event(event) + action = event.name.split(".").last + record = event.payload[:record] + + ModelVersion.create!(model: 'assignment', action:, new_data: record.to_json) + end +end diff --git a/app/subscribers/telegram_bot_subscriber.rb b/app/subscribers/telegram_bot_subscriber.rb index 15fefb0..27349ee 100644 --- a/app/subscribers/telegram_bot_subscriber.rb +++ b/app/subscribers/telegram_bot_subscriber.rb @@ -7,25 +7,23 @@ class TelegramBotSubscriber end def handle_event(event) - # 1. Extract Relevant Information - model_name = event.name.split(".").last # Get "session" or "speaker" + model_name, action = event.name.split(".") record = event.payload[:record] changes = event.payload[:changes] - # 2. Format Message for Telegram - message = format_telegram_message(model_name, record, changes) + message = format_telegram_message(model_name, action, record, changes) - # 3. Send Message via Telegram Bot API - # (Replace with your actual Telegram bot API integration) # TelegramBotAPI.sendMessage(chat_id: YOUR_CHAT_ID, text: message) Rails.logger.info("event: #{message}") + #TelegramGroupChatNotificationJob.perform_later(target: "-316096320", text: message) + TelegramGroupChatNotificationJob.perform_later(target: "2192297", text: message) end private - def format_telegram_message(model_name, record, changes) + def format_telegram_message(model_name, action, record, changes) # Customize this to your desired message format - "**#{model_name.capitalize} Updated:**\n\n" + + "**#{model_name.capitalize} #{action.capitalize}:**\n\n" + changes.map { |attr, (from, to)| "- #{attr}: #{from} -> #{to}" }.join("\n") end end diff --git a/config/cronotab.rb b/config/cronotab.rb new file mode 100644 index 0000000..976cc97 --- /dev/null +++ b/config/cronotab.rb @@ -0,0 +1,17 @@ +# cronotab.rb — Crono configuration file +# +# Here you can specify periodic jobs and schedule. +# You can use ActiveJob's jobs from `app/jobs/` +# You can use any class. The only requirement is that +# class should have a method `perform` without arguments. +# +# class TestJob +# def perform +# puts 'Test!' +# end +# end +# +# Crono.perform(TestJob).every 2.days, at: '15:30' +# + +Crono.perform(FetchConferenceDataJob, 'rp2024').every 5.minutes diff --git a/config/initializers/subscribers.rb b/config/initializers/subscribers.rb index cc511bf..ad9d4de 100644 --- a/config/initializers/subscribers.rb +++ b/config/initializers/subscribers.rb @@ -1,3 +1,4 @@ Rails.application.config.to_prepare do - TelegramBotSubscriber.subscribe # Subscribe to notifications -end \ No newline at end of file + TelegramBotSubscriber.subscribe + AssignmentAuditSubscriber.subscribe +end diff --git a/config/routes.rb b/config/routes.rb index 80999f2..ebfe923 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + mount Crono::Engine, at: '/crono' + get 'login', to: 'users#login', as: :login post 'login', to: 'users#login' post 'logout', to: 'users#logout', as: :logout diff --git a/db/migrate/20240526145439_create_model_versions.rb b/db/migrate/20240526145439_create_model_versions.rb new file mode 100644 index 0000000..8a55348 --- /dev/null +++ b/db/migrate/20240526145439_create_model_versions.rb @@ -0,0 +1,11 @@ +class CreateModelVersions < ActiveRecord::Migration[7.1] + def change + create_table :model_versions do |t| + t.string :model + t.text :changed_data + t.text :new_data + + t.timestamps + end + end +end diff --git a/db/migrate/20240526145815_add_action_to_model_version.rb b/db/migrate/20240526145815_add_action_to_model_version.rb new file mode 100644 index 0000000..f00bc31 --- /dev/null +++ b/db/migrate/20240526145815_add_action_to_model_version.rb @@ -0,0 +1,5 @@ +class AddActionToModelVersion < ActiveRecord::Migration[7.1] + def change + add_column :model_versions, :action, :string + end +end diff --git a/db/migrate/20240526151339_create_crono_jobs.rb b/db/migrate/20240526151339_create_crono_jobs.rb new file mode 100644 index 0000000..e0ed09e --- /dev/null +++ b/db/migrate/20240526151339_create_crono_jobs.rb @@ -0,0 +1,12 @@ +class CreateCronoJobs < ActiveRecord::Migration[6.1] + def change + create_table :crono_jobs do |t| + t.string :job_id, null: false + t.text :log, limit: 1073741823 # LONGTEXT for MySQL + t.datetime :last_performed_at + t.boolean :healthy + t.timestamps null: false + end + add_index :crono_jobs, [:job_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c3f6ce6..a593153 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_26_121600) do +ActiveRecord::Schema[7.1].define(version: 2024_05_26_151339) do create_table "assignments", force: :cascade do |t| t.integer "user_id", null: false t.integer "session_id", null: false @@ -35,6 +35,25 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_26_121600) do t.string "time_zone" end + create_table "crono_jobs", force: :cascade do |t| + t.string "job_id", null: false + t.text "log", limit: 1073741823 + t.datetime "last_performed_at", precision: nil + t.boolean "healthy" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["job_id"], name: "index_crono_jobs_on_job_id", unique: true + end + + create_table "model_versions", force: :cascade do |t| + t.string "model" + t.text "changed_data" + t.text "new_data" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "action" + end + create_table "notification_channels", force: :cascade do |t| t.string "name" t.text "data" diff --git a/test/fixtures/model_versions.yml b/test/fixtures/model_versions.yml new file mode 100644 index 0000000..8cd61a5 --- /dev/null +++ b/test/fixtures/model_versions.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + model: MyString + changed_data: MyText + new_data: MyText + +two: + model: MyString + changed_data: MyText + new_data: MyText diff --git a/test/models/model_version_test.rb b/test/models/model_version_test.rb new file mode 100644 index 0000000..6c23819 --- /dev/null +++ b/test/models/model_version_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ModelVersionTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end -- GitLab