Skip to content

Commit d1a69ec

Browse files
authored
Merge pull request #346 from joyofrails/feat/record-events
Add ApplicationEvent model
2 parents 346488f + 955f903 commit d1a69ec

File tree

14 files changed

+252
-90
lines changed

14 files changed

+252
-90
lines changed

Gemfile

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,42 +22,42 @@ gem "turbo-rails" # Hotwire's SPA-like page accelerator [https://turbo.hotwired.
2222
gem "vite_rails" # Leverage Vite to power the frontend of your Rails app [https://vite-ruby.netlify.app/guide/rails.html]
2323

2424
# Utilities
25+
gem "addressable" # Addressable is an alternative implementation to URI [https://github.com/sporkmonger/addressable]
2526
gem "bcrypt", "~> 3.1.7" # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
27+
gem "color_conversion" # A ruby gem to perform color conversions [https://github.com/devrieda/color_conversion]
28+
gem "commonmarker", require: false
29+
gem "device_detector" # DeviceDetector is a precise and fast user agent parser and device detector written in Ruby [https://github.com/podigee/device_detector]
30+
gem "fastimage", require: false # FastImage finds the size or type of an image given its uri by fetching as little as needed [https://github.com/sdsykes/fastimage]
2631
gem "flipper" # Feature flipping for Ruby [https://www.flippercloud.io/]
2732
gem "flipper-active_record" # ActiveRecord adapter for Flipper [https://www.flippercloud.io/docs/adapters/active-record]
28-
gem "device_detector" # DeviceDetector is a precise and fast user agent parser and device detector written in Ruby [https://github.com/podigee/device_detector]
29-
gem "warden" # General Rack Authentication Framework [https://github.com/wardencommunity/warden]
30-
gem "postmark-rails" # Postmark Rails gem [https://github.com/ActiveCampaign/postmark-rails]
31-
gem "scout_apm" # Scout APM Ruby Agent [https://scoutapm.com]
32-
gem "rails_admin" # RailsAdmin is a Rails engine that provides an easy-to-use interface for managing your data [https://github.com/railsadminteam/rails_admin]
33-
gem "addressable" # Addressable is an alternative implementation to URI [https://github.com/sporkmonger/addressable]
33+
gem "invisible_captcha" # Unobtrusive and flexible spam protection for Rails apps [https://github.com/markets/invisible_captcha]
34+
gem "json-schema" # JSON Schema validation [https://github.com/voxpupuli/json-schema]
3435
gem "ostruct" # OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values
3536
gem "parslet" # Parslet is a small Ruby library for constructing parsers [https://github.com/kschiess/parslet]
37+
gem "postmark-rails" # Postmark Rails gem [https://github.com/ActiveCampaign/postmark-rails]
38+
gem "rails_admin" # RailsAdmin is a Rails engine that provides an easy-to-use interface for managing your data [https://github.com/railsadminteam/rails_admin]
39+
gem "scout_apm" # Scout APM Ruby Agent [https://scoutapm.com]
40+
gem "warden" # General Rack Authentication Framework [https://github.com/wardencommunity/warden]
3641

3742
# Rendering
3843
gem "image_processing" # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
3944
gem "inline_svg" # Embed SVGs in Rails views and style them with CSS [https://github.com/jamesmartin/inline_svg
45+
gem "meta-tags" # Search Engine Optimization (SEO) for Ruby on Rails applications. [https://github.com/kpumuk/meta-tags]
4046
gem "rouge" # Pure Ruby syntax highlighter [https://github.com/rouge-ruby/rouge
4147
gem "sitepress-rails" # Static site generator for Rails [https://sitepress.cc/getting-started/rails]
4248

4349
gem "phlex", "2.0.0.rc1" # An object-oriented view layer. [https://github.com/phlex-ruby/phlex]
4450
gem "phlex-rails", "2.0.0.rc1" # Rails integration for Phlex [https://github.com/phlex-ruby/phlex-rails]
4551

46-
gem "commonmarker", require: false
47-
gem "invisible_captcha" # Unobtrusive and flexible spam protection for Rails apps [https://github.com/markets/invisible_captcha]
48-
gem "color_conversion" # A ruby gem to perform color conversions [https://github.com/devrieda/color_conversion]
49-
gem "meta-tags" # Search Engine Optimization (SEO) for Ruby on Rails applications. [https://github.com/kpumuk/meta-tags]
50-
gem "fastimage", require: false # FastImage finds the size or type of an image given its uri by fetching as little as needed [https://github.com/sdsykes/fastimage]
51-
5252
gem "bootsnap", require: false # Reduces boot times through caching; required in config/boot.rb [https://github.com/Shopify/bootsnap]
5353

5454
# Clients
55-
gem "httpx" # An HTTP client library for Ruby [https://gitlab.com/os85/httpx]
55+
gem "aws-sdk-s3" # Official AWS Ruby gem for Amazon S3 [https://github.com/aws/aws-sdk-ruby]
5656
gem "honeybadger", require: false # Error monitoring and uptime reporting [https://www.honeybadger.io]
57+
gem "httpx" # An HTTP client library for Ruby [https://gitlab.com/os85/httpx]
5758
gem "litestream" # Standalone streaming replication for SQLite [https://litestream.io]
58-
gem "web-push" # Web Push library for Ruby [https://github.com/pushpad/web-push]
59-
gem "aws-sdk-s3" # Official AWS Ruby gem for Amazon S3 [https://github.com/aws/aws-sdk-ruby]
6059
gem "ruby-openai" # Use OpenAI in Ruby [https://github.com/alexrudall/ruby-openai]
60+
gem "web-push" # Web Push library for Ruby [https://github.com/pushpad/web-push]
6161

6262
# Admin
6363
gem "flipper-ui" # UI for the Flipper gem [https://www.flippercloud.io/docs/ui]
@@ -75,18 +75,18 @@ group :test do
7575
gem "capybara" # Acceptance test framework for web applications [https://github.com/teamcapybara/capybara]
7676
gem "cuprite" # Headless Chrome driver for Capybara [https://github.com/rubycdp/cuprite]
7777
gem "simplecov", require: false # Code coverage for Ruby [https://github.com/simplecov-ruby/simplecov]
78-
gem "simplecov-tailwindcss", require: false # Alternative HTML formatter for SimpleCov [https://github.com/chiefpansancolt/simplecov-tailwindcss]
7978
gem "simplecov-cobertura", require: false # Produces Cobertura formatted XML from SimpleCov. [https://github.com/dashingrocket/simplecov-cobertura]
79+
gem "simplecov-tailwindcss", require: false # Alternative HTML formatter for SimpleCov [https://github.com/chiefpansancolt/simplecov-tailwindcss]
8080
gem "webmock", require: false # Library for stubbing HTTP requests [https://github.com/bblimke/webmock]
8181

8282
# Uncomment the following line and bundle to use Selenium with Firefox
8383
# gem "selenium-webdriver" # Ruby bindings for Selenium [https://www.rubydoc.info/gems/selenium-webdriver/frames]
8484
end
8585

8686
group :development, :test do
87-
gem "css_parser", require: false # A pure Ruby CSS parser based on the CSS Syntax Level 3 specification [https://github.com/rgrove/crass]
8887
gem "brakeman", require: false # A static analysis security vulnerability scanner for Ruby on Rails applications [https://github.com/presidentbeef/brakeman]
8988
gem "bundle-audit", require: false # Patch level verification for Bundler [https://github.com/rubysec/bundler-audit]
89+
gem "css_parser", require: false # A pure Ruby CSS parser based on the CSS Syntax Level 3 specification [https://github.com/rgrove/crass]
9090
gem "debug", platforms: %i[mri windows] # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
9191
gem "dotenv" # A Ruby gem to load environment variables from `.env` [https://github.com/bkeepers/dotenv]
9292
gem "factory_bot_rails", require: false # A library for setting up Ruby objects as test data [https://github.com/thoughtbot/factory_bot_rails]

Gemfile.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ GEM
289289
reline (>= 0.4.2)
290290
jmespath (1.6.2)
291291
json (2.9.1)
292+
json-schema (5.1.1)
293+
addressable (~> 2.8)
294+
bigdecimal (~> 3.1)
292295
jwt (2.9.3)
293296
base64
294297
kaminari (1.2.2)
@@ -652,6 +655,7 @@ DEPENDENCIES
652655
image_processing
653656
inline_svg
654657
invisible_captcha
658+
json-schema
655659
letter_opener
656660
litestream
657661
meta-tags

app/models/application_event.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# == Schema Information
2+
#
3+
# Table name: application_events
4+
#
5+
# id :string not null, primary key
6+
# data :text not null
7+
# metadata :text
8+
# type :string not null
9+
# created_at :datetime not null
10+
# updated_at :datetime not null
11+
#
12+
# Indexes
13+
#
14+
# index_application_events_on_created_at (created_at)
15+
#
16+
class ApplicationEvent < ApplicationRecord
17+
serialize :data, coder: JSON
18+
serialize :metadata, coder: JSON
19+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# == Schema Information
2+
#
3+
# Table name: application_events
4+
#
5+
# id :string not null, primary key
6+
# data :text not null
7+
# metadata :text
8+
# type :string not null
9+
# created_at :datetime not null
10+
# updated_at :datetime not null
11+
#
12+
# Indexes
13+
#
14+
# index_application_events_on_created_at (created_at)
15+
#
16+
module ApplicationEvents
17+
class Deploy < ::ApplicationEvent
18+
DATA_SCHEMA = {
19+
"type" => "object",
20+
"required" => ["sha"],
21+
"properties" => {
22+
"sha" => {"type" => "string"}
23+
}
24+
}.freeze
25+
26+
validates_with Validators::SchemaValidator, field: :data, schema: DATA_SCHEMA
27+
28+
def self.record!(sha: nil)
29+
sha = current_sha if sha.nil?
30+
31+
create! data: {sha: sha}
32+
end
33+
34+
def self.current_sha
35+
Repo.new.rev_parse("HEAD")
36+
rescue => e
37+
Honeybadger.notify(e)
38+
39+
"UNKNOWN"
40+
end
41+
end
42+
end

app/models/examples/app_file.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def extname
5959
alias_method :extension, :extname
6060

6161
def repo_url
62-
"https://github.com/joyofrails/joyofrails.com/blob/#{revision}/#{app_path}"
62+
repo.url("blob/#{revision}/#{app_path}")
6363
end
6464

6565
def source(lines: nil)
@@ -87,8 +87,11 @@ def file_read
8787
end
8888

8989
def git_read
90-
git_dir = ENV.fetch("REPOSITORY_ROOT", ".")
91-
`(cd #{git_dir} && git show #{@revision}:#{@path}) 2>/dev/null`
90+
repo.read_file(@path, revision: @revision)
91+
end
92+
93+
def repo
94+
@repo ||= Repo.new
9295
end
9396
end
9497
end

app/models/repo.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# We take advantage of the fact that our deployment tool is Hatchbox which uses
2+
# git on the server as part of the deploy process. We abstract git commands so callers
3+
# don’t need to be aware of the fact that the git repository is in a different location.
4+
class Repo
5+
def root = ENV.fetch("REPOSITORY_ROOT", ".")
6+
7+
def read_file(path, revision: "HEAD")
8+
run "git show #{revision}:#{path}"
9+
end
10+
11+
def rev_parse(revision = "HEAD")
12+
run("git rev-parse #{revision}").strip
13+
end
14+
15+
def url(path = "")
16+
path = path.drop(1) if path.start_with?("/")
17+
"https://github.com/joyofrails/joyofrails.com/#{path}"
18+
end
19+
20+
def run(command)
21+
`(cd #{root} && #{command}) 2>/dev/null`
22+
end
23+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require "json-schema"
2+
module Validators
3+
class SchemaValidator < ActiveModel::Validator
4+
def validate(record)
5+
schema = options.fetch(:schema)
6+
field_name = options.fetch(:field)
7+
value = record.send(field_name)
8+
9+
unless JSON::Validator.validate(schema, value)
10+
record.errors.add(field_name, "does not comply to JSON Schema: #{schema.inspect}")
11+
end
12+
end
13+
end
14+
end

config/brakeman.ignore

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,62 @@
11
{
22
"ignored_warnings": [
3+
{
4+
"warning_type": "Command Injection",
5+
"warning_code": 14,
6+
"fingerprint": "386b18e7b0e504ea42e4ba89ee6cb1b353a9177c7fa58439553cb92abb3d5364",
7+
"check_name": "Execute",
8+
"message": "Possible command injection",
9+
"file": "app/models/repo.rb",
10+
"line": 21,
11+
"link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
12+
"code": "`(cd #{root} && #{command}) 2>/dev/null`",
13+
"render_path": null,
14+
"location": {
15+
"type": "method",
16+
"class": "Repo",
17+
"method": "run"
18+
},
19+
"user_input": "root",
20+
"confidence": "Medium",
21+
"cwe_id": [
22+
77
23+
],
24+
"note": ""
25+
},
326
{
427
"warning_type": "Dynamic Render Path",
528
"warning_code": 15,
6-
"fingerprint": "690098d85fc8739353285debac5a26f57148c6150eac806a1ce8574d8b3db76f",
29+
"fingerprint": "8fef72728237431ad41e05126612820980b845a5fb952aac35e89b120b93c59b",
730
"check_name": "Render",
831
"message": "Render path contains parameter value",
9-
"file": "app/views/pwa/installation_instructions/_installation_instructions.html.erb",
10-
"line": 6,
32+
"file": "app/views/author/polls/show.html.erb",
33+
"line": 8,
1134
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
12-
"code": "render(action => (if params[:user_agent_nickname] then\n Pwa::NamedInstallationInstructions.find(params[:user_agent_nickname])\nelse\n Honeybadger.event(\"installation_instructions_controller.show\", :user_agent => request.user_agent)\n Pwa::UserAgentInstallationInstructions.new(request.user_agent)\nend).partial_name, { :installation_instructions => installation_instructions })",
35+
"code": "render(action => Current.user.polls.find(params[:id]).questions.includes(:answers).ordered, { :poll => Current.user.polls.find(params[:id]) })",
1336
"render_path": [
1437
{
1538
"type": "controller",
16-
"class": "Pwa::InstallationInstructionsController",
39+
"class": "Author::PollsController",
1740
"method": "show",
18-
"line": 4,
19-
"file": "app/controllers/pwa/installation_instructions_controller.rb",
41+
"line": 15,
42+
"file": "app/controllers/author/polls_controller.rb",
2043
"rendered": {
21-
"name": "pwa/installation_instructions/show",
22-
"file": "app/views/pwa/installation_instructions/show.html.erb"
23-
}
24-
},
25-
{
26-
"type": "template",
27-
"name": "pwa/installation_instructions/show",
28-
"line": 3,
29-
"file": "app/views/pwa/installation_instructions/show.html.erb",
30-
"rendered": {
31-
"name": "pwa/installation_instructions/_installation_instructions",
32-
"file": "app/views/pwa/installation_instructions/_installation_instructions.html.erb"
44+
"name": "author/polls/show",
45+
"file": "app/views/author/polls/show.html.erb"
3346
}
3447
}
3548
],
3649
"location": {
3750
"type": "template",
38-
"template": "pwa/installation_instructions/_installation_instructions"
51+
"template": "author/polls/show"
3952
},
40-
"user_input": "params[:user_agent_nickname]",
53+
"user_input": "params[:id]",
4154
"confidence": "Weak",
4255
"cwe_id": [
4356
22
4457
],
4558
"note": ""
46-
},
47-
{
48-
"warning_type": "SQL Injection",
49-
"warning_code": 0,
50-
"fingerprint": "75fc982bf0fdc1992076ca5a34bdf947d7cdc80b38d207e9b712fb6d8ae2af38",
51-
"check_name": "SQL",
52-
"message": "Possible SQL injection",
53-
"file": "app/models/concerns/searchable.rb",
54-
"line": 47,
55-
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
56-
"code": "joins(\"JOIN #{search_table_name} ON #{table_name}.id = #{search_table_name}.#{search_table_foreign_key}\")",
57-
"render_path": null,
58-
"location": {
59-
"type": "method",
60-
"class": "Searchable::ClassMethods",
61-
"method": "search"
62-
},
63-
"user_input": "search_table_name",
64-
"confidence": "Medium",
65-
"cwe_id": [
66-
89
67-
],
68-
"note": ""
69-
},
70-
{
71-
"warning_type": "Command Injection",
72-
"warning_code": 14,
73-
"fingerprint": "ee467aaea70b8a7b361ef6e8ee6c5082b3ff265dc67d798ea3f24c1687ff4584",
74-
"check_name": "Execute",
75-
"message": "Possible command injection",
76-
"file": "app/models/examples/app_file.rb",
77-
"line": 91,
78-
"link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
79-
"code": "`(cd #{ENV.fetch(\"REPOSITORY_ROOT\", \".\")} && git show #{@revision}:#{@path}) 2>/dev/null`",
80-
"render_path": null,
81-
"location": {
82-
"type": "method",
83-
"class": "Examples::AppFile",
84-
"method": "git_read"
85-
},
86-
"user_input": "ENV.fetch(\"REPOSITORY_ROOT\", \".\")",
87-
"confidence": "Medium",
88-
"cwe_id": [
89-
77
90-
],
91-
"note": ""
9259
}
9360
],
94-
"updated": "2024-11-01 07:34:07 -0400",
95-
"brakeman_version": "6.2.2"
61+
"brakeman_version": "7.0.0"
9662
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class IndexApplicationEventsCreatedAt < ActiveRecord::Migration[8.1]
2+
def change
3+
add_index :application_events, :created_at
4+
end
5+
end

db/schema.rb

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)