diff --git a/.gitignore b/.gitignore index 82701fed..0c05d48b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ /yarn-error.log .byebug_history +.idea/* +.DS_Store + diff --git a/Gemfile b/Gemfile index 5a8ffc43..038699f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,21 +2,37 @@ source 'https://rubygems.org' ruby File.read('.ruby-version').chomp -gem 'byebug', platforms: [:mri, :mingw, :x64_mingw], group: [:development, :test] -gem 'capybara', group: [:development, :test] -gem 'coffee-rails' -gem 'devise' -gem 'jbuilder' -gem 'listen', group: :development +gem 'rails', '~> 7.0.3' gem 'pg' -gem 'pry-rails' gem 'puma' -gem 'rails', '~> 7.0.3' -gem 'rspec-rails' gem 'sass-rails' -gem 'selenium-webdriver', group: [:development, :test] -gem 'spring', group: :development -gem 'turbolinks' -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'coffee-rails' gem 'uglifier' -gem 'web-console', group: :development +gem 'turbolinks' +gem 'jbuilder' +gem 'devise' + +group :development, :test do + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'capybara' + gem 'selenium-webdriver' + gem 'webdrivers' + gem 'rspec-rails' + gem 'database_cleaner-active_record' + gem 'factory_bot_rails' + gem 'webmock' +end + +group :development do + gem 'listen' + gem 'spring' + gem 'web-console' + gem 'pry-rails' +end + +group :test do + gem 'simplecov', require: false # Optional, for test coverage + gem 'shoulda-matchers' # Optional, for simplified model specs +end + +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 14ec6457..9d3d60d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,7 @@ GEM addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) bcrypt (3.1.18) + bigdecimal (3.1.8) bindex (0.8.1) builder (3.2.4) byebug (11.1.3) @@ -91,7 +92,14 @@ GEM execjs coffee-script-source (1.12.2) concurrent-ruby (1.1.10) + crack (1.0.0) + bigdecimal + rexml crass (1.0.6) + database_cleaner-active_record (2.2.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -100,11 +108,18 @@ GEM warden (~> 1.2.3) diff-lcs (1.5.0) digest (3.1.0) + docile (1.4.0) erubi (1.11.0) execjs (2.8.1) + factory_bot (6.4.6) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) ffi (1.15.5) globalid (1.0.0) activesupport (>= 5.0) + hashdiff (1.1.0) i18n (1.12.0) concurrent-ruby (~> 1.0) jbuilder (2.11.5) @@ -224,6 +239,14 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + shoulda-matchers (6.2.0) + activesupport (>= 5.2.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) spring (4.1.0) sprockets (4.1.1) concurrent-ruby (~> 1.0) @@ -250,6 +273,14 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webdrivers (5.3.1) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0, < 4.11) + webmock (3.23.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) @@ -265,7 +296,9 @@ DEPENDENCIES byebug capybara coffee-rails + database_cleaner-active_record devise + factory_bot_rails jbuilder listen pg @@ -275,11 +308,15 @@ DEPENDENCIES rspec-rails sass-rails selenium-webdriver + shoulda-matchers + simplecov spring turbolinks tzinfo-data uglifier web-console + webdrivers + webmock RUBY VERSION ruby 3.1.2p20 diff --git a/app/controllers/interesting_stories_controller.rb b/app/controllers/interesting_stories_controller.rb new file mode 100644 index 00000000..59bbcdc6 --- /dev/null +++ b/app/controllers/interesting_stories_controller.rb @@ -0,0 +1,7 @@ +class InterestingStoriesController < ApplicationController + before_action :authenticate_user! + + def index + @interesting_stories = InterestingStory.includes(:story, :user).all + end +end \ No newline at end of file diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb new file mode 100644 index 00000000..1b6877f2 --- /dev/null +++ b/app/controllers/stories_controller.rb @@ -0,0 +1,32 @@ +# app/controllers/stories_controller.rb +class StoriesController < ApplicationController + before_action :authenticate_user! + + def index + @stories = Story.fetch_top_stories.take(10).map { |story_id| Story.fetch_story_details(story_id) } + end + + def mark_interesting + story_details = Story.fetch_story_details(params[:id].to_i) + @story = Story.find_or_initialize_by(story_id: story_details["id"]) + @story.update(story_params(story_details)) + + InterestingStory.create(user: current_user, story: @story) + + redirect_to interesting_stories_path, notice: 'Story marked as interesting.' + end + + private + + def story_params(details) + { + title: details["title"], + url: details["url"], + by: details["by"], + score: details["score"], + time: Time.at(details["time"]), + descendants: details["descendants"], + story_type: details["type"] + } + end +end \ No newline at end of file diff --git a/app/models/interesting_story.rb b/app/models/interesting_story.rb new file mode 100644 index 00000000..c8267a9f --- /dev/null +++ b/app/models/interesting_story.rb @@ -0,0 +1,6 @@ +# app/models/interesting_story.rb +class InterestingStory < ApplicationRecord + belongs_to :user + belongs_to :story + validates :user_id, uniqueness: { scope: :story_id, message: "has already marked this story as interesting" } +end \ No newline at end of file diff --git a/app/models/story.rb b/app/models/story.rb new file mode 100644 index 00000000..885bbb90 --- /dev/null +++ b/app/models/story.rb @@ -0,0 +1,20 @@ +# app/models/story.rb +require 'net/http' +require 'json' + +class Story < ApplicationRecord + has_many :interesting_stories + has_many :users, through: :interesting_stories + + def self.fetch_top_stories + url = URI.parse('https://hacker-news.firebaseio.com/v0/topstories.json') + response = Net::HTTP.get_response(url) + JSON.parse(response.body) + end + + def self.fetch_story_details(story_id) + url = URI.parse("https://hacker-news.firebaseio.com/v0/item/#{story_id}.json") + response = Net::HTTP.get_response(url) + JSON.parse(response.body) + end +end \ No newline at end of file diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb new file mode 100644 index 00000000..b12dd0cb --- /dev/null +++ b/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 00000000..dc55f64f --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 00000000..32f4ba80 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 00000000..b41daf47 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 00000000..f667dc12 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 00000000..41e148bf --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 00000000..5fbb9ff0 --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb new file mode 100644 index 00000000..9b486b81 --- /dev/null +++ b/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb new file mode 100644 index 00000000..38d95b85 --- /dev/null +++ b/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,43 @@ +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %> + <% if @minimum_password_length %> +
+ <%= @minimum_password_length %> characters minimum + <% end %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 00000000..d655b66f --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,29 @@ +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 00000000..5ede9648 --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,26 @@ +

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ + <% if devise_mapping.rememberable? %> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end %> + +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb new file mode 100644 index 00000000..ba7ab887 --- /dev/null +++ b/app/views/devise/shared/_error_messages.html.erb @@ -0,0 +1,15 @@ +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+ +
+<% end %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb new file mode 100644 index 00000000..96a94124 --- /dev/null +++ b/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>
+ <% end %> +<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb new file mode 100644 index 00000000..ffc34de8 --- /dev/null +++ b/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/interesting_stories/index.html.erb b/app/views/interesting_stories/index.html.erb new file mode 100644 index 00000000..2e514054 --- /dev/null +++ b/app/views/interesting_stories/index.html.erb @@ -0,0 +1,10 @@ +

Interesting Stories

+
+ <% @interesting_stories.each do |interesting_story| %> +
+

<%= link_to interesting_story.story.title, interesting_story.story.url %>

+

by <%= interesting_story.story.by %> | <%= interesting_story.story.score %> points

+

Marked by: <%= interesting_story.user.email %>

+
+ <% end %> +
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 331a7ed0..014aa940 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,19 +1,91 @@ - + - - - Top News - <%= csrf_meta_tags %> - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - - -

- <%= notice %> -

-

- <%= alert %> -

- <%= yield %> - - + + + Top News + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + + +

<%= notice %>

+

<%= alert %>

+<%= yield %> + + \ No newline at end of file diff --git a/app/views/stories/index.html.erb b/app/views/stories/index.html.erb new file mode 100644 index 00000000..48187823 --- /dev/null +++ b/app/views/stories/index.html.erb @@ -0,0 +1,11 @@ + +

Top Stories

+ +<% @stories.each do |story| %> +
+

<%= link_to story['title'], story['url'] %>

+

by <%= story['by'] %>

+

Score: <%= story['score'] %>

+ <%= button_to "Mark as Interesting", mark_interesting_story_path(story['id']), method: :post %> +
+<% end %> \ No newline at end of file diff --git a/bin/rails b/bin/rails index 5badb2fd..efc03774 100755 --- a/bin/rails +++ b/bin/rails @@ -1,9 +1,4 @@ #!/usr/bin/env ruby -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end -APP_PATH = File.expand_path('../config/application', __dir__) -require_relative '../config/boot' -require 'rails/commands' +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake index d87d5f57..4fbf10b9 100755 --- a/bin/rake +++ b/bin/rake @@ -1,9 +1,4 @@ #!/usr/bin/env ruby -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end -require_relative '../config/boot' -require 'rake' +require_relative "../config/boot" +require "rake" Rake.application.run diff --git a/bin/setup b/bin/setup index 78c4e861..ec47b79b 100755 --- a/bin/setup +++ b/bin/setup @@ -1,38 +1,33 @@ #!/usr/bin/env ruby -require 'pathname' -require 'fileutils' -include FileUtils +require "fileutils" # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path("..", __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end -chdir APP_ROOT do - # This script is a starting point to setup your application. +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') - - # Install JavaScript dependencies if using Yarn - # system('bin/yarn') - + 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') - # cp 'config/database.yml.sample', 'config/database.yml' + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" # end puts "\n== Preparing database ==" - system! 'bin/rails db:setup' + system! "bin/rails db:prepare" puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' + system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" - system! 'bin/rails restart' + system! "bin/rails restart" end diff --git a/config/application.rb b/config/application.rb index dab4cec6..98326644 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,6 +1,6 @@ -require_relative 'boot' +require_relative "boot" -require 'rails/all' +require "rails/all" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. @@ -9,10 +9,14 @@ module Topnews class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 5.1 + config.load_defaults 7.0 - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. + # 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. + + # Use the new connection handling (default in 7.0) + config.active_record.legacy_connection_handling = false end end diff --git a/config/boot.rb b/config/boot.rb index 30f5120d..28201161 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,3 +1,3 @@ -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -require 'bundler/setup' # Set up gems listed in the Gemfile. +require "bundler/setup" # Set up gems listed in the Gemfile. diff --git a/config/environment.rb b/config/environment.rb index 426333bb..cac53157 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,5 @@ # Load the Rails application. -require_relative 'application' +require_relative "application" # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 5187e221..8500f459 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,8 +1,10 @@ +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 on - # every request. This slows down response time but is perfect for development + # 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 @@ -12,13 +14,18 @@ # 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. - if Rails.root.join('tmp/caching-dev.txt').exist? + # 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.seconds.to_i}" + "Cache-Control" => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -26,6 +33,9 @@ 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 @@ -34,21 +44,27 @@ # 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 - # Debug mode disables concatenation and preprocessing of assets. - # This option may cause significant delays in view rendering with a large - # number of complex assets. - config.assets.debug = true + # 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.action_view.raise_on_missing_translations = 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 - # Use an evented file watcher to asynchronously detect changes in source code, - # routes, locales, etc. This feature depends on the listen gem. - config.file_watcher = ActiveSupport::EventedFileUpdateChecker + # 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 index 73aa41e6..9bafce13 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/integer/time" + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -14,42 +16,41 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Attempt to read encrypted secrets from `config/secrets.yml.enc`. - # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or - # `config/secrets.yml.key`. - config.read_encrypted_secrets = 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? + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier + # 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 - # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = 'http://assets.example.com' + # 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 + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX - # Mount Action Cable outside main process or domain + # 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.*/ ] + # 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 - # Use the lowest log level to ensure availability of diagnostic information - # when problems arise. - config.log_level = :debug + # 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 ] @@ -57,9 +58,10 @@ # 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) + # 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 = "topnews_#{Rails.env}" + # config.active_job.queue_name_prefix = "topnews_production" + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. @@ -70,15 +72,15 @@ # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify + # 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') + # 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) diff --git a/config/environments/test.rb b/config/environments/test.rb index 8e5cbde5..35264b4f 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,32 +1,41 @@ +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. - # 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! - config.cache_classes = true + # Turn false under Spring and add config.action_view.cache_template_loading = true. + config.cache_classes = false - # Do not eager load code on boot. This avoids loading your whole application - # just for the purpose of running a single test. If you are using a tool that - # preloads Rails for running tests, you may have to set it to true. - config.eager_load = false + # 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.seconds.to_i}" + "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. @@ -37,6 +46,15 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr - # Raises error for missing translations - # config.action_view.raise_on_missing_translations = true + # 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/initializers/assets.rb b/config/initializers/assets.rb index 4b828e80..2eeef966 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -1,12 +1,10 @@ # 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' +Rails.application.config.assets.version = "1.0" # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path -# Add Yarn node_modules folder to the asset load path. -Rails.application.config.assets.paths << Rails.root.join('node_modules') # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..54f47cf1 --- /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/devise.rb b/config/initializers/devise.rb index 962d4a7c..802fee7f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,5 +1,11 @@ # frozen_string_literal: true +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. Devise.setup do |config| @@ -8,7 +14,11 @@ # confirmation, reset password and unlock tokens in the database. # Devise will use the `secret_key_base` as its `secret_key` # by default. You can change it below and use your own secret key. - # config.secret_key = '3d8fdc791713e92d72d135adbaa58657b3661394b046a293632d59b4cf4ba76fc5369d8f67202bce93a62fc0aacf24878bcd5f7879d859b588919783efcf7eb3' + # config.secret_key = 'e5de90b151ec63a2758118088ba6b4a5b09e5413407f24f58807c2a9e3cc3943a6b9bf083f24001d9fa63ce0705fbc07674c3706025dcf5e790894338e3fa3ef' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, @@ -64,7 +74,10 @@ # Tell if authentication through HTTP Auth is enabled. False by default. # It can be set to an array that will enable http authentication only for the # given strategies, for example, `config.http_authenticatable = [:database]` will - # enable it only for database authentication. The supported strategies are: + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: # :database = Support basic authentication with authentication key + password # config.http_authenticatable = false @@ -99,18 +112,21 @@ # config.reload_routes = true # ==> Configuration for :database_authenticatable - # For bcrypt, this is the cost for hashing the password and defaults to 11. If + # For bcrypt, this is the cost for hashing the password and defaults to 12. If # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. # # Limiting the stretches to just one in testing will increase the performance of # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use # a value less than 10 in other environments. Note that, for bcrypt (the default # algorithm), the cost increases exponentially with the number of stretches (e.g. # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). - config.stretches = Rails.env.test? ? 1 : 11 + config.stretches = Rails.env.test? ? 1 : 12 # Set up a pepper to generate the hashed password. - # config.pepper = '98f368775fc8e418915aa4d2185d5659bda9b2a2aae35a6bb5c2863536c1c7e1ff8884578d43324639ae4d9bc4c851a7c71739103378841fd7f8f9e0db32daea' + # config.pepper = 'fb587ae9f8c325472fbb6ac70668e2efa5383c0df1552f1fa54269c2c391ace5c98a071c3358b0ba08b04a4b8b55da497721c38c8771beddbdc2e9224bda6049' # Send a notification to the original email when the user's email is changed. # config.send_email_changed_notification = false @@ -122,8 +138,11 @@ # A period that the user is allowed to access the website even without # confirming their account. For instance, if set to 2.days, the user will be # able to access the website for two days without confirming their account, - # access will be blocked just in the third day. Default is 0.days, meaning - # the user cannot access the website without confirming their account. + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. # config.allow_unconfirmed_access_for = 2.days # A period that the user is allowed to confirm their account before their @@ -276,4 +295,17 @@ # When using OmniAuth, Devise cannot automatically set OmniAuth path, # so you need to do it manually. For the users scope, it would be: # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Turbolinks configuration + # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: + # + # ActiveSupport.on_load(:devise_failure_app) do + # include Turbolinks::Controller + # end + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index 4a994e1e..adc6568c 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,8 @@ # Be sure to restart your server when you modify this file. -# Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password] +# 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 index ac033bf9..3860f659 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -4,13 +4,13 @@ # 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.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' +# inflect.acronym "RESTful" # end diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb new file mode 100644 index 00000000..4d580245 --- /dev/null +++ b/config/initializers/new_framework_defaults_7_0.rb @@ -0,0 +1,135 @@ +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 7.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `7.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +# `button_to` view helper will render `