Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ gem "thruster", require: false
# gem "image_processing", "~> 1.2"

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible
# gem "rack-cors"
gem "rack-cors"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"

# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
gem "brakeman", require: false

gem "pry", "~> 0.15.2"
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false
end
Expand Down
12 changes: 11 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ GEM
bigdecimal (3.3.1)
bootsnap (1.18.6)
msgpack (~> 1.2)
brakeman (7.1.1)
brakeman (7.1.2)
racc
builder (3.3.0)
coderay (1.1.3)
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
crass (1.0.6)
Expand Down Expand Up @@ -138,6 +139,7 @@ GEM
net-pop
net-smtp
marcel (1.1.0)
method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.26.0)
msgpack (1.8.0)
Expand Down Expand Up @@ -180,6 +182,9 @@ GEM
prettyprint
prettyprint (0.2.0)
prism (1.5.2)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
psych (5.2.6)
date
stringio
Expand All @@ -188,6 +193,9 @@ GEM
raabro (1.4.0)
racc (1.8.1)
rack (3.2.3)
rack-cors (3.0.0)
logger
rack (>= 3.0.14)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
Expand Down Expand Up @@ -330,7 +338,9 @@ DEPENDENCIES
faraday (~> 2.14)
kamal
pagy (~> 9.4)
pry (~> 0.15.2)
puma (>= 5.0)
rack-cors
rails (~> 8.0.3)
rubocop-rails-omakase
solid_cable
Expand Down
4 changes: 4 additions & 0 deletions app/channels/application_cable/channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
4 changes: 4 additions & 0 deletions app/channels/application_cable/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
9 changes: 9 additions & 0 deletions app/channels/endpoint_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class EndpointChannel < ApplicationCable::Channel
def subscribed
stream_from "endpoint_#{params[:id]}"
end

def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
10 changes: 10 additions & 0 deletions app/jobs/uptime_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class UptimeJob < ApplicationJob
queue_as :default

def perform(endpoint_id)
endpoint = Endpoint.find(endpoint_id)
return unless Endpoint::ALLOWED_TYPES.include?(endpoint.request)

UptimeCheckerService.call(endpoint)
end
end
28 changes: 28 additions & 0 deletions app/models/endpoint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Endpoint < ApplicationRecord
ALLOWED_TYPES = %w[get post patch].freeze

validates :name, :url, presence: true, uniqueness: true
validates :request, :duration, presence: true

enum :duration_type, { minute: 0, hours: 1 }
has_many :logs, dependent: :destroy

# after_create_commit :set_gid
after_create_commit :trigger_uptime_check
after_destroy_commit :discard_job

def discard_job
return if job_id.blank?

job = SolidQueue::Job.scheduled.find_by(active_job_id: job_id)
job.discard if job.present?
end

def set_gid
update(gid: to_gid_param)
end

def trigger_uptime_check
UptimeJob.perform_now(self.id)
end
end
44 changes: 44 additions & 0 deletions app/services/uptime_checker_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class UptimeCheckerService
attr_accessor :endpoint, :type, :url, :logs, :status, :response_code

def initialize(endpoint)
@endpoint = endpoint
@url = endpoint.url
@type = endpoint.request
@logs = endpoint.logs
@status = ""
@log = @endpoint.logs.build
@response_code = 404
end

def self.call(endpoint)
new(endpoint).check
end

def check
return unless Endpoint::ALLOWED_TYPES.include?(type)
begin
latency = Benchmark.realtime do
response = Faraday.public_send(type, url)
@response_code = response.status
end
latency =(latency * 1000).round(2)
rescue => e
@response_code = 404
Rails.logger.error("Error checking #{url}: #{e.message}")
end

@log.assign_attributes(response_code: @response_code, latency:)
endpoint.transaction do
if @log.save && UptimeRaterService.call(endpoint) && endpoint.touch(:last_updated)
Rails.logger.error("OK!")
else
Rails.logger.error("Error")
end
delay = endpoint.minute? ? endpoint.duration.minutes : endpoint.duration.hours
job = UptimeJob.set(wait: delay).perform_later(endpoint.id)
endpoint.update(job_id: job.job_id)
ActionCable.server.broadcast("endpoint_#{endpoint.id}", endpoint.as_json)
end
end
end
20 changes: 20 additions & 0 deletions app/services/uptime_rater_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

class UptimeRaterService
attr_accessor :endpoint, :logs

def initialize(endpoint)
@endpoint = endpoint
@logs = @endpoint.logs
end

def self.call(endpoint)
new(endpoint).rate
end

def rate
total_requests = logs.size
success_requests = logs.where(response_code: 200).size
uptime = (success_requests/total_requests.to_f) * 100
endpoint.update(uptime:)
end
end
4 changes: 3 additions & 1 deletion config/cable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view
# to make the web console appear.
development:
adapter: async
adapter: solid_cable
polling_interval: 0.1.seconds
message_retention: 1.day

test:
adapter: test
Expand Down
2 changes: 1 addition & 1 deletion config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ default: &default
development:
<<: *default
database: storage/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
Expand Down
4 changes: 4 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
# Highlight code that enqueued background job in logs.
config.active_job.verbose_enqueue_logs = true

# Use Solid Queue in Development.
config.active_job.queue_adapter = :solid_queue

config.solid_queue.logger = ActiveSupport::Logger.new(STDOUT)
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true

Expand Down
18 changes: 9 additions & 9 deletions config/initializers/cors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

# Read more: https://github.com/cyu/rack-cors

# Rails.application.config.middleware.insert_before 0, Rack::Cors do
# allow do
# origins "example.com"
#
# resource "*",
# headers: :any,
# methods: [:get, :post, :put, :patch, :delete, :options, :head]
# end
# end
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "*"

resource "*",
headers: :any,
methods: [ :get, :post, :put, :patch, :delete, :options, :head ]
end
end
2 changes: 1 addition & 1 deletion config/puma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
plugin :tmp_restart

# Run the Solid Queue supervisor inside of Puma for single-server deployments
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] || Rails.env.development?

# Specify the PID file. Defaults to tmp/pids/server.pid in development.
# In other environments, only set the PID file if requested.
Expand Down
8 changes: 8 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
Rails.application.routes.draw do
mount ActionCable.server => "/api/v1/cable"
namespace :api do
namespace :v1 do
resources :endpoints do
resources :logs
end
end
end
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
Expand Down
11 changes: 0 additions & 11 deletions db/cable_schema.rb

This file was deleted.

14 changes: 0 additions & 14 deletions db/cache_schema.rb

This file was deleted.

16 changes: 16 additions & 0 deletions db/migrate/20251010060832_create_endpoints.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class CreateEndpoints < ActiveRecord::Migration[8.0]
def change
create_table :endpoints do |t|
t.string :url, null: false, index: true
t.string :request, null: false
t.string :name, null: false
t.integer :duration, null: false, default: 0
t.integer :duration_type, null: false, default: 0
t.decimal :uptime, precision: 5, scale: 2
t.string :job_id
t.datetime :last_updated

t.timestamps
end
end
end
12 changes: 12 additions & 0 deletions db/migrate/20251010063010_create_logs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class CreateLogs < ActiveRecord::Migration[8.0]
def change
create_table :logs do |t|
t.belongs_to :endpoint
t.integer :response_code
t.string :status
t.integer :latency, default: 0

t.timestamps
end
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
ActiveRecord::Schema[7.1].define(version: 1) do
class AddSolidQueue < ActiveRecord::Migration[8.0]
def change
create_table "solid_cable_messages", force: :cascade do |t|
t.binary "channel", limit: 1024, null: false
t.binary "payload", limit: 536870912, null: false
t.datetime "created_at", null: false
t.integer "channel_hash", limit: 8, null: false
t.index [ "channel" ], name: "index_solid_cable_messages_on_channel"
t.index [ "channel_hash" ], name: "index_solid_cable_messages_on_channel_hash"
t.index [ "created_at" ], name: "index_solid_cable_messages_on_created_at"
end
create_table "solid_queue_blocked_executions", force: :cascade do |t|
t.bigint "job_id", null: false
t.string "queue_name", null: false
Expand Down Expand Up @@ -120,10 +130,21 @@
t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true
end

create_table "solid_cache_entries", force: :cascade do |t|
t.binary "key", limit: 1024, null: false
t.binary "value", limit: 536870912, null: false
t.datetime "created_at", null: false
t.integer "key_hash", limit: 8, null: false
t.integer "byte_size", limit: 4, null: false
t.index [ "byte_size" ], name: "index_solid_cache_entries_on_byte_size"
t.index [ "key_hash", "byte_size" ], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
t.index [ "key_hash" ], name: "index_solid_cache_entries_on_key_hash", unique: true
end
add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
end
end
Loading