From 5864e3f46433a11a569eb5d74a7e0a7c7a896a72 Mon Sep 17 00:00:00 2001 From: Alpha Chen Date: Fri, 16 Jun 2023 20:29:18 -0700 Subject: [PATCH] roda-sequel-stack https://github.com/jeremyevans/roda-sequel-stack --- Gemfile | 4 ++ Gemfile.lock | 10 +++- Rakefile | 11 +++++ config.ru | 16 +++++-- lib/app.rb | 103 +++++++++++++++++++++++++++++++++++++++++- lib/db.rb | 3 ++ lib/models.rb | 26 +++++++++++ migrate/001_tables.rb | 63 ++++++++++++++++++++++++++ 8 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 lib/db.rb create mode 100644 lib/models.rb create mode 100644 migrate/001_tables.rb diff --git a/Gemfile b/Gemfile index 1d42402..4dc547b 100644 --- a/Gemfile +++ b/Gemfile @@ -4,8 +4,12 @@ source "https://rubygems.org" gem "minitest" gem "phlex" +gem "rack-unreloader" gem "rackup" gem "rake" gem "roda" gem "ruby-lsp" # TODO: Set up ruby-lsp +gem "sequel" +gem "sqlite3" gem "tilt" + diff --git a/Gemfile.lock b/Gemfile.lock index f6972e0..11b9534 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,6 +6,7 @@ GEM erb (4.0.2) cgi (>= 0.3.3) language_server-protocol (3.17.0.3) + mini_portile2 (2.8.1) minitest (5.18.0) phlex (1.8.1) concurrent-ruby (~> 1.2) @@ -13,6 +14,7 @@ GEM zeitwerk (~> 2.6) prettier_print (1.2.1) rack (3.0.8) + rack-unreloader (2.1.0) rackup (2.1.0) rack (>= 3) webrick (~> 1.8) @@ -23,7 +25,10 @@ GEM language_server-protocol (~> 3.17.0) sorbet-runtime syntax_tree (>= 6.1.1, < 7) + sequel (5.69.0) sorbet-runtime (0.5.10880) + sqlite3 (1.5.4) + mini_portile2 (~> 2.8.0) syntax_tree (6.1.1) prettier_print (>= 1.2.0) tilt (2.2.0) @@ -37,11 +42,14 @@ PLATFORMS DEPENDENCIES minitest phlex + rack-unreloader rackup rake roda ruby-lsp + sequel + sqlite3 tilt BUNDLED WITH - 2.3.18 + 2.4.1 diff --git a/Rakefile b/Rakefile index a71a36b..ac33200 100644 --- a/Rakefile +++ b/Rakefile @@ -3,3 +3,14 @@ require "minitest/test_task" task :default => :test Minitest::TestTask.create + +task :migrate do + # Always applies up to latest version for now + version = nil + + require_relative "db" + require "logger" + Sequel.extension :migration + DB.loggers << Logger.new($stdout) if DB.loggers.empty? + Sequel::Migrator.apply(DB, "migrate", version) +end diff --git a/config.ru b/config.ru index 0581cbf..6d5a898 100644 --- a/config.ru +++ b/config.ru @@ -1,3 +1,13 @@ -require "./lib/app" -App.freeze unless ENV["RACK_ENV"] == "development" -run App.app +dev = ENV["RACK_ENV"] == "development" + +if dev + require "logger" + logger = Logger.new($stdout) +end + + +require "rack/unreloader" +Unreloader = Rack::Unreloader.new(subclasses: %w[Roda Sequel::Model], logger: logger, reload: dev, autoload: dev){App} +require_relative "lib/models" +Unreloader.require("lib/app.rb"){'App'} +run(dev ? Unreloader : App.freeze.app) diff --git a/lib/app.rb b/lib/app.rb index 58e2c38..a652f8e 100644 --- a/lib/app.rb +++ b/lib/app.rb @@ -1,12 +1,111 @@ +# frozen_string_literal: true +require_relative "models" + require "roda" class App < Roda + opts[:check_dynamic_arity] = false + opts[:check_arity] = :warn + + plugin :default_headers, + "Content-Type" => "text/html", + # "Strict-Transport-Security" => "max-age=16070400;", # Uncomment if only allowing https:// access + "X-Frame-Options" => "deny", + "X-Content-Type-Options" => "nosniff", + "X-XSS-Protection" => "1; mode=block" + + plugin :content_security_policy do |csp| + csp.default_src :none + # csp.style_src :self, "https://cdn.jsdelivr.net" + csp.form_action :self + csp.script_src :self + csp.connect_src :self + csp.base_uri :none + csp.frame_ancestors :none + end + + # css_opts = {cache: false, style: :compressed} + # :nocov: + # if ENV["RACK_ENV"] == 'development' + # css_opts.merge!(source_map_embed: true, source_map_contents: true, source_map_file: ".") + # end + plugin :render_coverage if defined?(SimpleCov) + # :nocov: + + plugin :route_csrf + plugin :flash + # plugin :assets, css: "app.scss", css_opts: css_opts, timestamp_paths: true + # plugin :render, escape: true, layout: "./layout", :template_opts=>{chain_appends: !defined?(SimpleCov), freeze: true, skip_compiled_encoding_detection: true} plugin :render, engine: :phlex + # plugin :public + plugin :Integer_matcher_max + plugin :typecast_params_sized_integers, sizes: [64], default_size: 64 + plugin :hash_branch_view_subdir + + logger = if ENV["RACK_ENV"] == "test" + Class.new{def write(_) end}.new + else + $stderr + end + plugin :common_logger, logger + + plugin :not_found do + @page_title = "File Not Found" + view(content: "") + end + + if ENV["RACK_ENV"] == "development" + plugin :exception_page + class RodaRequest + def assets + exception_page_assets + super + end + end + else + def self.freeze + Sequel::Model.freeze_descendents + DB.freeze + super + end + end + + plugin :error_handler do |e| + case e + when Roda::RodaPlugins::RouteCsrf::InvalidToken + @page_title = "Invalid Security Token" + response.status = 400 + view(content: "

An invalid security token was submitted with this request, and this request could not be processed.

") + else + $stderr.print "#{e.class}: #{e.message}\n" + $stderr.puts e.backtrace + next exception_page(e, assets: true) if ENV["RACK_ENV"] == "development" + @page_title = "Internal Server Error" + view(content: "") + end + end + + plugin :sessions, + key: "_App.session", + #cookie_options: {secure: ENV['RACK_ENV'] != 'test'}, # Uncomment if only allowing https:// access + secret: ENV.send((ENV["RACK_ENV"] == "development" ? :[] : :delete), "APP_SESSION_SECRET") + + # if Unreloader.autoload? + # plugin :autoload_hash_branches + # autoload_hash_branch_dir("./routes") + # end + # Unreloader.autoload("routes", delete_hook: proc{|f| hash_branch(File.basename(f).delete_suffix(".rb"))}){} route do |r| + # r.public + # r.assets + check_csrf! + r.root do + @page_title = "Foo" + # render("hello", locals: {name: "Alice"}) - view("hello", locals: {name: "Alice"}) + view("hello", locals: {name: "Bob"}) end end end @@ -23,6 +122,8 @@ module Tilt # # https://github.com/phlex-ruby/phlex-rails/blob/main/lib/phlex/rails/layout.rb#L18 def evaluate(scope, locals, &block) + p scope + p locals klass = Class.new(Phlex::HTML) klass.class_eval(data, __FILE__, __LINE__) component = klass.new(**locals) diff --git a/lib/db.rb b/lib/db.rb new file mode 100644 index 0000000..cca0ac7 --- /dev/null +++ b/lib/db.rb @@ -0,0 +1,3 @@ +require "sequel/core" + +DB = Sequel.connect(ENV.delete("DATABASE_URL")) diff --git a/lib/models.rb b/lib/models.rb new file mode 100644 index 0000000..32eefb3 --- /dev/null +++ b/lib/models.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative "db" +require "sequel/model" + +if ENV["RACK_ENV"] == "development" + Sequel::Model.cache_associations = false +end + +Sequel::Model.plugin :auto_validations +Sequel::Model.plugin :require_valid_schema +Sequel::Model.plugin :subclasses unless ENV["RACK_ENV"] == "development" + +unless defined?(Unreloader) + require "rack/unreloader" + Unreloader = Rack::Unreloader.new(reload: false, autoload: !ENV["NO_AUTOLOAD"]) +end + +Unreloader.autoload("models"){|f| Sequel::Model.send(:camelize, File.basename(f).sub(/\.rb\z/, ''))} + +if ENV["RACK_ENV"] == "development" || ENV["RACK_ENV"] == "test" + require "logger" + LOGGER = Logger.new($stdout) + LOGGER.level = Logger::FATAL if ENV["RACK_ENV"] == "test" + DB.loggers << LOGGER +end diff --git a/migrate/001_tables.rb b/migrate/001_tables.rb new file mode 100644 index 0000000..61e6806 --- /dev/null +++ b/migrate/001_tables.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +Sequel.migration do + change do + create_table(:pools) do + primary_key :id + + String :name, null: false + + DateTime :created_at, null: false + DateTime :updated_at, null: false + end + + create_table(:items) do + primary_key :id + + foreign_key :pool_id, :pools, null: false + + String :title, null: false + String :body, null: false + + DateTime :created_at, null: false + DateTime :updated_at, null: false + end + + create_table(:axes) do + primary_key :id + + foreign_key :pool_id, :pools, null: false + + String :name, null: false + String :better_legend, null: false + String :worse_legend, null: false + + DateTime :created_at, null: false + DateTime :updated_at, null: false + end + + create_table(:ratings) do + primary_key :id + + foreign_key :axis_id, :axes, null: false + foreign_key :winner_id, :items, null: false + foreign_key :loser_id, :items, null: false + + DateTime :created_at, null: false + DateTime :updated_at, null: false + end + + create_table(:rankings) do + primary_key :id + + foreign_key :axis_id, :axes, null: false + foreign_key :item_id, :items, null: false + + float :mu, null: false + float :sigma, null: false + + DateTime :created_at, null: false + DateTime :updated_at, null: false + end + end +end