diff --git a/.erdconfig b/.erdconfig index 296f1bfb2cebcd596cc9e5e8aba14a633069b0a3..ef0d80c6501e7edf04e0f9f390317256bf423c0a 100644 --- a/.erdconfig +++ b/.erdconfig @@ -1,3 +1,19 @@ +title: RailsTest +attributes: + - content + - foreign_keys + - inheritance + - primary_keys + - timestamps +disconnected: true +indirect: true +inheritance: false +markup: true +notation: bachman +filetype: png +cluster: false +splines: spline +prepend_primary: false fonts: normal: "Dejavu" bold: "Dejavu Bold" diff --git a/Dockerfile b/Dockerfile index 988ea497981235d109c2b98ec4b6f7ddeac45994..882bfa5ad22ea4347eb6e8418585b7ae477953a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,4 @@ RUN bundle install -j8 EXPOSE 3000 ENTRYPOINT ["bash", "entrypoint.sh"] -CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] +CMD ["bundle", "exec", "bin/dev"] diff --git a/Gemfile b/Gemfile index 94ed7366995e16d2a6234405b1fb258f1e66c566..47b2d01212d9c67f0e96cf95ecedd5fe5948d3ae 100644 --- a/Gemfile +++ b/Gemfile @@ -46,9 +46,9 @@ gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] # gem "bootsnap", require: false # Use Sass to process CSS -gem "sassc-rails" +# gem "sassc-rails" -gem 'bootstrap', '~> 5.2.2' +# gem 'bootstrap', '~> 5.2.2' gem 'awesome_print' # gem 'stisla-rails' gem 'simple_form' @@ -79,6 +79,7 @@ group :development do # gem "spring" gem 'rails-erd' + gem 'foreman' end group :test do @@ -90,4 +91,7 @@ end group :development, :test do gem 'rspec-rails' + gem 'shoulda-matchers' + gem 'factory_bot_rails' + gem 'faker' end diff --git a/Gemfile.lock b/Gemfile.lock index d061b7ffd0bbbcbaa8651111b52a8e9b0d429171..ec2b816d0adaddcb0fa4792ae07762b03cca39bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,18 +66,12 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - autoprefixer-rails (10.4.7.0) - execjs (~> 2) awesome_print (1.9.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) bindex (0.8.1) - bootstrap (5.2.3) - autoprefixer-rails (>= 9.1.0) - popper_js (>= 2.11.6, < 3) - sassc-rails (>= 2.0.0) builder (3.2.4) byebug (11.1.3) cancancan (3.4.0) @@ -92,8 +86,14 @@ GEM thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.5.0) erubi (1.12.0) - execjs (2.8.1) - ffi (1.15.5) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + faker (3.1.1) + i18n (>= 1.8.11, < 2) + foreman (0.87.2) globalid (1.1.0) activesupport (>= 5.0) i18n (1.12.0) @@ -129,7 +129,6 @@ GEM nio4r (2.5.8) nokogiri (1.14.2-x86_64-linux) racc (~> 1.4) - popper_js (2.11.6) puma (5.6.5) nio4r (~> 2.0) racc (1.6.2) @@ -198,14 +197,8 @@ GEM ruby-graphviz (1.2.5) rexml ruby2_keywords (0.0.5) - sassc (2.4.0) - ffi (~> 1.9) - sassc-rails (2.1.2) - railties (>= 4.0.0) - sassc (>= 2.0) - sprockets (> 3.0) - sprockets-rails - tilt + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) simple_form (5.2.0) actionpack (>= 5.2) activemodel (>= 5.2) @@ -226,7 +219,6 @@ GEM railties (>= 6.0.0) thor (1.2.1) thread_safe (0.3.6) - tilt (2.1.0) timeout (0.3.2) turbo-rails (1.3.3) actionpack (>= 6.0.0) @@ -253,10 +245,12 @@ PLATFORMS DEPENDENCIES awesome_print - bootstrap (~> 5.2.2) byebug cancancan debug + factory_bot_rails + faker + foreman importmap-rails jbuilder puma (~> 5.0) @@ -265,7 +259,7 @@ DEPENDENCIES rails-patterns ransack rspec-rails - sassc-rails + shoulda-matchers simple_form simple_form-tailwind sprockets-rails diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 0000000000000000000000000000000000000000..d98e69f4ece9db4567fc6b751b7508ceff823d33 --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: bundle exec rails server -b 0.0.0.0 -p 3000 +css: bundle exec rails tailwindcss:watch diff --git a/README.md b/README.md index 9bdbe06a004069515a7779e5e76f36d391a29d33..25feb39a753c7b7074d716b3a1a3454ec7c7702b 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,8 @@ $ docker run --rm -it -v $PWD:/ruby -u $(id -u):$(id -g) registry.gitlab.railsfo ## 1. Rails bootstrap https://guides.rubyonrails.org/getting_started.html \ -https://api.rubyonrails.org/ \ No newline at end of file +https://api.rubyonrails.org/ + +### ERD + + \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000000000000000000000000000000000..9a5ea7383aa83eec12490380a7391d1bb93eeb96 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000000000000000000000000000000000000..b06fc42ac1e19c93f65011f4a0f8ff769f31a3a4 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,5 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js +//= link_tree ../builds diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000000000000000000000000000000000000..288b9ab7182c015d9c381e27708895c22dfd9a0a --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css new file mode 100644 index 0000000000000000000000000000000000000000..8666d2f3c607e889fe4ee3c22ff6dfa66dad5da4 --- /dev/null +++ b/app/assets/stylesheets/application.tailwind.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* + +@layer components { + .btn-primary { + @apply py-2 px-4 bg-blue-200; + } +} + +*/ diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000000000000000000000000000000000000..d67269728300b9dac6a4a0db443ece02d7b6c513 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000000000000000000000000000000000000..0ff5442f476f98d578f77221b57164cffcf08de0 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..09705d12ab4dfe301535a973e2607fad4efc9d0d --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..7bd8608db23e9f5043948c73361738292466f284 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,74 @@ +module ApplicationHelper + def flash_classes(flash_type) + flash_base = "px-2 py-4 mx-auto font-sans font-medium text-center text-white" + { + notice: "bg-indigo-600 #{flash_base}", + error: "bg-red-600 #{flash_base}", + alert: "bg-red-600 #{flash_base}" + }.stringify_keys[flash_type.to_s] || flash_type.to_s + end + + def nav_classes + ["devise/registrations", "devise/sessions", "devise/confirmations", "devise/passwords", "devise/unlocks"].include?(params[:controller]) ? "hidden" : nil + end + + def label_class(options={}) + "block mb-1 font-normal leading-normal #{options[:extended_classes]}" + end + + def input_class(options={}) + "rounded border border-gray-300 block w-full focus:outline-none focus:border-gray-400 outline-none focus-within:outline-none focus:ring-2 focus:ring-gray-200 #{options[:extended_classes]}" + end + + def checkbox_class(options={}) + "rounded border-gray-300 border focus:ring-2 focus:ring-gray-200 text-blue-500 mr-1 #{options[:extended_classes]}" + end + + def link_class(options={}) + "text-gray-700 underline hover:no-underline hover:text-gray-800 block #{options[:extended_classes]}" + end + + def button_class(options={}) + variant = options[:variant] + theme = options[:theme] + + style_button(variant, theme_button(theme)) + end + + def theme_button(theme) + themes = { + primary: "primary", + secondary: "secondary", + transparent: "transparent", + dark: "dark" + } + + case theme + when themes[:primary] + "bg-indigo-600 hover:bg-indigo-700 text-white" + when themes[:secondary] + "bg-teal-600 hover:bg-teal-700 text-white" + when themes[:transparent] + "bg-transparent hover:bg-gray-100 text-gray-700" + when themes[:dark] + "bg-gray-800 text-white shadow-sm hover:bg-gray-900" + else + "bg-white border border-gray-300 shadow-sm hover:bg-gray-100" + end + end + + def style_button(variant, theme) + base = "rounded text-center font-sans font-normal outline-none leading-normal cursor-pointer transition ease-in-out duration-200 font-medium" + + case variant + when "large" + "px-5 py-4 text-lg #{base} #{theme}" + when "small" + "py-2 px-4 text-sm #{base} #{theme}" + when "expanded" + "p-3 w-full block #{base} #{theme}" + else + "px-5 py-2 text-base #{base} #{theme}" + end + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 0000000000000000000000000000000000000000..0d7b49404c35d6a69db67a6050d83927ef46f320 --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 0000000000000000000000000000000000000000..1213e85c7ac805be18b4ea04f93ab028cc975255 --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000000000000000000000000000000000000..5975c0789d7cf1cc918034ba31a8f102e06e93b9 --- /dev/null +++ b/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 0000000000000000000000000000000000000000..54ad4cad4d448fb5598447916ac5535d03376761 --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000000000000000000000000000000000000..d394c3d106230849e4ff6f4020d41b3e62589061 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000000000000000000000000000000000000..3c34c8148f105d699e8ec9b769531192f8637d9a --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000000000000000000000000000000000000..b63caeb8a5c4a21feec3ea870c4e4a52b89a8fa5 --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..faf566ccc06cadceb6d6c83c288983430581b0af --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html class="h-full antialiased"> + <head> + <title><%= content_for?(:title) ? yield(:title) : "RailsTest" %></title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <% if !Rails.env.test? -%> + <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> + <% end -%> + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + </head> + + <body class="font-sans font-normal leading-normal text-gray-800 bg-white flex flex-col min-h-screen"> + <header> + <%= render "shared/flash_notice" %> + <%= render "shared/navbar" %> + </header> + <main class="flex-grow"> + <%= content_for?(:content) ? yield(:content) : yield %> + </main> + </body> +</html> \ No newline at end of file diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..cbd34d2e9dd1176aeddb1efe6e1aeb0e2afd2931 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <style> + /* Email styles need to be inline */ + </style> + </head> + + <body> + <%= yield %> + </body> +</html> diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000000000000000000000000000000000000..37f0bddbd746bc24923ce9a8eb0dae1ca3076284 --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/shared/_flash_notice.html.erb b/app/views/shared/_flash_notice.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..e88c110c7e0cebe0aaef9ec6e6305aeb643baaa6 --- /dev/null +++ b/app/views/shared/_flash_notice.html.erb @@ -0,0 +1,5 @@ +<% flash.each do |type, message| %> + <div class="<%= flash_classes(type) %>"> + <%= message %> + </div> +<% end %> \ No newline at end of file diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..a82d5338abf211370772b585e69383a0c9a1e909 --- /dev/null +++ b/app/views/shared/_navbar.html.erb @@ -0,0 +1,54 @@ +<nav class="flex flex-wrap items-center justify-between py-3 text-gray-700 bg-white container mx-auto px-4 <%= nav_classes %>" data-controller="nav"> + <div class="flex items-center mr-6 flex-no-shrink"> + <%= link_to root_path, class:"link text-xl tracking-tight font-black" do %> + <span class="sr-only">RailsTest</span> + <%# Remove this logo and add your own %> + <svg xmlns="http://www.w3.org/2000/svg" width="411" height="155" viewBox="0 0 411 155" class="w-16 h-16"> + <g fill="#CC0000" fill-rule="evenodd" transform="translate(8 8)"> + <path d="M344.6 121.5L344.6 139.6 377.3 139.6C384 139.6 395.5 134.7 395.9 121L395.9 114C395.9 102.3 386.3 95.4 377.3 95.4L361 95.4 361 87 393.3 87 393.3 68.8 362.3 68.8C354.3 68.8 343.6 75.4 343.6 87.7L343.6 94C343.6 106.3 354.2 112.6 362.3 112.6 384.8 112.7 356.9 112.6 377.7 112.6L377.7 121.4M169.4 117.1C169.4 117.1 186.9 115.6 186.9 93 186.9 70.4 165.7 68.3 165.7 68.3L127.5 68.3 127.5 139.6 146.7 139.6 146.7 122.4 163.3 139.6 191.7 139.6 169.4 117.1zM162 102.5L146.7 102.5 146.7 86.2 162.1 86.2C162.1 86.2 166.4 87.8 166.4 94.3 166.4 100.8 162 102.5 162 102.5zM234.3 68.8L214.8 68.8C200.9 68.8 196.2 81.4 196.2 87.4L196.2 139.6 215.7 139.6 215.7 127.1 234 127.1 234 139.6 252.9 139.6 252.9 87.4C252.9 72.2 239.1 68.8 234.3 68.8zM234 106.9L215.6 106.9 215.6 89.6C215.6 89.6 215.6 85.7 221.7 85.7L228.4 85.7C233.8 85.7 233.9 89.6 233.9 89.6L233.9 106.9 234 106.9z"/> + <rect width="20.3" height="70.8" x="261.8" y="68.8"/> + <polygon points="310.6 121.3 310.6 68.8 290.4 68.8 290.4 121.3 290.4 139.6 310.6 139.6 337.9 139.6 337.9 121.3"/> + <path d="M7,139.6 L86,139.6 C86,139.6 70.9,70.7 120.9,42.8 C131.8,37.5 166.5,17.7 223.3,59.7 C225.1,58.2 226.8,57 226.8,57 C226.8,57 174.8,5.1 116.9,10.9 C87.8,13.5 52,40 31,75 C10,110 7,139.6 7,139.6 Z"/> + <path d="M7,139.6 L86,139.6 C86,139.6 70.9,70.7 120.9,42.8 C131.8,37.5 166.5,17.7 223.3,59.7 C225.1,58.2 226.8,57 226.8,57 C226.8,57 174.8,5.1 116.9,10.9 C87.8,13.5 52,40 31,75 C10,110 7,139.6 7,139.6 Z"/> + <path d="M7 139.6L86 139.6C86 139.6 70.9 70.7 120.9 42.8 131.8 37.5 166.5 17.7 223.3 59.7 225.1 58.2 226.8 57 226.8 57 226.8 57 174.8 5.1 116.9 10.9 87.7 13.5 51.9 40 30.9 75 9.9 110 7 139.6 7 139.6zM171.6 16.5L172 9.8C171.1 9.3 168.6 8.1 162.3 6.3L161.9 12.9C165.2 14 168.4 15.2 171.6 16.5z"/> + <path d="M162.1 37.7L161.7 44C165 44.1 168.3 44.5 171.6 45.2L172 39C168.6 38.3 165.3 37.9 162.1 37.7zM125.1 6.5L126.1 6.5 124.1.4C121 .4 117.8.6 114.5 1L116.4 6.9C119.3 6.6 122.2 6.5 125.1 6.5zM129.9 43.3L132.2 50.2C135.1 48.8 138 47.6 140.9 46.7L138.7 40.1C135.3 41.1 132.4 42.2 129.9 43.3zM84.5 17L80 10.1C77.5 11.4 74.9 12.8 72.2 14.4L76.8 21.4C79.4 19.8 81.9 18.3 84.5 17zM105 62L109.8 69.2C111.5 66.7 113.5 64.4 115.7 62.1L111.2 55.3C108.9 57.4 106.8 59.7 105 62zM90.5 94.2L98.6 100.6C99 96.7 99.7 92.8 100.7 88.9L93.5 83.2C92.2 86.9 91.3 90.6 90.5 94.2zM46.7 46.7L39.6 40.5C37 43 34.5 45.5 32.2 48L39.9 54.6C42 51.9 44.3 49.2 46.7 46.7zM16.5 91.4L5 87.2C3.1 91.5 1 96.5 0 99.2L11.5 103.4C12.8 100 14.9 95.1 16.5 91.4zM89 119.6C89.2 124.9 89.7 129.2 90.2 132.2L102.2 136.5C101.3 132.6 100.4 128.2 99.8 123.5L89 119.6z"/> + </g> + </svg> + <% end %> + </div> + <div class="block lg:hidden"> + <%# Mobile nav toggle %> + <button class="flex items-center p-2 -mr-1 rounded-lg focus:bg-gray-100" data-action="click->nav#toggleMenu touch->nav#toggleMenu"> + <svg class="stroke-current text-gray-500 w-7 h-7" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Menu</title><g fill="none"><path d="M4 6h16M4 12h8m-8 6h16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></g></svg> + </button> + </div> + <div class="items-center w-full text-center lg:flex-1 lg:flex lg:text-left pt-2 lg:pt-0 hidden" data-nav-target="menu"> + <div class="lg:flex-grow"> + <%= link_to "Home", root_path, class: "p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + </div> + <div class="items-center block w-full mt-2 text-center lg:flex lg:flex-row lg:flex-1 lg:mt-0 lg:text-left lg:justify-end"> + <div> + <%# More links can go here %> + <%= link_to "Users", users_path, class: "p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + <%= link_to "Projects", projects_path, class: "p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + <%= link_to "Priorities", priorities_path, class: "p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + <%= link_to "Issues", issues_path, class: "p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + </div> + <% if Gem.loaded_specs["devise"] %> + <% if user_signed_in? %> + <%= link_to "Edit account", edit_user_registration_path, class: "p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + <%= button_to "Log out", destroy_user_session_path, method: :delete, class:"p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + <% else %> + <%= link_to "Sign in", new_user_session_path, class:"p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block" %> + <%= link_to "Sign up", new_user_registration_path, class:"p-3 hover:bg-gray-50 transition ease duration-300 rounded-lg text-center focus:bg-gray-100 lg:inline-block block lg:ml-2" %> + + <span class="h-5 w-px bg-gray-200 mx-3"></span> + + <%= link_to "https://github.com/justalever/kickoff_tailwind", target: :_blank, class: "group lg:ml-2 flex justify-center" do %> + <svg class="fill-current text-gray-400 group-hover:text-gray-600" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg> + <% end %> + <% end %> + <% end %> + </div> + </div> +</nav> \ No newline at end of file diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000000000000000000000000000000000000..ee73929e56b56a1ae02cdfcbbd4d69fe9a1e57f3 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/dev b/bin/dev new file mode 100755 index 0000000000000000000000000000000000000000..74ade16641049c0fdf521d70aaf980e6cbca9910 --- /dev/null +++ b/bin/dev @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if ! gem list foreman -i --silent; then + echo "Installing foreman..." + gem install foreman +fi + +exec foreman start -f Procfile.dev "$@" diff --git a/bin/importmap b/bin/importmap new file mode 100755 index 0000000000000000000000000000000000000000..36502ab16c7adf42dfc804702966e684d68610dd --- /dev/null +++ b/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000000000000000000000000000000000000..efc0377492f7e0ec9f6cedf7b5e1f6119bbbd24e --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000000000000000000000000000000000000..4fbf10b960ef780b748861e6a616a4d88b00b50a --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000000000000000000000000000000000000..ec47b79b3b3a002be18adafe9a5fcd070bcd808d --- /dev/null +++ b/bin/setup @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +require "fileutils" + +# path to your application root. +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + puts "\n== Restarting application server ==" + system! "bin/rails restart" +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000000000000000000000000000000000..4a3c09a6889a97a54af8cbcc1a47e03e58cce376 --- /dev/null +++ b/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000000000000000000000000000000000..8400c6f777c024185a1099be863767f133970566 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,49 @@ +require_relative "boot" + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "active_storage/engine" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_mailbox/engine" +require "action_text/engine" +require "action_view/railtie" +require "action_cable/engine" +# require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Ruby + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 7.0 + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + config.time_zone = "Europe/Prague" + # config.eager_load_paths << Rails.root.join("extras") + + # Don't generate system test files. + config.generators.system_tests = nil + + config.generators do |g| + g.orm :active_record + g.template_engine :erb + g.test_framework :rspec + g.stylesheets false + g.helper false + end + + if Rails.env.development? + config.web_console.permissions = ['10.0.0.0/8', '172.0.0.0/8', '192.168.0.0/16'] + end + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000000000000000000000000000000000..282011619d92227924de59017a1eda9c5b555047 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000000000000000000000000000000000000..ae172b57e8fea04e19b57546f00079c24d31821d --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: ruby_production diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000000000000000000000000000000000000..a1a50d8edc2026e82c12849747d574f8d45f23d9 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +B2JiVUrtghEB649jFY/5Gvr47H5QPlA4f62qBTw0A5LFi0CKcmEzfnYCffZFTmHwRYrTZTYwjzApiPcNohWSRyvaXrfOnZG+FYC2JpSxUs027bWj9u1YKvTL2+gLOvk6GwbiIrKB+N4hPiEEBvKum8FrRdRK7nRtyjVVZixp1hnRzzoTJjQRcvmQ9WuyCM88iOxVAAspm6C0qvqQAs9q9KyphEfdH4M1ozGi7Zm0IxEQDUwT5d/0baTj3z+EtMNPvP+PMtzhdv9wV9YVT2a1t+31w9qoQ3zG0M9dCjbUsUSU0dgyh7CZ85xrpKGKSFwFpQgjQ3V7M5gotALT27cRqHxLUrWuuh6ZZ5djz0fcb2+zR4533RIIc6rQMlFrm3/xFfgqCJ/nBSZfOggjW69bsm/Rjqf22ftfL4+9--yZb9WH9YqriSZ++Z--pArojR7JfJ23UW4ZkmLi+A== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000000000000000000000000000000000000..fcba57f19f06ec815dc78969d2f0a1e526ca56be --- /dev/null +++ b/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/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. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000000000000000000000000000000000..cac5315775258a68f5e18885605d3fb1b758319e --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000000000000000000000000000000000000..8500f459a88e53326371bb4539b381b209e48c01 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,70 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000000000000000000000000000000000000..a43c2f3ec82317a3dd703a1e76edd6e8c84b8001 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,93 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Include generic and useful information about system operation, but avoid logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). + config.log_level = :info + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "ruby_production" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require "syslog/logger" + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000000000000000000000000000000000000..6ea4d1e70631fd2d20aa0d4b32b20d2f34f69d60 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,60 @@ +require "active_support/core_ext/integer/time" + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Turn false under Spring and add config.action_view.cache_template_loading = true. + config.cache_classes = true + + # Eager loading loads your whole application. When running a single test locally, + # this probably isn't necessary. It's a good idea to do in a continuous integration + # system, or in some way before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + config.cache_store = :null_store + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true +end diff --git a/config/importmap.rb b/config/importmap.rb new file mode 100644 index 0000000000000000000000000000000000000000..8dce42d4060e0d7f714e37574acf43563a3aa348 --- /dev/null +++ b/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000000000000000000000000000000000..2eeef966fe8720932a80b0eca296eafdf53d71aa --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,12 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..54f47cf15fe5026bede1bd6a9acb4ef815bf22ab --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap and inline scripts +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000000000000000000000000000000000..adc6568ce83724d2b01d7232b0873bda7c249b11 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be filtered from the log file. Use this to limit dissemination of +# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported +# notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000000000000000000000000000000000000..3860f659ead02224f524351d612cd92aefcd5d8b --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/config/initializers/permissions_policy.rb b/config/initializers/permissions_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..00f64d71b03e029116af7b1713f450e9635fb10f --- /dev/null +++ b/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/config/initializers/simple_form_tailwind.rb b/config/initializers/simple_form_tailwind.rb new file mode 100644 index 0000000000000000000000000000000000000000..a3c081208b348a4daddecf127fc0f5f4ded767ab --- /dev/null +++ b/config/initializers/simple_form_tailwind.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + # Default class for buttons + config.button_class = 'my-2 bg-blue-500 hover:bg-blue-700 text-white font-bold text-sm py-2 px-4 rounded' + + # Define the default class of the input wrapper of the boolean input. + config.boolean_label_class = '' + + # How the label text should be generated altogether with the required text. + config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" } + + # Define the way to render check boxes / radio buttons with labels. + config.boolean_style = :inline + + # You can wrap each item in a collection of radio/check boxes with a tag + config.item_wrapper_tag = :div + + # Defines if the default input wrapper class should be included in radio + # collection wrappers. + config.include_default_input_wrapper_class = false + + # CSS class to add for error notification helper. + config.error_notification_class = 'text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-400' + + # Method used to tidy up errors. Specify any Rails Array method. + # :first lists the first message for each field. + # :to_sentence to list all errors for each field. + config.error_method = :to_sentence + + # add validation classes to `input_field` + config.input_field_error_class = 'border-red-500' + config.input_field_valid_class = 'border-green-400' + config.label_class = 'text-sm font-medium text-gray-600' + + + # vertical forms + # + # vertical default_wrapper + config.wrappers :vertical_form, tag: 'div', class: 'mb-4' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'block', error_class: 'text-red-500' + b.use :input, class: 'shadow appearance-none border border-gray-300 rounded w-full py-2 px-3 bg-white focus:outline-none focus:ring-0 focus:border-blue-500 text-gray-400 leading-6 transition-colors duration-200 ease-in-out', error_class: 'border-red-500', valid_class: 'border-green-400' + b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } + b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } + end + + # vertical input for boolean (aka checkboxes) + config.wrappers :vertical_boolean, tag: 'div', class: 'mb-4 flex items-start', error_class: '' do |b| + b.use :html5 + b.optional :readonly + b.wrapper tag: 'div', class: 'flex items-center h-5' do |ba| + ba.use :input, class: 'focus:ring-2 focus:ring-indigo-500:focus ring-offset-2 h-4 w-4 text-indigo-600 border-gray-300 rounded' + end + b.wrapper tag: 'div', class: 'ml-3 text-sm' do |bb| + bb.use :label, class: 'block', error_class: 'text-red-500' + bb.use :hint, wrap_with: { tag: 'p', class: 'block text-grey-700 text-xs italic' } + bb.use :full_error, wrap_with: { tag: 'p', class: 'block text-red-500 text-xs italic' } + end + + end + + # vertical input for radio buttons and check boxes + config.wrappers :vertical_collection, item_wrapper_class: 'flex items-center', item_label_class: 'my-1 ml-3 block text-sm font-medium text-gray-400', tag: 'div', class: 'my-4' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :legend_tag, tag: 'legend', class: 'text-sm font-medium text-gray-600', error_class: 'text-red-500' do |ba| + ba.use :label_text + end + b.use :input, class: 'focus:ring-2 focus:ring-indigo-500 ring-offset-2 h-4 w-4 text-indigo-600 border-gray-300 rounded', error_class: 'text-red-500', valid_class: 'text-green-400' + b.use :full_error, wrap_with: { tag: 'p', class: 'block mt-2 text-red-500 text-xs italic' } + b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } + end + + # vertical file input + config.wrappers :vertical_file, tag: 'div', class: '' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :readonly + b.use :label, class: 'text-sm font-medium text-gray-600 block', error_class: 'text-red-500' + b.use :input, class: 'w-full text-gray-500 px-3 py-2 border rounded', error_class: 'text-red-500 border-red-500', valid_class: 'text-green-400' + b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } + b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } + end + + # vertical multi select + config.wrappers :vertical_multi_select, tag: 'div', class: 'my-4', error_class: 'f', valid_class: '' do |b| + b.use :html5 + b.optional :readonly + b.wrapper :legend_tag, tag: 'legend', class: 'text-sm font-medium text-gray-600', error_class: 'text-red-500' do |ba| + ba.use :label_text + end + b.wrapper tag: 'div', class: 'inline-flex space-x-1' do |ba| + # ba.use :input, class: 'flex w-auto w-auto text-gray-500 text-sm border-gray-300 rounded p-2', error_class: 'text-red-500', valid_class: 'text-green-400' + ba.use :input, class: 'flex w-auto w-auto shadow appearance-none border border-gray-300 rounded w-full p-2 bg-white focus:outline-none focus:border-blue-500 text-gray-400 leading-4 transition-colors duration-200 ease-in-out' + end + b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } + b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } + end + + # vertical range input + config.wrappers :vertical_range, tag: 'div', class: 'my-4', error_class: 'text-red-500', valid_class: 'text-green-400' do |b| + b.use :html5 + b.use :placeholder + b.optional :readonly + b.optional :step + b.use :label, class: 'text-sm font-medium text-gray-600 block', error_class: 'text-red-500' + b.wrapper tag: 'div', class: 'flex items-center h-5' do |ba| + ba.use :input, class: 'rounded-lg overflow-hidden appearance-none bg-gray-400 h-3 w-full text-gray-300', error_class: 'text-red-500', valid_class: 'text-green-400' + end + b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } + b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } + end + + # The default wrapper to be used by the FormBuilder. + config.default_wrapper = :vertical_form + + # Custom wrappers for input types. This should be a hash containing an input + # type as key and the wrapper that will be used for all inputs with specified type. + config.wrapper_mappings = { + boolean: :vertical_boolean, + check_boxes: :vertical_collection, + date: :vertical_multi_select, + datetime: :vertical_multi_select, + file: :vertical_file, + radio_buttons: :vertical_collection, + range: :vertical_range, + time: :vertical_multi_select + } +end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000000000000000000000000000000000000..8ca56fc74f3ae57d7ad828ec16007a12de7b592b --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000000000000000000000000000000000000..daaf0369998ecde46d30a52ee793d9c6de3a6a00 --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,43 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +# +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked web server processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000000000000000000000000000000000000..4942ab66948b7fbb64de6600664e2159c243f9a9 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/config/tailwind.config.js b/config/tailwind.config.js new file mode 100644 index 0000000000000000000000000000000000000000..094432ff5a154c040d4be1a114ceb1254476a55f --- /dev/null +++ b/config/tailwind.config.js @@ -0,0 +1,22 @@ +const defaultTheme = require('tailwindcss/defaultTheme') + +module.exports = { + content: [ + './public/*.html', + './app/helpers/**/*.rb', + './app/javascript/**/*.js', + './app/views/**/*.{erb,haml,html,slim}' + ], + theme: { + extend: { + fontFamily: { + sans: ['Inter var', ...defaultTheme.fontFamily.sans], + }, + }, + }, + plugins: [ + require('@tailwindcss/forms'), + require('@tailwindcss/aspect-ratio'), + require('@tailwindcss/typography'), + ] +} diff --git a/erd.png b/erd.png new file mode 100644 index 0000000000000000000000000000000000000000..7a0e11c8adc874fc3fa95a0352b0fa2edbb7e340 Binary files /dev/null and b/erd.png differ diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..b4f83b343569a5ebcc4ae1ee48b4d76a1a7ff47d --- /dev/null +++ b/lib/templates/erb/scaffold/_form.html.erb @@ -0,0 +1,48 @@ +<%# frozen_string_literal: true %> +<%%= simple_form_for(@<%= singular_table_name %>) do |f| %> +<%% if <%= singular_table_name %>.errors.any? %> +<div id="error_explanation" class="bg-red-50 p-6 rounded text-red-800"> + <h2 class="text-lg font-semibold mb-3"><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> + + <ul class="list-disc leading-relaxed"> + <%% <%= singular_table_name %>.errors.full_messages.each do |message| %> + <li><%%= message %></li> + <%% end %> + </ul> +</div> +<br /> +<%% end %> + +<% attributes.each do |attribute| -%> + <div class="mb-6"> + <% if attribute.password_digest? -%> + <%%= f.label :password, class: label_class %> + <%%= f.password_field :password, class: input_class %> + </div> + + <%%= f.label :password_confirmation, class: label_class %> + <%%= f.password_field :password_confirmation, class: input_class %> + <% else -%> + <% if attribute.field_type == "checkbox" -%> + <%%= f.label :<%= attribute.column_name %>, class: label_class %> + <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %>, class: input_checkbox %> + <% else -%> + <% if attribute.reference? -%> + <%%= f.association :<%= attribute.name %> %> + <% else -%> + <%%= f.label :<%= attribute.column_name %>, class: label_class %> + <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %>, class: input_class %> + <% end -%> + <% end -%> + <% end -%> +</div> +<% end -%> + +<%%= f.submit class: button_class(theme: 'primary') %> + +<%% if <%= model_resource_name %>.persisted? %> +<%%= link_to "Cancel", <%= model_resource_name %>, class: "text-neutral-700 underline inline-block ml-3" %> +<%% else %> +<%%= link_to "Cancel", <%= index_helper %>_path, class: "text-neutral-700 underline inline-block ml-3" %> +<%% end %> +<%% end %> \ No newline at end of file diff --git a/lib/templates/erb/scaffold/edit.html.erb b/lib/templates/erb/scaffold/edit.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..0fd64b47f508ffb58c557f1193013512596f4f2c --- /dev/null +++ b/lib/templates/erb/scaffold/edit.html.erb @@ -0,0 +1,7 @@ +<div class="max-w-3xl mx-auto px-4"> + <div class="flex items-center justify-between mb-6 flex-wrap"> + <h1 class="font-bold text-3xl mb-6">Edit <%= singular_table_name %></h1> + </div> + + <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %> +</div> \ No newline at end of file diff --git a/lib/templates/erb/scaffold/index.html.erb b/lib/templates/erb/scaffold/index.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..a9b2542aebc16af3a0ecd9607ed02f586b32db2c --- /dev/null +++ b/lib/templates/erb/scaffold/index.html.erb @@ -0,0 +1,43 @@ +<% name_attribute = attributes.find{ |a| a.name == "name" } %> +<% has_name = !!name_attribute %> + +<div class="container mx-auto px-4"> + <div class="flex items-center justify-between flex-wrap mb-6"> + <h1 class="text-3xl font-bold"><%= plural_table_name.capitalize %></h1> + + <%%= link_to 'Add New <%= human_name %>', new_<%= singular_table_name %>_path, class: button_class(theme: "primary") %> + </div> + + <table class="table-auto border-collapse border-slate-400 w-full"> + <thead class="bg-slate-50 dark:bg-slate-700"> + <tr> + <% if has_name %> + <th class="border border-slate-300 dark:border-slate-600 font-semibold p-4 text-slate-900 dark:text-slate-200 text-left">Name</th> + <% end %> + <% attributes.without(name_attribute).each do |attribute| -%> + <th class="border border-slate-300 dark:border-slate-600 font-semibold p-4 text-slate-900 dark:text-slate-200 text-left"><%= attribute.human_name %></th> + <% end -%> + <th class="border border-slate-300 dark:border-slate-600 font-semibold p-4 text-slate-900 dark:text-slate-200 text-left">...</th> + </tr> + </thead> + + <tbody> + <%% @<%= plural_table_name%>.each do |<%= singular_table_name %>| %> + <%%= content_tag :tr, id: dom_id(<%= singular_table_name %>), class: dom_class(<%= singular_table_name %>) do %> + <% if has_name %> + <td class="border border-slate-300 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"><%%= link_to <%= singular_table_name %>.name, <%= singular_table_name %> %></td> + <% end %> + <% attributes.without(name_attribute).each do |attribute| -%> + <td class="border border-slate-300 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> + <% end -%> + <td class="border border-slate-300 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"> + <%%= link_to 'Show', <%= singular_table_name %>, class: "text-neutral-700 underline" %> + <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>), class: "text-indigo-700 underline" %> + <%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, class: "text-red-700 underline", data: { "turbo-method": :delete, "turbo-confirm": "Are you sure?" } %> + </td> + <%% end %> + <%% end %> + </tbody> + </table> +</div> +</div> \ No newline at end of file diff --git a/lib/templates/erb/scaffold/new.html.erb b/lib/templates/erb/scaffold/new.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..ae09077ccd877456459958fa53450c958d6675b8 --- /dev/null +++ b/lib/templates/erb/scaffold/new.html.erb @@ -0,0 +1,5 @@ +<div class="max-w-3xl mx-auto px-4"> + <h1 class="font-bold text-3xl mb-6">New <%= singular_table_name %></h1> + + <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %> +</div> \ No newline at end of file diff --git a/lib/templates/erb/scaffold/show.html.erb b/lib/templates/erb/scaffold/show.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..58ec887ad408e8fc40ccce6ab81c85be9c1a8f43 --- /dev/null +++ b/lib/templates/erb/scaffold/show.html.erb @@ -0,0 +1,19 @@ +<div class="max-w-3xl mx-auto px-4"> + <div class="flex items-center justify-between"> + <h1 class="flex-1 font-bold text-3xl"><%= singular_table_name.capitalize %></h1> + + <div class="flex-wrap lg:flex items-center lg:space-x-3"> + <%%= link_to 'All <%= plural_table_name.capitalize %>', <%= index_helper %>_path, class: button_class(theme: "transparent") %> + <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>), class: button_class(theme:"primary") %> + </div> + </div> + + <ul class="text-slate-700"> + <%- attributes.each do |attribute| -%> + <li> + <p class="font-semibold"><%= attribute.human_name %>:</p> + <p><%%= @<%= singular_table_name %>.<%= attribute.name %> %></p> + </li> + <%- end -%> + </ul> +</div> \ No newline at end of file diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000000000000000000000000000000000..2be3af26fc5a3d019690b50e0849651dde258794 --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The page you were looking for doesn't exist (404)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + .rails-default-error-page { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + .rails-default-error-page div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + .rails-default-error-page div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + .rails-default-error-page h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + .rails-default-error-page div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body class="rails-default-error-page"> + <!-- This file lives in public/404.html --> + <div class="dialog"> + <div> + <h1>The page you were looking for doesn't exist.</h1> + <p>You may have mistyped the address or the page may have moved.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000000000000000000000000000000000000..c08eac0d1df79f30154726ea242c08e0ee851c4b --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The change you wanted was rejected (422)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + .rails-default-error-page { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + .rails-default-error-page div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + .rails-default-error-page div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + .rails-default-error-page h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + .rails-default-error-page div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body class="rails-default-error-page"> + <!-- This file lives in public/422.html --> + <div class="dialog"> + <div> + <h1>The change you wanted was rejected.</h1> + <p>Maybe you tried to change something you didn't have access to.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000000000000000000000000000000000000..78a030af22ea129d02a7745790b79fbe81a9e3ab --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <title>We're sorry, but something went wrong (500)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + .rails-default-error-page { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + .rails-default-error-page div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + .rails-default-error-page div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + .rails-default-error-page h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + .rails-default-error-page div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body class="rails-default-error-page"> + <!-- This file lives in public/500.html --> + <div class="dialog"> + <div> + <h1>We're sorry, but something went wrong.</h1> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..c19f78ab6836efd56c42aa56457d58871d9e2782 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb new file mode 100644 index 0000000000000000000000000000000000000000..13f33fcb813ebefc00a6d873159916c29bc6ba19 --- /dev/null +++ b/spec/factories/issues.rb @@ -0,0 +1,15 @@ +FactoryBot.define do + factory :issue do + subject { "MyString" } + description { "MyText" } + start_date { "2023-03-08" } + due_date { "2023-03-08" } + status { 1 } + estimated_hours { 1 } + done_ratio { 1 } + priority { nil } + user { nil } + project { nil } + assigned_to_id { 1 } + end +end diff --git a/spec/factories/priorities.rb b/spec/factories/priorities.rb new file mode 100644 index 0000000000000000000000000000000000000000..7c45fd8e39ec01b26db353d6499693e3637ab7de --- /dev/null +++ b/spec/factories/priorities.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :priority do + + end +end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb new file mode 100644 index 0000000000000000000000000000000000000000..1376077958780196b8642bdfa9f28ab55044ce2e --- /dev/null +++ b/spec/factories/projects.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :project do + sequence(:name) { |n| "Project #{n}" } + description { Faker::Lorem.sentences } + association(:user) + end +end diff --git a/spec/factories/time_entries.rb b/spec/factories/time_entries.rb new file mode 100644 index 0000000000000000000000000000000000000000..d3fd186d06df8400b7611f293c572ec8eaf1831b --- /dev/null +++ b/spec/factories/time_entries.rb @@ -0,0 +1,10 @@ +FactoryBot.define do + factory :time_entry do + spent_on { "2023-03-08" } + hours { 1.5 } + comments { "MyString" } + project { nil } + user { nil } + issue { nil } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000000000000000000000000000000000000..b8ae227c1f3688de14e59a8b8e079745809264ec --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :user do + first_name { Faker::Name.first_name } + last_name { Faker::Name.last_name } + email { Faker::Internet.email } + end +end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..97bc0bd86c51b7c7f296cb2b7ebf79df5baa6675 --- /dev/null +++ b/spec/models/issue_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe Issue, type: :model do + describe 'enums' do + it { is_expected.to define_enum_for(:status).with_values(new: 1, in_progress: 2, waiting: 3, solved: 4, closed: 5).with_prefix } + end + + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:priority) } + it { is_expected.to belong_to(:assigned_to).class_name('User') } + + it { is_expected.to have_many(:time_entries).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:subject) } + it { is_expected.to validate_length_of(:subject).is_at_least(1).is_at_most(255) } + end +end diff --git a/spec/models/priority_spec.rb b/spec/models/priority_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..e339caa9867e28b674f0bcaee7a910c2ec5e1792 --- /dev/null +++ b/spec/models/priority_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe Priority, type: :model do + describe 'associations' do + it { is_expected.to have_many(:issues).dependent(:restrict_with_error) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_length_of(:name).is_at_least(1).is_at_most(255) } + it { is_expected.to validate_uniqueness_of(:name).case_insensitive } + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..10757a4cc25c07fa9b32b0c1e68c39f1065e2ccb --- /dev/null +++ b/spec/models/project_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe Project, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:issues).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_length_of(:name).is_at_least(1).is_at_most(255) } + it { is_expected.to validate_presence_of(:user) } + end +end diff --git a/spec/models/time_entry_spec.rb b/spec/models/time_entry_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cf227116de491c11bf16ded3e13a88a5dce8f378 --- /dev/null +++ b/spec/models/time_entry_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe TimeEntry, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..550285a5427b6ec934b498d881732f411c8aa51b --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + describe 'associations' do + it { is_expected.to have_many(:projects).dependent(:nullify) } + it { is_expected.to have_many(:issues).dependent(:restrict_with_error) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:first_name) } + it { is_expected.to validate_presence_of(:last_name) } + it { is_expected.to validate_presence_of(:email) } + it { is_expected.to validate_length_of(:first_name).is_at_least(1).is_at_most(255) } + it { is_expected.to validate_length_of(:last_name).is_at_least(1).is_at_most(255) } + it { is_expected.to validate_length_of(:email).is_at_least(1).is_at_most(255) } + it { is_expected.to allow_value('john@doe.com').for(:email) } + it { is_expected.not_to allow_value('john').for(:email) } + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..5f535b645700791f1874ab220499d79603a81f19 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,72 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + abort e.to_s.strip +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + # config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") + + config.include FactoryBot::Syntax::Methods +end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/spec/routing/issues_routing_spec.rb b/spec/routing/issues_routing_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..e5cb172466c9590afcd54554e6fc999a48b6253a --- /dev/null +++ b/spec/routing/issues_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe IssuesController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/issues").to route_to("issues#index") + end + + it "routes to #new" do + expect(get: "/issues/new").to route_to("issues#new") + end + + it "routes to #show" do + expect(get: "/issues/1").to route_to("issues#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/issues/1/edit").to route_to("issues#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/issues").to route_to("issues#create") + end + + it "routes to #update via PUT" do + expect(put: "/issues/1").to route_to("issues#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/issues/1").to route_to("issues#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/issues/1").to route_to("issues#destroy", id: "1") + end + end +end diff --git a/spec/routing/priorities_routing_spec.rb b/spec/routing/priorities_routing_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a70e47b32f017f152aee2ee6897dadcbcd9bfdc0 --- /dev/null +++ b/spec/routing/priorities_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe PrioritiesController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/priorities").to route_to("priorities#index") + end + + it "routes to #new" do + expect(get: "/priorities/new").to route_to("priorities#new") + end + + it "routes to #show" do + expect(get: "/priorities/1").to route_to("priorities#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/priorities/1/edit").to route_to("priorities#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/priorities").to route_to("priorities#create") + end + + it "routes to #update via PUT" do + expect(put: "/priorities/1").to route_to("priorities#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/priorities/1").to route_to("priorities#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/priorities/1").to route_to("priorities#destroy", id: "1") + end + end +end diff --git a/spec/routing/projects_routing_spec.rb b/spec/routing/projects_routing_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..19b9fe1cb8d84c504a47ca21038fd7f70121455f --- /dev/null +++ b/spec/routing/projects_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe ProjectsController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/projects").to route_to("projects#index") + end + + it "routes to #new" do + expect(get: "/projects/new").to route_to("projects#new") + end + + it "routes to #show" do + expect(get: "/projects/1").to route_to("projects#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/projects/1/edit").to route_to("projects#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/projects").to route_to("projects#create") + end + + it "routes to #update via PUT" do + expect(put: "/projects/1").to route_to("projects#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/projects/1").to route_to("projects#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/projects/1").to route_to("projects#destroy", id: "1") + end + end +end diff --git a/spec/routing/users_routing_spec.rb b/spec/routing/users_routing_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..5f0e48d4df2ec673c5535474d9883e200b512a70 --- /dev/null +++ b/spec/routing/users_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe UsersController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/users").to route_to("users#index") + end + + it "routes to #new" do + expect(get: "/users/new").to route_to("users#new") + end + + it "routes to #show" do + expect(get: "/users/1").to route_to("users#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/users/1/edit").to route_to("users#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/users").to route_to("users#create") + end + + it "routes to #update via PUT" do + expect(put: "/users/1").to route_to("users#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/users/1").to route_to("users#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/users/1").to route_to("users#destroy", id: "1") + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..a0d4080592b01aca1eaf0946a567fc81bae334f2 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,94 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end