From 2d0e1f63f4662116abd90152883818ce3e7fd875 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 27 Oct 2024 14:50:41 +0900 Subject: [PATCH 01/63] =?UTF-8?q?rubocop=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rubocop-rails-omakaseを利用 --- serverside_challenge_2/challenge/.rubocop.yml | 5 ++ serverside_challenge_2/challenge/Gemfile | 3 +- serverside_challenge_2/challenge/Gemfile.lock | 46 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 serverside_challenge_2/challenge/.rubocop.yml diff --git a/serverside_challenge_2/challenge/.rubocop.yml b/serverside_challenge_2/challenge/.rubocop.yml new file mode 100644 index 000000000..7f61e97c8 --- /dev/null +++ b/serverside_challenge_2/challenge/.rubocop.yml @@ -0,0 +1,5 @@ +# Omakase Ruby styling for Rails +inherit_gem: + rubocop-rails-omakase: rubocop.yml + +# Your own specialized rules go here \ No newline at end of file diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 43bf67fe3..9b1a9fc21 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -44,5 +44,6 @@ end group :development do # Speed up commands on slow machines / big apps [https://github.com/rails/spring] # gem "spring" + gem "rubocop-rails-omakase", require: false + gem "rubocop-rspec" end - diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index a47fb85f5..b2eeac2d5 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -66,6 +66,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + ast (2.4.2) bootsnap (1.18.3) msgpack (~> 1.2) builder (3.2.4) @@ -84,6 +85,8 @@ GEM irb (1.11.2) rdoc reline (>= 0.4.2) + json (2.7.4) + language_server-protocol (3.17.0.3) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -109,8 +112,14 @@ GEM nio4r (2.7.0) nokogiri (1.16.2-aarch64-linux) racc (~> 1.4) + nokogiri (1.16.2-arm64-darwin) + racc (~> 1.4) nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) + parallel (1.26.3) + parser (3.3.5.0) + ast (~> 2.4.1) + racc pg (1.5.4) psych (5.1.2) stringio @@ -148,16 +157,50 @@ GEM rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.1.0) rdoc (6.6.2) psych (>= 4.0.0) + regexp_parser (2.9.2) reline (0.4.2) io-console (~> 0.5) + rubocop (1.67.0) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.4, < 3.0) + rubocop-ast (>= 1.32.2, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.32.3) + parser (>= 3.3.1.0) + rubocop-minitest (0.36.0) + rubocop (>= 1.61, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-performance (1.22.1) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.27.0) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.52.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails-omakase (1.0.0) + rubocop + rubocop-minitest + rubocop-performance + rubocop-rails + rubocop-rspec (3.2.0) + rubocop (~> 1.61) + ruby-progressbar (1.13.0) stringio (3.1.0) thor (1.3.0) timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (2.6.0) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -165,6 +208,7 @@ GEM PLATFORMS aarch64-linux + arm64-darwin-24 x86_64-linux DEPENDENCIES @@ -173,6 +217,8 @@ DEPENDENCIES pg (~> 1.1) puma (~> 5.0) rails (~> 7.0.8) + rubocop-rails-omakase + rubocop-rspec tzinfo-data RUBY VERSION From fbf8c577d068bd6b04066b77c8f3417fa1755ad7 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 27 Oct 2024 15:14:24 +0900 Subject: [PATCH 02/63] =?UTF-8?q?rspec=E3=81=AE=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/.rspec | 1 + serverside_challenge_2/challenge/Gemfile | 3 + serverside_challenge_2/challenge/Gemfile.lock | 30 +++++ .../challenge/spec/rails_helper.rb | 66 +++++++++++ .../challenge/spec/spec_helper.rb | 109 ++++++++++++++++++ 5 files changed, 209 insertions(+) create mode 100644 serverside_challenge_2/challenge/.rspec create mode 100644 serverside_challenge_2/challenge/spec/rails_helper.rb create mode 100644 serverside_challenge_2/challenge/spec/spec_helper.rb diff --git a/serverside_challenge_2/challenge/.rspec b/serverside_challenge_2/challenge/.rspec new file mode 100644 index 000000000..c99d2e739 --- /dev/null +++ b/serverside_challenge_2/challenge/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 9b1a9fc21..0b3f29d59 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -39,6 +39,9 @@ gem "bootsnap", require: false group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri mingw x64_mingw ] + gem "rspec-rails" + gem "factory_bot_rails" + gem "database_cleaner-active_record" end group :development do diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index b2eeac2d5..ed43f0ae0 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -72,11 +72,21 @@ GEM builder (3.2.4) concurrent-ruby (1.2.3) 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) date (3.3.4) debug (1.9.1) irb (~> 1.10) reline (>= 0.3.8) + diff-lcs (1.5.1) erubi (1.12.0) + factory_bot (6.5.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) + railties (>= 5.0.0) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.1) @@ -164,6 +174,23 @@ GEM regexp_parser (2.9.2) reline (0.4.2) io-console (~> 0.5) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-rails (7.0.1) + actionpack (>= 7.0) + activesupport (>= 7.0) + railties (>= 7.0) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.1) rubocop (1.67.0) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -213,10 +240,13 @@ PLATFORMS DEPENDENCIES bootsnap + database_cleaner-active_record debug + factory_bot_rails pg (~> 1.1) puma (~> 5.0) rails (~> 7.0.8) + rspec-rails rubocop-rails-omakase rubocop-rspec tzinfo-data diff --git a/serverside_challenge_2/challenge/spec/rails_helper.rb b/serverside_challenge_2/challenge/spec/rails_helper.rb new file mode 100644 index 000000000..5451ad8a9 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/rails_helper.rb @@ -0,0 +1,66 @@ +# 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? +# Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file +# that will avoid rails generators crashing because migrations haven't been run yet +# return unless Rails.env.test? +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. +# +# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).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.join('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://rspec.info/features/7-0/rspec-rails + 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") +end diff --git a/serverside_challenge_2/challenge/spec/spec_helper.rb b/serverside_challenge_2/challenge/spec/spec_helper.rb new file mode 100644 index 000000000..899a1b9be --- /dev/null +++ b/serverside_challenge_2/challenge/spec/spec_helper.rb @@ -0,0 +1,109 @@ +require 'database_cleaner/active_record' + +# 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://rspec.info/features/3-12/rspec-core/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 + + config.before(:suite) do + DatabaseCleaner.strategy = :transaction + DatabaseCleaner.clean_with :truncation + end + + config.before do + DatabaseCleaner.start + end + + config.after do + DatabaseCleaner.clean + end +end From ce209c89b1055670a0d2188776a3844f12cbe90d Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 27 Oct 2024 15:18:04 +0900 Subject: [PATCH 03/63] =?UTF-8?q?BreakMan=E3=81=AE=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Gemfile | 1 + serverside_challenge_2/challenge/Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 0b3f29d59..99ecdeb4e 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -49,4 +49,5 @@ group :development do # gem "spring" gem "rubocop-rails-omakase", require: false gem "rubocop-rspec" + gem 'brakeman', require: false end diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index ed43f0ae0..a586efcb0 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -69,6 +69,8 @@ GEM ast (2.4.2) bootsnap (1.18.3) msgpack (~> 1.2) + brakeman (6.2.2) + racc builder (3.2.4) concurrent-ruby (1.2.3) crass (1.0.6) @@ -240,6 +242,7 @@ PLATFORMS DEPENDENCIES bootsnap + brakeman database_cleaner-active_record debug factory_bot_rails From 0da6997e73cc344b4609fba5223c6e64ab377eb4 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 27 Oct 2024 15:27:14 +0900 Subject: [PATCH 04/63] =?UTF-8?q?ERD=E5=87=BA=E5=8A=9B=E3=81=AE=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Gemfile | 1 + serverside_challenge_2/challenge/Gemfile.lock | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 99ecdeb4e..0d8b507d7 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -50,4 +50,5 @@ group :development do gem "rubocop-rails-omakase", require: false gem "rubocop-rspec" gem 'brakeman', require: false + gem 'rails-erd', '~> 1.7', '>= 1.7.2' end diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index a586efcb0..10214306d 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -72,6 +72,7 @@ GEM brakeman (6.2.2) racc builder (3.2.4) + choice (0.2.0) concurrent-ruby (1.2.3) crass (1.0.6) database_cleaner-active_record (2.2.0) @@ -159,6 +160,11 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) + rails-erd (1.7.2) + activerecord (>= 4.2) + activesupport (>= 4.2) + choice (~> 0.2.0) + ruby-graphviz (~> 1.2) rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) @@ -176,6 +182,7 @@ GEM regexp_parser (2.9.2) reline (0.4.2) io-console (~> 0.5) + rexml (3.3.9) rspec-core (3.13.2) rspec-support (~> 3.13.0) rspec-expectations (3.13.3) @@ -223,6 +230,8 @@ GEM rubocop-rails rubocop-rspec (3.2.0) rubocop (~> 1.61) + ruby-graphviz (1.2.5) + rexml ruby-progressbar (1.13.0) stringio (3.1.0) thor (1.3.0) @@ -249,6 +258,7 @@ DEPENDENCIES pg (~> 1.1) puma (~> 5.0) rails (~> 7.0.8) + rails-erd (~> 1.7, >= 1.7.2) rspec-rails rubocop-rails-omakase rubocop-rspec From 71d3b24779929adf18a9e62ddd86cdac1890b597 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 27 Oct 2024 15:28:19 +0900 Subject: [PATCH 05/63] =?UTF-8?q?=E9=96=8B=E7=99=BA=E7=92=B0=E5=A2=83?= =?UTF-8?q?=E7=94=A8=E3=81=AEdotenv=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Gemfile | 5 +++-- serverside_challenge_2/challenge/Gemfile.lock | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 0d8b507d7..705bcc661 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -42,6 +42,7 @@ group :development, :test do gem "rspec-rails" gem "factory_bot_rails" gem "database_cleaner-active_record" + gem "dotenv-rails" end group :development do @@ -49,6 +50,6 @@ group :development do # gem "spring" gem "rubocop-rails-omakase", require: false gem "rubocop-rspec" - gem 'brakeman', require: false - gem 'rails-erd', '~> 1.7', '>= 1.7.2' + gem "brakeman", require: false + gem "rails-erd", "~> 1.7", ">= 1.7.2" end diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index 10214306d..c8720b8f7 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -84,6 +84,10 @@ GEM irb (~> 1.10) reline (>= 0.3.8) diff-lcs (1.5.1) + dotenv (3.1.4) + dotenv-rails (3.1.4) + dotenv (= 3.1.4) + railties (>= 6.1) erubi (1.12.0) factory_bot (6.5.0) activesupport (>= 5.0.0) @@ -254,6 +258,7 @@ DEPENDENCIES brakeman database_cleaner-active_record debug + dotenv-rails factory_bot_rails pg (~> 1.1) puma (~> 5.0) From f6c033e2ff130488cd11af5f59e7733a9687d189 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 27 Oct 2024 15:51:40 +0900 Subject: [PATCH 06/63] =?UTF-8?q?DB=E6=8E=A5=E7=B6=9A=E6=83=85=E5=A0=B1?= =?UTF-8?q?=E3=82=92=E7=92=B0=E5=A2=83=E5=A4=89=E6=95=B0=E3=81=A7=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E5=8F=AF=E8=83=BD=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/.env.sample | 4 ++++ serverside_challenge_2/challenge/.env.test | 4 ++++ serverside_challenge_2/challenge/.gitignore | 3 +++ serverside_challenge_2/challenge/config/database.yml | 9 +++++---- serverside_challenge_2/challenge/docker-compose.yml | 2 ++ 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 serverside_challenge_2/challenge/.env.sample create mode 100644 serverside_challenge_2/challenge/.env.test diff --git a/serverside_challenge_2/challenge/.env.sample b/serverside_challenge_2/challenge/.env.sample new file mode 100644 index 000000000..41a42205a --- /dev/null +++ b/serverside_challenge_2/challenge/.env.sample @@ -0,0 +1,4 @@ +DATABASE_HOST=db +DATABASE_PORT=5432 +DATABASE_USER=postgres +DATABASE_PASSWORD=password \ No newline at end of file diff --git a/serverside_challenge_2/challenge/.env.test b/serverside_challenge_2/challenge/.env.test new file mode 100644 index 000000000..41a42205a --- /dev/null +++ b/serverside_challenge_2/challenge/.env.test @@ -0,0 +1,4 @@ +DATABASE_HOST=db +DATABASE_PORT=5432 +DATABASE_USER=postgres +DATABASE_PASSWORD=password \ No newline at end of file diff --git a/serverside_challenge_2/challenge/.gitignore b/serverside_challenge_2/challenge/.gitignore index 88381219c..55254ed9b 100644 --- a/serverside_challenge_2/challenge/.gitignore +++ b/serverside_challenge_2/challenge/.gitignore @@ -27,3 +27,6 @@ # Ignore master key for decrypting credentials and more. /config/master.key + +# Application +.env \ No newline at end of file diff --git a/serverside_challenge_2/challenge/config/database.yml b/serverside_challenge_2/challenge/config/database.yml index 9c66b2942..2efa8d325 100644 --- a/serverside_challenge_2/challenge/config/database.yml +++ b/serverside_challenge_2/challenge/config/database.yml @@ -20,9 +20,10 @@ default: &default # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - host: db - username: postgres - password: password + host: <%= ENV.fetch("DATABASE_HOST") %> + username: <%= ENV.fetch("DATABASE_USER") %> + password: <%= ENV.fetch("DATABASE_PASSWORD") %> + port: <%= ENV.fetch("DATABASE_PORT", '5432') %> development: <<: *default @@ -84,4 +85,4 @@ test: # production: <<: *default - database: app_production + database: app diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 723d14368..9f65b0413 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -1,6 +1,7 @@ version: '3' services: db: + container_name: challenge-db image: postgres environment: POSTGRES_USER: postgres @@ -9,6 +10,7 @@ services: - postgres_volume:/var/lib/postgresql/data restart: always web: + container_name: challenge-api build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" environment: From 675c97e3733eb9ba6df9a23dfba2ea20dd4e2a41 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 09:37:59 +0900 Subject: [PATCH 07/63] =?UTF-8?q?=E9=9B=BB=E5=8A=9B=E6=96=99=E9=87=91?= =?UTF-8?q?=E8=A1=A8CSV=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/db/seeds/basic_fee.csv | 26 +++++++++++++++++++ .../challenge/db/seeds/measured_rates.csv | 11 ++++++++ 2 files changed, 37 insertions(+) create mode 100644 serverside_challenge_2/challenge/db/seeds/basic_fee.csv create mode 100644 serverside_challenge_2/challenge/db/seeds/measured_rates.csv diff --git a/serverside_challenge_2/challenge/db/seeds/basic_fee.csv b/serverside_challenge_2/challenge/db/seeds/basic_fee.csv new file mode 100644 index 000000000..3edbd1eca --- /dev/null +++ b/serverside_challenge_2/challenge/db/seeds/basic_fee.csv @@ -0,0 +1,26 @@ +電力会社,プラン,契約アンペア数(A),基本料金(円) +東京電力エナジーパートナー,従量電灯B,10,286.00 +東京電力エナジーパートナー,従量電灯B,15,429.00 +東京電力エナジーパートナー,従量電灯B,20,572.00 +東京電力エナジーパートナー,従量電灯B,30,858.00 +東京電力エナジーパートナー,従量電灯B,40,1144.00 +東京電力エナジーパートナー,従量電灯B,50,1430.00 +東京電力エナジーパートナー,従量電灯B,60,1716.00 +東京電力エナジーパートナー,スタンダードS,10,311.75 +東京電力エナジーパートナー,スタンダードS,15,467.63 +東京電力エナジーパートナー,スタンダードS,20,623.50 +東京電力エナジーパートナー,スタンダードS,30,935.25 +東京電力エナジーパートナー,スタンダードS,40,1247.00 +東京電力エナジーパートナー,スタンダードS,50,1558.75 +東京電力エナジーパートナー,スタンダードS,60,1870.50 +東京ガス,ずっとも電気1,30,858.00 +東京ガス,ずっとも電気1,40,1144.00 +東京ガス,ずっとも電気1,50,1430.00 +東京ガス,ずっとも電気1,60,1716.00 +Looopでんき,おうちプラン,10,0.00 +Looopでんき,おうちプラン,15,0.00 +Looopでんき,おうちプラン,20,0.00 +Looopでんき,おうちプラン,30,0.00 +Looopでんき,おうちプラン,40,0.00 +Looopでんき,おうちプラン,50,0.00 +Looopでんき,おうちプラン,60,0.00 diff --git a/serverside_challenge_2/challenge/db/seeds/measured_rates.csv b/serverside_challenge_2/challenge/db/seeds/measured_rates.csv new file mode 100644 index 000000000..f41e35b4c --- /dev/null +++ b/serverside_challenge_2/challenge/db/seeds/measured_rates.csv @@ -0,0 +1,11 @@ +電力会社,プラン,電気使用量(kWh),従量料金単価(円/kWh) +東京電力エナジーパートナー,従量電灯B,0-120,19.88 +東京電力エナジーパートナー,従量電灯B,121-300,26.48 +東京電力エナジーパートナー,従量電灯B,301,30.57 +東京電力エナジーパートナー,スタンダードS,0-120,29.80 +東京電力エナジーパートナー,スタンダードS,121-300,36.40 +東京電力エナジーパートナー,スタンダードS,301,40.49 +東京ガス,ずっとも電気1,0-140,23.67 +東京ガス,ずっとも電気1,141-350,23.88 +東京ガス,ずっとも電気1,351,26.41 +Looopでんき,おうちプラン,0,28.8 \ No newline at end of file From f8786312a1db62eee2ca1169379b3897a0ce80b2 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 09:46:38 +0900 Subject: [PATCH 08/63] =?UTF-8?q?fix:=20=E3=83=AD=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=AB=E5=AE=9F=E8=A1=8C=E7=92=B0=E5=A2=83=E3=81=AEdb?= =?UTF-8?q?=E3=81=AEport=E3=82=92=E6=8C=87=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 9f65b0413..8ef85e300 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -6,6 +6,8 @@ services: environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password + ports: + - "55432:5432" volumes: - postgres_volume:/var/lib/postgresql/data restart: always From bb28fbe6d049caa7a416da3fd99664bc94ff28f8 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 10:40:04 +0900 Subject: [PATCH 09/63] =?UTF-8?q?Rails=E3=81=AEtimezone=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/config/application.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/serverside_challenge_2/challenge/config/application.rb b/serverside_challenge_2/challenge/config/application.rb index b39913bf7..9595bffee 100644 --- a/serverside_challenge_2/challenge/config/application.rb +++ b/serverside_challenge_2/challenge/config/application.rb @@ -11,6 +11,10 @@ class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 + # Timezoneの設定 + config.time_zone = 'Tokyo' + config.active_record.default_timezone = :local + # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files From b587b0d57f76829b2aa3d8db4edcfb417c49222d Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 10:40:29 +0900 Subject: [PATCH 10/63] =?UTF-8?q?Factorybot=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/spec/rails_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/serverside_challenge_2/challenge/spec/rails_helper.rb b/serverside_challenge_2/challenge/spec/rails_helper.rb index 5451ad8a9..557737b4b 100644 --- a/serverside_challenge_2/challenge/spec/rails_helper.rb +++ b/serverside_challenge_2/challenge/spec/rails_helper.rb @@ -33,6 +33,8 @@ abort e.to_s.strip end RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = Rails.root.join('spec/fixtures') From 7897eb572fd9810994eb1270b74faaaf58763eec Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 10:42:56 +0900 Subject: [PATCH 11/63] =?UTF-8?q?ElectricPowerCompany(=E9=9B=BB=E5=8A=9B?= =?UTF-8?q?=E4=BC=9A=E7=A4=BE)=20model=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/models/electric_power_company.rb | 5 ++ ...7065846_create_electric_power_companies.rb | 10 ++++ .../spec/factories/electric_power_company.rb | 5 ++ .../models/electric_power_company_spec.rb | 49 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 serverside_challenge_2/challenge/app/models/electric_power_company.rb create mode 100644 serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb create mode 100644 serverside_challenge_2/challenge/spec/factories/electric_power_company.rb create mode 100644 serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb diff --git a/serverside_challenge_2/challenge/app/models/electric_power_company.rb b/serverside_challenge_2/challenge/app/models/electric_power_company.rb new file mode 100644 index 000000000..3e5cd67fc --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/electric_power_company.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ElectricPowerCompany < ApplicationRecord + validates :name, presence: true, length: { minimum: 1, maximum: 255 }, uniqueness: true +end diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb b/serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb new file mode 100644 index 000000000..29fec097e --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb @@ -0,0 +1,10 @@ +class CreateElectricPowerCompanies < ActiveRecord::Migration[7.0] + def change + create_table :electric_power_companies do |t| + t.string :name, null: false + + t.timestamps + end + add_index :electric_power_companies, [ :name ], unique: true + end +end diff --git a/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb b/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb new file mode 100644 index 000000000..198f58586 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :electric_power_company do + name { '電力会社' } + end +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb b/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb new file mode 100644 index 000000000..fd69e9157 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe ElectricPowerCompany, type: :model do + describe 'FactoryBot' do + it '有効なファクトリを持つこと' do + expect(build(:electric_power_company)).to be_valid + end + end + + describe 'Validation' do + context 'name' do + it '1文字の場合有効であること' do + instance = build(:electric_power_company, name: 'a') + expect(instance).to be_valid + end + + it '255文字の場合有効であること' do + instance = build(:electric_power_company, name: 'a' * 255) + expect(instance).to be_valid + end + + it 'nilの場合無効であること' do + instance = build(:electric_power_company, name: nil) + expect(instance).to be_invalid + instance.errors[:name].include?("can't be blank") + end + + it '空文字の場合無効であること' do + instance = build(:electric_power_company, name: '') + expect(instance).to be_invalid + instance.errors[:name].include?("can't be blank") + end + + it '256文字の場合無効であること' do + instance = build(:electric_power_company, name: 'a' * 256) + expect(instance).to be_invalid + instance.errors[:name].include?("is too long (maximum is 255 characters)") + end + + it '重複した場合無効であること' do + create(:electric_power_company, name: '電力会社') + + instance = build(:electric_power_company, name: '電力会社') + expect(instance).to be_invalid + instance.errors[:name].include?("has already been taken") + end + end + end +end \ No newline at end of file From 4ae8dcfe586b4ef29925e2be3fcf9c84bde90fbe Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 11:17:15 +0900 Subject: [PATCH 12/63] =?UTF-8?q?Plan=20model=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/models/electric_power_company.rb | 2 + .../challenge/app/models/plan.rb | 7 ++ .../db/migrate/20241027065931_create_plans.rb | 11 +++ .../spec/factories/electric_power_company.rb | 2 +- .../challenge/spec/factories/plan.rb | 5 ++ .../models/electric_power_company_spec.rb | 29 ++++++- .../challenge/spec/models/plan_spec.rb | 76 +++++++++++++++++++ 7 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 serverside_challenge_2/challenge/app/models/plan.rb create mode 100644 serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb create mode 100644 serverside_challenge_2/challenge/spec/factories/plan.rb create mode 100644 serverside_challenge_2/challenge/spec/models/plan_spec.rb diff --git a/serverside_challenge_2/challenge/app/models/electric_power_company.rb b/serverside_challenge_2/challenge/app/models/electric_power_company.rb index 3e5cd67fc..5832c5d54 100644 --- a/serverside_challenge_2/challenge/app/models/electric_power_company.rb +++ b/serverside_challenge_2/challenge/app/models/electric_power_company.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true class ElectricPowerCompany < ApplicationRecord + has_many :plans, dependent: :destroy + validates :name, presence: true, length: { minimum: 1, maximum: 255 }, uniqueness: true end diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb new file mode 100644 index 000000000..29634221d --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Plan < ApplicationRecord + belongs_to :electric_power_company + + validates :name, presence: true, length: { minimum: 1, maximum: 255 }, uniqueness: { scope: :electric_power_company_id } +end diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb b/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb new file mode 100644 index 000000000..b98584d8e --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb @@ -0,0 +1,11 @@ +class CreatePlans < ActiveRecord::Migration[7.0] + def change + create_table :plans do |t| + t.string :name, null: false + t.references :electric_power_company, null: false, type: :integer, foreign_key: { on_delete: :cascade } + + t.timestamps + end + add_index :plans, [ :electric_power_company_id, :name ], unique: true + end +end diff --git a/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb b/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb index 198f58586..049d21661 100644 --- a/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb +++ b/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb @@ -2,4 +2,4 @@ factory :electric_power_company do name { '電力会社' } end -end \ No newline at end of file +end diff --git a/serverside_challenge_2/challenge/spec/factories/plan.rb b/serverside_challenge_2/challenge/spec/factories/plan.rb new file mode 100644 index 000000000..8cc308e83 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/factories/plan.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :plan do + name { 'プラン' } + end +end diff --git a/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb b/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb index fd69e9157..160ff5bcb 100644 --- a/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb @@ -37,7 +37,7 @@ instance.errors[:name].include?("is too long (maximum is 255 characters)") end - it '重複した場合無効であること' do + it '重複する場合無効であること' do create(:electric_power_company, name: '電力会社') instance = build(:electric_power_company, name: '電力会社') @@ -46,4 +46,29 @@ end end end -end \ No newline at end of file + + describe 'associations' do + context 'plans' do + it '関連の設定が正しいか' do + association = described_class.reflect_on_association(:plans) + expect(association.macro).to eq :has_many + expect(association.options[:dependent]).to eq :destroy + end + + it '関連が参照できること' do + electric_power_company = create(:electric_power_company) + plan = create(:plan, electric_power_company: electric_power_company) + + expect(electric_power_company.plans).to include(plan) + end + + it '削除された場合、紐づくプランが削除されること' do + electric_power_company = create(:electric_power_company) + plan_id = create(:plan, electric_power_company: electric_power_company).id + + expect { electric_power_company.destroy }.to change { Plan.count }.by(-1) + expect(Plan.exists?(plan_id)).to be_falsey + end + end + end +end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb new file mode 100644 index 000000000..e7347249d --- /dev/null +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -0,0 +1,76 @@ +require "rails_helper" + +RSpec.describe Plan, type: :model do + let(:electric_power_company) { create(:electric_power_company) } + + describe 'FactoryBot' do + it '有効なファクトリを持つこと' do + expect(build(:plan, electric_power_company: electric_power_company)).to be_valid + end + end + + describe 'Validation' do + context 'name' do + it '1文字の場合有効であること' do + instance = build(:plan, electric_power_company: electric_power_company, name: 'a') + expect(instance).to be_valid + end + + it '255文字の場合有効であること' do + instance = build(:plan, electric_power_company: electric_power_company, name: 'a' * 255) + expect(instance).to be_valid + end + + it 'nilの場合無効であること' do + instance = build(:plan, electric_power_company: electric_power_company, name: nil) + expect(instance).to be_invalid + instance.errors[:name].include?("can't be blank") + end + + it '空文字の場合無効であること' do + instance = build(:plan, electric_power_company: electric_power_company, name: '') + expect(instance).to be_invalid + instance.errors[:name].include?("can't be blank") + end + + it '256文字の場合無効であること' do + instance = build(:plan, electric_power_company: electric_power_company, name: 'a' * 256) + expect(instance).to be_invalid + instance.errors[:name].include?("is too long (maximum is 255 characters)") + end + + context 'uniqueness' do + it 'electric_power_companyが重複しない場合有効であること' do + create(:plan, electric_power_company: electric_power_company, name: 'プラン') + company2 = create(:electric_power_company, name: '電力会社2') + + instance = build(:plan, electric_power_company: company2, name: 'プラン') + expect(instance).to be_valid + end + + it '重複する場合無効であること' do + create(:plan, electric_power_company: electric_power_company, name: 'プラン') + + instance = build(:plan, electric_power_company: electric_power_company, name: 'プラン') + expect(instance).to be_invalid + instance.errors[:name].include?("has already been taken") + end + end + end + end + + describe 'associations' do + context ':electric_power_company' do + it '関連の設定が正しいか' do + association = described_class.reflect_on_association(:electric_power_company) + expect(association.macro).to eq :belongs_to + expect(association.options[:dependent]).to be_nil + end + + it '関連が参照できること' do + plan = create(:plan, electric_power_company: electric_power_company) + expect(plan.electric_power_company).to eq electric_power_company + end + end + end +end From 2f38a6f06999d8caf8d063479dc71d0b60f969f6 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 11:19:56 +0900 Subject: [PATCH 13/63] =?UTF-8?q?Rubocop=E3=81=AB=E3=82=88=E3=82=8B?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/config/application.rb b/serverside_challenge_2/challenge/config/application.rb index 9595bffee..ccfa69e6d 100644 --- a/serverside_challenge_2/challenge/config/application.rb +++ b/serverside_challenge_2/challenge/config/application.rb @@ -12,7 +12,7 @@ class Application < Rails::Application config.load_defaults 7.0 # Timezoneの設定 - config.time_zone = 'Tokyo' + config.time_zone = "Tokyo" config.active_record.default_timezone = :local # Configuration for the application, engines, and railties goes here. From 361e9ed24d0b8494c0db3ed78e64c144d9bc8d21 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 11:24:40 +0900 Subject: [PATCH 14/63] =?UTF-8?q?Plan=20model=E3=81=AB=E5=AF=BE=E3=81=99?= =?UTF-8?q?=E3=82=8Belectric=5Fpower=5Fcompany=E3=81=AEvalidation=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/app/models/plan.rb | 3 ++- serverside_challenge_2/challenge/spec/models/plan_spec.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 29634221d..1155b436c 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -3,5 +3,6 @@ class Plan < ApplicationRecord belongs_to :electric_power_company - validates :name, presence: true, length: { minimum: 1, maximum: 255 }, uniqueness: { scope: :electric_power_company_id } + validates :name, presence: true, length: { minimum: 1, maximum: 255 } + validates :electric_power_company, presence: true, uniqueness: { scope: :name } end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index e7347249d..61bca91b8 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -57,6 +57,14 @@ end end end + + context ':electric_power_company' do + it 'nilの場合無効であること' do + instance = build(:plan, electric_power_company: nil) + expect(instance).to be_invalid + instance.errors[:electric_power_company].include?("must exist") + end + end end describe 'associations' do From 4433905cdbfb4c326842f744d43e13d529dd004d Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 11:56:26 +0900 Subject: [PATCH 15/63] =?UTF-8?q?BasicPrice=20model=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/basic_price.rb | 11 ++ .../challenge/app/models/plan.rb | 1 + .../20241027072120_create_basic_prices.rb | 12 ++ .../challenge/spec/factories/basic_price.rb | 6 + .../challenge/spec/models/basic_price_spec.rb | 133 ++++++++++++++++++ .../challenge/spec/models/plan_spec.rb | 16 ++- 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 serverside_challenge_2/challenge/app/models/basic_price.rb create mode 100644 serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb create mode 100644 serverside_challenge_2/challenge/spec/factories/basic_price.rb create mode 100644 serverside_challenge_2/challenge/spec/models/basic_price_spec.rb diff --git a/serverside_challenge_2/challenge/app/models/basic_price.rb b/serverside_challenge_2/challenge/app/models/basic_price.rb new file mode 100644 index 000000000..f6018cbc2 --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/basic_price.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class BasicPrice < ApplicationRecord + belongs_to :plan + + AMPERAGE_LIST = [ 10, 15, 20, 30, 40, 50, 60 ].freeze + + validates :amperage, presence: true, inclusion: { in: AMPERAGE_LIST } + validates :price, numericality: { only_numeric: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 99999.99 } + validates :plan, presence: true, uniqueness: { scope: :amperage } +end diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 1155b436c..d69149fb0 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -2,6 +2,7 @@ class Plan < ApplicationRecord belongs_to :electric_power_company + has_many :basic_prices, dependent: :destroy validates :name, presence: true, length: { minimum: 1, maximum: 255 } validates :electric_power_company, presence: true, uniqueness: { scope: :name } diff --git a/serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb b/serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb new file mode 100644 index 000000000..9bbf63585 --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb @@ -0,0 +1,12 @@ +class CreateBasicPrices < ActiveRecord::Migration[7.0] + def change + create_table :basic_prices do |t| + t.integer :amperage, limit: 2, null: false + t.decimal :price, precision: 7, scale: 2, null: false + t.references :plan, null: false, type: :integer, foreign_key: { on_delete: :cascade } + + t.timestamps + end + add_index :basic_prices, [ :plan_id, :amperage ], unique: true + end +end diff --git a/serverside_challenge_2/challenge/spec/factories/basic_price.rb b/serverside_challenge_2/challenge/spec/factories/basic_price.rb new file mode 100644 index 000000000..a374578f6 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/factories/basic_price.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :basic_price do + amperage { BasicPrice::AMPERAGE_LIST[0] } + price { 1.01 } + end +end diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb new file mode 100644 index 000000000..2625df7a5 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -0,0 +1,133 @@ +require "rails_helper" + +RSpec.describe BasicPrice, type: :model do + let(:electric_power_company) { create(:electric_power_company) } + let(:plan) { create(:plan, electric_power_company: electric_power_company) } + + describe 'FactoryBot' do + it '有効なファクトリを持つこと' do + expect(build(:basic_price, plan: plan)).to be_valid + end + end + + describe 'Validation' do + context 'amperage' do + it 'AMPERAGE_LISTに含まれる値の場合有効であること' do + BasicPrice::AMPERAGE_LIST.each do |amperage| + instance = build(:basic_price, plan: plan, amperage: amperage) + expect(instance).to be_valid + end + end + + it 'nilの場合無効であること' do + instance = build(:basic_price, plan: plan, amperage: nil) + expect(instance).to be_invalid + instance.errors[:amperage].include?("can't be blank") + end + + it '空文字の場合無効であること' do + instance = build(:basic_price, plan: plan, amperage: '') + expect(instance).to be_invalid + instance.errors[:amperage].include?("can't be blank") + end + + it '文字列の場合無効であること' do + instance = build(:basic_price, plan: plan, amperage: 'a') + expect(instance).to be_invalid + instance.errors[:amperage].include?("is not a number") + end + + it 'AMPERAGE_LISTに定義されない値の場合無効であること' do + instance = build(:basic_price, plan: plan, amperage: 1) + expect(instance).to be_invalid + instance.errors[:amperage].include?("is not included in the list") + end + + context 'price' do + it 'nilの場合無効であること' do + instance = build(:basic_price, plan: plan, price: nil) + expect(instance).to be_invalid + instance.errors[:price].include?("can't be blank") + end + + it '空文字の場合無効であること' do + instance = build(:basic_price, plan: plan, price: '') + expect(instance).to be_invalid + instance.errors[:price].include?("is not a number") + end + + it '文字列の場合無効であること' do + instance = build(:basic_price, plan: plan, price: 'a') + expect(instance).to be_invalid + instance.errors[:price].include?("is not a number") + end + + it '0の場合有効であること' do + instance = build(:basic_price, plan: plan, price: 0) + expect(instance).to be_valid + end + + it '99999.99の場合有効であること' do + instance = build(:basic_price, plan: plan, price: 99999.99) + expect(instance).to be_valid + end + + it '100000.00の場合無効であること' do + instance = build(:basic_price, plan: plan, price: 100000.00) + expect(instance).to be_invalid + instance.errors[:price].include?("must be less than or equal to 99999.99") + end + end + + context 'uniqueness' do + it '異なるplan_idの場合有効であること' do + create(:basic_price, plan: plan, amperage: 10) + plan2 = create(:plan, name: 'プラン2', electric_power_company: electric_power_company) + + instance = build(:basic_price, plan: plan2, amperage: 10) + expect(instance).to be_valid + end + + it '同じplan_idとamperageの組み合わせの場合無効であること' do + create(:basic_price, plan: plan, amperage: 10) + + instance = build(:basic_price, plan: plan, amperage: 10) + expect(instance).to be_invalid + instance.errors[:plan].include?("has already been taken") + end + end + end + + context 'plan' do + it 'nilの場合無効であること' do + instance = build(:basic_price, plan: nil) + expect(instance).to be_invalid + instance.errors[:plan].include?("must exist") + end + end + end + + describe 'created' do + context 'price' do + it '小数点以下2桁のまで保存される' do + instance = create(:basic_price, plan: plan, price: 1.01) + expect(instance.price).to eq 1.01 + end + end + end + + describe 'associations' do + context 'plan' do + it '関連の設定が正しいか' do + association = described_class.reflect_on_association(:plan) + expect(association.macro).to eq :belongs_to + expect(association.options[:dependent]).to be_nil + end + + it '関連が参照できること' do + basic_price = create(:basic_price, plan: plan) + expect(basic_price.plan).to eq plan + end + end + end +end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index 61bca91b8..7f30ee04a 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -68,7 +68,7 @@ end describe 'associations' do - context ':electric_power_company' do + context 'electric_power_company' do it '関連の設定が正しいか' do association = described_class.reflect_on_association(:electric_power_company) expect(association.macro).to eq :belongs_to @@ -80,5 +80,19 @@ expect(plan.electric_power_company).to eq electric_power_company end end + + context 'basic_prices' do + it '関連の設定が正しいか' do + association = described_class.reflect_on_association(:basic_prices) + expect(association.macro).to eq :has_many + expect(association.options[:dependent]).to eq :destroy + end + + it '関連が参照できること' do + plan = create(:plan, electric_power_company: electric_power_company) + basic_price = create(:basic_price, plan: plan) + expect(plan.basic_prices).to eq [ basic_price ] + end + end end end From 61f72164d78bf39f3294cab99b5db88d294d4f0f Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 15:09:14 +0900 Subject: [PATCH 16/63] =?UTF-8?q?MeasuredRate=20model=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/measured_rate.rb | 55 ++++ .../challenge/app/models/plan.rb | 1 + .../20241027072347_create_measured_rates.rb | 12 + .../challenge/spec/factories/measure_rate.rb | 7 + .../spec/models/measure_rate_spec.rb | 248 ++++++++++++++++++ .../challenge/spec/models/plan_spec.rb | 17 +- 6 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 serverside_challenge_2/challenge/app/models/measured_rate.rb create mode 100644 serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb create mode 100644 serverside_challenge_2/challenge/spec/factories/measure_rate.rb create mode 100644 serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb diff --git a/serverside_challenge_2/challenge/app/models/measured_rate.rb b/serverside_challenge_2/challenge/app/models/measured_rate.rb new file mode 100644 index 000000000..7638c2868 --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/measured_rate.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class MeasuredRate < ApplicationRecord + MAX_SMALL_INT_VALUE = 32767 + + belongs_to :plan + + validates :electricity_usage_min, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: MAX_SMALL_INT_VALUE } + validates :electricity_usage_max, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_SMALL_INT_VALUE } + validates :price, numericality: { only_numeric: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 99999.99 } + validates :plan, presence: true + validate :validate_max_greater_than_min + validate :validate_electricity_usage + + def electricity_usage_max=(value) + super(value.nil? ? MAX_SMALL_INT_VALUE : value) + end + + private + + def validate_max_greater_than_min + return if electricity_usage_max.nil? || electricity_usage_min.nil? + + if electricity_usage_max < electricity_usage_min + errors.add(:electricity_usage_max, "must be greater than or equal to electricity_usage_min") + end + end + + def validate_electricity_usage + return if electricity_usage_max.nil? || electricity_usage_min.nil? + + rates = self.class.where(plan: plan).where.not(id: id) + validate_electricity_usage_min(rates) + validate_electricity_usage_max(rates) + validate_electricity_usage_contain(rates) + end + + def validate_electricity_usage_min(rates) + if rates.find { |rate| rate.electricity_usage_min <= electricity_usage_min && electricity_usage_min <= rate.electricity_usage_max }.present? + errors.add(:electricity_usage_min, "range overlaps with an existing range") + end + end + + def validate_electricity_usage_max(rates) + if rates.find { |rate| rate.electricity_usage_min <= electricity_usage_max && electricity_usage_max <= rate.electricity_usage_max }.present? + errors.add(:electricity_usage_max, "range overlaps with an existing range") + end + end + + def validate_electricity_usage_contain(rates) + if rates.find { |rate| electricity_usage_min <= rate.electricity_usage_min && rate.electricity_usage_max <= electricity_usage_max }.present? + errors.add(:electricity_usage_max, "range overlaps with an existing range") + end + end +end diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index d69149fb0..07606627c 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -3,6 +3,7 @@ class Plan < ApplicationRecord belongs_to :electric_power_company has_many :basic_prices, dependent: :destroy + has_many :measured_rates, dependent: :destroy validates :name, presence: true, length: { minimum: 1, maximum: 255 } validates :electric_power_company, presence: true, uniqueness: { scope: :name } diff --git a/serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb b/serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb new file mode 100644 index 000000000..fa0c275e9 --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb @@ -0,0 +1,12 @@ +class CreateMeasuredRates < ActiveRecord::Migration[7.0] + def change + create_table :measured_rates do |t| + t.integer :electricity_usage_min, limit: 2, null: false + t.integer :electricity_usage_max, limit: 2, null: false + t.decimal :price, precision: 7, scale: 2, null: false + t.references :plan, null: false, type: :integer, foreign_key: { on_delete: :cascade } + + t.timestamps + end + end +end diff --git a/serverside_challenge_2/challenge/spec/factories/measure_rate.rb b/serverside_challenge_2/challenge/spec/factories/measure_rate.rb new file mode 100644 index 000000000..841f36e92 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/factories/measure_rate.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :measured_rate do + electricity_usage_min { 0 } + electricity_usage_max { 100 } + price { 1.01 } + end +end diff --git a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb new file mode 100644 index 000000000..b7b49ba38 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb @@ -0,0 +1,248 @@ +require "rails_helper" + +RSpec.describe MeasuredRate, type: :model do + let(:electric_power_company) { create(:electric_power_company) } + let(:plan) { create(:plan, electric_power_company: electric_power_company) } + + describe 'FactoryBot' do + it '有効なファクトリを持つこと' do + expect(build(:measured_rate, plan: plan)).to be_valid + end + end + + describe "validation" do + context "electricity_usage_min" do + it 'nilの場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_min: nil) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include("is not a number") + end + + it 'マイナス値の場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_min: -1) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include("must be greater than or equal to 0") + end + + it '空文字の場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_min: '') + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include("is not a number") + end + + it '文字列の場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_min: 'a') + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include("is not a number") + end + + it '1以上の場合有効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_min: 1) + expect(instance).to be_valid + end + + it '最大値の場合有効であること' do + instance = build(:measured_rate, plan: plan, + electricity_usage_min: MeasuredRate::MAX_SMALL_INT_VALUE, + electricity_usage_max: MeasuredRate::MAX_SMALL_INT_VALUE) + expect(instance).to be_valid + end + + it '最大値を超える場合無効であること' do + instance = build(:measured_rate, plan: plan, + electricity_usage_min: MeasuredRate::MAX_SMALL_INT_VALUE + 1, + electricity_usage_max: MeasuredRate::MAX_SMALL_INT_VALUE + 1) + expect(instance).not_to be_valid + expect(instance.errors[:electricity_usage_min]).to include("must be less than or equal to 32767") + end + end + + context "electricity_usage_max" do + it 'nilの場合有効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_max: nil) + expect(instance).to be_valid + expect(instance.electricity_usage_max).to eq MeasuredRate::MAX_SMALL_INT_VALUE + end + + it 'マイナス値の場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_max: -1) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_max]).to include("must be greater than or equal to 1") + end + + it '空文字の場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_max: '') + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_max]).to include("is not a number") + end + + it '文字列の場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_max: 'a') + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_max]).to include("is not a number") + end + + it '1以上の場合有効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_max: 1) + expect(instance).to be_valid + end + + it '最大値の場合有効であること' do + instance = build(:measured_rate, plan: plan, + electricity_usage_min: MeasuredRate::MAX_SMALL_INT_VALUE, + electricity_usage_max: MeasuredRate::MAX_SMALL_INT_VALUE) + expect(instance).to be_valid + end + + it '最大値を超える場合無効であること' do + instance = build(:measured_rate, plan: plan, + electricity_usage_min: MeasuredRate::MAX_SMALL_INT_VALUE + 1, + electricity_usage_max: MeasuredRate::MAX_SMALL_INT_VALUE + 1) + expect(instance).not_to be_valid + expect(instance.errors[:electricity_usage_max]).to include("must be less than or equal to 32767") + end + end + + context 'electricity_usage_min, electricity_usage_max' do + it 'electricity_usage_max < electricity_usage_minの場合無効であること' do + instance = build(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 1) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_max]).to include("must be greater than or equal to electricity_usage_min") + end + end + + context 'uniqueness' do + context 'create時' do + context 'electricity_usage_min' do + it '他のrateに重複する場合無効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include("range overlaps with an existing range") + end + end + + context 'electricity_usage_max' do + it '他のrateに重複する場合無効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_max]).to include("range overlaps with an existing range") + end + end + + context 'electricity_usage_min, electricity_usage_maxのoverlap' do + it '他のrateに重複する場合無効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 4) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_max]).to include("range overlaps with an existing range") + end + end + end + + context 'update時の自インスタンスとの重複' do + context 'electricity_usage_min' do + it '重複として判断されないこと' do + instance = create(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) + instance.electricity_usage_min = 2 + instance.electricity_usage_max = 3 + + expect(instance).to be_valid + end + end + + context 'electricity_usage_max' do + it '重複として判断されないこと' do + instance = create(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) + instance.electricity_usage_min = 1 + instance.electricity_usage_max = 2 + + expect(instance).to be_valid + end + end + + context 'electricity_usage_min, electricity_usage_maxのoverlap' do + it '重複として判断されないこと' do + instance = create(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) + instance.electricity_usage_min = 1 + instance.electricity_usage_max = 4 + + expect(instance).to be_valid + end + end + end + end + + context 'price' do + it 'nilの場合無効であること' do + instance = build(:measured_rate, plan: plan, price: nil) + expect(instance).to be_invalid + instance.errors[:price].include?("can't be blank") + end + + it '空文字の場合無効であること' do + instance = build(:measured_rate, plan: plan, price: '') + expect(instance).to be_invalid + instance.errors[:price].include?("is not a number") + end + + it '文字列の場合無効であること' do + instance = build(:measured_rate, plan: plan, price: 'a') + expect(instance).to be_invalid + instance.errors[:price].include?("is not a number") + end + + it '0の場合有効であること' do + instance = build(:measured_rate, plan: plan, price: 0) + expect(instance).to be_valid + end + + it '99999.99の場合有効であること' do + instance = build(:measured_rate, plan: plan, price: 99999.99) + expect(instance).to be_valid + end + + it '100000.00の場合無効であること' do + instance = build(:measured_rate, plan: plan, price: 100000.00) + expect(instance).to be_invalid + instance.errors[:price].include?("must be less than or equal to 99999.99") + end + end + + context 'plan' do + it 'nilの場合無効であること' do + instance = build(:measured_rate, plan: nil) + expect(instance).to be_invalid + instance.errors[:plan].include?("must exist") + end + end + end + + describe 'created' do + context 'price' do + it '小数点以下2桁のまで保存される' do + instance = build(:measured_rate, plan: plan, price: 1.011) + expect(instance.price).to eq 1.01 + end + end + end + + describe 'associations' do + context 'plan' do + it '関連の設定が正しいか' do + association = described_class.reflect_on_association(:plan) + expect(association.macro).to eq :belongs_to + expect(association.options[:dependent]).to be_nil + end + + it '関連が参照できること' do + instance = create(:measured_rate, plan: plan) + expect(instance.plan).to eq plan + end + end + end +end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index 7f30ee04a..e6bff5aa8 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -68,6 +68,8 @@ end describe 'associations' do + let(:plan) { create(:plan, electric_power_company: electric_power_company) } + context 'electric_power_company' do it '関連の設定が正しいか' do association = described_class.reflect_on_association(:electric_power_company) @@ -76,7 +78,6 @@ end it '関連が参照できること' do - plan = create(:plan, electric_power_company: electric_power_company) expect(plan.electric_power_company).to eq electric_power_company end end @@ -89,10 +90,22 @@ end it '関連が参照できること' do - plan = create(:plan, electric_power_company: electric_power_company) basic_price = create(:basic_price, plan: plan) expect(plan.basic_prices).to eq [ basic_price ] end end + + context "measured_rates" do + it "関連の設定が正しいか" do + association = described_class.reflect_on_association(:measured_rates) + expect(association.macro).to eq :has_many + expect(association.options[:dependent]).to eq :destroy + end + + it "関連が参照できること" do + measured_rate = create(:measured_rate, plan: plan) + expect(plan.measured_rates).to eq [ measured_rate ] + end + end end end From 02e8d61f9c422769faf41f0606bacad8b4f73961 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 15:09:48 +0900 Subject: [PATCH 17/63] =?UTF-8?q?model=20spec=E3=81=AEcontext=E3=81=AE?= =?UTF-8?q?=E9=9A=8E=E5=B1=A4=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/spec/models/basic_price_spec.rb | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb index 2625df7a5..cceac6068 100644 --- a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -43,42 +43,6 @@ instance.errors[:amperage].include?("is not included in the list") end - context 'price' do - it 'nilの場合無効であること' do - instance = build(:basic_price, plan: plan, price: nil) - expect(instance).to be_invalid - instance.errors[:price].include?("can't be blank") - end - - it '空文字の場合無効であること' do - instance = build(:basic_price, plan: plan, price: '') - expect(instance).to be_invalid - instance.errors[:price].include?("is not a number") - end - - it '文字列の場合無効であること' do - instance = build(:basic_price, plan: plan, price: 'a') - expect(instance).to be_invalid - instance.errors[:price].include?("is not a number") - end - - it '0の場合有効であること' do - instance = build(:basic_price, plan: plan, price: 0) - expect(instance).to be_valid - end - - it '99999.99の場合有効であること' do - instance = build(:basic_price, plan: plan, price: 99999.99) - expect(instance).to be_valid - end - - it '100000.00の場合無効であること' do - instance = build(:basic_price, plan: plan, price: 100000.00) - expect(instance).to be_invalid - instance.errors[:price].include?("must be less than or equal to 99999.99") - end - end - context 'uniqueness' do it '異なるplan_idの場合有効であること' do create(:basic_price, plan: plan, amperage: 10) @@ -98,6 +62,42 @@ end end + context 'price' do + it 'nilの場合無効であること' do + instance = build(:basic_price, plan: plan, price: nil) + expect(instance).to be_invalid + instance.errors[:price].include?("can't be blank") + end + + it '空文字の場合無効であること' do + instance = build(:basic_price, plan: plan, price: '') + expect(instance).to be_invalid + instance.errors[:price].include?("is not a number") + end + + it '文字列の場合無効であること' do + instance = build(:basic_price, plan: plan, price: 'a') + expect(instance).to be_invalid + instance.errors[:price].include?("is not a number") + end + + it '0の場合有効であること' do + instance = build(:basic_price, plan: plan, price: 0) + expect(instance).to be_valid + end + + it '99999.99の場合有効であること' do + instance = build(:basic_price, plan: plan, price: 99999.99) + expect(instance).to be_valid + end + + it '100000.00の場合無効であること' do + instance = build(:basic_price, plan: plan, price: 100000.00) + expect(instance).to be_invalid + instance.errors[:price].include?("must be less than or equal to 99999.99") + end + end + context 'plan' do it 'nilの場合無効であること' do instance = build(:basic_price, plan: nil) @@ -110,7 +110,7 @@ describe 'created' do context 'price' do it '小数点以下2桁のまで保存される' do - instance = create(:basic_price, plan: plan, price: 1.01) + instance = create(:basic_price, plan: plan, price: 1.011) expect(instance.price).to eq 1.01 end end From 55c0bd6a23391d048cfa204041c1057d50ecd313 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 15:10:38 +0900 Subject: [PATCH 18/63] =?UTF-8?q?db=20schema.rb=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/db/schema.rb | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 serverside_challenge_2/challenge/db/schema.rb diff --git a/serverside_challenge_2/challenge/db/schema.rb b/serverside_challenge_2/challenge/db/schema.rb new file mode 100644 index 000000000..6a08cbca2 --- /dev/null +++ b/serverside_challenge_2/challenge/db/schema.rb @@ -0,0 +1,56 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.0].define(version: 2024_10_27_072347) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "basic_prices", force: :cascade do |t| + t.integer "amperage", limit: 2, null: false + t.decimal "price", precision: 7, scale: 2, null: false + t.integer "plan_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["plan_id", "amperage"], name: "index_basic_prices_on_plan_id_and_amperage", unique: true + t.index ["plan_id"], name: "index_basic_prices_on_plan_id" + end + + create_table "electric_power_companies", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["name"], name: "index_electric_power_companies_on_name", unique: true + end + + create_table "measured_rates", force: :cascade do |t| + t.integer "electricity_usage_min", limit: 2, null: false + t.integer "electricity_usage_max", limit: 2, null: false + t.decimal "price", precision: 7, scale: 2, null: false + t.integer "plan_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["plan_id"], name: "index_measured_rates_on_plan_id" + end + + create_table "plans", force: :cascade do |t| + t.string "name", null: false + t.integer "electric_power_company_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["electric_power_company_id", "name"], name: "index_plans_on_electric_power_company_id_and_name", unique: true + t.index ["electric_power_company_id"], name: "index_plans_on_electric_power_company_id" + end + + add_foreign_key "basic_prices", "plans", on_delete: :cascade + add_foreign_key "measured_rates", "plans", on_delete: :cascade + add_foreign_key "plans", "electric_power_companies", on_delete: :cascade +end From f56dc8f135b967f992f34ef9bb0f7114fb6629ea Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 15:19:12 +0900 Subject: [PATCH 19/63] =?UTF-8?q?Seed=E3=83=87=E3=83=BC=E3=82=BF=E3=81=AE?= =?UTF-8?q?=E6=8A=95=E5=85=A5=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/db/seeds.rb | 27 +++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/serverside_challenge_2/challenge/db/seeds.rb b/serverside_challenge_2/challenge/db/seeds.rb index bc25fce30..fe4888839 100644 --- a/serverside_challenge_2/challenge/db/seeds.rb +++ b/serverside_challenge_2/challenge/db/seeds.rb @@ -1,7 +1,20 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Examples: -# -# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) -# Character.create(name: "Luke", movie: movies.first) +require 'csv' + +CSV.foreach(Rails.root.join('db/seeds/basic_fee.csv'), headers: true) do |row| + company = ElectricPowerCompany.find_or_create_by(name: row[0]) + plan = company.plans.find_or_create_by(name: row[1]) + # データ投入 + plan.basic_prices.delete_all + plan.basic_prices.create!(amperage: row[2], price: row[3]) +end + +CSV.foreach(Rails.root.join('db/seeds/measured_rates.csv'), headers: true) do |row| + company = ElectricPowerCompany.find_or_create_by(name: row[0]) + plan = company.plans.find_or_create_by(name: row[1]) + electricity_usages = row[2].split('-') + # データ投入 + plan.measured_rates.delete_all + plan.measured_rates.create!(electricity_usage_min: electricity_usages[0].to_i, + electricity_usage_max: electricity_usages[1]&.to_i, + price: row[3]) +end From 8048f64b329a5a608bcaf3052550e72cac1cea31 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 15:58:11 +0900 Subject: [PATCH 20/63] =?UTF-8?q?CSV=E3=83=87=E3=83=BC=E3=82=BF=E6=8A=95?= =?UTF-8?q?=E5=85=A5=E3=81=AE=E5=A6=A5=E5=BD=93=E6=80=A7=E6=A4=9C=E8=A8=BC?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/db/seeds.rb | 6 +-- .../seeds/{basic_fee.csv => basic_prices.csv} | 0 .../challenge/spec/db/seeds_spec.rb | 49 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) rename serverside_challenge_2/challenge/db/seeds/{basic_fee.csv => basic_prices.csv} (100%) create mode 100644 serverside_challenge_2/challenge/spec/db/seeds_spec.rb diff --git a/serverside_challenge_2/challenge/db/seeds.rb b/serverside_challenge_2/challenge/db/seeds.rb index fe4888839..8def3ce5a 100644 --- a/serverside_challenge_2/challenge/db/seeds.rb +++ b/serverside_challenge_2/challenge/db/seeds.rb @@ -1,19 +1,19 @@ require 'csv' -CSV.foreach(Rails.root.join('db/seeds/basic_fee.csv'), headers: true) do |row| +BasicPrice.delete_all +CSV.foreach(Rails.root.join('db/seeds/basic_prices.csv'), headers: true) do |row| company = ElectricPowerCompany.find_or_create_by(name: row[0]) plan = company.plans.find_or_create_by(name: row[1]) # データ投入 - plan.basic_prices.delete_all plan.basic_prices.create!(amperage: row[2], price: row[3]) end +MeasuredRate.delete_all CSV.foreach(Rails.root.join('db/seeds/measured_rates.csv'), headers: true) do |row| company = ElectricPowerCompany.find_or_create_by(name: row[0]) plan = company.plans.find_or_create_by(name: row[1]) electricity_usages = row[2].split('-') # データ投入 - plan.measured_rates.delete_all plan.measured_rates.create!(electricity_usage_min: electricity_usages[0].to_i, electricity_usage_max: electricity_usages[1]&.to_i, price: row[3]) diff --git a/serverside_challenge_2/challenge/db/seeds/basic_fee.csv b/serverside_challenge_2/challenge/db/seeds/basic_prices.csv similarity index 100% rename from serverside_challenge_2/challenge/db/seeds/basic_fee.csv rename to serverside_challenge_2/challenge/db/seeds/basic_prices.csv diff --git a/serverside_challenge_2/challenge/spec/db/seeds_spec.rb b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb new file mode 100644 index 000000000..71ce6ebf2 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb @@ -0,0 +1,49 @@ +describe 'Seedデータ作成', type: :helper do + let(:basic_prices_csv_path) { Rails.root.join('db/seeds/basic_prices.csv') } + let(:measured_rates_csv_path) { Rails.root.join('db/seeds/measured_rates.csv') } + + before do + Rails.application.load_seed + end + + it 'ElectricPowerCompanyが作成されること' do + CSV.foreach(basic_prices_csv_path, headers: true) do |row| + company = ElectricPowerCompany.find_by!(name: row[0]) + expect(company).to be_present + end + end + + it 'Planが作成されること' do + CSV.foreach(basic_prices_csv_path, headers: true) do |row| + plan = Plan.find_by!(name: row[1]) + expect(plan).to be_present + + expect(plan.electric_power_company.name).to eq row[0] + end + end + + it '基本料金が作成されること' do + CSV.foreach(basic_prices_csv_path, headers: true) do |row| + plan = Plan.find_by!(name: row[1]) + basic_price = BasicPrice.find_by!(amperage: row[2], plan: plan) + expect(basic_price).to be_present + + expect(basic_price.price).to eq row[3].to_f + end + end + + it '従量料金が作成されること' do + CSV.foreach(measured_rates_csv_path, headers: true) do |row| + plan = Plan.find_by!(name: row[1]) + + usage = row[2].split('-').map(&:to_i) + electricity_usage_min = usage[0] + electricity_usage_max = usage[1] || MeasuredRate::MAX_SMALL_INT_VALUE + measured_rate = MeasuredRate.find_by!(electricity_usage_min: electricity_usage_min, + electricity_usage_max: electricity_usage_max, + plan: plan) + expect(measured_rate).to be_present + expect(measured_rate.price).to eq row[3].to_f + end + end +end From bfe6ec6c6ff0c1d1b77f70944c4d654f337c34e2 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 16:03:48 +0900 Subject: [PATCH 21/63] =?UTF-8?q?ER=E5=9B=B3=E3=81=AE=E5=87=BA=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Dockerfile | 2 +- serverside_challenge_2/challenge/erd.pdf | Bin 0 -> 32845 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 serverside_challenge_2/challenge/erd.pdf diff --git a/serverside_challenge_2/challenge/Dockerfile b/serverside_challenge_2/challenge/Dockerfile index 166bd4432..ea9798547 100644 --- a/serverside_challenge_2/challenge/Dockerfile +++ b/serverside_challenge_2/challenge/Dockerfile @@ -1,5 +1,5 @@ FROM ruby:3.1.2 -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs vim +RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs vim graphviz RUN mkdir /app WORKDIR /app ADD Gemfile /app/Gemfile diff --git a/serverside_challenge_2/challenge/erd.pdf b/serverside_challenge_2/challenge/erd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6b6390eb71b6279e87ea8381bbde67d47f02034e GIT binary patch literal 32845 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 literal 0 HcmV?d00001 From e8d7e97014ce764640989a2eeff485983661ba70 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 16:20:49 +0900 Subject: [PATCH 22/63] =?UTF-8?q?rack-timeout,=20rack-cors=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/.env.sample | 8 +++++++- serverside_challenge_2/challenge/Gemfile | 3 ++- serverside_challenge_2/challenge/Gemfile.lock | 5 +++++ .../challenge/config/initializers/cors.rb | 18 +++++++++--------- .../config/initializers/rack_timeout.rb | 1 + 5 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 serverside_challenge_2/challenge/config/initializers/rack_timeout.rb diff --git a/serverside_challenge_2/challenge/.env.sample b/serverside_challenge_2/challenge/.env.sample index 41a42205a..cba9fddd8 100644 --- a/serverside_challenge_2/challenge/.env.sample +++ b/serverside_challenge_2/challenge/.env.sample @@ -1,4 +1,10 @@ +# DB設定 DATABASE_HOST=db DATABASE_PORT=5432 DATABASE_USER=postgres -DATABASE_PASSWORD=password \ No newline at end of file +DATABASE_PASSWORD=password + +# サービスのドメイン設定 +SERVICE_PROTOCOL=http +SERVICE_DOMAIN=localhost +SERVICE_PORT=3000 diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 705bcc661..5e208352f 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -34,7 +34,8 @@ gem "bootsnap", require: false # gem "image_processing", "~> 1.2" # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible -# gem "rack-cors" +gem 'rack-cors', '~> 2.0', '>= 2.0.2' +gem 'rack-timeout', '~> 0.7.0' group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index c8720b8f7..b37fb5d16 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -144,8 +144,11 @@ GEM nio4r (~> 2.0) racc (1.7.3) rack (2.2.8) + rack-cors (2.0.2) + rack (>= 2.0.0) rack-test (2.1.0) rack (>= 1.3) + rack-timeout (0.7.0) rails (7.0.8) actioncable (= 7.0.8) actionmailbox (= 7.0.8) @@ -262,6 +265,8 @@ DEPENDENCIES factory_bot_rails pg (~> 1.1) puma (~> 5.0) + rack-cors (~> 2.0, >= 2.0.2) + rack-timeout (~> 0.7.0) rails (~> 7.0.8) rails-erd (~> 1.7, >= 1.7.2) rspec-rails diff --git a/serverside_challenge_2/challenge/config/initializers/cors.rb b/serverside_challenge_2/challenge/config/initializers/cors.rb index e5a82f162..587689398 100644 --- a/serverside_challenge_2/challenge/config/initializers/cors.rb +++ b/serverside_challenge_2/challenge/config/initializers/cors.rb @@ -5,12 +5,12 @@ # Read more: https://github.com/cyu/rack-cors -# Rails.application.config.middleware.insert_before 0, Rack::Cors do -# allow do -# origins "example.com" -# -# resource "*", -# headers: :any, -# methods: [:get, :post, :put, :patch, :delete, :options, :head] -# end -# end +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins "#{ENV['SERVICE_PROTOCOL']}://#{ENV['SERVICE_DOMAIN']}#{ENV['SERVICE_PORT']}" + + resource "*", + headers: :any, + methods: [:get, :post, :put, :patch, :delete, :options, :head] + end +end diff --git a/serverside_challenge_2/challenge/config/initializers/rack_timeout.rb b/serverside_challenge_2/challenge/config/initializers/rack_timeout.rb new file mode 100644 index 000000000..8cbfada54 --- /dev/null +++ b/serverside_challenge_2/challenge/config/initializers/rack_timeout.rb @@ -0,0 +1 @@ +Rails.application.config.middleware.insert_before Rack::Runtime, Rack::Timeout, service_timeout: 15 From b21efd40d3a5532923c43aee208d71cad3ca19d2 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 17:13:32 +0900 Subject: [PATCH 23/63] =?UTF-8?q?=E9=9B=BB=E5=8A=9B=E4=BC=9A=E7=A4=BE?= =?UTF-8?q?=E3=81=AEmodel=E5=90=8D=E3=82=92ElectricPowerCompany=E3=81=8B?= =?UTF-8?q?=E3=82=89Provider=E3=81=B8=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/plan.rb | 4 +- ...{electric_power_company.rb => provider.rb} | 2 +- ...7065846_create_electric_power_companies.rb | 10 ----- .../20241027065846_create_providers.rb | 10 +++++ .../db/migrate/20241027065931_create_plans.rb | 4 +- serverside_challenge_2/challenge/db/schema.rb | 22 +++++----- serverside_challenge_2/challenge/db/seeds.rb | 8 ++-- serverside_challenge_2/challenge/erd.pdf | Bin 32845 -> 32320 bytes .../challenge/spec/db/seeds_spec.rb | 8 ++-- ...{electric_power_company.rb => provider.rb} | 2 +- .../challenge/spec/models/basic_price_spec.rb | 6 +-- .../spec/models/measure_rate_spec.rb | 4 +- .../challenge/spec/models/plan_spec.rb | 40 +++++++++--------- ...power_company_spec.rb => provider_spec.rb} | 30 ++++++------- 14 files changed, 75 insertions(+), 75 deletions(-) rename serverside_challenge_2/challenge/app/models/{electric_power_company.rb => provider.rb} (77%) delete mode 100644 serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb create mode 100644 serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb rename serverside_challenge_2/challenge/spec/factories/{electric_power_company.rb => provider.rb} (61%) rename serverside_challenge_2/challenge/spec/models/{electric_power_company_spec.rb => provider_spec.rb} (61%) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 07606627c..fef19aae7 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class Plan < ApplicationRecord - belongs_to :electric_power_company + belongs_to :provider has_many :basic_prices, dependent: :destroy has_many :measured_rates, dependent: :destroy validates :name, presence: true, length: { minimum: 1, maximum: 255 } - validates :electric_power_company, presence: true, uniqueness: { scope: :name } + validates :provider, presence: true, uniqueness: { scope: :name } end diff --git a/serverside_challenge_2/challenge/app/models/electric_power_company.rb b/serverside_challenge_2/challenge/app/models/provider.rb similarity index 77% rename from serverside_challenge_2/challenge/app/models/electric_power_company.rb rename to serverside_challenge_2/challenge/app/models/provider.rb index 5832c5d54..42afc54bb 100644 --- a/serverside_challenge_2/challenge/app/models/electric_power_company.rb +++ b/serverside_challenge_2/challenge/app/models/provider.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ElectricPowerCompany < ApplicationRecord +class Provider < ApplicationRecord has_many :plans, dependent: :destroy validates :name, presence: true, length: { minimum: 1, maximum: 255 }, uniqueness: true diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb b/serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb deleted file mode 100644 index 29fec097e..000000000 --- a/serverside_challenge_2/challenge/db/migrate/20241027065846_create_electric_power_companies.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateElectricPowerCompanies < ActiveRecord::Migration[7.0] - def change - create_table :electric_power_companies do |t| - t.string :name, null: false - - t.timestamps - end - add_index :electric_power_companies, [ :name ], unique: true - end -end diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb b/serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb new file mode 100644 index 000000000..d06cea277 --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb @@ -0,0 +1,10 @@ +class CreateProviders < ActiveRecord::Migration[7.0] + def change + create_table :providers do |t| + t.string :name, null: false + + t.timestamps + end + add_index :providers, [ :name ], unique: true + end +end diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb b/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb index b98584d8e..f9dfe0f0d 100644 --- a/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb +++ b/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb @@ -2,10 +2,10 @@ class CreatePlans < ActiveRecord::Migration[7.0] def change create_table :plans do |t| t.string :name, null: false - t.references :electric_power_company, null: false, type: :integer, foreign_key: { on_delete: :cascade } + t.references :provider, null: false, type: :integer, foreign_key: { on_delete: :cascade } t.timestamps end - add_index :plans, [ :electric_power_company_id, :name ], unique: true + add_index :plans, [ :provider_id, :name ], unique: true end end diff --git a/serverside_challenge_2/challenge/db/schema.rb b/serverside_challenge_2/challenge/db/schema.rb index 6a08cbca2..8349ee4d2 100644 --- a/serverside_challenge_2/challenge/db/schema.rb +++ b/serverside_challenge_2/challenge/db/schema.rb @@ -24,13 +24,6 @@ t.index ["plan_id"], name: "index_basic_prices_on_plan_id" end - create_table "electric_power_companies", force: :cascade do |t| - t.string "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["name"], name: "index_electric_power_companies_on_name", unique: true - end - create_table "measured_rates", force: :cascade do |t| t.integer "electricity_usage_min", limit: 2, null: false t.integer "electricity_usage_max", limit: 2, null: false @@ -43,14 +36,21 @@ create_table "plans", force: :cascade do |t| t.string "name", null: false - t.integer "electric_power_company_id", null: false + t.integer "provider_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["provider_id", "name"], name: "index_plans_on_provider_id_and_name", unique: true + t.index ["provider_id"], name: "index_plans_on_provider_id" + end + + create_table "providers", force: :cascade do |t| + t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["electric_power_company_id", "name"], name: "index_plans_on_electric_power_company_id_and_name", unique: true - t.index ["electric_power_company_id"], name: "index_plans_on_electric_power_company_id" + t.index ["name"], name: "index_providers_on_name", unique: true end add_foreign_key "basic_prices", "plans", on_delete: :cascade add_foreign_key "measured_rates", "plans", on_delete: :cascade - add_foreign_key "plans", "electric_power_companies", on_delete: :cascade + add_foreign_key "plans", "providers", on_delete: :cascade end diff --git a/serverside_challenge_2/challenge/db/seeds.rb b/serverside_challenge_2/challenge/db/seeds.rb index 8def3ce5a..0ffa8afd1 100644 --- a/serverside_challenge_2/challenge/db/seeds.rb +++ b/serverside_challenge_2/challenge/db/seeds.rb @@ -2,16 +2,16 @@ BasicPrice.delete_all CSV.foreach(Rails.root.join('db/seeds/basic_prices.csv'), headers: true) do |row| - company = ElectricPowerCompany.find_or_create_by(name: row[0]) - plan = company.plans.find_or_create_by(name: row[1]) + provider = Provider.find_or_create_by(name: row[0]) + plan = provider.plans.find_or_create_by(name: row[1]) # データ投入 plan.basic_prices.create!(amperage: row[2], price: row[3]) end MeasuredRate.delete_all CSV.foreach(Rails.root.join('db/seeds/measured_rates.csv'), headers: true) do |row| - company = ElectricPowerCompany.find_or_create_by(name: row[0]) - plan = company.plans.find_or_create_by(name: row[1]) + provider = Provider.find_or_create_by(name: row[0]) + plan = provider.plans.find_or_create_by(name: row[1]) electricity_usages = row[2].split('-') # データ投入 plan.measured_rates.create!(electricity_usage_min: electricity_usages[0].to_i, diff --git a/serverside_challenge_2/challenge/erd.pdf b/serverside_challenge_2/challenge/erd.pdf index 6b6390eb71b6279e87ea8381bbde67d47f02034e..6d85b0119bd644e87c0e3c10c9c04ad97b80d2e3 100644 GIT binary patch delta 16042 zcmZX(W0)9E80DL>ZQHh;v67jLZQJazZQHhOGa1`<#<>68yBC}7FI~^CyX(B?y#1k` zs%byazITu~1z_D4j|s)&<89`q zMONlKGOmRYUYSoaE1SgZ2%bg(=Y5(RUdg;;Enyqxeq|a$K$F$eLG}o4K>RqXac$^S z0pp*f>&Y|+Uy~T z7Ba_?_H_%ewerghJ;We}%pGGMj^GwgYT%GA7Hz2d<*ot|rDA+H_+2B(SKK2RIe|oF z=0|hDF93r0bwD3^e5{zA1XMc8s$$5%a|I(|dRRyKZ%+@N5~5SITNFzjh7&_%iVJH| z#*YrRC@V5ICz~rLn^n=JjXnF-Gt)n!f*+viMHzUxTqp-|5-y+=$IW3D3HvDV@`tr_ zGGww!r31*6LXdVZJC_Z~X`AZ!sNMd%CCR7`^MJN-lqnZq9oiQW`}V!`y&*GFGG+c; z-vGfyB=ksv28womkG>G+Gd=Rhil<%7mJUN7-E58>P7?YxDcVf{7|kJUv*c^sER4vnmKCJng)qRR2INbuFqW zYf>sF2^dDh9Yw%x{ZCfV*D57ajTqU=L!e6J?$fx^;p+^UKix6jT$xa6+2KIDezLuk zVN?@DP=|EUbE|vw9l`p%mq+a7Qg}v~`$OTgGQwA(sb>?$r!5H+p3RG!rO~dMXBvZCO8@@`A;F-Mtf^V2qZ4koEoHh zNo%(_=f3QO{P`1ijxpt^2xVg$t;}DvP+jb2`GMcXNF?qml7y!)I<gHGkP$jr{jh$>SUs z&1<)-c-ZC}UJ`Fs>ccuw1jsI^3S$C%fLMd1(VBZ!14iQ;mz7zTkPd4JF!6w+B1le< zb+}_%>E&M=Ohr;96ogAhJd_p+t5^=J1mnLw7PFLtB$#thtP`nXWuG+^ILbYi+>?r8feXcn`j@Qzt+ia%v#og zry(EtD6LXv+~|L|1S1Myygg%ITiSth4!r!ppoigQI_bfb*0pdb$+$%izic{b3bWco zp@E}zHDM`hlPboX*%}H*Dq5vzBuCbg(kaX*YH?tCk52W0=+rEy(P<|sQ#$6{D⁣ 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 diff --git a/serverside_challenge_2/challenge/spec/db/seeds_spec.rb b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb index 71ce6ebf2..00cbf5ab2 100644 --- a/serverside_challenge_2/challenge/spec/db/seeds_spec.rb +++ b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb @@ -6,10 +6,10 @@ Rails.application.load_seed end - it 'ElectricPowerCompanyが作成されること' do + it 'Providerが作成されること' do CSV.foreach(basic_prices_csv_path, headers: true) do |row| - company = ElectricPowerCompany.find_by!(name: row[0]) - expect(company).to be_present + provider = Provider.find_by!(name: row[0]) + expect(provider).to be_present end end @@ -18,7 +18,7 @@ plan = Plan.find_by!(name: row[1]) expect(plan).to be_present - expect(plan.electric_power_company.name).to eq row[0] + expect(plan.provider.name).to eq row[0] end end diff --git a/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb b/serverside_challenge_2/challenge/spec/factories/provider.rb similarity index 61% rename from serverside_challenge_2/challenge/spec/factories/electric_power_company.rb rename to serverside_challenge_2/challenge/spec/factories/provider.rb index 049d21661..7c2a61c8a 100644 --- a/serverside_challenge_2/challenge/spec/factories/electric_power_company.rb +++ b/serverside_challenge_2/challenge/spec/factories/provider.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :electric_power_company do + factory :provider do name { '電力会社' } end end diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb index cceac6068..51ae52885 100644 --- a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -1,8 +1,8 @@ require "rails_helper" RSpec.describe BasicPrice, type: :model do - let(:electric_power_company) { create(:electric_power_company) } - let(:plan) { create(:plan, electric_power_company: electric_power_company) } + let(:provider) { create(:provider) } + let(:plan) { create(:plan, provider: provider) } describe 'FactoryBot' do it '有効なファクトリを持つこと' do @@ -46,7 +46,7 @@ context 'uniqueness' do it '異なるplan_idの場合有効であること' do create(:basic_price, plan: plan, amperage: 10) - plan2 = create(:plan, name: 'プラン2', electric_power_company: electric_power_company) + plan2 = create(:plan, name: 'プラン2', provider: provider) instance = build(:basic_price, plan: plan2, amperage: 10) expect(instance).to be_valid diff --git a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb index b7b49ba38..2b7bd4fe0 100644 --- a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb @@ -1,8 +1,8 @@ require "rails_helper" RSpec.describe MeasuredRate, type: :model do - let(:electric_power_company) { create(:electric_power_company) } - let(:plan) { create(:plan, electric_power_company: electric_power_company) } + let(:provider) { create(:provider) } + let(:plan) { create(:plan, provider: provider) } describe 'FactoryBot' do it '有効なファクトリを持つこと' do diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index e6bff5aa8..a4f023081 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -1,84 +1,84 @@ require "rails_helper" RSpec.describe Plan, type: :model do - let(:electric_power_company) { create(:electric_power_company) } + let(:provider) { create(:provider) } describe 'FactoryBot' do it '有効なファクトリを持つこと' do - expect(build(:plan, electric_power_company: electric_power_company)).to be_valid + expect(build(:plan, provider: provider)).to be_valid end end describe 'Validation' do context 'name' do it '1文字の場合有効であること' do - instance = build(:plan, electric_power_company: electric_power_company, name: 'a') + instance = build(:plan, provider: provider, name: 'a') expect(instance).to be_valid end it '255文字の場合有効であること' do - instance = build(:plan, electric_power_company: electric_power_company, name: 'a' * 255) + instance = build(:plan, provider: provider, name: 'a' * 255) expect(instance).to be_valid end it 'nilの場合無効であること' do - instance = build(:plan, electric_power_company: electric_power_company, name: nil) + instance = build(:plan, provider: provider, name: nil) expect(instance).to be_invalid instance.errors[:name].include?("can't be blank") end it '空文字の場合無効であること' do - instance = build(:plan, electric_power_company: electric_power_company, name: '') + instance = build(:plan, provider: provider, name: '') expect(instance).to be_invalid instance.errors[:name].include?("can't be blank") end it '256文字の場合無効であること' do - instance = build(:plan, electric_power_company: electric_power_company, name: 'a' * 256) + instance = build(:plan, provider: provider, name: 'a' * 256) expect(instance).to be_invalid instance.errors[:name].include?("is too long (maximum is 255 characters)") end context 'uniqueness' do - it 'electric_power_companyが重複しない場合有効であること' do - create(:plan, electric_power_company: electric_power_company, name: 'プラン') - company2 = create(:electric_power_company, name: '電力会社2') + it 'providerが重複しない場合有効であること' do + create(:plan, provider: provider, name: 'プラン') + provider2 = create(:provider, name: '電力会社2') - instance = build(:plan, electric_power_company: company2, name: 'プラン') + instance = build(:plan, provider: provider2, name: 'プラン') expect(instance).to be_valid end it '重複する場合無効であること' do - create(:plan, electric_power_company: electric_power_company, name: 'プラン') + create(:plan, provider: provider, name: 'プラン') - instance = build(:plan, electric_power_company: electric_power_company, name: 'プラン') + instance = build(:plan, provider: provider, name: 'プラン') expect(instance).to be_invalid instance.errors[:name].include?("has already been taken") end end end - context ':electric_power_company' do + context ':provider' do it 'nilの場合無効であること' do - instance = build(:plan, electric_power_company: nil) + instance = build(:plan, provider: nil) expect(instance).to be_invalid - instance.errors[:electric_power_company].include?("must exist") + instance.errors[:provider].include?("must exist") end end end describe 'associations' do - let(:plan) { create(:plan, electric_power_company: electric_power_company) } + let(:plan) { create(:plan, provider: provider) } - context 'electric_power_company' do + context 'provider' do it '関連の設定が正しいか' do - association = described_class.reflect_on_association(:electric_power_company) + association = described_class.reflect_on_association(:provider) expect(association.macro).to eq :belongs_to expect(association.options[:dependent]).to be_nil end it '関連が参照できること' do - expect(plan.electric_power_company).to eq electric_power_company + expect(plan.provider).to eq provider end end diff --git a/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb b/serverside_challenge_2/challenge/spec/models/provider_spec.rb similarity index 61% rename from serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb rename to serverside_challenge_2/challenge/spec/models/provider_spec.rb index 160ff5bcb..15cb6217e 100644 --- a/serverside_challenge_2/challenge/spec/models/electric_power_company_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/provider_spec.rb @@ -1,46 +1,46 @@ require "rails_helper" -RSpec.describe ElectricPowerCompany, type: :model do +RSpec.describe Provider, type: :model do describe 'FactoryBot' do it '有効なファクトリを持つこと' do - expect(build(:electric_power_company)).to be_valid + expect(build(:provider)).to be_valid end end describe 'Validation' do context 'name' do it '1文字の場合有効であること' do - instance = build(:electric_power_company, name: 'a') + instance = build(:provider, name: 'a') expect(instance).to be_valid end it '255文字の場合有効であること' do - instance = build(:electric_power_company, name: 'a' * 255) + instance = build(:provider, name: 'a' * 255) expect(instance).to be_valid end it 'nilの場合無効であること' do - instance = build(:electric_power_company, name: nil) + instance = build(:provider, name: nil) expect(instance).to be_invalid instance.errors[:name].include?("can't be blank") end it '空文字の場合無効であること' do - instance = build(:electric_power_company, name: '') + instance = build(:provider, name: '') expect(instance).to be_invalid instance.errors[:name].include?("can't be blank") end it '256文字の場合無効であること' do - instance = build(:electric_power_company, name: 'a' * 256) + instance = build(:provider, name: 'a' * 256) expect(instance).to be_invalid instance.errors[:name].include?("is too long (maximum is 255 characters)") end it '重複する場合無効であること' do - create(:electric_power_company, name: '電力会社') + create(:provider, name: '電力会社') - instance = build(:electric_power_company, name: '電力会社') + instance = build(:provider, name: '電力会社') expect(instance).to be_invalid instance.errors[:name].include?("has already been taken") end @@ -56,17 +56,17 @@ end it '関連が参照できること' do - electric_power_company = create(:electric_power_company) - plan = create(:plan, electric_power_company: electric_power_company) + provider = create(:provider) + plan = create(:plan, provider: provider) - expect(electric_power_company.plans).to include(plan) + expect(provider.plans).to include(plan) end it '削除された場合、紐づくプランが削除されること' do - electric_power_company = create(:electric_power_company) - plan_id = create(:plan, electric_power_company: electric_power_company).id + provider = create(:provider) + plan_id = create(:plan, provider: provider).id - expect { electric_power_company.destroy }.to change { Plan.count }.by(-1) + expect { provider.destroy }.to change { Plan.count }.by(-1) expect(Plan.exists?(plan_id)).to be_falsey end end From a561a15ff5408b4eaeff9857bb11af2be7766f08 Mon Sep 17 00:00:00 2001 From: kuni Date: Sat, 2 Nov 2024 23:16:15 +0900 Subject: [PATCH 24/63] =?UTF-8?q?A=E6=95=B0=E3=81=AB=E5=90=88=E8=87=B4?= =?UTF-8?q?=E3=81=99=E3=82=8B=E5=9F=BA=E6=9C=AC=E6=96=99=E9=87=91=E3=81=AE?= =?UTF-8?q?=E5=8F=8E=E9=9B=86method=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/basic_price.rb | 16 +++++++ .../challenge/spec/models/basic_price_spec.rb | 44 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/serverside_challenge_2/challenge/app/models/basic_price.rb b/serverside_challenge_2/challenge/app/models/basic_price.rb index f6018cbc2..7a5e1b161 100644 --- a/serverside_challenge_2/challenge/app/models/basic_price.rb +++ b/serverside_challenge_2/challenge/app/models/basic_price.rb @@ -8,4 +8,20 @@ class BasicPrice < ApplicationRecord validates :amperage, presence: true, inclusion: { in: AMPERAGE_LIST } validates :price, numericality: { only_numeric: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 99999.99 } validates :plan, presence: true, uniqueness: { scope: :amperage } + + class << self + def calculate_prices(amperage) + rows = BasicPrice.where(amperage: amperage) + rows.inject({}) do |sum, row| + sum[row.plan_id] = row.price + sum + end + end + + def check_amperage?(amperage) + res = { is_error: !AMPERAGE_LIST.include?(amperage) } + res[:error_object] = { field: "amperage", message: "#{AMPERAGE_LIST.join('/')}のいずれかを指定してください。" } if res[:is_error] + res + end + end end diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb index 51ae52885..4c6e4c122 100644 --- a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -130,4 +130,48 @@ end end end + + describe 'class methods' do + describe 'check_amperage?' do + it 'AMPERAGE_LISTに存在する値はis_error=falseとなる' do + BasicPrice::AMPERAGE_LIST.each do |amperage| + res = BasicPrice.check_amperage?(amperage) + expect(res[:is_error]).to be_falsey + expect(res[:error_object]).to be_nil + end + end + + it 'nilの場合はis_error=trueとなる' do + res = BasicPrice.check_amperage?(nil) + expect(res[:is_error]).to be_truthy + expect(res[:error_object][:field]).to eq 'amperage' + expect(res[:error_object][:message]).to eq "#{BasicPrice::AMPERAGE_LIST.join('/')}のいずれかを指定してください。" + end + end + + describe 'calculate_prices' do + let(:provider1) { create(:provider, name: 'provider1') } + let(:provider2) { create(:provider, name: 'provider2') } + let(:plan1_provider1) { create(:plan, name: 'plan1', provider: provider1) } + let(:plan1_amp_10) { create(:basic_price, plan: plan1_provider1, amperage: 10, price: 110.00) } + let(:plan2_provider1) { create(:plan, name: 'plan2', provider: provider1) } + let(:plan2_amp_10) { create(:basic_price, plan: plan2_provider1, amperage: 10, price: 210.00) } + let(:plan3_provider2) { create(:plan, name: 'plan3', provider: provider2) } + let(:plan1_amp_15) { create(:basic_price, plan: plan3_provider2, amperage: 15, price: 315.00) } + + before do + plan1_amp_10 + plan2_amp_10 + plan1_amp_15 + end + + it 'amperageに対応するプランの料金を取得できる' do + res = BasicPrice.calculate_prices(10) + + expect(res.keys.size).to eq 2 + expect(res[plan1_provider1.id]).to eq 110.00 + expect(res[plan2_provider1.id]).to eq 210.00 + end + end + end end From f4484155b6eaa9ece40c15a6f9d37fd4c9272021 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 11:26:48 +0900 Subject: [PATCH 25/63] =?UTF-8?q?fix:=20=E5=9F=BA=E6=9C=AC=E6=96=99?= =?UTF-8?q?=E9=87=91=E3=81=AE=E8=A8=88=E7=AE=97method=E3=81=AE=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/app/models/basic_price.rb | 2 +- .../challenge/spec/models/basic_price_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/basic_price.rb b/serverside_challenge_2/challenge/app/models/basic_price.rb index 7a5e1b161..ffeaa6cbe 100644 --- a/serverside_challenge_2/challenge/app/models/basic_price.rb +++ b/serverside_challenge_2/challenge/app/models/basic_price.rb @@ -10,7 +10,7 @@ class BasicPrice < ApplicationRecord validates :plan, presence: true, uniqueness: { scope: :amperage } class << self - def calculate_prices(amperage) + def calc_prices(amperage) rows = BasicPrice.where(amperage: amperage) rows.inject({}) do |sum, row| sum[row.plan_id] = row.price diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb index 4c6e4c122..7357ad0e4 100644 --- a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -166,7 +166,7 @@ end it 'amperageに対応するプランの料金を取得できる' do - res = BasicPrice.calculate_prices(10) + res = BasicPrice.calc_prices(10) expect(res.keys.size).to eq 2 expect(res[plan1_provider1.id]).to eq 110.00 From 374c00c991e078154a497f85dfa6e49e6254123a Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 11:29:02 +0900 Subject: [PATCH 26/63] =?UTF-8?q?=E5=BE=93=E9=87=8F=E6=96=99=E9=87=91?= =?UTF-8?q?=E3=81=AE=E8=A8=88=E7=AE=97method=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/measured_rate.rb | 31 +++ .../spec/models/measure_rate_spec.rb | 205 ++++++++++++++++++ 2 files changed, 236 insertions(+) diff --git a/serverside_challenge_2/challenge/app/models/measured_rate.rb b/serverside_challenge_2/challenge/app/models/measured_rate.rb index 7638c2868..efc20b25b 100644 --- a/serverside_challenge_2/challenge/app/models/measured_rate.rb +++ b/serverside_challenge_2/challenge/app/models/measured_rate.rb @@ -16,8 +16,39 @@ def electricity_usage_max=(value) super(value.nil? ? MAX_SMALL_INT_VALUE : value) end + class << self + def calc_prices(electricity_usage_kwh) + rows = MeasuredRate.where("electricity_usage_min <= ?", electricity_usage_kwh).includes(:plan) + rows.inject({}) do |sum, row| + sum[row.plan_id] ||= { price: 0 } + sum[row.plan_id][:price] += row.calc_row_price(electricity_usage_kwh) + sum + end + end + + def validate_electricity_usage?(electricity_usage) + res = { is_error: !electricity_usage.is_a?(Integer) } + res[:error_object] = { field: "electricity_usage_kwh", message: "整数を指定してください。" } if res[:is_error] + res + end + end + + def calc_row_price(electricity_usage_kwh) + return 0 if electricity_usage_kwh.to_i.zero? + + min = self.electricity_usage_min_for_calc + max = self.electricity_usage_max <= electricity_usage_kwh ? + self.electricity_usage_max : + electricity_usage_kwh + (max - min) * self.price + end + private + def electricity_usage_min_for_calc + self.electricity_usage_min.zero? ? 0 : self.electricity_usage_min - 1 + end + def validate_max_greater_than_min return if electricity_usage_max.nil? || electricity_usage_min.nil? diff --git a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb index 2b7bd4fe0..751c79c47 100644 --- a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb @@ -245,4 +245,209 @@ end end end + + describe 'Methods' do + let(:provider1) { create(:provider, name: 'provider1') } + let(:provider2) { create(:provider, name: 'provider2') } + let(:plan1_provider1) { create(:plan, name: 'plan1', provider: provider1) } + let(:plan2_provider1) { create(:plan, name: 'plan2', provider: provider1) } + let(:plan3_provider2) { create(:plan, name: 'plan3', provider: provider2) } + + describe 'validate_electricity_usage?' do + it '整数の場合エラーではないこと' do + res = MeasuredRate.validate_electricity_usage?(1) + expect(res[:is_error]).to be_falsey + expect(res[:error_object]).to be_nil + end + + it 'nilの場合エラーであること' do + res = MeasuredRate.validate_electricity_usage?(nil) + expect(res[:is_error]).to be_truthy + expect(res[:error_object][:field]).to eq 'electricity_usage_kwh' + expect(res[:error_object][:message]).to eq "整数を指定してください。" + end + + it '実数の場合エラーであること' do + res = MeasuredRate.validate_electricity_usage?(1.1) + expect(res[:is_error]).to be_truthy + expect(res[:error_object][:field]).to eq 'electricity_usage_kwh' + expect(res[:error_object][:message]).to eq "整数を指定してください。" + end + end + + describe 'calc_row_price' do + context 'instance.electricity_usage_min == 0の場合' do + let(:measured_rate) { create(:measured_rate, plan: plan, electricity_usage_min: 0, electricity_usage_max: 10, price: 20.01) } + before do + measured_rate + end + + it 'instance.electricity_usage_max < 引数の場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(11)).to eq 200.10 + end + + it '引数 == instance.electricity_usage_maxの場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(10)).to eq 200.10 + end + + it '引数 < instance.electricity_usage_maxの場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(9)).to eq 180.09 + end + + it '引数 == 1の場合、0となる' do + expect(measured_rate.calc_row_price(1)).to eq 20.01 + end + + it '引数 == 0の場合、0となる' do + expect(measured_rate.calc_row_price(0)).to eq 0 + end + end + + context 'instance.electricity_usage_min > 0の場合' do + let(:measured_rate) { create(:measured_rate, plan: plan, electricity_usage_min: 121, electricity_usage_max: 300, price: 10.01) } + before do + measured_rate + end + + it 'instance.electricity_usage_max < 引数の場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(301)).to eq 1801.8 + end + + it '引数 == instance.electricity_usage_maxの場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(300)).to eq 1801.8 + end + + it '引数 < instance.electricity_usage_maxの場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(299)).to eq 1791.79 + end + + it '引数 == instance.electricity_usage_minの場合、価格計算が妥当であること' do + expect(measured_rate.calc_row_price(121)).to eq 10.01 + end + + it '引数 == 0の場合、0となる' do + expect(measured_rate.calc_row_price(0)).to eq 0 + end + + it '引数 == nilの場合、0となる' do + expect(measured_rate.calc_row_price(nil)).to eq 0 + end + end + end + + describe 'electricity_usage_min_for_calc' do + it 'electricity_usage_minが0の場合0を返す' do + instance = create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 0, electricity_usage_max: 10) + expect(instance.send(:electricity_usage_min_for_calc)).to eq 0 + end + + it 'electricity_usage_minが0以外の場合electricity_usage_min - 1を返す' do + instance = create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 121, electricity_usage_max: 300) + expect(instance.send(:electricity_usage_min_for_calc)).to eq 120 + end + end + + describe 'calc_prices' do + # plan1 + let(:plan1_rate_0_120) { create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 0, electricity_usage_max: 120, price: 19.88) } + let(:plan1_rate_121_300) { create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 121, electricity_usage_max: 300, price: 26.48) } + let(:plan1_rate_301) { create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 301, electricity_usage_max: nil, price: 30.57) } + # plan2 + let(:plan2_rate_0_120) { create(:measured_rate, plan: plan2_provider1, electricity_usage_min: 0, electricity_usage_max: 120, price: 10.0) } + let(:plan2_rate_121_300) { create(:measured_rate, plan: plan2_provider1, electricity_usage_min: 121, electricity_usage_max: 300, price: 20.0) } + let(:plan2_rate_301) { create(:measured_rate, plan: plan2_provider1, electricity_usage_min: 301, electricity_usage_max: nil, price: 30.0) } + # plan3 + let(:plan3_rate_0) { create(:measured_rate, plan: plan3_provider2, electricity_usage_min: 0, electricity_usage_max: nil, price: 10.0) } + + context '無段階の価格表の場合' do + before do + plan3_rate_0 + end + + it '価格計算が妥当であること' do + res = MeasuredRate.calc_prices(1000) + + expect(res.size).to eq 1 + expect(res[plan3_provider2.id][:price]).to eq 10000 + end + end + + context '複数(3)段階の価格表の場合' do + before do + plan1_rate_0_120 + plan1_rate_121_300 + plan1_rate_301 + end + + context '1段階目の場合' do + it '最小値の場合、価格計算が妥当であること' do + res = MeasuredRate.calc_prices(1) + + expect(res.size).to eq 1 + expect(res[plan1_provider1.id][:price]).to eq 19.88 + end + + it '最大値の場合、価格計算が妥当であること' do + res = MeasuredRate.calc_prices(120) + + expect(res.size).to eq 1 + expect(res[plan1_provider1.id][:price]).to eq 2385.6 + end + end + + context '2段階目の場合' do + it '最小値の場合、価格計算が妥当であること' do + res = MeasuredRate.calc_prices(121) + + expect(res.size).to eq 1 + expect(res[plan1_provider1.id][:price]).to eq 2385.6 + 26.48 + end + + it '最大値の場合、価格計算が妥当であること' do + res = MeasuredRate.calc_prices(300) + + expect(res.size).to eq 1 + expect(res[plan1_provider1.id][:price]).to eq 2385.6 + 4766.4 + end + end + + context '3段階目の場合' do + it '最小値の場合、価格計算が妥当であること' do + res = MeasuredRate.calc_prices(301) + + expect(res.size).to eq 1 + expect(res[plan1_provider1.id][:price]).to eq 2385.6 + 4766.4 + 30.57 + end + + it '最小値以上の場合、価格計算が妥当であること' do + res = MeasuredRate.calc_prices(500) + + expect(res.size).to eq 1 + expect(res[plan1_provider1.id][:price]).to eq 2385.6 + 4766.4 + 6114 + end + end + end + + context '複数のプランがある場合' do + before do + plan1_rate_0_120.update!(price: 20.0) + plan1_rate_121_300.update!(price: 30.0) + plan1_rate_301.update!(price: 40.0) + plan2_rate_0_120 + plan2_rate_121_300 + plan2_rate_301 + plan3_rate_0 + end + + it '使用量の条件に合致する複数のプランが集計されること' do + res = MeasuredRate.calc_prices(301) + + expect(res.size).to eq 3 + expect(res[plan1_provider1.id][:price]).to eq 7840 + expect(res[plan2_provider1.id][:price]).to eq 4830 + expect(res[plan3_provider2.id][:price]).to eq 3010 + end + end + end + end end From aa7a79c37b568667cf48a19e61d6ce47a7cf7226 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 12:07:25 +0900 Subject: [PATCH 27/63] =?UTF-8?q?=E3=83=97=E3=83=A9=E3=83=B3=E4=BA=8B?= =?UTF-8?q?=E3=81=AE=E5=9F=BA=E6=9C=AC=E6=96=99=E9=87=91=E3=80=81=E5=BE=93?= =?UTF-8?q?=E9=87=8F=E6=96=99=E9=87=91=E3=81=AE=E8=A8=88=E7=AE=97=E3=81=99?= =?UTF-8?q?=E3=82=8Bmethod=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/plan.rb | 38 ++++++++ .../challenge/spec/models/plan_spec.rb | 87 +++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index fef19aae7..1c1cc129c 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -7,4 +7,42 @@ class Plan < ApplicationRecord validates :name, presence: true, length: { minimum: 1, maximum: 255 } validates :provider, presence: true, uniqueness: { scope: :name } + + class << self + def calc_prices(amperage, electricity_usage_kwh) + errors = check_parameters(amperage, electricity_usage_kwh) + if errors.present? + return { errors: { + message: "リクエストパラメーターが正しくありません。", + details: errors + } } + end + + plans = Plan.all.includes(:provider) + basic_prices_hash = BasicPrice.calc_prices(amperage) + measured_rates_hash = MeasuredRate.calc_prices(electricity_usage_kwh) + response = plans.map do |plan| + basic_price = basic_prices_hash[plan.id] + next if basic_price.nil? + + measured_rate_price = measured_rates_hash[plan.id]&.[](:price) || 0 + price = (basic_price + measured_rate_price).floor + { + plan: plan, + provider: plan.provider, + price: price + } + end + { plans: response } + end + + def check_parameters(amperage, electricity_usage_kwh) + errors = [ + BasicPrice.check_amperage?(amperage), + MeasuredRate.validate_electricity_usage?(electricity_usage_kwh) + ] + errors.select { |error| error[:is_error] } + .map { |error| error[:error_object] } + end + end end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index a4f023081..74a4c8f7c 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -108,4 +108,91 @@ end end end + + describe 'Methods' do + describe 'check_parameters' do + context 'amperage' do + it '正常な場合、レスポンスにエラーが含まれないこと' do + expect(Plan.check_parameters(20, 1000)).to be_empty + end + + it 'エラーの場合、レスポンスにエラーが含まれること' do + res = Plan.check_parameters(0, 1000) + expect(res.size).to eq 1 + expect(res[0][:field]).to eq 'amperage' + expect(res[0][:message]).to eq "#{BasicPrice::AMPERAGE_LIST.join('/')}のいずれかを指定してください。" + end + end + + context 'electricity_usage_kwh' do + it 'エラーの場合、レスポンスにエラーが含まれること' do + res = Plan.check_parameters(20, nil) + expect(res.size).to eq 1 + expect(res[0][:field]).to eq 'electricity_usage_kwh' + expect(res[0][:message]).to eq '整数を指定してください。' + end + end + end + + describe 'calc_prices' do + let(:provider1) { create(:provider, name: 'provider1') } + let(:provider2) { create(:provider, name: 'provider2') } + # plan1 + let(:plan1_provider1) { create(:plan, name: 'plan1', provider: provider1) } + let(:plan1_amp_10) { create(:basic_price, plan: plan1_provider1, amperage: 10, price: 100.01) } + let(:plan1_rate_0) { create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 0, electricity_usage_max: nil, price: 10.01) } + # plan2 + let(:plan2_provider2) { create(:plan, name: 'plan2', provider: provider2) } + let(:plan2_amp_10) { create(:basic_price, plan: plan2_provider2, amperage: 10, price: 200.01) } + let(:plan2_rate_0) { create(:measured_rate, plan: plan2_provider2, electricity_usage_min: 0, electricity_usage_max: nil, price: 20.03) } + + before do + plan1_amp_10 + plan1_rate_0 + plan2_amp_10 + plan2_rate_0 + end + + context 'エラーの場合' do + it 'レスポンスにエラーが設定されること' do + res = Plan.calc_prices(0, 1) + expect(res[:errors][:message]).to eq 'リクエストパラメーターが正しくありません。' + expect(res[:errors][:details].size).to eq 1 + expect(res[:errors][:details][0][:field]).to eq 'amperage' + expect(res[:errors][:details][0][:message]).to include "のいずれかを指定してください。" + end + end + + context '正常な場合' do + it 'プランの料金が集計されること' do + res = Plan.calc_prices(10, 1) + + expect(res[:errors]).to be_nil + expect(res[:plans].size).to eq 2 + + plans = res[:plans] + expect(plans[0][:plan]).to eq plan1_provider1 + expect(plans[0][:provider]).to eq provider1 + expect(plans[0][:price]).to eq 110 + + expect(plans[1][:plan]).to eq plan2_provider2 + expect(plans[1][:provider]).to eq provider2 + expect(plans[1][:price]).to eq 220 + end + + it '存在しないプランを指定した場合、レスポンスに含まれないこと' do + plan2_amp_10.update!(amperage: 20) + res = Plan.calc_prices(10, 1) + + expect(res[:errors]).to be_nil + expect(res[:plans].size).to eq 2 + + plans = res[:plans] + expect(plans[0][:plan]).to eq plan1_provider1 + expect(plans[0][:provider]).to eq provider1 + expect(plans[0][:price]).to eq 110 + end + end + end + end end From dfdf0be349b4ee8ce3cbc2494455fb08f86fe22a Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 12:08:52 +0900 Subject: [PATCH 28/63] =?UTF-8?q?API=E7=94=A8=E3=81=AE=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=96=E3=83=A9=E3=83=AA=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Gemfile | 5 +++-- serverside_challenge_2/challenge/Gemfile.lock | 2 ++ serverside_challenge_2/challenge/config/initializers/cors.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 5e208352f..4065682b4 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -34,8 +34,9 @@ gem "bootsnap", require: false # gem "image_processing", "~> 1.2" # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible -gem 'rack-cors', '~> 2.0', '>= 2.0.2' -gem 'rack-timeout', '~> 0.7.0' +gem "rack-cors", "~> 2.0", ">= 2.0.2" +gem "rack-timeout", "~> 0.7.0" +gem "jb", "~> 0.8.2" group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index b37fb5d16..efddf708a 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -102,6 +102,7 @@ GEM irb (1.11.2) rdoc reline (>= 0.4.2) + jb (0.8.2) json (2.7.4) language_server-protocol (3.17.0.3) loofah (2.22.0) @@ -263,6 +264,7 @@ DEPENDENCIES debug dotenv-rails factory_bot_rails + jb (~> 0.8.2) pg (~> 1.1) puma (~> 5.0) rack-cors (~> 2.0, >= 2.0.2) diff --git a/serverside_challenge_2/challenge/config/initializers/cors.rb b/serverside_challenge_2/challenge/config/initializers/cors.rb index 587689398..39fee8669 100644 --- a/serverside_challenge_2/challenge/config/initializers/cors.rb +++ b/serverside_challenge_2/challenge/config/initializers/cors.rb @@ -11,6 +11,6 @@ resource "*", headers: :any, - methods: [:get, :post, :put, :patch, :delete, :options, :head] + methods: [ :get, :post, :put, :patch, :delete, :options, :head ] end end From c6132e2619a5cc791e4fb19d4b7462cc9c7a630a Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 12:54:11 +0900 Subject: [PATCH 29/63] =?UTF-8?q?=E8=A8=88=E7=AE=97=E7=94=A8API=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/electricity/calculate_controller.rb | 21 +++ .../electricity/calculate/create.json.jb | 14 ++ .../challenge/config/routes.rb | 9 +- serverside_challenge_2/challenge/erd.pdf | Bin 32320 -> 32320 bytes .../api/electricity/calculate_spec.rb | 126 ++++++++++++++++++ 5 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb create mode 100644 serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb create mode 100644 serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb diff --git a/serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb b/serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb new file mode 100644 index 000000000..eaf4b3175 --- /dev/null +++ b/serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb @@ -0,0 +1,21 @@ +class Api::Electricity::CalculateController < ApplicationController + before_action :price_calculate, only: :create + + def create + if @prices[:errors].present? + render json: @prices[:errors], status: :bad_request + else + render "electricity/calculate/create", status: :ok + end + end + + private + + def create_params + params.permit(:amperage, :electricity_usage_kwh) + end + + def price_calculate + @prices = Plan.calc_prices(create_params[:amperage], create_params[:electricity_usage_kwh]) + end +end diff --git a/serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb b/serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb new file mode 100644 index 000000000..44f23b19f --- /dev/null +++ b/serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb @@ -0,0 +1,14 @@ +data = @prices[:plans].map do |row| + { + provider: { + id: row[:provider].id, + name: row[:provider].name + }, + plan: { + id: row[:plan].id, + name: row[:plan].name, + price: row[:price] + } + } +end +{ data: data } diff --git a/serverside_challenge_2/challenge/config/routes.rb b/serverside_challenge_2/challenge/config/routes.rb index 262ffd547..076f3ec3f 100644 --- a/serverside_challenge_2/challenge/config/routes.rb +++ b/serverside_challenge_2/challenge/config/routes.rb @@ -1,6 +1,7 @@ Rails.application.routes.draw do - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - - # Defines the root path route ("/") - # root "articles#index" + namespace :api, { format: "json" } do + namespace :electricity do + resources :calculate, only: [ :create ] + end + end end diff --git a/serverside_challenge_2/challenge/erd.pdf b/serverside_challenge_2/challenge/erd.pdf index 6d85b0119bd644e87c0e3c10c9c04ad97b80d2e3..398e72a36d7ebabb201cf444b9497c978c976f16 100644 GIT binary patch delta 22 ecmX@`hw;E4#tl=d*o_U0O^r<~HqWkF$_xN?TL~Hf delta 22 ecmX@`hw;E4#tl=d*o_P<3=NG8H_xtG$_xN?6$uXj diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb new file mode 100644 index 000000000..a52671175 --- /dev/null +++ b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb @@ -0,0 +1,126 @@ +require 'rails_helper' + +RSpec.describe 'API /api/electricity/calculate', type: :request do + let(:provider1) { create(:provider, name: 'provider1') } + let(:provider2) { create(:provider, name: 'provider2') } + # plan1 + let(:plan1_provider1) { create(:plan, name: 'plan1', provider: provider1) } + let(:plan1_amp_10) { create(:basic_price, plan: plan1_provider1, amperage: 10, price: 100.01) } + let(:plan1_rate_0) { create(:measured_rate, plan: plan1_provider1, electricity_usage_min: 0, electricity_usage_max: nil, price: 10.01) } + # plan2 + let(:plan2_provider2) { create(:plan, name: 'plan2', provider: provider2) } + let(:plan2_amp_10) { create(:basic_price, plan: plan2_provider2, amperage: 10, price: 200.01) } + let(:plan2_rate_0) { create(:measured_rate, plan: plan2_provider2, electricity_usage_min: 0, electricity_usage_max: nil, price: 20.03) } + + describe 'create' do + let(:params) do + { + amperage: 10, + electricity_usage_kwh: 1 + } + end + let(:headers) { { "Content-Type" => "application/json" } } + + before do + plan1_amp_10 + plan1_rate_0 + plan2_amp_10 + plan2_rate_0 + end + + context '正常' do + it 'レスポンスが正常に帰ること' do + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(200) + body = JSON.parse(response.body, symbolize_names: true) + + expect(body[:errors]).to be_nil + expect(body[:data].size).to eq 2 + data = body[:data] + expect(data[0][:provider][:id]).to eq provider1.id + expect(data[0][:provider][:name]).to eq provider1.name + expect(data[0][:plan][:id]).to eq plan1_provider1.id + expect(data[0][:plan][:name]).to eq plan1_provider1.name + expect(data[0][:plan][:price]).to eq 110 + + expect(data[1][:provider][:id]).to eq provider2.id + expect(data[1][:provider][:name]).to eq provider2.name + expect(data[1][:plan][:id]).to eq plan2_provider2.id + expect(data[1][:plan][:name]).to eq plan2_provider2.name + expect(data[1][:plan][:price]).to eq 220 + end + end + + context 'リクエストパラメータ不正' do + context 'amperage' do + it '指定されていない場合、エラーとなる' do + params.delete(:amperage) + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(400) + body = JSON.parse(response.body, symbolize_names: true) + expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + + expect(body[:details].size).to eq 1 + expect(body[:details][0][:field]).to eq 'amperage' + expect(body[:details][0][:message]).to include('のいずれかを指定してください。') + end + + it '文字列の場合、エラーとなる' do + params[:amperage] = '10' + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(400) + body = JSON.parse(response.body, symbolize_names: true) + expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + + expect(body[:details].size).to eq 1 + expect(body[:details][0][:field]).to eq 'amperage' + expect(body[:details][0][:message]).to include('のいずれかを指定してください。') + end + end + + context 'electricity_usage_kwh' do + it '指定されていない場合、エラーとなる' do + params.delete(:electricity_usage_kwh) + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(400) + body = JSON.parse(response.body, symbolize_names: true) + expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + + expect(body[:details].size).to eq 1 + expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' + expect(body[:details][0][:message]).to eq '整数を指定してください。' + end + + it '文字列の場合、エラーとなる' do + params[:electricity_usage_kwh] = '20' + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(400) + body = JSON.parse(response.body, symbolize_names: true) + expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + + expect(body[:details].size).to eq 1 + expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' + expect(body[:details][0][:message]).to eq '整数を指定してください。' + end + + it '実数の場合、エラーとなる' do + params[:electricity_usage_kwh] = 20.0 + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(400) + body = JSON.parse(response.body, symbolize_names: true) + expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + + expect(body[:details].size).to eq 1 + expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' + expect(body[:details][0][:message]).to eq '整数を指定してください。' + end + end + end + end +end From f85d282dada31e96e3c8109348c88033de078688 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 14:27:54 +0900 Subject: [PATCH 30/63] =?UTF-8?q?Seed=E3=81=AE=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=92=E6=8A=95=E5=85=A5=E3=81=97=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E7=99=BA=E7=94=9F=E3=81=97=E3=81=AA=E3=81=84=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/measured_rate.rb | 2 +- serverside_challenge_2/challenge/app/models/plan.rb | 2 +- .../challenge/spec/models/plan_spec.rb | 2 +- .../spec/requests/api/electricity/calculate_spec.rb | 11 +++++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/measured_rate.rb b/serverside_challenge_2/challenge/app/models/measured_rate.rb index efc20b25b..d20b06349 100644 --- a/serverside_challenge_2/challenge/app/models/measured_rate.rb +++ b/serverside_challenge_2/challenge/app/models/measured_rate.rb @@ -18,7 +18,7 @@ def electricity_usage_max=(value) class << self def calc_prices(electricity_usage_kwh) - rows = MeasuredRate.where("electricity_usage_min <= ?", electricity_usage_kwh).includes(:plan) + rows = MeasuredRate.where("electricity_usage_min <= ?", electricity_usage_kwh) rows.inject({}) do |sum, row| sum[row.plan_id] ||= { price: 0 } sum[row.plan_id][:price] += row.calc_row_price(electricity_usage_kwh) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 1c1cc129c..41e41ef1b 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -21,7 +21,7 @@ def calc_prices(amperage, electricity_usage_kwh) plans = Plan.all.includes(:provider) basic_prices_hash = BasicPrice.calc_prices(amperage) measured_rates_hash = MeasuredRate.calc_prices(electricity_usage_kwh) - response = plans.map do |plan| + response = plans.filter_map do |plan| basic_price = basic_prices_hash[plan.id] next if basic_price.nil? diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index 74a4c8f7c..0ce86846f 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -185,7 +185,7 @@ res = Plan.calc_prices(10, 1) expect(res[:errors]).to be_nil - expect(res[:plans].size).to eq 2 + expect(res[:plans].size).to eq 1 plans = res[:plans] expect(plans[0][:plan]).to eq plan1_provider1 diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb index a52671175..cb3f729ec 100644 --- a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb +++ b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb @@ -50,6 +50,17 @@ expect(data[1][:plan][:name]).to eq plan2_provider2.name expect(data[1][:plan][:price]).to eq 220 end + + it 'Seedデータを使用して計算可能であること' do + Rails.application.load_seed + post '/api/electricity/calculate', headers: headers, params: params.to_json + + expect(response).to have_http_status(200) + body = JSON.parse(response.body, symbolize_names: true) + + expect(body[:errors]).to be_nil + expect(body[:data]).to be_present + end end context 'リクエストパラメータ不正' do From 5f2b97ab4e8b5bcf7e8de7c4809cf449188e02e8 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 14:58:02 +0900 Subject: [PATCH 31/63] =?UTF-8?q?openapi=E5=AE=9A=E7=BE=A9=E3=81=AE?= =?UTF-8?q?=E5=87=BA=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Gemfile | 1 + serverside_challenge_2/challenge/Gemfile.lock | 5 + .../challenge/doc/openapi.yaml | 123 ++++++++++++++++++ .../api/electricity/calculate_spec.rb | 27 +++- .../challenge/spec/spec_helper.rb | 5 + 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 serverside_challenge_2/challenge/doc/openapi.yaml diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 4065682b4..118a93de7 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -45,6 +45,7 @@ group :development, :test do gem "factory_bot_rails" gem "database_cleaner-active_record" gem "dotenv-rails" + gem "rspec-openapi" end group :development do diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index efddf708a..d8dacf95d 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -199,6 +199,10 @@ GEM rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-openapi (0.18.3) + actionpack (>= 5.2.0) + rails-dom-testing + rspec-core rspec-rails (7.0.1) actionpack (>= 7.0) activesupport (>= 7.0) @@ -271,6 +275,7 @@ DEPENDENCIES rack-timeout (~> 0.7.0) rails (~> 7.0.8) rails-erd (~> 1.7, >= 1.7.2) + rspec-openapi rspec-rails rubocop-rails-omakase rubocop-rspec diff --git a/serverside_challenge_2/challenge/doc/openapi.yaml b/serverside_challenge_2/challenge/doc/openapi.yaml new file mode 100644 index 000000000..c9194b3df --- /dev/null +++ b/serverside_challenge_2/challenge/doc/openapi.yaml @@ -0,0 +1,123 @@ +--- +openapi: 3.0.3 +info: + title: app + version: 1.0.0 +servers: [] +paths: + "/api/electricity/calculate": + post: + summary: create + tags: + - Api::Electricity::Calculate + requestBody: + content: + application/json: + schema: + type: object + properties: + amperage: + type: integer + electricity_usage_kwh: + type: integer + required: + - amperage + - electricity_usage_kwh + example: + amperage: 10 + electricity_usage_kwh: 1 + responses: + '200': + description: 正常 + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + type: object + properties: + provider: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + plan: + type: object + properties: + id: + type: integer + name: + type: string + price: + type: integer + required: + - id + - name + - price + required: + - provider + - plan + required: + - data + example: + data: + - provider: + id: 108 + name: 東京電力エナジーパートナー + plan: + id: 107 + name: 従量電灯B + price: 305 + - provider: + id: 108 + name: 東京電力エナジーパートナー + plan: + id: 108 + name: スタンダードS + price: 341 + - provider: + id: 110 + name: Looopでんき + plan: + id: 110 + name: おうちプラン + price: 28 + '400': + description: パラメータ不正 + content: + application/json: + schema: + type: object + properties: + message: + type: string + details: + type: array + items: + type: object + properties: + field: + type: string + message: + type: string + required: + - field + - message + required: + - message + - details + example: + message: リクエストパラメーターが正しくありません。 + details: + - field: amperage + message: 10/15/20/30/40/50/60のいずれかを指定してください。 + - field: electricity_usage_kwh + message: 整数を指定してください。 diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb index cb3f729ec..2cecc30ce 100644 --- a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb +++ b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb @@ -12,7 +12,9 @@ let(:plan2_amp_10) { create(:basic_price, plan: plan2_provider2, amperage: 10, price: 200.01) } let(:plan2_rate_0) { create(:measured_rate, plan: plan2_provider2, electricity_usage_min: 0, electricity_usage_max: nil, price: 20.03) } - describe 'create' do + describe 'create', openapi: { + summary: '電気料金を計算する' + } do let(:params) do { amperage: 10, @@ -51,7 +53,9 @@ expect(data[1][:plan][:price]).to eq 220 end - it 'Seedデータを使用して計算可能であること' do + it 'Seedデータを使用して計算可能であること', openapi: { + description: '正常' + } do Rails.application.load_seed post '/api/electricity/calculate', headers: headers, params: params.to_json @@ -131,6 +135,25 @@ expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' expect(body[:details][0][:message]).to eq '整数を指定してください。' end + + it 'amperage,electricity_usage_kwhが両方不正の場合、エラーとなる', openapi: { + description: 'パラメータ不正' + } do + post '/api/electricity/calculate', headers: headers, params: { + amperage: '10', + electricity_usage_kwh: '20.0' + }.to_json + + expect(response).to have_http_status(400) + body = JSON.parse(response.body, symbolize_names: true) + expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + + expect(body[:details].size).to eq 2 + expect(body[:details][0][:field]).to eq 'amperage' + expect(body[:details][0][:message]).to include('いずれかを指定してください。') + expect(body[:details][1][:field]).to eq 'electricity_usage_kwh' + expect(body[:details][1][:message]).to eq '整数を指定してください。' + end end end end diff --git a/serverside_challenge_2/challenge/spec/spec_helper.rb b/serverside_challenge_2/challenge/spec/spec_helper.rb index 899a1b9be..7fed70dee 100644 --- a/serverside_challenge_2/challenge/spec/spec_helper.rb +++ b/serverside_challenge_2/challenge/spec/spec_helper.rb @@ -1,4 +1,9 @@ require 'database_cleaner/active_record' +require 'rspec/openapi' + +RSpec::OpenAPI.description_builder = ->(example) { + example.metadata[:openapi_description] || example.description +} # 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`. From 667295a82e863b5ea4195403e1a85913c16599d5 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 15:08:30 +0900 Subject: [PATCH 32/63] =?UTF-8?q?openapi=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E5=80=A4=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/doc/openapi.yaml | 22 +++++++++++-------- .../api/electricity/calculate_spec.rb | 1 + .../challenge/spec/spec_helper.rb | 10 +++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/serverside_challenge_2/challenge/doc/openapi.yaml b/serverside_challenge_2/challenge/doc/openapi.yaml index c9194b3df..16821f62d 100644 --- a/serverside_challenge_2/challenge/doc/openapi.yaml +++ b/serverside_challenge_2/challenge/doc/openapi.yaml @@ -1,15 +1,19 @@ --- openapi: 3.0.3 info: - title: app + title: Challenge API version: 1.0.0 + description: '' + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html servers: [] paths: "/api/electricity/calculate": post: - summary: create + summary: 電気料金を計算する tags: - - Api::Electricity::Calculate + - 電気料金 requestBody: content: application/json: @@ -70,24 +74,24 @@ paths: example: data: - provider: - id: 108 + id: 5 name: 東京電力エナジーパートナー plan: - id: 107 + id: 5 name: 従量電灯B price: 305 - provider: - id: 108 + id: 5 name: 東京電力エナジーパートナー plan: - id: 108 + id: 6 name: スタンダードS price: 341 - provider: - id: 110 + id: 7 name: Looopでんき plan: - id: 110 + id: 8 name: おうちプラン price: 28 '400': diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb index 2cecc30ce..8d292688a 100644 --- a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb +++ b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb @@ -13,6 +13,7 @@ let(:plan2_rate_0) { create(:measured_rate, plan: plan2_provider2, electricity_usage_min: 0, electricity_usage_max: nil, price: 20.03) } describe 'create', openapi: { + tags: [ '電気料金' ], summary: '電気料金を計算する' } do let(:params) do diff --git a/serverside_challenge_2/challenge/spec/spec_helper.rb b/serverside_challenge_2/challenge/spec/spec_helper.rb index 7fed70dee..1b04f2e5a 100644 --- a/serverside_challenge_2/challenge/spec/spec_helper.rb +++ b/serverside_challenge_2/challenge/spec/spec_helper.rb @@ -1,9 +1,19 @@ require 'database_cleaner/active_record' require 'rspec/openapi' +RSpec::OpenAPI.title = 'Challenge API' +RSpec::OpenAPI.info = { + description: '' +} RSpec::OpenAPI.description_builder = ->(example) { example.metadata[:openapi_description] || example.description } +RSpec::OpenAPI.summary_builder = ->(example) { + example.metadata.dig(:example_group, :openapi, :summary) +} +RSpec::OpenAPI.tags_builder = ->(example) { + example.metadata.dig(:example_group, :parent_example_group, :openapi, :tags) +} # 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`. From d78268d7394763d231ead72e32cdc750a5cb99d1 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 16:48:32 +0900 Subject: [PATCH 33/63] =?UTF-8?q?react=20project=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/frontend/.gitignore | 24 + .../challenge/frontend/eslint.config.js | 28 + .../challenge/frontend/index.html | 13 + .../challenge/frontend/package.json | 29 + .../challenge/frontend/pnpm-lock.yaml | 1989 +++++++++++++++++ .../challenge/frontend/public/.gitkeep | 0 .../challenge/frontend/src/App.css | 42 + .../challenge/frontend/src/App.tsx | 25 + .../challenge/frontend/src/index.css | 68 + .../challenge/frontend/src/main.tsx | 10 + .../challenge/frontend/src/vite-env.d.ts | 1 + .../challenge/frontend/tsconfig.app.json | 26 + .../challenge/frontend/tsconfig.json | 7 + .../challenge/frontend/tsconfig.node.json | 24 + .../challenge/frontend/vite.config.ts | 7 + 15 files changed, 2293 insertions(+) create mode 100644 serverside_challenge_2/challenge/frontend/.gitignore create mode 100644 serverside_challenge_2/challenge/frontend/eslint.config.js create mode 100644 serverside_challenge_2/challenge/frontend/index.html create mode 100644 serverside_challenge_2/challenge/frontend/package.json create mode 100644 serverside_challenge_2/challenge/frontend/pnpm-lock.yaml create mode 100644 serverside_challenge_2/challenge/frontend/public/.gitkeep create mode 100644 serverside_challenge_2/challenge/frontend/src/App.css create mode 100644 serverside_challenge_2/challenge/frontend/src/App.tsx create mode 100644 serverside_challenge_2/challenge/frontend/src/index.css create mode 100644 serverside_challenge_2/challenge/frontend/src/main.tsx create mode 100644 serverside_challenge_2/challenge/frontend/src/vite-env.d.ts create mode 100644 serverside_challenge_2/challenge/frontend/tsconfig.app.json create mode 100644 serverside_challenge_2/challenge/frontend/tsconfig.json create mode 100644 serverside_challenge_2/challenge/frontend/tsconfig.node.json create mode 100644 serverside_challenge_2/challenge/frontend/vite.config.ts diff --git a/serverside_challenge_2/challenge/frontend/.gitignore b/serverside_challenge_2/challenge/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/serverside_challenge_2/challenge/frontend/eslint.config.js b/serverside_challenge_2/challenge/frontend/eslint.config.js new file mode 100644 index 000000000..092408a9f --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/serverside_challenge_2/challenge/frontend/index.html b/serverside_challenge_2/challenge/frontend/index.html new file mode 100644 index 000000000..e4b78eae1 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/serverside_challenge_2/challenge/frontend/package.json b/serverside_challenge_2/challenge/frontend/package.json new file mode 100644 index 000000000..b34f335da --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/package.json @@ -0,0 +1,29 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "eslint": "^9.13.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.10" + } +} diff --git a/serverside_challenge_2/challenge/frontend/pnpm-lock.yaml b/serverside_challenge_2/challenge/frontend/pnpm-lock.yaml new file mode 100644 index 000000000..84d3789b4 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/pnpm-lock.yaml @@ -0,0 +1,1989 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@eslint/js': + specifier: ^9.13.0 + version: 9.14.0 + '@types/react': + specifier: ^18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + '@vitejs/plugin-react': + specifier: ^4.3.3 + version: 4.3.3(vite@5.4.10) + eslint: + specifier: ^9.13.0 + version: 9.14.0 + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.0.0(eslint@9.14.0) + eslint-plugin-react-refresh: + specifier: ^0.4.14 + version: 0.4.14(eslint@9.14.0) + globals: + specifier: ^15.11.0 + version: 15.11.0 + typescript: + specifier: ~5.6.2 + version: 5.6.3 + typescript-eslint: + specifier: ^8.11.0 + version: 8.12.2(eslint@9.14.0)(typescript@5.6.3) + vite: + specifier: ^5.4.10 + version: 5.4.10 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.7.0': + resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.14.0': + resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.2': + resolution: {integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.0': + resolution: {integrity: sha512-xnRgu9DxZbkWak/te3fcytNyp8MTbuiZIaueg2rgEvBuN55n04nwLYLU9TX/VVlusc9L2ZNXi99nUFNkHXtr5g==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rollup/rollup-android-arm-eabi@4.24.3': + resolution: {integrity: sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.24.3': + resolution: {integrity: sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.24.3': + resolution: {integrity: sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.24.3': + resolution: {integrity: sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.24.3': + resolution: {integrity: sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.24.3': + resolution: {integrity: sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.24.3': + resolution: {integrity: sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.24.3': + resolution: {integrity: sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.24.3': + resolution: {integrity: sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.24.3': + resolution: {integrity: sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': + resolution: {integrity: sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.24.3': + resolution: {integrity: sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.24.3': + resolution: {integrity: sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.24.3': + resolution: {integrity: sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.24.3': + resolution: {integrity: sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.24.3': + resolution: {integrity: sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.24.3': + resolution: {integrity: sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.24.3': + resolution: {integrity: sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + + '@typescript-eslint/eslint-plugin@8.12.2': + resolution: {integrity: sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.12.2': + resolution: {integrity: sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@8.12.2': + resolution: {integrity: sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.12.2': + resolution: {integrity: sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@8.12.2': + resolution: {integrity: sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.12.2': + resolution: {integrity: sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.12.2': + resolution: {integrity: sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.12.2': + resolution: {integrity: sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@4.3.3': + resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001677: + resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + electron-to-chromium@1.5.50: + resolution: {integrity: sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.0.0: + resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.14: + resolution: {integrity: sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==} + peerDependencies: + eslint: '>=7' + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.14.0: + resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.11.0: + resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + engines: {node: '>=18'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.24.3: + resolution: {integrity: sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.4.0: + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.12.2: + resolution: {integrity: sha512-UbuVUWSrHVR03q9CWx+JDHeO6B/Hr9p4U5lRH++5tq/EbFq1faYZe50ZSBePptgfIKLEti0aPQ3hFgnPVcd8ZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite@5.4.10: + resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.2': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-compilation-targets@7.25.9': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.14.0)': + dependencies: + eslint: 9.14.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.7.0': {} + + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.14.0': {} + + '@eslint/object-schema@2.1.4': {} + + '@eslint/plugin-kit@0.2.2': + dependencies: + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.0': {} + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@rollup/rollup-android-arm-eabi@4.24.3': + optional: true + + '@rollup/rollup-android-arm64@4.24.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.24.3': + optional: true + + '@rollup/rollup-darwin-x64@4.24.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.24.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.24.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.24.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.24.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.24.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.24.3': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.24.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.24.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.24.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.24.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.24.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.24.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.24.3': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.0 + + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.1': + dependencies: + '@types/react': 18.3.12 + + '@types/react@18.3.12': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.12.2(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.12.2 + '@typescript-eslint/type-utils': 8.12.2(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.12.2 + eslint: 9.14.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.12.2 + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.12.2 + debug: 4.3.7 + eslint: 9.14.0 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.12.2': + dependencies: + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/visitor-keys': 8.12.2 + + '@typescript-eslint/type-utils@8.12.2(eslint@9.14.0)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.14.0)(typescript@5.6.3) + debug: 4.3.7 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.12.2': {} + + '@typescript-eslint/typescript-estree@8.12.2(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/visitor-keys': 8.12.2 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.12.2(eslint@9.14.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@typescript-eslint/scope-manager': 8.12.2 + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) + eslint: 9.14.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.12.2': + dependencies: + '@typescript-eslint/types': 8.12.2 + eslint-visitor-keys: 3.4.3 + + '@vitejs/plugin-react@4.3.3(vite@5.4.10)': + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.10 + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.2: + dependencies: + caniuse-lite: 1.0.30001677 + electron-to-chromium: 1.5.50 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001677: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.3: {} + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + electron-to-chromium@1.5.50: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.0.0(eslint@9.14.0): + dependencies: + eslint: 9.14.0 + + eslint-plugin-react-refresh@0.4.14(eslint@9.14.0): + dependencies: + eslint: 9.14.0 + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.14.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.7.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.14.0 + '@eslint/plugin-kit': 0.2.2 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.0 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.11.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + ms@2.1.3: {} + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.18: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + resolve-from@4.0.0: {} + + reusify@1.0.4: {} + + rollup@4.24.3: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.24.3 + '@rollup/rollup-android-arm64': 4.24.3 + '@rollup/rollup-darwin-arm64': 4.24.3 + '@rollup/rollup-darwin-x64': 4.24.3 + '@rollup/rollup-freebsd-arm64': 4.24.3 + '@rollup/rollup-freebsd-x64': 4.24.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.3 + '@rollup/rollup-linux-arm-musleabihf': 4.24.3 + '@rollup/rollup-linux-arm64-gnu': 4.24.3 + '@rollup/rollup-linux-arm64-musl': 4.24.3 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.3 + '@rollup/rollup-linux-riscv64-gnu': 4.24.3 + '@rollup/rollup-linux-s390x-gnu': 4.24.3 + '@rollup/rollup-linux-x64-gnu': 4.24.3 + '@rollup/rollup-linux-x64-musl': 4.24.3 + '@rollup/rollup-win32-arm64-msvc': 4.24.3 + '@rollup/rollup-win32-ia32-msvc': 4.24.3 + '@rollup/rollup-win32-x64-msvc': 4.24.3 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + text-table@0.2.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.4.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.12.2(eslint@9.14.0)(typescript@5.6.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/parser': 8.12.2(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.14.0)(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + typescript@5.6.3: {} + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@5.4.10: + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.24.3 + optionalDependencies: + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/serverside_challenge_2/challenge/frontend/public/.gitkeep b/serverside_challenge_2/challenge/frontend/public/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/serverside_challenge_2/challenge/frontend/src/App.css b/serverside_challenge_2/challenge/frontend/src/App.css new file mode 100644 index 000000000..b9d355df2 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/serverside_challenge_2/challenge/frontend/src/App.tsx b/serverside_challenge_2/challenge/frontend/src/App.tsx new file mode 100644 index 000000000..ed2fbd876 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/App.tsx @@ -0,0 +1,25 @@ +import { useState } from 'react' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App diff --git a/serverside_challenge_2/challenge/frontend/src/index.css b/serverside_challenge_2/challenge/frontend/src/index.css new file mode 100644 index 000000000..6119ad9a8 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/serverside_challenge_2/challenge/frontend/src/main.tsx b/serverside_challenge_2/challenge/frontend/src/main.tsx new file mode 100644 index 000000000..bef5202a3 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/serverside_challenge_2/challenge/frontend/src/vite-env.d.ts b/serverside_challenge_2/challenge/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/serverside_challenge_2/challenge/frontend/tsconfig.app.json b/serverside_challenge_2/challenge/frontend/tsconfig.app.json new file mode 100644 index 000000000..f867de0dd --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/serverside_challenge_2/challenge/frontend/tsconfig.json b/serverside_challenge_2/challenge/frontend/tsconfig.json new file mode 100644 index 000000000..1ffef600d --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/serverside_challenge_2/challenge/frontend/tsconfig.node.json b/serverside_challenge_2/challenge/frontend/tsconfig.node.json new file mode 100644 index 000000000..abcd7f0da --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/serverside_challenge_2/challenge/frontend/vite.config.ts b/serverside_challenge_2/challenge/frontend/vite.config.ts new file mode 100644 index 000000000..8b0f57b91 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) From 37153482902063b5a885c4462b7f8d9b528c417a Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 23:13:57 +0900 Subject: [PATCH 34/63] =?UTF-8?q?frontend=E3=81=AEapi=E3=83=AA=E3=82=AF?= =?UTF-8?q?=E3=82=A8=E3=82=B9=E3=83=88=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/frontend/.env.production | 1 + .../challenge/frontend/index.html | 3 +- .../challenge/frontend/src/App.tsx | 103 +++++++++++++++--- 3 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 serverside_challenge_2/challenge/frontend/.env.production diff --git a/serverside_challenge_2/challenge/frontend/.env.production b/serverside_challenge_2/challenge/frontend/.env.production new file mode 100644 index 000000000..4c50c3dcf --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/.env.production @@ -0,0 +1 @@ +VITE_API_URL=https://three-chairs.com diff --git a/serverside_challenge_2/challenge/frontend/index.html b/serverside_challenge_2/challenge/frontend/index.html index e4b78eae1..797c7d5f9 100644 --- a/serverside_challenge_2/challenge/frontend/index.html +++ b/serverside_challenge_2/challenge/frontend/index.html @@ -2,9 +2,8 @@ - - Vite + React + TS + Challenge
diff --git a/serverside_challenge_2/challenge/frontend/src/App.tsx b/serverside_challenge_2/challenge/frontend/src/App.tsx index ed2fbd876..c5592b7f3 100644 --- a/serverside_challenge_2/challenge/frontend/src/App.tsx +++ b/serverside_challenge_2/challenge/frontend/src/App.tsx @@ -1,25 +1,98 @@ import { useState } from 'react' import './App.css' +const AMPERAGE_LIST = [10, 15, 20, 30, 40, 50, 60]; +type ResponseData = { + provider: { + id: number; + name: string; + }, + plan: { + id: number; + name: string; + price: number; + }, +} + function App() { - const [count, setCount] = useState(0) + const [electricityUsageKwh, setElectricityUsageKwh] = useState(0); + const [amperage, setAmperage] = useState(undefined); + const [prices, setPrices] = useState([]); + + const requestCalcPrices = async () => { + if (amperage === undefined) return; + try { + const response = await fetch(`${import.meta.env.VITE_API_URL}/api/electricity/calculate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + amperage, + electricity_usage_kwh: electricityUsageKwh, + }), + }); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } - return ( + const data: { data: ResponseData[] } = await response.json(); + setPrices(data.data); + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + return ( <> -

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

+

電力料金計算

+
+
+ + +
+
+ + { + const found = event.target.value.match(/\d/g) + const text = found ? found.join('') : ''; + setElectricityUsageKwh(parseInt(text.length ? text : '0', 10)) + }} + /> +
+
+ +
+
+ +
+ {prices.map((price) => { + return ( +
+

{price.provider.name}

+ {price.plan.name}{price.plan.price}円 +
+ ); + })}
-

- Click on the Vite and React logos to learn more -

- ) -} + ) + } export default App From ff816527d70c8960e24aa6cdb87025acc6438ad4 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 3 Nov 2024 23:14:51 +0900 Subject: [PATCH 35/63] =?UTF-8?q?CORS=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/.env.sample | 3 +-- serverside_challenge_2/challenge/.env.test | 5 ++++- serverside_challenge_2/challenge/config/initializers/cors.rb | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/serverside_challenge_2/challenge/.env.sample b/serverside_challenge_2/challenge/.env.sample index cba9fddd8..af69cb27b 100644 --- a/serverside_challenge_2/challenge/.env.sample +++ b/serverside_challenge_2/challenge/.env.sample @@ -6,5 +6,4 @@ DATABASE_PASSWORD=password # サービスのドメイン設定 SERVICE_PROTOCOL=http -SERVICE_DOMAIN=localhost -SERVICE_PORT=3000 +FRONTEND_DOMAIN=localhost:5173 diff --git a/serverside_challenge_2/challenge/.env.test b/serverside_challenge_2/challenge/.env.test index 41a42205a..e77acf0cc 100644 --- a/serverside_challenge_2/challenge/.env.test +++ b/serverside_challenge_2/challenge/.env.test @@ -1,4 +1,7 @@ DATABASE_HOST=db DATABASE_PORT=5432 DATABASE_USER=postgres -DATABASE_PASSWORD=password \ No newline at end of file +DATABASE_PASSWORD=password + +SERVICE_PROTOCOL=http +FRONTEND_DOMAIN=localhost:5173 diff --git a/serverside_challenge_2/challenge/config/initializers/cors.rb b/serverside_challenge_2/challenge/config/initializers/cors.rb index 39fee8669..1f3cf8c3e 100644 --- a/serverside_challenge_2/challenge/config/initializers/cors.rb +++ b/serverside_challenge_2/challenge/config/initializers/cors.rb @@ -7,7 +7,7 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do - origins "#{ENV['SERVICE_PROTOCOL']}://#{ENV['SERVICE_DOMAIN']}#{ENV['SERVICE_PORT']}" + origins "#{ENV['SERVICE_PROTOCOL']}://#{ENV['FRONTEND_DOMAIN']}" resource "*", headers: :any, From ab8039cb78778ec7804eb23a514709f8efef6758 Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 4 Nov 2024 00:22:40 +0900 Subject: [PATCH 36/63] =?UTF-8?q?api=E3=81=AE=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E6=A7=8B=E6=88=90=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/.gitignore | 35 ++---------------- .../challenge/{ => api}/.env.sample | 0 .../challenge/{ => api}/.env.test | 0 .../challenge/{ => api}/.gitattributes | 0 .../challenge/api/.gitignore | 32 ++++++++++++++++ .../challenge/{ => api}/.rspec | 0 .../challenge/{ => api}/.rubocop.yml | 0 .../challenge/{ => api}/.ruby-version | 0 .../challenge/{ => api}/Dockerfile | 0 .../challenge/{ => api}/Gemfile | 0 .../challenge/{ => api}/Gemfile.lock | 0 .../challenge/{ => api}/README.md | 0 .../challenge/{ => api}/Rakefile | 0 .../app/channels/application_cable/channel.rb | 0 .../channels/application_cable/connection.rb | 0 .../api/electricity/calculate_controller.rb | 0 .../app/controllers/application_controller.rb | 0 .../{ => api}/app/controllers/concerns/.keep | 0 .../{ => api}/app/jobs/application_job.rb | 0 .../app/mailers/application_mailer.rb | 0 .../app/models/application_record.rb | 0 .../{ => api}/app/models/basic_price.rb | 0 .../{ => api}/app/models/concerns/.keep | 0 .../{ => api}/app/models/measured_rate.rb | 0 .../challenge/{ => api}/app/models/plan.rb | 0 .../{ => api}/app/models/provider.rb | 0 .../electricity/calculate/create.json.jb | 0 .../app/views/layouts/mailer.html.erb | 0 .../app/views/layouts/mailer.text.erb | 0 .../challenge/{ => api}/bin/rails | 0 .../challenge/{ => api}/bin/rake | 0 .../challenge/{ => api}/bin/setup | 0 .../challenge/{ => api}/config.ru | 0 .../challenge/{ => api}/config/application.rb | 0 .../challenge/{ => api}/config/boot.rb | 0 .../challenge/{ => api}/config/cable.yml | 0 .../{ => api}/config/credentials.yml.enc | 0 .../challenge/{ => api}/config/database.yml | 0 .../challenge/{ => api}/config/environment.rb | 0 .../config/environments/development.rb | 0 .../config/environments/production.rb | 0 .../{ => api}/config/environments/test.rb | 0 .../{ => api}/config/initializers/cors.rb | 0 .../initializers/filter_parameter_logging.rb | 0 .../config/initializers/inflections.rb | 0 .../config/initializers/rack_timeout.rb | 0 .../challenge/{ => api}/config/locales/en.yml | 0 .../challenge/{ => api}/config/puma.rb | 0 .../challenge/{ => api}/config/routes.rb | 0 .../challenge/{ => api}/config/storage.yml | 0 .../20241027065846_create_providers.rb | 0 .../db/migrate/20241027065931_create_plans.rb | 0 .../20241027072120_create_basic_prices.rb | 0 .../20241027072347_create_measured_rates.rb | 0 .../challenge/{ => api}/db/schema.rb | 0 .../challenge/{ => api}/db/seeds.rb | 0 .../{ => api}/db/seeds/basic_prices.csv | 0 .../{ => api}/db/seeds/measured_rates.csv | 0 .../challenge/{ => api}/doc/openapi.yaml | 0 .../challenge/{ => api}/erd.pdf | Bin .../challenge/{ => api}/lib/tasks/.keep | 0 .../challenge/{ => api}/log/.keep | 0 .../challenge/{ => api}/public/robots.txt | 0 .../challenge/{ => api}/spec/db/seeds_spec.rb | 0 .../{ => api}/spec/factories/basic_price.rb | 0 .../{ => api}/spec/factories/measure_rate.rb | 0 .../{ => api}/spec/factories/plan.rb | 0 .../{ => api}/spec/factories/provider.rb | 0 .../{ => api}/spec/models/basic_price_spec.rb | 0 .../spec/models/measure_rate_spec.rb | 0 .../{ => api}/spec/models/plan_spec.rb | 0 .../{ => api}/spec/models/provider_spec.rb | 0 .../challenge/{ => api}/spec/rails_helper.rb | 0 .../api/electricity/calculate_spec.rb | 0 .../challenge/{ => api}/spec/spec_helper.rb | 0 .../challenge/{ => api}/storage/.keep | 0 .../application_cable/connection_test.rb | 0 .../{ => api}/test/controllers/.keep | 0 .../{ => api}/test/fixtures/files/.keep | 0 .../{ => api}/test/integration/.keep | 0 .../challenge/{ => api}/test/mailers/.keep | 0 .../challenge/{ => api}/test/models/.keep | 0 .../challenge/{ => api}/test/test_helper.rb | 0 .../challenge/{ => api}/tmp/.keep | 0 .../challenge/{ => api}/tmp/pids/.keep | 0 .../challenge/{ => api}/tmp/storage/.keep | 0 .../challenge/{ => api}/vendor/.keep | 0 .../challenge/docker-compose.yml | 6 ++- 88 files changed, 39 insertions(+), 34 deletions(-) rename serverside_challenge_2/challenge/{ => api}/.env.sample (100%) rename serverside_challenge_2/challenge/{ => api}/.env.test (100%) rename serverside_challenge_2/challenge/{ => api}/.gitattributes (100%) create mode 100644 serverside_challenge_2/challenge/api/.gitignore rename serverside_challenge_2/challenge/{ => api}/.rspec (100%) rename serverside_challenge_2/challenge/{ => api}/.rubocop.yml (100%) rename serverside_challenge_2/challenge/{ => api}/.ruby-version (100%) rename serverside_challenge_2/challenge/{ => api}/Dockerfile (100%) rename serverside_challenge_2/challenge/{ => api}/Gemfile (100%) rename serverside_challenge_2/challenge/{ => api}/Gemfile.lock (100%) rename serverside_challenge_2/challenge/{ => api}/README.md (100%) rename serverside_challenge_2/challenge/{ => api}/Rakefile (100%) rename serverside_challenge_2/challenge/{ => api}/app/channels/application_cable/channel.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/channels/application_cable/connection.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/controllers/api/electricity/calculate_controller.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/controllers/application_controller.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/controllers/concerns/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/app/jobs/application_job.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/mailers/application_mailer.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/models/application_record.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/models/basic_price.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/models/concerns/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/app/models/measured_rate.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/models/plan.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/models/provider.rb (100%) rename serverside_challenge_2/challenge/{ => api}/app/views/electricity/calculate/create.json.jb (100%) rename serverside_challenge_2/challenge/{ => api}/app/views/layouts/mailer.html.erb (100%) rename serverside_challenge_2/challenge/{ => api}/app/views/layouts/mailer.text.erb (100%) rename serverside_challenge_2/challenge/{ => api}/bin/rails (100%) rename serverside_challenge_2/challenge/{ => api}/bin/rake (100%) rename serverside_challenge_2/challenge/{ => api}/bin/setup (100%) rename serverside_challenge_2/challenge/{ => api}/config.ru (100%) rename serverside_challenge_2/challenge/{ => api}/config/application.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/boot.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/cable.yml (100%) rename serverside_challenge_2/challenge/{ => api}/config/credentials.yml.enc (100%) rename serverside_challenge_2/challenge/{ => api}/config/database.yml (100%) rename serverside_challenge_2/challenge/{ => api}/config/environment.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/environments/development.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/environments/production.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/environments/test.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/initializers/cors.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/initializers/filter_parameter_logging.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/initializers/inflections.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/initializers/rack_timeout.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/locales/en.yml (100%) rename serverside_challenge_2/challenge/{ => api}/config/puma.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/routes.rb (100%) rename serverside_challenge_2/challenge/{ => api}/config/storage.yml (100%) rename serverside_challenge_2/challenge/{ => api}/db/migrate/20241027065846_create_providers.rb (100%) rename serverside_challenge_2/challenge/{ => api}/db/migrate/20241027065931_create_plans.rb (100%) rename serverside_challenge_2/challenge/{ => api}/db/migrate/20241027072120_create_basic_prices.rb (100%) rename serverside_challenge_2/challenge/{ => api}/db/migrate/20241027072347_create_measured_rates.rb (100%) rename serverside_challenge_2/challenge/{ => api}/db/schema.rb (100%) rename serverside_challenge_2/challenge/{ => api}/db/seeds.rb (100%) rename serverside_challenge_2/challenge/{ => api}/db/seeds/basic_prices.csv (100%) rename serverside_challenge_2/challenge/{ => api}/db/seeds/measured_rates.csv (100%) rename serverside_challenge_2/challenge/{ => api}/doc/openapi.yaml (100%) rename serverside_challenge_2/challenge/{ => api}/erd.pdf (100%) rename serverside_challenge_2/challenge/{ => api}/lib/tasks/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/log/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/public/robots.txt (100%) rename serverside_challenge_2/challenge/{ => api}/spec/db/seeds_spec.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/factories/basic_price.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/factories/measure_rate.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/factories/plan.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/factories/provider.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/models/basic_price_spec.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/models/measure_rate_spec.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/models/plan_spec.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/models/provider_spec.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/rails_helper.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/requests/api/electricity/calculate_spec.rb (100%) rename serverside_challenge_2/challenge/{ => api}/spec/spec_helper.rb (100%) rename serverside_challenge_2/challenge/{ => api}/storage/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/test/channels/application_cable/connection_test.rb (100%) rename serverside_challenge_2/challenge/{ => api}/test/controllers/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/test/fixtures/files/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/test/integration/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/test/mailers/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/test/models/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/test/test_helper.rb (100%) rename serverside_challenge_2/challenge/{ => api}/tmp/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/tmp/pids/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/tmp/storage/.keep (100%) rename serverside_challenge_2/challenge/{ => api}/vendor/.keep (100%) diff --git a/serverside_challenge_2/challenge/.gitignore b/serverside_challenge_2/challenge/.gitignore index 55254ed9b..ebf869bcf 100644 --- a/serverside_challenge_2/challenge/.gitignore +++ b/serverside_challenge_2/challenge/.gitignore @@ -1,32 +1,3 @@ -# See https://help.github.com/articles/ignoring-files for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile '~/.gitignore_global' - -# Ignore bundler config. -/.bundle - -# Ignore all logfiles and tempfiles. -/log/* -/tmp/* -!/log/.keep -!/tmp/.keep - -# Ignore pidfiles, but keep the directory. -/tmp/pids/* -!/tmp/pids/ -!/tmp/pids/.keep - -# Ignore uploaded files in development. -/storage/* -!/storage/.keep -/tmp/storage/* -!/tmp/storage/ -!/tmp/storage/.keep - -# Ignore master key for decrypting credentials and more. -/config/master.key - -# Application -.env \ No newline at end of file +.idea +.vscode +.DS_Store \ No newline at end of file diff --git a/serverside_challenge_2/challenge/.env.sample b/serverside_challenge_2/challenge/api/.env.sample similarity index 100% rename from serverside_challenge_2/challenge/.env.sample rename to serverside_challenge_2/challenge/api/.env.sample diff --git a/serverside_challenge_2/challenge/.env.test b/serverside_challenge_2/challenge/api/.env.test similarity index 100% rename from serverside_challenge_2/challenge/.env.test rename to serverside_challenge_2/challenge/api/.env.test diff --git a/serverside_challenge_2/challenge/.gitattributes b/serverside_challenge_2/challenge/api/.gitattributes similarity index 100% rename from serverside_challenge_2/challenge/.gitattributes rename to serverside_challenge_2/challenge/api/.gitattributes diff --git a/serverside_challenge_2/challenge/api/.gitignore b/serverside_challenge_2/challenge/api/.gitignore new file mode 100644 index 000000000..55254ed9b --- /dev/null +++ b/serverside_challenge_2/challenge/api/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Application +.env \ No newline at end of file diff --git a/serverside_challenge_2/challenge/.rspec b/serverside_challenge_2/challenge/api/.rspec similarity index 100% rename from serverside_challenge_2/challenge/.rspec rename to serverside_challenge_2/challenge/api/.rspec diff --git a/serverside_challenge_2/challenge/.rubocop.yml b/serverside_challenge_2/challenge/api/.rubocop.yml similarity index 100% rename from serverside_challenge_2/challenge/.rubocop.yml rename to serverside_challenge_2/challenge/api/.rubocop.yml diff --git a/serverside_challenge_2/challenge/.ruby-version b/serverside_challenge_2/challenge/api/.ruby-version similarity index 100% rename from serverside_challenge_2/challenge/.ruby-version rename to serverside_challenge_2/challenge/api/.ruby-version diff --git a/serverside_challenge_2/challenge/Dockerfile b/serverside_challenge_2/challenge/api/Dockerfile similarity index 100% rename from serverside_challenge_2/challenge/Dockerfile rename to serverside_challenge_2/challenge/api/Dockerfile diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/api/Gemfile similarity index 100% rename from serverside_challenge_2/challenge/Gemfile rename to serverside_challenge_2/challenge/api/Gemfile diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/api/Gemfile.lock similarity index 100% rename from serverside_challenge_2/challenge/Gemfile.lock rename to serverside_challenge_2/challenge/api/Gemfile.lock diff --git a/serverside_challenge_2/challenge/README.md b/serverside_challenge_2/challenge/api/README.md similarity index 100% rename from serverside_challenge_2/challenge/README.md rename to serverside_challenge_2/challenge/api/README.md diff --git a/serverside_challenge_2/challenge/Rakefile b/serverside_challenge_2/challenge/api/Rakefile similarity index 100% rename from serverside_challenge_2/challenge/Rakefile rename to serverside_challenge_2/challenge/api/Rakefile diff --git a/serverside_challenge_2/challenge/app/channels/application_cable/channel.rb b/serverside_challenge_2/challenge/api/app/channels/application_cable/channel.rb similarity index 100% rename from serverside_challenge_2/challenge/app/channels/application_cable/channel.rb rename to serverside_challenge_2/challenge/api/app/channels/application_cable/channel.rb diff --git a/serverside_challenge_2/challenge/app/channels/application_cable/connection.rb b/serverside_challenge_2/challenge/api/app/channels/application_cable/connection.rb similarity index 100% rename from serverside_challenge_2/challenge/app/channels/application_cable/connection.rb rename to serverside_challenge_2/challenge/api/app/channels/application_cable/connection.rb diff --git a/serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb b/serverside_challenge_2/challenge/api/app/controllers/api/electricity/calculate_controller.rb similarity index 100% rename from serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb rename to serverside_challenge_2/challenge/api/app/controllers/api/electricity/calculate_controller.rb diff --git a/serverside_challenge_2/challenge/app/controllers/application_controller.rb b/serverside_challenge_2/challenge/api/app/controllers/application_controller.rb similarity index 100% rename from serverside_challenge_2/challenge/app/controllers/application_controller.rb rename to serverside_challenge_2/challenge/api/app/controllers/application_controller.rb diff --git a/serverside_challenge_2/challenge/app/controllers/concerns/.keep b/serverside_challenge_2/challenge/api/app/controllers/concerns/.keep similarity index 100% rename from serverside_challenge_2/challenge/app/controllers/concerns/.keep rename to serverside_challenge_2/challenge/api/app/controllers/concerns/.keep diff --git a/serverside_challenge_2/challenge/app/jobs/application_job.rb b/serverside_challenge_2/challenge/api/app/jobs/application_job.rb similarity index 100% rename from serverside_challenge_2/challenge/app/jobs/application_job.rb rename to serverside_challenge_2/challenge/api/app/jobs/application_job.rb diff --git a/serverside_challenge_2/challenge/app/mailers/application_mailer.rb b/serverside_challenge_2/challenge/api/app/mailers/application_mailer.rb similarity index 100% rename from serverside_challenge_2/challenge/app/mailers/application_mailer.rb rename to serverside_challenge_2/challenge/api/app/mailers/application_mailer.rb diff --git a/serverside_challenge_2/challenge/app/models/application_record.rb b/serverside_challenge_2/challenge/api/app/models/application_record.rb similarity index 100% rename from serverside_challenge_2/challenge/app/models/application_record.rb rename to serverside_challenge_2/challenge/api/app/models/application_record.rb diff --git a/serverside_challenge_2/challenge/app/models/basic_price.rb b/serverside_challenge_2/challenge/api/app/models/basic_price.rb similarity index 100% rename from serverside_challenge_2/challenge/app/models/basic_price.rb rename to serverside_challenge_2/challenge/api/app/models/basic_price.rb diff --git a/serverside_challenge_2/challenge/app/models/concerns/.keep b/serverside_challenge_2/challenge/api/app/models/concerns/.keep similarity index 100% rename from serverside_challenge_2/challenge/app/models/concerns/.keep rename to serverside_challenge_2/challenge/api/app/models/concerns/.keep diff --git a/serverside_challenge_2/challenge/app/models/measured_rate.rb b/serverside_challenge_2/challenge/api/app/models/measured_rate.rb similarity index 100% rename from serverside_challenge_2/challenge/app/models/measured_rate.rb rename to serverside_challenge_2/challenge/api/app/models/measured_rate.rb diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/api/app/models/plan.rb similarity index 100% rename from serverside_challenge_2/challenge/app/models/plan.rb rename to serverside_challenge_2/challenge/api/app/models/plan.rb diff --git a/serverside_challenge_2/challenge/app/models/provider.rb b/serverside_challenge_2/challenge/api/app/models/provider.rb similarity index 100% rename from serverside_challenge_2/challenge/app/models/provider.rb rename to serverside_challenge_2/challenge/api/app/models/provider.rb diff --git a/serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb b/serverside_challenge_2/challenge/api/app/views/electricity/calculate/create.json.jb similarity index 100% rename from serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb rename to serverside_challenge_2/challenge/api/app/views/electricity/calculate/create.json.jb diff --git a/serverside_challenge_2/challenge/app/views/layouts/mailer.html.erb b/serverside_challenge_2/challenge/api/app/views/layouts/mailer.html.erb similarity index 100% rename from serverside_challenge_2/challenge/app/views/layouts/mailer.html.erb rename to serverside_challenge_2/challenge/api/app/views/layouts/mailer.html.erb diff --git a/serverside_challenge_2/challenge/app/views/layouts/mailer.text.erb b/serverside_challenge_2/challenge/api/app/views/layouts/mailer.text.erb similarity index 100% rename from serverside_challenge_2/challenge/app/views/layouts/mailer.text.erb rename to serverside_challenge_2/challenge/api/app/views/layouts/mailer.text.erb diff --git a/serverside_challenge_2/challenge/bin/rails b/serverside_challenge_2/challenge/api/bin/rails similarity index 100% rename from serverside_challenge_2/challenge/bin/rails rename to serverside_challenge_2/challenge/api/bin/rails diff --git a/serverside_challenge_2/challenge/bin/rake b/serverside_challenge_2/challenge/api/bin/rake similarity index 100% rename from serverside_challenge_2/challenge/bin/rake rename to serverside_challenge_2/challenge/api/bin/rake diff --git a/serverside_challenge_2/challenge/bin/setup b/serverside_challenge_2/challenge/api/bin/setup similarity index 100% rename from serverside_challenge_2/challenge/bin/setup rename to serverside_challenge_2/challenge/api/bin/setup diff --git a/serverside_challenge_2/challenge/config.ru b/serverside_challenge_2/challenge/api/config.ru similarity index 100% rename from serverside_challenge_2/challenge/config.ru rename to serverside_challenge_2/challenge/api/config.ru diff --git a/serverside_challenge_2/challenge/config/application.rb b/serverside_challenge_2/challenge/api/config/application.rb similarity index 100% rename from serverside_challenge_2/challenge/config/application.rb rename to serverside_challenge_2/challenge/api/config/application.rb diff --git a/serverside_challenge_2/challenge/config/boot.rb b/serverside_challenge_2/challenge/api/config/boot.rb similarity index 100% rename from serverside_challenge_2/challenge/config/boot.rb rename to serverside_challenge_2/challenge/api/config/boot.rb diff --git a/serverside_challenge_2/challenge/config/cable.yml b/serverside_challenge_2/challenge/api/config/cable.yml similarity index 100% rename from serverside_challenge_2/challenge/config/cable.yml rename to serverside_challenge_2/challenge/api/config/cable.yml diff --git a/serverside_challenge_2/challenge/config/credentials.yml.enc b/serverside_challenge_2/challenge/api/config/credentials.yml.enc similarity index 100% rename from serverside_challenge_2/challenge/config/credentials.yml.enc rename to serverside_challenge_2/challenge/api/config/credentials.yml.enc diff --git a/serverside_challenge_2/challenge/config/database.yml b/serverside_challenge_2/challenge/api/config/database.yml similarity index 100% rename from serverside_challenge_2/challenge/config/database.yml rename to serverside_challenge_2/challenge/api/config/database.yml diff --git a/serverside_challenge_2/challenge/config/environment.rb b/serverside_challenge_2/challenge/api/config/environment.rb similarity index 100% rename from serverside_challenge_2/challenge/config/environment.rb rename to serverside_challenge_2/challenge/api/config/environment.rb diff --git a/serverside_challenge_2/challenge/config/environments/development.rb b/serverside_challenge_2/challenge/api/config/environments/development.rb similarity index 100% rename from serverside_challenge_2/challenge/config/environments/development.rb rename to serverside_challenge_2/challenge/api/config/environments/development.rb diff --git a/serverside_challenge_2/challenge/config/environments/production.rb b/serverside_challenge_2/challenge/api/config/environments/production.rb similarity index 100% rename from serverside_challenge_2/challenge/config/environments/production.rb rename to serverside_challenge_2/challenge/api/config/environments/production.rb diff --git a/serverside_challenge_2/challenge/config/environments/test.rb b/serverside_challenge_2/challenge/api/config/environments/test.rb similarity index 100% rename from serverside_challenge_2/challenge/config/environments/test.rb rename to serverside_challenge_2/challenge/api/config/environments/test.rb diff --git a/serverside_challenge_2/challenge/config/initializers/cors.rb b/serverside_challenge_2/challenge/api/config/initializers/cors.rb similarity index 100% rename from serverside_challenge_2/challenge/config/initializers/cors.rb rename to serverside_challenge_2/challenge/api/config/initializers/cors.rb diff --git a/serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb b/serverside_challenge_2/challenge/api/config/initializers/filter_parameter_logging.rb similarity index 100% rename from serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb rename to serverside_challenge_2/challenge/api/config/initializers/filter_parameter_logging.rb diff --git a/serverside_challenge_2/challenge/config/initializers/inflections.rb b/serverside_challenge_2/challenge/api/config/initializers/inflections.rb similarity index 100% rename from serverside_challenge_2/challenge/config/initializers/inflections.rb rename to serverside_challenge_2/challenge/api/config/initializers/inflections.rb diff --git a/serverside_challenge_2/challenge/config/initializers/rack_timeout.rb b/serverside_challenge_2/challenge/api/config/initializers/rack_timeout.rb similarity index 100% rename from serverside_challenge_2/challenge/config/initializers/rack_timeout.rb rename to serverside_challenge_2/challenge/api/config/initializers/rack_timeout.rb diff --git a/serverside_challenge_2/challenge/config/locales/en.yml b/serverside_challenge_2/challenge/api/config/locales/en.yml similarity index 100% rename from serverside_challenge_2/challenge/config/locales/en.yml rename to serverside_challenge_2/challenge/api/config/locales/en.yml diff --git a/serverside_challenge_2/challenge/config/puma.rb b/serverside_challenge_2/challenge/api/config/puma.rb similarity index 100% rename from serverside_challenge_2/challenge/config/puma.rb rename to serverside_challenge_2/challenge/api/config/puma.rb diff --git a/serverside_challenge_2/challenge/config/routes.rb b/serverside_challenge_2/challenge/api/config/routes.rb similarity index 100% rename from serverside_challenge_2/challenge/config/routes.rb rename to serverside_challenge_2/challenge/api/config/routes.rb diff --git a/serverside_challenge_2/challenge/config/storage.yml b/serverside_challenge_2/challenge/api/config/storage.yml similarity index 100% rename from serverside_challenge_2/challenge/config/storage.yml rename to serverside_challenge_2/challenge/api/config/storage.yml diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb b/serverside_challenge_2/challenge/api/db/migrate/20241027065846_create_providers.rb similarity index 100% rename from serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb rename to serverside_challenge_2/challenge/api/db/migrate/20241027065846_create_providers.rb diff --git a/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb b/serverside_challenge_2/challenge/api/db/migrate/20241027065931_create_plans.rb similarity index 100% rename from serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb rename to serverside_challenge_2/challenge/api/db/migrate/20241027065931_create_plans.rb diff --git a/serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb b/serverside_challenge_2/challenge/api/db/migrate/20241027072120_create_basic_prices.rb similarity index 100% rename from serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb rename to serverside_challenge_2/challenge/api/db/migrate/20241027072120_create_basic_prices.rb diff --git a/serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb b/serverside_challenge_2/challenge/api/db/migrate/20241027072347_create_measured_rates.rb similarity index 100% rename from serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb rename to serverside_challenge_2/challenge/api/db/migrate/20241027072347_create_measured_rates.rb diff --git a/serverside_challenge_2/challenge/db/schema.rb b/serverside_challenge_2/challenge/api/db/schema.rb similarity index 100% rename from serverside_challenge_2/challenge/db/schema.rb rename to serverside_challenge_2/challenge/api/db/schema.rb diff --git a/serverside_challenge_2/challenge/db/seeds.rb b/serverside_challenge_2/challenge/api/db/seeds.rb similarity index 100% rename from serverside_challenge_2/challenge/db/seeds.rb rename to serverside_challenge_2/challenge/api/db/seeds.rb diff --git a/serverside_challenge_2/challenge/db/seeds/basic_prices.csv b/serverside_challenge_2/challenge/api/db/seeds/basic_prices.csv similarity index 100% rename from serverside_challenge_2/challenge/db/seeds/basic_prices.csv rename to serverside_challenge_2/challenge/api/db/seeds/basic_prices.csv diff --git a/serverside_challenge_2/challenge/db/seeds/measured_rates.csv b/serverside_challenge_2/challenge/api/db/seeds/measured_rates.csv similarity index 100% rename from serverside_challenge_2/challenge/db/seeds/measured_rates.csv rename to serverside_challenge_2/challenge/api/db/seeds/measured_rates.csv diff --git a/serverside_challenge_2/challenge/doc/openapi.yaml b/serverside_challenge_2/challenge/api/doc/openapi.yaml similarity index 100% rename from serverside_challenge_2/challenge/doc/openapi.yaml rename to serverside_challenge_2/challenge/api/doc/openapi.yaml diff --git a/serverside_challenge_2/challenge/erd.pdf b/serverside_challenge_2/challenge/api/erd.pdf similarity index 100% rename from serverside_challenge_2/challenge/erd.pdf rename to serverside_challenge_2/challenge/api/erd.pdf diff --git a/serverside_challenge_2/challenge/lib/tasks/.keep b/serverside_challenge_2/challenge/api/lib/tasks/.keep similarity index 100% rename from serverside_challenge_2/challenge/lib/tasks/.keep rename to serverside_challenge_2/challenge/api/lib/tasks/.keep diff --git a/serverside_challenge_2/challenge/log/.keep b/serverside_challenge_2/challenge/api/log/.keep similarity index 100% rename from serverside_challenge_2/challenge/log/.keep rename to serverside_challenge_2/challenge/api/log/.keep diff --git a/serverside_challenge_2/challenge/public/robots.txt b/serverside_challenge_2/challenge/api/public/robots.txt similarity index 100% rename from serverside_challenge_2/challenge/public/robots.txt rename to serverside_challenge_2/challenge/api/public/robots.txt diff --git a/serverside_challenge_2/challenge/spec/db/seeds_spec.rb b/serverside_challenge_2/challenge/api/spec/db/seeds_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/db/seeds_spec.rb rename to serverside_challenge_2/challenge/api/spec/db/seeds_spec.rb diff --git a/serverside_challenge_2/challenge/spec/factories/basic_price.rb b/serverside_challenge_2/challenge/api/spec/factories/basic_price.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/factories/basic_price.rb rename to serverside_challenge_2/challenge/api/spec/factories/basic_price.rb diff --git a/serverside_challenge_2/challenge/spec/factories/measure_rate.rb b/serverside_challenge_2/challenge/api/spec/factories/measure_rate.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/factories/measure_rate.rb rename to serverside_challenge_2/challenge/api/spec/factories/measure_rate.rb diff --git a/serverside_challenge_2/challenge/spec/factories/plan.rb b/serverside_challenge_2/challenge/api/spec/factories/plan.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/factories/plan.rb rename to serverside_challenge_2/challenge/api/spec/factories/plan.rb diff --git a/serverside_challenge_2/challenge/spec/factories/provider.rb b/serverside_challenge_2/challenge/api/spec/factories/provider.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/factories/provider.rb rename to serverside_challenge_2/challenge/api/spec/factories/provider.rb diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/api/spec/models/basic_price_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/models/basic_price_spec.rb rename to serverside_challenge_2/challenge/api/spec/models/basic_price_spec.rb diff --git a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/api/spec/models/measure_rate_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb rename to serverside_challenge_2/challenge/api/spec/models/measure_rate_spec.rb diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/api/spec/models/plan_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/models/plan_spec.rb rename to serverside_challenge_2/challenge/api/spec/models/plan_spec.rb diff --git a/serverside_challenge_2/challenge/spec/models/provider_spec.rb b/serverside_challenge_2/challenge/api/spec/models/provider_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/models/provider_spec.rb rename to serverside_challenge_2/challenge/api/spec/models/provider_spec.rb diff --git a/serverside_challenge_2/challenge/spec/rails_helper.rb b/serverside_challenge_2/challenge/api/spec/rails_helper.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/rails_helper.rb rename to serverside_challenge_2/challenge/api/spec/rails_helper.rb diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/api/spec/requests/api/electricity/calculate_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb rename to serverside_challenge_2/challenge/api/spec/requests/api/electricity/calculate_spec.rb diff --git a/serverside_challenge_2/challenge/spec/spec_helper.rb b/serverside_challenge_2/challenge/api/spec/spec_helper.rb similarity index 100% rename from serverside_challenge_2/challenge/spec/spec_helper.rb rename to serverside_challenge_2/challenge/api/spec/spec_helper.rb diff --git a/serverside_challenge_2/challenge/storage/.keep b/serverside_challenge_2/challenge/api/storage/.keep similarity index 100% rename from serverside_challenge_2/challenge/storage/.keep rename to serverside_challenge_2/challenge/api/storage/.keep diff --git a/serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb b/serverside_challenge_2/challenge/api/test/channels/application_cable/connection_test.rb similarity index 100% rename from serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb rename to serverside_challenge_2/challenge/api/test/channels/application_cable/connection_test.rb diff --git a/serverside_challenge_2/challenge/test/controllers/.keep b/serverside_challenge_2/challenge/api/test/controllers/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/controllers/.keep rename to serverside_challenge_2/challenge/api/test/controllers/.keep diff --git a/serverside_challenge_2/challenge/test/fixtures/files/.keep b/serverside_challenge_2/challenge/api/test/fixtures/files/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/fixtures/files/.keep rename to serverside_challenge_2/challenge/api/test/fixtures/files/.keep diff --git a/serverside_challenge_2/challenge/test/integration/.keep b/serverside_challenge_2/challenge/api/test/integration/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/integration/.keep rename to serverside_challenge_2/challenge/api/test/integration/.keep diff --git a/serverside_challenge_2/challenge/test/mailers/.keep b/serverside_challenge_2/challenge/api/test/mailers/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/mailers/.keep rename to serverside_challenge_2/challenge/api/test/mailers/.keep diff --git a/serverside_challenge_2/challenge/test/models/.keep b/serverside_challenge_2/challenge/api/test/models/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/models/.keep rename to serverside_challenge_2/challenge/api/test/models/.keep diff --git a/serverside_challenge_2/challenge/test/test_helper.rb b/serverside_challenge_2/challenge/api/test/test_helper.rb similarity index 100% rename from serverside_challenge_2/challenge/test/test_helper.rb rename to serverside_challenge_2/challenge/api/test/test_helper.rb diff --git a/serverside_challenge_2/challenge/tmp/.keep b/serverside_challenge_2/challenge/api/tmp/.keep similarity index 100% rename from serverside_challenge_2/challenge/tmp/.keep rename to serverside_challenge_2/challenge/api/tmp/.keep diff --git a/serverside_challenge_2/challenge/tmp/pids/.keep b/serverside_challenge_2/challenge/api/tmp/pids/.keep similarity index 100% rename from serverside_challenge_2/challenge/tmp/pids/.keep rename to serverside_challenge_2/challenge/api/tmp/pids/.keep diff --git a/serverside_challenge_2/challenge/tmp/storage/.keep b/serverside_challenge_2/challenge/api/tmp/storage/.keep similarity index 100% rename from serverside_challenge_2/challenge/tmp/storage/.keep rename to serverside_challenge_2/challenge/api/tmp/storage/.keep diff --git a/serverside_challenge_2/challenge/vendor/.keep b/serverside_challenge_2/challenge/api/vendor/.keep similarity index 100% rename from serverside_challenge_2/challenge/vendor/.keep rename to serverside_challenge_2/challenge/api/vendor/.keep diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 8ef85e300..6ef7c592d 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -13,13 +13,15 @@ services: restart: always web: container_name: challenge-api - build: . + build: + context: ./api + dockerfile: Dockerfile command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password volumes: - - .:/app + - ./api:/app ports: - "3000:3000" restart: always From 2732db691d14253a889b3ef089f00b823789aa4a Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 4 Nov 2024 00:27:56 +0900 Subject: [PATCH 37/63] =?UTF-8?q?docs=20dir=E3=81=ABerd=E3=81=A8openapi?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/{api => docs}/erd.pdf | Bin .../challenge/{api/doc => docs}/openapi.yaml | 15 ++++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) rename serverside_challenge_2/challenge/{api => docs}/erd.pdf (100%) rename serverside_challenge_2/challenge/{api/doc => docs}/openapi.yaml (93%) diff --git a/serverside_challenge_2/challenge/api/erd.pdf b/serverside_challenge_2/challenge/docs/erd.pdf similarity index 100% rename from serverside_challenge_2/challenge/api/erd.pdf rename to serverside_challenge_2/challenge/docs/erd.pdf diff --git a/serverside_challenge_2/challenge/api/doc/openapi.yaml b/serverside_challenge_2/challenge/docs/openapi.yaml similarity index 93% rename from serverside_challenge_2/challenge/api/doc/openapi.yaml rename to serverside_challenge_2/challenge/docs/openapi.yaml index 16821f62d..08dddc119 100644 --- a/serverside_challenge_2/challenge/api/doc/openapi.yaml +++ b/serverside_challenge_2/challenge/docs/openapi.yaml @@ -4,9 +4,6 @@ info: title: Challenge API version: 1.0.0 description: '' - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0.html servers: [] paths: "/api/electricity/calculate": @@ -74,24 +71,24 @@ paths: example: data: - provider: - id: 5 + id: 108 name: 東京電力エナジーパートナー plan: - id: 5 + id: 107 name: 従量電灯B price: 305 - provider: - id: 5 + id: 108 name: 東京電力エナジーパートナー plan: - id: 6 + id: 108 name: スタンダードS price: 341 - provider: - id: 7 + id: 110 name: Looopでんき plan: - id: 8 + id: 110 name: おうちプラン price: 28 '400': From b44d71625802fdcfbfa9c44eaf6a691b9cea458b Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 4 Nov 2024 00:44:34 +0900 Subject: [PATCH 38/63] =?UTF-8?q?infra=E6=A7=8B=E6=88=90=E5=9B=B3=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/docs/infra.drawio | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 serverside_challenge_2/challenge/docs/infra.drawio diff --git a/serverside_challenge_2/challenge/docs/infra.drawio b/serverside_challenge_2/challenge/docs/infra.drawio new file mode 100644 index 000000000..fa3fd6943 --- /dev/null +++ b/serverside_challenge_2/challenge/docs/infra.drawio @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From ad109cbc9c7d17fc115bb66ed270ac138cb39303 Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 4 Nov 2024 22:02:52 +0900 Subject: [PATCH 39/63] =?UTF-8?q?frontendn=E3=81=8A=E6=9C=80=E4=BD=8E?= =?UTF-8?q?=E9=99=90=E3=81=AEUI=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/frontend/src/App.css | 86 +++++++++++++------ .../challenge/frontend/src/App.tsx | 42 ++++----- .../challenge/frontend/src/index.css | 68 --------------- .../challenge/frontend/src/main.tsx | 1 - 4 files changed, 80 insertions(+), 117 deletions(-) delete mode 100644 serverside_challenge_2/challenge/frontend/src/index.css diff --git a/serverside_challenge_2/challenge/frontend/src/App.css b/serverside_challenge_2/challenge/frontend/src/App.css index b9d355df2..48b97a567 100644 --- a/serverside_challenge_2/challenge/frontend/src/App.css +++ b/serverside_challenge_2/challenge/frontend/src/App.css @@ -1,42 +1,72 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + margin: 0; + padding: 0; +} + +h1 { text-align: center; + color: #333; +} + +.container { + max-width: 600px; + margin: 20px auto; + padding: 20px; + background-color: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + border-radius: 8px; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; +label { + display: block; + margin-bottom: 8px; + font-weight: bold; } -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); + +select, input { + width: 100%; + padding: 8px; + margin-bottom: 16px; + border: 1px solid #ccc; + border-radius: 4px; +} +input[type="text"] { + box-sizing: border-box; } -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); + +button { + width: 100%; + padding: 10px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; } -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +button:disabled { + background-color: #ccc; } -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } +.price-list { + margin-top: 20px; } -.card { - padding: 2em; +.price-item { + padding: 10px; + border-bottom: 1px solid #eee; } -.read-the-docs { - color: #888; +.price-item h2 { + margin: 0; + font-size: 18px; + color: #007bff; } + +.price-item span { + display: block; + margin-top: 4px; + color: #555; +} \ No newline at end of file diff --git a/serverside_challenge_2/challenge/frontend/src/App.tsx b/serverside_challenge_2/challenge/frontend/src/App.tsx index c5592b7f3..c04bdead9 100644 --- a/serverside_challenge_2/challenge/frontend/src/App.tsx +++ b/serverside_challenge_2/challenge/frontend/src/App.tsx @@ -19,6 +19,7 @@ function App() { const [amperage, setAmperage] = useState(undefined); const [prices, setPrices] = useState([]); + const formatPrice = (val: number) => (val.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })); const requestCalcPrices = async () => { if (amperage === undefined) return; try { @@ -34,18 +35,18 @@ function App() { }); if (!response.ok) { - throw new Error('Network response was not ok'); + throw new Error('電力計算に失敗しました。'); } const data: { data: ResponseData[] } = await response.json(); setPrices(data.data); } catch (error) { - console.error('There was a problem with the fetch operation:', error); + console.error('エラー:', error); } }; - return ( - <> + return ( +

電力料金計算

@@ -64,16 +65,17 @@ function App() {
- + { - const found = event.target.value.match(/\d/g) - const text = found ? found.join('') : ''; - setElectricityUsageKwh(parseInt(text.length ? text : '0', 10)) - }} + type="text" + id="electricity-usage" + maxLength={4} + value={electricityUsageKwh} + onChange={(event) => { + const found = event.target.value.match(/\d/g) + const text = found ? found.join('') : ''; + setElectricityUsageKwh(parseInt(text.length ? text : '0', 10)) + }} />
@@ -81,18 +83,18 @@ function App() {
-
+
{prices.map((price) => { return ( -
+

{price.provider.name}

- {price.plan.name}{price.plan.price}円 + {price.plan.name}{formatPrice(price.plan.price)}円
); })}
- - ) - } +
+ ) +} -export default App +export default App \ No newline at end of file diff --git a/serverside_challenge_2/challenge/frontend/src/index.css b/serverside_challenge_2/challenge/frontend/src/index.css deleted file mode 100644 index 6119ad9a8..000000000 --- a/serverside_challenge_2/challenge/frontend/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/serverside_challenge_2/challenge/frontend/src/main.tsx b/serverside_challenge_2/challenge/frontend/src/main.tsx index bef5202a3..4aff0256e 100644 --- a/serverside_challenge_2/challenge/frontend/src/main.tsx +++ b/serverside_challenge_2/challenge/frontend/src/main.tsx @@ -1,6 +1,5 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import './index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( From 83e2963da75b6e9d2c14e5e77add1cde2e02624d Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 4 Nov 2024 22:05:03 +0900 Subject: [PATCH 40/63] =?UTF-8?q?infra=E6=A7=8B=E6=88=90=E5=9B=B3=E3=81=AE?= =?UTF-8?q?png=E5=BD=A2=E5=BC=8F=E3=82=92=E7=99=BB=E9=8C=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/docs/infra.png | Bin 0 -> 29795 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 serverside_challenge_2/challenge/docs/infra.png diff --git a/serverside_challenge_2/challenge/docs/infra.png b/serverside_challenge_2/challenge/docs/infra.png new file mode 100644 index 0000000000000000000000000000000000000000..ba4935b59b09352fe64a2eab744113af12b7ee44 GIT binary patch literal 29795 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 Date: Mon, 4 Nov 2024 23:52:13 +0900 Subject: [PATCH 41/63] =?UTF-8?q?frontend=E3=81=AE=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=82=AB=E3=83=AB=E5=AE=9F=E8=A1=8C=E7=92=B0=E5=A2=83=E3=82=92?= =?UTF-8?q?docker=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/docker-compose.yml | 13 +++++++++- .../challenge/frontend/Dockerfile | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 serverside_challenge_2/challenge/frontend/Dockerfile diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 6ef7c592d..3cdf309c1 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -11,7 +11,7 @@ services: volumes: - postgres_volume:/var/lib/postgresql/data restart: always - web: + api: container_name: challenge-api build: context: ./api @@ -29,5 +29,16 @@ services: stdin_open: true depends_on: - db + frontend: + container_name: challenge-frontend + build: + context: ./frontend + dockerfile: Dockerfile + volumes: + - ./frontend/src:/app/src + - ./frontend/public:/app/public + restart: always + ports: + - "5173:5173" volumes: postgres_volume: \ No newline at end of file diff --git a/serverside_challenge_2/challenge/frontend/Dockerfile b/serverside_challenge_2/challenge/frontend/Dockerfile new file mode 100644 index 000000000..7306771e1 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/Dockerfile @@ -0,0 +1,24 @@ +FROM node:23-slim + +WORKDIR /app + +COPY package.json pnpm-lock.yaml* ./ +RUN \ + if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i; \ + else echo "Lockfile not found." && exit 1; \ + fi + +COPY src ./src +COPY public ./public +COPY .env.local . +COPY index.html . +COPY eslint.config.js . +COPY tsconfig.json . +COPY tsconfig.app.json . +COPY tsconfig.node.json . +COPY vite.config.ts . + +CMD \ + if [ -f pnpm-lock.yaml ]; then pnpm dev --host; \ + else echo "Lockfile not found." && exit 1; \ + fi \ No newline at end of file From 8237268b5f1ceb389493b7c9c31184b486037424 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:09:12 +0900 Subject: [PATCH 42/63] =?UTF-8?q?=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=E7=92=B0=E5=A2=83=E3=81=A7=E5=85=B1=E9=80=9A=E3=81=AEDocker=20?= =?UTF-8?q?file=E3=82=92=E5=88=A9=E7=94=A8=E3=81=99=E3=82=8B=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=81=AB=E3=80=81Dockerfile=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/api/Dockerfile | 6 +++++- serverside_challenge_2/challenge/docker-compose.yml | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/serverside_challenge_2/challenge/api/Dockerfile b/serverside_challenge_2/challenge/api/Dockerfile index ea9798547..80feb2957 100644 --- a/serverside_challenge_2/challenge/api/Dockerfile +++ b/serverside_challenge_2/challenge/api/Dockerfile @@ -5,4 +5,8 @@ WORKDIR /app ADD Gemfile /app/Gemfile ADD Gemfile.lock /app/Gemfile.lock RUN bundle install -ADD . /app \ No newline at end of file +ADD . /app + +CMD ["rm", "f", "tmp/pids/server"] +EXPOSE 3000 +CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"] diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 3cdf309c1..5976340b9 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -16,7 +16,6 @@ services: build: context: ./api dockerfile: Dockerfile - command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password From 1fe47bd28e349b289b21a3e5d993518c5b632c2a Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:10:56 +0900 Subject: [PATCH 43/63] =?UTF-8?q?yaml=E3=81=AE=E4=BB=95=E6=A7=98=E3=81=A7?= =?UTF-8?q?=E5=80=A4=E3=81=AB=E8=A8=98=E5=8F=B7=E3=81=8C=E5=85=A5=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AE=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/api/config/database.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/api/config/database.yml b/serverside_challenge_2/challenge/api/config/database.yml index 2efa8d325..6985538d2 100644 --- a/serverside_challenge_2/challenge/api/config/database.yml +++ b/serverside_challenge_2/challenge/api/config/database.yml @@ -22,7 +22,7 @@ default: &default pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> host: <%= ENV.fetch("DATABASE_HOST") %> username: <%= ENV.fetch("DATABASE_USER") %> - password: <%= ENV.fetch("DATABASE_PASSWORD") %> + password: "<%= ENV.fetch('DATABASE_PASSWORD') %>" port: <%= ENV.fetch("DATABASE_PORT", '5432') %> development: From b50eb035633f033ebd187e1d9f82fae463552b46 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:11:52 +0900 Subject: [PATCH 44/63] =?UTF-8?q?=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=E7=92=B0=E5=A2=83=E7=94=A8=E3=81=AEsecret=E6=83=85=E5=A0=B1?= =?UTF-8?q?=E3=81=AE=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/api/config/credentials/production.yml.enc | 1 + 1 file changed, 1 insertion(+) create mode 100644 serverside_challenge_2/challenge/api/config/credentials/production.yml.enc diff --git a/serverside_challenge_2/challenge/api/config/credentials/production.yml.enc b/serverside_challenge_2/challenge/api/config/credentials/production.yml.enc new file mode 100644 index 000000000..059368c2d --- /dev/null +++ b/serverside_challenge_2/challenge/api/config/credentials/production.yml.enc @@ -0,0 +1 @@ +sSsxp5ZRpnNhQwY7KFiDp6X904j3rxlsZAO3+dbEnpaPbfrYRJ2fgV1YAckYk1urNkpJPelkn7P4kvHgnD8wlXb42Ao=--tLnGT7M5v8iOm+fl--CCNhur994I64fWXiaAtfLw== \ No newline at end of file From 16eb210b93b55f216872c756f52b7341b6a54327 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:12:54 +0900 Subject: [PATCH 45/63] =?UTF-8?q?deploy=E7=92=B0=E5=A2=83=E7=94=A8?= =?UTF-8?q?=E3=81=AEapi=20URL=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/frontend/.env.production | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/frontend/.env.production b/serverside_challenge_2/challenge/frontend/.env.production index 4c50c3dcf..eac31563c 100644 --- a/serverside_challenge_2/challenge/frontend/.env.production +++ b/serverside_challenge_2/challenge/frontend/.env.production @@ -1 +1 @@ -VITE_API_URL=https://three-chairs.com +VITE_API_URL=https://api.three-chairs.com From 3073ba158b32517e4d525e12713148ee1153d90e Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:13:26 +0900 Subject: [PATCH 46/63] =?UTF-8?q?seed=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/api/db/seeds.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/serverside_challenge_2/challenge/api/db/seeds.rb b/serverside_challenge_2/challenge/api/db/seeds.rb index 0ffa8afd1..5287d0560 100644 --- a/serverside_challenge_2/challenge/api/db/seeds.rb +++ b/serverside_challenge_2/challenge/api/db/seeds.rb @@ -1,5 +1,6 @@ require 'csv' +puts '* Start BasicPrice data' BasicPrice.delete_all CSV.foreach(Rails.root.join('db/seeds/basic_prices.csv'), headers: true) do |row| provider = Provider.find_or_create_by(name: row[0]) @@ -8,6 +9,7 @@ plan.basic_prices.create!(amperage: row[2], price: row[3]) end +puts '* Start MeasuredRate data' MeasuredRate.delete_all CSV.foreach(Rails.root.join('db/seeds/measured_rates.csv'), headers: true) do |row| provider = Provider.find_or_create_by(name: row[0]) From 4d25d26ce16858fd80db818ba371605c15a089c5 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:15:05 +0900 Subject: [PATCH 47/63] =?UTF-8?q?migration=20&=20seed=E3=81=A7=E5=88=A9?= =?UTF-8?q?=E7=94=A8=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81=E3=81=AEcontainer?= =?UTF-8?q?=20image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/api/Dockerfile.migration | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 serverside_challenge_2/challenge/api/Dockerfile.migration diff --git a/serverside_challenge_2/challenge/api/Dockerfile.migration b/serverside_challenge_2/challenge/api/Dockerfile.migration new file mode 100644 index 000000000..3c41590ff --- /dev/null +++ b/serverside_challenge_2/challenge/api/Dockerfile.migration @@ -0,0 +1,8 @@ +FROM ruby:3.1.2 +RUN apt-get update -qq && apt-get install -y build-essential libpq-dev +RUN mkdir /app +WORKDIR /app +ADD Gemfile /app/Gemfile +ADD Gemfile.lock /app/Gemfile.lock +RUN bundle install +ADD . /app From 84151f9eb8e239435216458fd81256f1194d8132 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:18:04 +0900 Subject: [PATCH 48/63] =?UTF-8?q?gitignore=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/api/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/api/.gitignore b/serverside_challenge_2/challenge/api/.gitignore index 55254ed9b..f53e9d333 100644 --- a/serverside_challenge_2/challenge/api/.gitignore +++ b/serverside_challenge_2/challenge/api/.gitignore @@ -29,4 +29,5 @@ /config/master.key # Application -.env \ No newline at end of file +.env +/config/credentials/production.key From cb54d614b4cdec7b27c200fed6662a18c3bcadb5 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 01:19:22 +0900 Subject: [PATCH 49/63] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=95=E3=83=A9?= =?UTF-8?q?=E6=A7=8B=E6=88=90=E5=9B=B3=E3=81=AE=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/docs/infra.drawio | 45 ++++++++++++------ .../challenge/docs/infra.png | Bin 29795 -> 42964 bytes 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/serverside_challenge_2/challenge/docs/infra.drawio b/serverside_challenge_2/challenge/docs/infra.drawio index fa3fd6943..ba0129920 100644 --- a/serverside_challenge_2/challenge/docs/infra.drawio +++ b/serverside_challenge_2/challenge/docs/infra.drawio @@ -1,18 +1,18 @@ - + - + - + - + @@ -22,44 +22,61 @@ - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + diff --git a/serverside_challenge_2/challenge/docs/infra.png b/serverside_challenge_2/challenge/docs/infra.png index ba4935b59b09352fe64a2eab744113af12b7ee44..5c27305c9e414f340c03a5e1533861a32795bb77 100644 GIT binary patch literal 42964 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 delta 19102 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 VPl-2}0P-h>j)uN^m8wm|{{;=!Y$*T$ From abbaaf9a2b8190964cb16780f2beacb0086e1d26 Mon Sep 17 00:00:00 2001 From: kuni Date: Wed, 6 Nov 2024 23:46:45 +0900 Subject: [PATCH 50/63] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=95=E3=83=A9?= =?UTF-8?q?=E6=A7=8B=E6=88=90=E5=9B=B3=E3=81=AE=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/docs/infra.drawio | 12 ++++++------ .../challenge/docs/infra.png | Bin 42964 -> 42914 bytes 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/serverside_challenge_2/challenge/docs/infra.drawio b/serverside_challenge_2/challenge/docs/infra.drawio index ba0129920..ad989e517 100644 --- a/serverside_challenge_2/challenge/docs/infra.drawio +++ b/serverside_challenge_2/challenge/docs/infra.drawio @@ -1,6 +1,6 @@ - + @@ -60,21 +60,21 @@ - + - + - + - + - + diff --git a/serverside_challenge_2/challenge/docs/infra.png b/serverside_challenge_2/challenge/docs/infra.png index 5c27305c9e414f340c03a5e1533861a32795bb77..92af5779c78a26f87ceff07dd5f02540a8d882bf 100644 GIT binary patch delta 28409 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 E3wqjuWdHyG delta 28422 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 From 8f5a0b93952978550fc1b3d51c993d934f41a6e8 Mon Sep 17 00:00:00 2001 From: kuni Date: Thu, 7 Nov 2024 00:07:01 +0900 Subject: [PATCH 51/63] =?UTF-8?q?dir=E6=A7=8B=E6=88=90=E3=82=92=E5=85=83?= =?UTF-8?q?=E3=81=AB=E6=88=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/{api => }/.env.sample | 0 .../challenge/{api => }/.env.test | 0 .../challenge/{api => }/.gitattributes | 0 serverside_challenge_2/challenge/.gitignore | 36 +++++++++++++++++-- .../challenge/{api => }/.rspec | 0 .../challenge/{api => }/.rubocop.yml | 0 .../challenge/{api => }/.ruby-version | 0 .../challenge/{api => }/Dockerfile | 0 .../challenge/{api => }/Dockerfile.migration | 0 .../challenge/{api => }/Gemfile | 0 .../challenge/{api => }/Gemfile.lock | 0 .../challenge/{api => }/README.md | 0 .../challenge/{api => }/Rakefile | 0 .../challenge/api/.gitignore | 33 ----------------- .../app/channels/application_cable/channel.rb | 0 .../channels/application_cable/connection.rb | 0 .../api/electricity/calculate_controller.rb | 0 .../app/controllers/application_controller.rb | 0 .../{api => }/app/controllers/concerns/.keep | 0 .../{api => }/app/jobs/application_job.rb | 0 .../app/mailers/application_mailer.rb | 0 .../app/models/application_record.rb | 0 .../{api => }/app/models/basic_price.rb | 0 .../{api => }/app/models/concerns/.keep | 0 .../{api => }/app/models/measured_rate.rb | 0 .../challenge/{api => }/app/models/plan.rb | 0 .../{api => }/app/models/provider.rb | 0 .../electricity/calculate/create.json.jb | 0 .../app/views/layouts/mailer.html.erb | 0 .../app/views/layouts/mailer.text.erb | 0 .../challenge/{api => }/bin/rails | 0 .../challenge/{api => }/bin/rake | 0 .../challenge/{api => }/bin/setup | 0 .../challenge/{api => }/config.ru | 0 .../challenge/{api => }/config/application.rb | 0 .../challenge/{api => }/config/boot.rb | 0 .../challenge/{api => }/config/cable.yml | 0 .../{api => }/config/credentials.yml.enc | 0 .../config/credentials/production.yml.enc | 0 .../challenge/{api => }/config/database.yml | 0 .../challenge/{api => }/config/environment.rb | 0 .../config/environments/development.rb | 0 .../config/environments/production.rb | 0 .../{api => }/config/environments/test.rb | 0 .../{api => }/config/initializers/cors.rb | 0 .../initializers/filter_parameter_logging.rb | 0 .../config/initializers/inflections.rb | 0 .../config/initializers/rack_timeout.rb | 0 .../challenge/{api => }/config/locales/en.yml | 0 .../challenge/{api => }/config/puma.rb | 0 .../challenge/{api => }/config/routes.rb | 0 .../challenge/{api => }/config/storage.yml | 0 .../20241027065846_create_providers.rb | 0 .../db/migrate/20241027065931_create_plans.rb | 0 .../20241027072120_create_basic_prices.rb | 0 .../20241027072347_create_measured_rates.rb | 0 .../challenge/{api => }/db/schema.rb | 0 .../challenge/{api => }/db/seeds.rb | 0 .../{api => }/db/seeds/basic_prices.csv | 0 .../{api => }/db/seeds/measured_rates.csv | 0 .../challenge/docker-compose.yml | 4 +-- .../challenge/{api => }/lib/tasks/.keep | 0 .../challenge/{api => }/log/.keep | 0 .../challenge/{api => }/public/robots.txt | 0 .../challenge/{api => }/spec/db/seeds_spec.rb | 0 .../{api => }/spec/factories/basic_price.rb | 0 .../{api => }/spec/factories/measure_rate.rb | 0 .../{api => }/spec/factories/plan.rb | 0 .../{api => }/spec/factories/provider.rb | 0 .../{api => }/spec/models/basic_price_spec.rb | 0 .../spec/models/measure_rate_spec.rb | 0 .../{api => }/spec/models/plan_spec.rb | 0 .../{api => }/spec/models/provider_spec.rb | 0 .../challenge/{api => }/spec/rails_helper.rb | 0 .../api/electricity/calculate_spec.rb | 0 .../challenge/{api => }/spec/spec_helper.rb | 0 .../challenge/{api => }/storage/.keep | 0 .../application_cable/connection_test.rb | 0 .../{api => }/test/controllers/.keep | 0 .../{api => }/test/fixtures/files/.keep | 0 .../{api => }/test/integration/.keep | 0 .../challenge/{api => }/test/mailers/.keep | 0 .../challenge/{api => }/test/models/.keep | 0 .../challenge/{api => }/test/test_helper.rb | 0 .../challenge/{api => }/tmp/.keep | 0 .../challenge/{api => }/tmp/pids/.keep | 0 .../challenge/{api => }/tmp/storage/.keep | 0 .../challenge/{api => }/vendor/.keep | 0 88 files changed, 35 insertions(+), 38 deletions(-) rename serverside_challenge_2/challenge/{api => }/.env.sample (100%) rename serverside_challenge_2/challenge/{api => }/.env.test (100%) rename serverside_challenge_2/challenge/{api => }/.gitattributes (100%) rename serverside_challenge_2/challenge/{api => }/.rspec (100%) rename serverside_challenge_2/challenge/{api => }/.rubocop.yml (100%) rename serverside_challenge_2/challenge/{api => }/.ruby-version (100%) rename serverside_challenge_2/challenge/{api => }/Dockerfile (100%) rename serverside_challenge_2/challenge/{api => }/Dockerfile.migration (100%) rename serverside_challenge_2/challenge/{api => }/Gemfile (100%) rename serverside_challenge_2/challenge/{api => }/Gemfile.lock (100%) rename serverside_challenge_2/challenge/{api => }/README.md (100%) rename serverside_challenge_2/challenge/{api => }/Rakefile (100%) delete mode 100644 serverside_challenge_2/challenge/api/.gitignore rename serverside_challenge_2/challenge/{api => }/app/channels/application_cable/channel.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/channels/application_cable/connection.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/controllers/api/electricity/calculate_controller.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/controllers/application_controller.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/controllers/concerns/.keep (100%) rename serverside_challenge_2/challenge/{api => }/app/jobs/application_job.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/mailers/application_mailer.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/models/application_record.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/models/basic_price.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/models/concerns/.keep (100%) rename serverside_challenge_2/challenge/{api => }/app/models/measured_rate.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/models/plan.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/models/provider.rb (100%) rename serverside_challenge_2/challenge/{api => }/app/views/electricity/calculate/create.json.jb (100%) rename serverside_challenge_2/challenge/{api => }/app/views/layouts/mailer.html.erb (100%) rename serverside_challenge_2/challenge/{api => }/app/views/layouts/mailer.text.erb (100%) rename serverside_challenge_2/challenge/{api => }/bin/rails (100%) rename serverside_challenge_2/challenge/{api => }/bin/rake (100%) rename serverside_challenge_2/challenge/{api => }/bin/setup (100%) rename serverside_challenge_2/challenge/{api => }/config.ru (100%) rename serverside_challenge_2/challenge/{api => }/config/application.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/boot.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/cable.yml (100%) rename serverside_challenge_2/challenge/{api => }/config/credentials.yml.enc (100%) rename serverside_challenge_2/challenge/{api => }/config/credentials/production.yml.enc (100%) rename serverside_challenge_2/challenge/{api => }/config/database.yml (100%) rename serverside_challenge_2/challenge/{api => }/config/environment.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/environments/development.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/environments/production.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/environments/test.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/initializers/cors.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/initializers/filter_parameter_logging.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/initializers/inflections.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/initializers/rack_timeout.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/locales/en.yml (100%) rename serverside_challenge_2/challenge/{api => }/config/puma.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/routes.rb (100%) rename serverside_challenge_2/challenge/{api => }/config/storage.yml (100%) rename serverside_challenge_2/challenge/{api => }/db/migrate/20241027065846_create_providers.rb (100%) rename serverside_challenge_2/challenge/{api => }/db/migrate/20241027065931_create_plans.rb (100%) rename serverside_challenge_2/challenge/{api => }/db/migrate/20241027072120_create_basic_prices.rb (100%) rename serverside_challenge_2/challenge/{api => }/db/migrate/20241027072347_create_measured_rates.rb (100%) rename serverside_challenge_2/challenge/{api => }/db/schema.rb (100%) rename serverside_challenge_2/challenge/{api => }/db/seeds.rb (100%) rename serverside_challenge_2/challenge/{api => }/db/seeds/basic_prices.csv (100%) rename serverside_challenge_2/challenge/{api => }/db/seeds/measured_rates.csv (100%) rename serverside_challenge_2/challenge/{api => }/lib/tasks/.keep (100%) rename serverside_challenge_2/challenge/{api => }/log/.keep (100%) rename serverside_challenge_2/challenge/{api => }/public/robots.txt (100%) rename serverside_challenge_2/challenge/{api => }/spec/db/seeds_spec.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/factories/basic_price.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/factories/measure_rate.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/factories/plan.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/factories/provider.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/models/basic_price_spec.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/models/measure_rate_spec.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/models/plan_spec.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/models/provider_spec.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/rails_helper.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/requests/api/electricity/calculate_spec.rb (100%) rename serverside_challenge_2/challenge/{api => }/spec/spec_helper.rb (100%) rename serverside_challenge_2/challenge/{api => }/storage/.keep (100%) rename serverside_challenge_2/challenge/{api => }/test/channels/application_cable/connection_test.rb (100%) rename serverside_challenge_2/challenge/{api => }/test/controllers/.keep (100%) rename serverside_challenge_2/challenge/{api => }/test/fixtures/files/.keep (100%) rename serverside_challenge_2/challenge/{api => }/test/integration/.keep (100%) rename serverside_challenge_2/challenge/{api => }/test/mailers/.keep (100%) rename serverside_challenge_2/challenge/{api => }/test/models/.keep (100%) rename serverside_challenge_2/challenge/{api => }/test/test_helper.rb (100%) rename serverside_challenge_2/challenge/{api => }/tmp/.keep (100%) rename serverside_challenge_2/challenge/{api => }/tmp/pids/.keep (100%) rename serverside_challenge_2/challenge/{api => }/tmp/storage/.keep (100%) rename serverside_challenge_2/challenge/{api => }/vendor/.keep (100%) diff --git a/serverside_challenge_2/challenge/api/.env.sample b/serverside_challenge_2/challenge/.env.sample similarity index 100% rename from serverside_challenge_2/challenge/api/.env.sample rename to serverside_challenge_2/challenge/.env.sample diff --git a/serverside_challenge_2/challenge/api/.env.test b/serverside_challenge_2/challenge/.env.test similarity index 100% rename from serverside_challenge_2/challenge/api/.env.test rename to serverside_challenge_2/challenge/.env.test diff --git a/serverside_challenge_2/challenge/api/.gitattributes b/serverside_challenge_2/challenge/.gitattributes similarity index 100% rename from serverside_challenge_2/challenge/api/.gitattributes rename to serverside_challenge_2/challenge/.gitattributes diff --git a/serverside_challenge_2/challenge/.gitignore b/serverside_challenge_2/challenge/.gitignore index ebf869bcf..f53e9d333 100644 --- a/serverside_challenge_2/challenge/.gitignore +++ b/serverside_challenge_2/challenge/.gitignore @@ -1,3 +1,33 @@ -.idea -.vscode -.DS_Store \ No newline at end of file +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Application +.env +/config/credentials/production.key diff --git a/serverside_challenge_2/challenge/api/.rspec b/serverside_challenge_2/challenge/.rspec similarity index 100% rename from serverside_challenge_2/challenge/api/.rspec rename to serverside_challenge_2/challenge/.rspec diff --git a/serverside_challenge_2/challenge/api/.rubocop.yml b/serverside_challenge_2/challenge/.rubocop.yml similarity index 100% rename from serverside_challenge_2/challenge/api/.rubocop.yml rename to serverside_challenge_2/challenge/.rubocop.yml diff --git a/serverside_challenge_2/challenge/api/.ruby-version b/serverside_challenge_2/challenge/.ruby-version similarity index 100% rename from serverside_challenge_2/challenge/api/.ruby-version rename to serverside_challenge_2/challenge/.ruby-version diff --git a/serverside_challenge_2/challenge/api/Dockerfile b/serverside_challenge_2/challenge/Dockerfile similarity index 100% rename from serverside_challenge_2/challenge/api/Dockerfile rename to serverside_challenge_2/challenge/Dockerfile diff --git a/serverside_challenge_2/challenge/api/Dockerfile.migration b/serverside_challenge_2/challenge/Dockerfile.migration similarity index 100% rename from serverside_challenge_2/challenge/api/Dockerfile.migration rename to serverside_challenge_2/challenge/Dockerfile.migration diff --git a/serverside_challenge_2/challenge/api/Gemfile b/serverside_challenge_2/challenge/Gemfile similarity index 100% rename from serverside_challenge_2/challenge/api/Gemfile rename to serverside_challenge_2/challenge/Gemfile diff --git a/serverside_challenge_2/challenge/api/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock similarity index 100% rename from serverside_challenge_2/challenge/api/Gemfile.lock rename to serverside_challenge_2/challenge/Gemfile.lock diff --git a/serverside_challenge_2/challenge/api/README.md b/serverside_challenge_2/challenge/README.md similarity index 100% rename from serverside_challenge_2/challenge/api/README.md rename to serverside_challenge_2/challenge/README.md diff --git a/serverside_challenge_2/challenge/api/Rakefile b/serverside_challenge_2/challenge/Rakefile similarity index 100% rename from serverside_challenge_2/challenge/api/Rakefile rename to serverside_challenge_2/challenge/Rakefile diff --git a/serverside_challenge_2/challenge/api/.gitignore b/serverside_challenge_2/challenge/api/.gitignore deleted file mode 100644 index f53e9d333..000000000 --- a/serverside_challenge_2/challenge/api/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# See https://help.github.com/articles/ignoring-files for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile '~/.gitignore_global' - -# Ignore bundler config. -/.bundle - -# Ignore all logfiles and tempfiles. -/log/* -/tmp/* -!/log/.keep -!/tmp/.keep - -# Ignore pidfiles, but keep the directory. -/tmp/pids/* -!/tmp/pids/ -!/tmp/pids/.keep - -# Ignore uploaded files in development. -/storage/* -!/storage/.keep -/tmp/storage/* -!/tmp/storage/ -!/tmp/storage/.keep - -# Ignore master key for decrypting credentials and more. -/config/master.key - -# Application -.env -/config/credentials/production.key diff --git a/serverside_challenge_2/challenge/api/app/channels/application_cable/channel.rb b/serverside_challenge_2/challenge/app/channels/application_cable/channel.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/channels/application_cable/channel.rb rename to serverside_challenge_2/challenge/app/channels/application_cable/channel.rb diff --git a/serverside_challenge_2/challenge/api/app/channels/application_cable/connection.rb b/serverside_challenge_2/challenge/app/channels/application_cable/connection.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/channels/application_cable/connection.rb rename to serverside_challenge_2/challenge/app/channels/application_cable/connection.rb diff --git a/serverside_challenge_2/challenge/api/app/controllers/api/electricity/calculate_controller.rb b/serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/controllers/api/electricity/calculate_controller.rb rename to serverside_challenge_2/challenge/app/controllers/api/electricity/calculate_controller.rb diff --git a/serverside_challenge_2/challenge/api/app/controllers/application_controller.rb b/serverside_challenge_2/challenge/app/controllers/application_controller.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/controllers/application_controller.rb rename to serverside_challenge_2/challenge/app/controllers/application_controller.rb diff --git a/serverside_challenge_2/challenge/api/app/controllers/concerns/.keep b/serverside_challenge_2/challenge/app/controllers/concerns/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/app/controllers/concerns/.keep rename to serverside_challenge_2/challenge/app/controllers/concerns/.keep diff --git a/serverside_challenge_2/challenge/api/app/jobs/application_job.rb b/serverside_challenge_2/challenge/app/jobs/application_job.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/jobs/application_job.rb rename to serverside_challenge_2/challenge/app/jobs/application_job.rb diff --git a/serverside_challenge_2/challenge/api/app/mailers/application_mailer.rb b/serverside_challenge_2/challenge/app/mailers/application_mailer.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/mailers/application_mailer.rb rename to serverside_challenge_2/challenge/app/mailers/application_mailer.rb diff --git a/serverside_challenge_2/challenge/api/app/models/application_record.rb b/serverside_challenge_2/challenge/app/models/application_record.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/models/application_record.rb rename to serverside_challenge_2/challenge/app/models/application_record.rb diff --git a/serverside_challenge_2/challenge/api/app/models/basic_price.rb b/serverside_challenge_2/challenge/app/models/basic_price.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/models/basic_price.rb rename to serverside_challenge_2/challenge/app/models/basic_price.rb diff --git a/serverside_challenge_2/challenge/api/app/models/concerns/.keep b/serverside_challenge_2/challenge/app/models/concerns/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/app/models/concerns/.keep rename to serverside_challenge_2/challenge/app/models/concerns/.keep diff --git a/serverside_challenge_2/challenge/api/app/models/measured_rate.rb b/serverside_challenge_2/challenge/app/models/measured_rate.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/models/measured_rate.rb rename to serverside_challenge_2/challenge/app/models/measured_rate.rb diff --git a/serverside_challenge_2/challenge/api/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/models/plan.rb rename to serverside_challenge_2/challenge/app/models/plan.rb diff --git a/serverside_challenge_2/challenge/api/app/models/provider.rb b/serverside_challenge_2/challenge/app/models/provider.rb similarity index 100% rename from serverside_challenge_2/challenge/api/app/models/provider.rb rename to serverside_challenge_2/challenge/app/models/provider.rb diff --git a/serverside_challenge_2/challenge/api/app/views/electricity/calculate/create.json.jb b/serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb similarity index 100% rename from serverside_challenge_2/challenge/api/app/views/electricity/calculate/create.json.jb rename to serverside_challenge_2/challenge/app/views/electricity/calculate/create.json.jb diff --git a/serverside_challenge_2/challenge/api/app/views/layouts/mailer.html.erb b/serverside_challenge_2/challenge/app/views/layouts/mailer.html.erb similarity index 100% rename from serverside_challenge_2/challenge/api/app/views/layouts/mailer.html.erb rename to serverside_challenge_2/challenge/app/views/layouts/mailer.html.erb diff --git a/serverside_challenge_2/challenge/api/app/views/layouts/mailer.text.erb b/serverside_challenge_2/challenge/app/views/layouts/mailer.text.erb similarity index 100% rename from serverside_challenge_2/challenge/api/app/views/layouts/mailer.text.erb rename to serverside_challenge_2/challenge/app/views/layouts/mailer.text.erb diff --git a/serverside_challenge_2/challenge/api/bin/rails b/serverside_challenge_2/challenge/bin/rails similarity index 100% rename from serverside_challenge_2/challenge/api/bin/rails rename to serverside_challenge_2/challenge/bin/rails diff --git a/serverside_challenge_2/challenge/api/bin/rake b/serverside_challenge_2/challenge/bin/rake similarity index 100% rename from serverside_challenge_2/challenge/api/bin/rake rename to serverside_challenge_2/challenge/bin/rake diff --git a/serverside_challenge_2/challenge/api/bin/setup b/serverside_challenge_2/challenge/bin/setup similarity index 100% rename from serverside_challenge_2/challenge/api/bin/setup rename to serverside_challenge_2/challenge/bin/setup diff --git a/serverside_challenge_2/challenge/api/config.ru b/serverside_challenge_2/challenge/config.ru similarity index 100% rename from serverside_challenge_2/challenge/api/config.ru rename to serverside_challenge_2/challenge/config.ru diff --git a/serverside_challenge_2/challenge/api/config/application.rb b/serverside_challenge_2/challenge/config/application.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/application.rb rename to serverside_challenge_2/challenge/config/application.rb diff --git a/serverside_challenge_2/challenge/api/config/boot.rb b/serverside_challenge_2/challenge/config/boot.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/boot.rb rename to serverside_challenge_2/challenge/config/boot.rb diff --git a/serverside_challenge_2/challenge/api/config/cable.yml b/serverside_challenge_2/challenge/config/cable.yml similarity index 100% rename from serverside_challenge_2/challenge/api/config/cable.yml rename to serverside_challenge_2/challenge/config/cable.yml diff --git a/serverside_challenge_2/challenge/api/config/credentials.yml.enc b/serverside_challenge_2/challenge/config/credentials.yml.enc similarity index 100% rename from serverside_challenge_2/challenge/api/config/credentials.yml.enc rename to serverside_challenge_2/challenge/config/credentials.yml.enc diff --git a/serverside_challenge_2/challenge/api/config/credentials/production.yml.enc b/serverside_challenge_2/challenge/config/credentials/production.yml.enc similarity index 100% rename from serverside_challenge_2/challenge/api/config/credentials/production.yml.enc rename to serverside_challenge_2/challenge/config/credentials/production.yml.enc diff --git a/serverside_challenge_2/challenge/api/config/database.yml b/serverside_challenge_2/challenge/config/database.yml similarity index 100% rename from serverside_challenge_2/challenge/api/config/database.yml rename to serverside_challenge_2/challenge/config/database.yml diff --git a/serverside_challenge_2/challenge/api/config/environment.rb b/serverside_challenge_2/challenge/config/environment.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/environment.rb rename to serverside_challenge_2/challenge/config/environment.rb diff --git a/serverside_challenge_2/challenge/api/config/environments/development.rb b/serverside_challenge_2/challenge/config/environments/development.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/environments/development.rb rename to serverside_challenge_2/challenge/config/environments/development.rb diff --git a/serverside_challenge_2/challenge/api/config/environments/production.rb b/serverside_challenge_2/challenge/config/environments/production.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/environments/production.rb rename to serverside_challenge_2/challenge/config/environments/production.rb diff --git a/serverside_challenge_2/challenge/api/config/environments/test.rb b/serverside_challenge_2/challenge/config/environments/test.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/environments/test.rb rename to serverside_challenge_2/challenge/config/environments/test.rb diff --git a/serverside_challenge_2/challenge/api/config/initializers/cors.rb b/serverside_challenge_2/challenge/config/initializers/cors.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/initializers/cors.rb rename to serverside_challenge_2/challenge/config/initializers/cors.rb diff --git a/serverside_challenge_2/challenge/api/config/initializers/filter_parameter_logging.rb b/serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/initializers/filter_parameter_logging.rb rename to serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb diff --git a/serverside_challenge_2/challenge/api/config/initializers/inflections.rb b/serverside_challenge_2/challenge/config/initializers/inflections.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/initializers/inflections.rb rename to serverside_challenge_2/challenge/config/initializers/inflections.rb diff --git a/serverside_challenge_2/challenge/api/config/initializers/rack_timeout.rb b/serverside_challenge_2/challenge/config/initializers/rack_timeout.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/initializers/rack_timeout.rb rename to serverside_challenge_2/challenge/config/initializers/rack_timeout.rb diff --git a/serverside_challenge_2/challenge/api/config/locales/en.yml b/serverside_challenge_2/challenge/config/locales/en.yml similarity index 100% rename from serverside_challenge_2/challenge/api/config/locales/en.yml rename to serverside_challenge_2/challenge/config/locales/en.yml diff --git a/serverside_challenge_2/challenge/api/config/puma.rb b/serverside_challenge_2/challenge/config/puma.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/puma.rb rename to serverside_challenge_2/challenge/config/puma.rb diff --git a/serverside_challenge_2/challenge/api/config/routes.rb b/serverside_challenge_2/challenge/config/routes.rb similarity index 100% rename from serverside_challenge_2/challenge/api/config/routes.rb rename to serverside_challenge_2/challenge/config/routes.rb diff --git a/serverside_challenge_2/challenge/api/config/storage.yml b/serverside_challenge_2/challenge/config/storage.yml similarity index 100% rename from serverside_challenge_2/challenge/api/config/storage.yml rename to serverside_challenge_2/challenge/config/storage.yml diff --git a/serverside_challenge_2/challenge/api/db/migrate/20241027065846_create_providers.rb b/serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb similarity index 100% rename from serverside_challenge_2/challenge/api/db/migrate/20241027065846_create_providers.rb rename to serverside_challenge_2/challenge/db/migrate/20241027065846_create_providers.rb diff --git a/serverside_challenge_2/challenge/api/db/migrate/20241027065931_create_plans.rb b/serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb similarity index 100% rename from serverside_challenge_2/challenge/api/db/migrate/20241027065931_create_plans.rb rename to serverside_challenge_2/challenge/db/migrate/20241027065931_create_plans.rb diff --git a/serverside_challenge_2/challenge/api/db/migrate/20241027072120_create_basic_prices.rb b/serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb similarity index 100% rename from serverside_challenge_2/challenge/api/db/migrate/20241027072120_create_basic_prices.rb rename to serverside_challenge_2/challenge/db/migrate/20241027072120_create_basic_prices.rb diff --git a/serverside_challenge_2/challenge/api/db/migrate/20241027072347_create_measured_rates.rb b/serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb similarity index 100% rename from serverside_challenge_2/challenge/api/db/migrate/20241027072347_create_measured_rates.rb rename to serverside_challenge_2/challenge/db/migrate/20241027072347_create_measured_rates.rb diff --git a/serverside_challenge_2/challenge/api/db/schema.rb b/serverside_challenge_2/challenge/db/schema.rb similarity index 100% rename from serverside_challenge_2/challenge/api/db/schema.rb rename to serverside_challenge_2/challenge/db/schema.rb diff --git a/serverside_challenge_2/challenge/api/db/seeds.rb b/serverside_challenge_2/challenge/db/seeds.rb similarity index 100% rename from serverside_challenge_2/challenge/api/db/seeds.rb rename to serverside_challenge_2/challenge/db/seeds.rb diff --git a/serverside_challenge_2/challenge/api/db/seeds/basic_prices.csv b/serverside_challenge_2/challenge/db/seeds/basic_prices.csv similarity index 100% rename from serverside_challenge_2/challenge/api/db/seeds/basic_prices.csv rename to serverside_challenge_2/challenge/db/seeds/basic_prices.csv diff --git a/serverside_challenge_2/challenge/api/db/seeds/measured_rates.csv b/serverside_challenge_2/challenge/db/seeds/measured_rates.csv similarity index 100% rename from serverside_challenge_2/challenge/api/db/seeds/measured_rates.csv rename to serverside_challenge_2/challenge/db/seeds/measured_rates.csv diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 5976340b9..de8f598d5 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -14,13 +14,13 @@ services: api: container_name: challenge-api build: - context: ./api + context: ./ dockerfile: Dockerfile environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password volumes: - - ./api:/app + - ./:/app ports: - "3000:3000" restart: always diff --git a/serverside_challenge_2/challenge/api/lib/tasks/.keep b/serverside_challenge_2/challenge/lib/tasks/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/lib/tasks/.keep rename to serverside_challenge_2/challenge/lib/tasks/.keep diff --git a/serverside_challenge_2/challenge/api/log/.keep b/serverside_challenge_2/challenge/log/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/log/.keep rename to serverside_challenge_2/challenge/log/.keep diff --git a/serverside_challenge_2/challenge/api/public/robots.txt b/serverside_challenge_2/challenge/public/robots.txt similarity index 100% rename from serverside_challenge_2/challenge/api/public/robots.txt rename to serverside_challenge_2/challenge/public/robots.txt diff --git a/serverside_challenge_2/challenge/api/spec/db/seeds_spec.rb b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/db/seeds_spec.rb rename to serverside_challenge_2/challenge/spec/db/seeds_spec.rb diff --git a/serverside_challenge_2/challenge/api/spec/factories/basic_price.rb b/serverside_challenge_2/challenge/spec/factories/basic_price.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/factories/basic_price.rb rename to serverside_challenge_2/challenge/spec/factories/basic_price.rb diff --git a/serverside_challenge_2/challenge/api/spec/factories/measure_rate.rb b/serverside_challenge_2/challenge/spec/factories/measure_rate.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/factories/measure_rate.rb rename to serverside_challenge_2/challenge/spec/factories/measure_rate.rb diff --git a/serverside_challenge_2/challenge/api/spec/factories/plan.rb b/serverside_challenge_2/challenge/spec/factories/plan.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/factories/plan.rb rename to serverside_challenge_2/challenge/spec/factories/plan.rb diff --git a/serverside_challenge_2/challenge/api/spec/factories/provider.rb b/serverside_challenge_2/challenge/spec/factories/provider.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/factories/provider.rb rename to serverside_challenge_2/challenge/spec/factories/provider.rb diff --git a/serverside_challenge_2/challenge/api/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/models/basic_price_spec.rb rename to serverside_challenge_2/challenge/spec/models/basic_price_spec.rb diff --git a/serverside_challenge_2/challenge/api/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/models/measure_rate_spec.rb rename to serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb diff --git a/serverside_challenge_2/challenge/api/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/models/plan_spec.rb rename to serverside_challenge_2/challenge/spec/models/plan_spec.rb diff --git a/serverside_challenge_2/challenge/api/spec/models/provider_spec.rb b/serverside_challenge_2/challenge/spec/models/provider_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/models/provider_spec.rb rename to serverside_challenge_2/challenge/spec/models/provider_spec.rb diff --git a/serverside_challenge_2/challenge/api/spec/rails_helper.rb b/serverside_challenge_2/challenge/spec/rails_helper.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/rails_helper.rb rename to serverside_challenge_2/challenge/spec/rails_helper.rb diff --git a/serverside_challenge_2/challenge/api/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/requests/api/electricity/calculate_spec.rb rename to serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb diff --git a/serverside_challenge_2/challenge/api/spec/spec_helper.rb b/serverside_challenge_2/challenge/spec/spec_helper.rb similarity index 100% rename from serverside_challenge_2/challenge/api/spec/spec_helper.rb rename to serverside_challenge_2/challenge/spec/spec_helper.rb diff --git a/serverside_challenge_2/challenge/api/storage/.keep b/serverside_challenge_2/challenge/storage/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/storage/.keep rename to serverside_challenge_2/challenge/storage/.keep diff --git a/serverside_challenge_2/challenge/api/test/channels/application_cable/connection_test.rb b/serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb similarity index 100% rename from serverside_challenge_2/challenge/api/test/channels/application_cable/connection_test.rb rename to serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb diff --git a/serverside_challenge_2/challenge/api/test/controllers/.keep b/serverside_challenge_2/challenge/test/controllers/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/test/controllers/.keep rename to serverside_challenge_2/challenge/test/controllers/.keep diff --git a/serverside_challenge_2/challenge/api/test/fixtures/files/.keep b/serverside_challenge_2/challenge/test/fixtures/files/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/test/fixtures/files/.keep rename to serverside_challenge_2/challenge/test/fixtures/files/.keep diff --git a/serverside_challenge_2/challenge/api/test/integration/.keep b/serverside_challenge_2/challenge/test/integration/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/test/integration/.keep rename to serverside_challenge_2/challenge/test/integration/.keep diff --git a/serverside_challenge_2/challenge/api/test/mailers/.keep b/serverside_challenge_2/challenge/test/mailers/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/test/mailers/.keep rename to serverside_challenge_2/challenge/test/mailers/.keep diff --git a/serverside_challenge_2/challenge/api/test/models/.keep b/serverside_challenge_2/challenge/test/models/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/test/models/.keep rename to serverside_challenge_2/challenge/test/models/.keep diff --git a/serverside_challenge_2/challenge/api/test/test_helper.rb b/serverside_challenge_2/challenge/test/test_helper.rb similarity index 100% rename from serverside_challenge_2/challenge/api/test/test_helper.rb rename to serverside_challenge_2/challenge/test/test_helper.rb diff --git a/serverside_challenge_2/challenge/api/tmp/.keep b/serverside_challenge_2/challenge/tmp/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/tmp/.keep rename to serverside_challenge_2/challenge/tmp/.keep diff --git a/serverside_challenge_2/challenge/api/tmp/pids/.keep b/serverside_challenge_2/challenge/tmp/pids/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/tmp/pids/.keep rename to serverside_challenge_2/challenge/tmp/pids/.keep diff --git a/serverside_challenge_2/challenge/api/tmp/storage/.keep b/serverside_challenge_2/challenge/tmp/storage/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/tmp/storage/.keep rename to serverside_challenge_2/challenge/tmp/storage/.keep diff --git a/serverside_challenge_2/challenge/api/vendor/.keep b/serverside_challenge_2/challenge/vendor/.keep similarity index 100% rename from serverside_challenge_2/challenge/api/vendor/.keep rename to serverside_challenge_2/challenge/vendor/.keep From c7e43e2758870ea14790b3e6b119f5ca7613fbef Mon Sep 17 00:00:00 2001 From: kuni Date: Thu, 7 Nov 2024 00:26:15 +0900 Subject: [PATCH 52/63] =?UTF-8?q?Readme=E3=81=AE=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/README.md | 43 ++++++++++------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/serverside_challenge_2/challenge/README.md b/serverside_challenge_2/challenge/README.md index 7db80e4ca..78c991815 100644 --- a/serverside_challenge_2/challenge/README.md +++ b/serverside_challenge_2/challenge/README.md @@ -1,24 +1,19 @@ -# README - -This README would normally document whatever steps are necessary to get the -application up and running. - -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... +## 環境構築 +- Dockerの起動 +```sh +$ cd serverside_challenge_2/challenge +$ docker compose build +$ docker compose up +$ docker exec -it challenge-api /bin/bash +``` + +- データベースの初期化 +```sh +root@a35275a66e97:/app# rails db:create +root@a35275a66e97:/app# rails db:migrate +root@a35275a66e97:/app# rails db:seed +``` + +- アプリケーションの実行 + ブラウザにて以下のURLにアクセス + http://localhost:5173/ From cfa68d6cb823ef08264dafdd217df8755dfc1ccd Mon Sep 17 00:00:00 2001 From: kuni Date: Thu, 7 Nov 2024 00:34:23 +0900 Subject: [PATCH 53/63] =?UTF-8?q?Seed=E7=94=A8CSV=E3=81=AE=E8=A1=8C?= =?UTF-8?q?=E3=81=8C=E5=A2=97=E3=81=88=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AB?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=8C=E7=99=BA=E7=94=9F=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/spec/db/seeds_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/serverside_challenge_2/challenge/spec/db/seeds_spec.rb b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb index 00cbf5ab2..425955e86 100644 --- a/serverside_challenge_2/challenge/spec/db/seeds_spec.rb +++ b/serverside_challenge_2/challenge/spec/db/seeds_spec.rb @@ -15,10 +15,9 @@ it 'Planが作成されること' do CSV.foreach(basic_prices_csv_path, headers: true) do |row| - plan = Plan.find_by!(name: row[1]) + provider = Provider.find_by!(name: row[0]) + plan = Plan.find_by!(name: row[1], provider: provider) expect(plan).to be_present - - expect(plan.provider.name).to eq row[0] end end From 6a2c0ab74a92d21c77e9ac808aae79f9e1f0736c Mon Sep 17 00:00:00 2001 From: kuni Date: Thu, 7 Nov 2024 00:41:18 +0900 Subject: [PATCH 54/63] =?UTF-8?q?Frontend=E3=81=AE=E9=9B=BB=E6=B0=97?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=87=8F=E3=81=AE=E5=85=A5=E5=8A=9B=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E6=A1=81=E6=95=B0=E3=82=924=E3=81=8B=E3=82=895?= =?UTF-8?q?=E3=81=B8=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/frontend/src/App.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/serverside_challenge_2/challenge/frontend/src/App.tsx b/serverside_challenge_2/challenge/frontend/src/App.tsx index c04bdead9..9be2daf0e 100644 --- a/serverside_challenge_2/challenge/frontend/src/App.tsx +++ b/serverside_challenge_2/challenge/frontend/src/App.tsx @@ -69,12 +69,12 @@ function App() { { const found = event.target.value.match(/\d/g) - const text = found ? found.join('') : ''; - setElectricityUsageKwh(parseInt(text.length ? text : '0', 10)) + const text = found ? found.join('') : '0'; + setElectricityUsageKwh(parseInt(text, 10)) }} />
From 7cf00d2c74b606b76b471e4be0f13ac4df5cec80 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 10 Nov 2024 21:05:19 +0900 Subject: [PATCH 55/63] =?UTF-8?q?fix:=20Plan=20model=E3=81=AEcheck=5Fparam?= =?UTF-8?q?eters=20method=E3=82=92private=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/plan.rb | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 41e41ef1b..3ebc0a315 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -8,41 +8,41 @@ class Plan < ApplicationRecord validates :name, presence: true, length: { minimum: 1, maximum: 255 } validates :provider, presence: true, uniqueness: { scope: :name } - class << self - def calc_prices(amperage, electricity_usage_kwh) - errors = check_parameters(amperage, electricity_usage_kwh) - if errors.present? - return { errors: { - message: "リクエストパラメーターが正しくありません。", - details: errors - } } - end + def self.calc_prices(amperage, electricity_usage_kwh) + errors = check_parameters(amperage, electricity_usage_kwh) + if errors.present? + return { errors: { + message: "リクエストパラメーターが正しくありません。", + details: errors + } } + end - plans = Plan.all.includes(:provider) - basic_prices_hash = BasicPrice.calc_prices(amperage) - measured_rates_hash = MeasuredRate.calc_prices(electricity_usage_kwh) - response = plans.filter_map do |plan| - basic_price = basic_prices_hash[plan.id] - next if basic_price.nil? + plans = Plan.all.includes(:provider) + basic_prices_hash = BasicPrice.calc_prices(amperage) + measured_rates_hash = MeasuredRate.calc_prices(electricity_usage_kwh) + response = plans.filter_map do |plan| + basic_price = basic_prices_hash[plan.id] + next if basic_price.nil? - measured_rate_price = measured_rates_hash[plan.id]&.[](:price) || 0 - price = (basic_price + measured_rate_price).floor - { - plan: plan, - provider: plan.provider, - price: price - } - end - { plans: response } + measured_rate_price = measured_rates_hash[plan.id]&.[](:price) || 0 + price = (basic_price + measured_rate_price).floor + { + plan: plan, + provider: plan.provider, + price: price + } end + { plans: response } + end - def check_parameters(amperage, electricity_usage_kwh) - errors = [ - BasicPrice.check_amperage?(amperage), - MeasuredRate.validate_electricity_usage?(electricity_usage_kwh) - ] - errors.select { |error| error[:is_error] } - .map { |error| error[:error_object] } - end + private + + def self.check_parameters(amperage, electricity_usage_kwh) + errors = [ + BasicPrice.check_amperage?(amperage), + MeasuredRate.validate_electricity_usage?(electricity_usage_kwh) + ] + errors.select { |error| error[:is_error] } + .map { |error| error[:error_object] } end end From a461768f70243de7083c446d652eecafae2efa74 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 10 Nov 2024 21:43:29 +0900 Subject: [PATCH 56/63] =?UTF-8?q?Revert=20"fix:=20Plan=20model=E3=81=AEche?= =?UTF-8?q?ck=5Fparameters=20method=E3=82=92private=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7cf00d2c74b606b76b471e4be0f13ac4df5cec80. --- .../challenge/app/models/plan.rb | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 3ebc0a315..41e41ef1b 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -8,41 +8,41 @@ class Plan < ApplicationRecord validates :name, presence: true, length: { minimum: 1, maximum: 255 } validates :provider, presence: true, uniqueness: { scope: :name } - def self.calc_prices(amperage, electricity_usage_kwh) - errors = check_parameters(amperage, electricity_usage_kwh) - if errors.present? - return { errors: { - message: "リクエストパラメーターが正しくありません。", - details: errors - } } - end + class << self + def calc_prices(amperage, electricity_usage_kwh) + errors = check_parameters(amperage, electricity_usage_kwh) + if errors.present? + return { errors: { + message: "リクエストパラメーターが正しくありません。", + details: errors + } } + end - plans = Plan.all.includes(:provider) - basic_prices_hash = BasicPrice.calc_prices(amperage) - measured_rates_hash = MeasuredRate.calc_prices(electricity_usage_kwh) - response = plans.filter_map do |plan| - basic_price = basic_prices_hash[plan.id] - next if basic_price.nil? + plans = Plan.all.includes(:provider) + basic_prices_hash = BasicPrice.calc_prices(amperage) + measured_rates_hash = MeasuredRate.calc_prices(electricity_usage_kwh) + response = plans.filter_map do |plan| + basic_price = basic_prices_hash[plan.id] + next if basic_price.nil? - measured_rate_price = measured_rates_hash[plan.id]&.[](:price) || 0 - price = (basic_price + measured_rate_price).floor - { - plan: plan, - provider: plan.provider, - price: price - } + measured_rate_price = measured_rates_hash[plan.id]&.[](:price) || 0 + price = (basic_price + measured_rate_price).floor + { + plan: plan, + provider: plan.provider, + price: price + } + end + { plans: response } end - { plans: response } - end - private - - def self.check_parameters(amperage, electricity_usage_kwh) - errors = [ - BasicPrice.check_amperage?(amperage), - MeasuredRate.validate_electricity_usage?(electricity_usage_kwh) - ] - errors.select { |error| error[:is_error] } - .map { |error| error[:error_object] } + def check_parameters(amperage, electricity_usage_kwh) + errors = [ + BasicPrice.check_amperage?(amperage), + MeasuredRate.validate_electricity_usage?(electricity_usage_kwh) + ] + errors.select { |error| error[:is_error] } + .map { |error| error[:error_object] } + end end end From 2d21a1a7359680380b1ae1aaadd7145e285375f7 Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 10 Nov 2024 21:46:59 +0900 Subject: [PATCH 57/63] =?UTF-8?q?fix:=20Plan.check=5Fparameters=20method?= =?UTF-8?q?=E3=82=92private=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/app/models/plan.rb | 2 ++ serverside_challenge_2/challenge/spec/models/plan_spec.rb | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/plan.rb b/serverside_challenge_2/challenge/app/models/plan.rb index 41e41ef1b..84195ccca 100644 --- a/serverside_challenge_2/challenge/app/models/plan.rb +++ b/serverside_challenge_2/challenge/app/models/plan.rb @@ -36,6 +36,8 @@ def calc_prices(amperage, electricity_usage_kwh) { plans: response } end + private + def check_parameters(amperage, electricity_usage_kwh) errors = [ BasicPrice.check_amperage?(amperage), diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index 0ce86846f..b8753273d 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -113,11 +113,11 @@ describe 'check_parameters' do context 'amperage' do it '正常な場合、レスポンスにエラーが含まれないこと' do - expect(Plan.check_parameters(20, 1000)).to be_empty + expect(Plan.send(:check_parameters, 20, 1000)).to be_empty end it 'エラーの場合、レスポンスにエラーが含まれること' do - res = Plan.check_parameters(0, 1000) + res = Plan.send(:check_parameters, 0, 1000) expect(res.size).to eq 1 expect(res[0][:field]).to eq 'amperage' expect(res[0][:message]).to eq "#{BasicPrice::AMPERAGE_LIST.join('/')}のいずれかを指定してください。" @@ -126,7 +126,7 @@ context 'electricity_usage_kwh' do it 'エラーの場合、レスポンスにエラーが含まれること' do - res = Plan.check_parameters(20, nil) + res = Plan.send(:check_parameters, 20, nil) expect(res.size).to eq 1 expect(res[0][:field]).to eq 'electricity_usage_kwh' expect(res[0][:message]).to eq '整数を指定してください。' From 56a75f6aebdc70e8875f9b21acc66a990b9f15df Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 10 Nov 2024 22:15:52 +0900 Subject: [PATCH 58/63] =?UTF-8?q?fix:=20API=E3=83=AA=E3=82=AF=E3=82=A8?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E6=99=82?= =?UTF-8?q?=E3=81=AE=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE?= =?UTF-8?q?=E6=A4=9C=E8=A8=BC=E3=82=92include=E3=81=8B=E3=82=89=E5=8E=B3?= =?UTF-8?q?=E5=AF=86=E3=81=AB=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81eq?= =?UTF-8?q?=E3=81=B8=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/basic_price.rb | 3 ++- .../challenge/spec/models/basic_price_spec.rb | 16 +++++++++++++++- .../challenge/spec/models/plan_spec.rb | 4 ++-- .../requests/api/electricity/calculate_spec.rb | 16 ++++++++-------- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/basic_price.rb b/serverside_challenge_2/challenge/app/models/basic_price.rb index ffeaa6cbe..9c27adc59 100644 --- a/serverside_challenge_2/challenge/app/models/basic_price.rb +++ b/serverside_challenge_2/challenge/app/models/basic_price.rb @@ -4,6 +4,7 @@ class BasicPrice < ApplicationRecord belongs_to :plan AMPERAGE_LIST = [ 10, 15, 20, 30, 40, 50, 60 ].freeze + ERR_MESS_INVALID_AMPERAGE = "#{AMPERAGE_LIST.join('/')}のいずれかを指定してください。".freeze validates :amperage, presence: true, inclusion: { in: AMPERAGE_LIST } validates :price, numericality: { only_numeric: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 99999.99 } @@ -20,7 +21,7 @@ def calc_prices(amperage) def check_amperage?(amperage) res = { is_error: !AMPERAGE_LIST.include?(amperage) } - res[:error_object] = { field: "amperage", message: "#{AMPERAGE_LIST.join('/')}のいずれかを指定してください。" } if res[:is_error] + res[:error_object] = { field: "amperage", message: ERR_MESS_INVALID_AMPERAGE } if res[:is_error] res end end diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb index 7357ad0e4..f32adbefe 100644 --- a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -131,6 +131,20 @@ end end + describe 'Constants' do + context 'AMPERAGE_LIST' do + it '10, 15, 20, 30, 40, 50, 60が含まれる' do + expect(BasicPrice::AMPERAGE_LIST).to eq [ 10, 15, 20, 30, 40, 50, 60 ] + end + end + + context 'ERR_MESS_INVALID_AMPERAGE' do + it 'AMPERAGE_LISTの値を含む' do + expect(BasicPrice::ERR_MESS_INVALID_AMPERAGE).to eq '10/15/20/30/40/50/60のいずれかを指定してください。' + end + end + end + describe 'class methods' do describe 'check_amperage?' do it 'AMPERAGE_LISTに存在する値はis_error=falseとなる' do @@ -145,7 +159,7 @@ res = BasicPrice.check_amperage?(nil) expect(res[:is_error]).to be_truthy expect(res[:error_object][:field]).to eq 'amperage' - expect(res[:error_object][:message]).to eq "#{BasicPrice::AMPERAGE_LIST.join('/')}のいずれかを指定してください。" + expect(res[:error_object][:message]).to eq BasicPrice::ERR_MESS_INVALID_AMPERAGE end end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index b8753273d..674899dd4 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -120,7 +120,7 @@ res = Plan.send(:check_parameters, 0, 1000) expect(res.size).to eq 1 expect(res[0][:field]).to eq 'amperage' - expect(res[0][:message]).to eq "#{BasicPrice::AMPERAGE_LIST.join('/')}のいずれかを指定してください。" + expect(res[0][:message]).to eq BasicPrice::ERR_MESS_INVALID_AMPERAGE end end @@ -159,7 +159,7 @@ expect(res[:errors][:message]).to eq 'リクエストパラメーターが正しくありません。' expect(res[:errors][:details].size).to eq 1 expect(res[:errors][:details][0][:field]).to eq 'amperage' - expect(res[:errors][:details][0][:message]).to include "のいずれかを指定してください。" + expect(res[:errors][:details][0][:message]).to eq BasicPrice::ERR_MESS_INVALID_AMPERAGE end end diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb index 8d292688a..963d3a62a 100644 --- a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb +++ b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb @@ -76,11 +76,11 @@ expect(response).to have_http_status(400) body = JSON.parse(response.body, symbolize_names: true) - expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + expect(body[:message]).to eq('リクエストパラメーターが正しくありません。') expect(body[:details].size).to eq 1 expect(body[:details][0][:field]).to eq 'amperage' - expect(body[:details][0][:message]).to include('のいずれかを指定してください。') + expect(body[:details][0][:message]).to eq BasicPrice::ERR_MESS_INVALID_AMPERAGE end it '文字列の場合、エラーとなる' do @@ -89,11 +89,11 @@ expect(response).to have_http_status(400) body = JSON.parse(response.body, symbolize_names: true) - expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + expect(body[:message]).to eq('リクエストパラメーターが正しくありません。') expect(body[:details].size).to eq 1 expect(body[:details][0][:field]).to eq 'amperage' - expect(body[:details][0][:message]).to include('のいずれかを指定してください。') + expect(body[:details][0][:message]).to eq BasicPrice::ERR_MESS_INVALID_AMPERAGE end end @@ -104,7 +104,7 @@ expect(response).to have_http_status(400) body = JSON.parse(response.body, symbolize_names: true) - expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + expect(body[:message]).to eq('リクエストパラメーターが正しくありません。') expect(body[:details].size).to eq 1 expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' @@ -117,7 +117,7 @@ expect(response).to have_http_status(400) body = JSON.parse(response.body, symbolize_names: true) - expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + expect(body[:message]).to eq('リクエストパラメーターが正しくありません。') expect(body[:details].size).to eq 1 expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' @@ -130,7 +130,7 @@ expect(response).to have_http_status(400) body = JSON.parse(response.body, symbolize_names: true) - expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + expect(body[:message]).to eq('リクエストパラメーターが正しくありません。') expect(body[:details].size).to eq 1 expect(body[:details][0][:field]).to eq 'electricity_usage_kwh' @@ -147,7 +147,7 @@ expect(response).to have_http_status(400) body = JSON.parse(response.body, symbolize_names: true) - expect(body[:message]).to include('リクエストパラメーターが正しくありません。') + expect(body[:message]).to eq('リクエストパラメーターが正しくありません。') expect(body[:details].size).to eq 2 expect(body[:details][0][:field]).to eq 'amperage' From 1ed43bb29b2fd783840b82450f0347631ebcda4b Mon Sep 17 00:00:00 2001 From: kuni Date: Sun, 10 Nov 2024 22:55:44 +0900 Subject: [PATCH 59/63] =?UTF-8?q?fix:=20Dockerfile=E3=81=AECMD=E3=81=8C?= =?UTF-8?q?=E8=A4=87=E6=95=B0=E5=9B=9E=E6=8C=87=E5=AE=9A=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=82=8B=E5=95=8F=E9=A1=8C=E3=81=AE=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Dockerfile | 4 +--- serverside_challenge_2/challenge/start-api.sh | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100755 serverside_challenge_2/challenge/start-api.sh diff --git a/serverside_challenge_2/challenge/Dockerfile b/serverside_challenge_2/challenge/Dockerfile index 80feb2957..763a068cc 100644 --- a/serverside_challenge_2/challenge/Dockerfile +++ b/serverside_challenge_2/challenge/Dockerfile @@ -7,6 +7,4 @@ ADD Gemfile.lock /app/Gemfile.lock RUN bundle install ADD . /app -CMD ["rm", "f", "tmp/pids/server"] -EXPOSE 3000 -CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"] +CMD ["./start-api.sh"] \ No newline at end of file diff --git a/serverside_challenge_2/challenge/start-api.sh b/serverside_challenge_2/challenge/start-api.sh new file mode 100755 index 000000000..07aa26be1 --- /dev/null +++ b/serverside_challenge_2/challenge/start-api.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +cd /app +rm -f /app/tmp/pids/server.pid +bundle exec rails s -p 3000 -b '0.0.0.0' From cfeacf9380c2f70bc0f1fd1751bf8b8cc1eebbcf Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 11 Nov 2024 21:17:52 +0900 Subject: [PATCH 60/63] =?UTF-8?q?fix:=20=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=8C=E8=8B=B1?= =?UTF-8?q?=E8=AA=9E=E3=81=A8=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=8C=E6=B7=B7?= =?UTF-8?q?=E5=9C=A8=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=81=9F=E3=82=81?= =?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AB=E7=B5=B1=E4=B8=80=20-=20i1?= =?UTF-8?q?8n=E3=82=92=E5=B0=8E=E5=85=A5=20-=20locale=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AB=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=20-=20=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/Gemfile | 2 +- serverside_challenge_2/challenge/Gemfile.lock | 4 ++ .../challenge/app/models/measured_rate.rb | 9 +++-- .../challenge/config/application.rb | 5 +++ .../challenge/spec/models/basic_price_spec.rb | 20 +++++----- .../spec/models/measure_rate_spec.rb | 37 ++++++++++--------- .../challenge/spec/models/plan_spec.rb | 10 ++--- .../challenge/spec/models/provider_spec.rb | 8 ++-- 8 files changed, 53 insertions(+), 42 deletions(-) diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 118a93de7..6498a1516 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -5,7 +5,7 @@ ruby "3.1.2" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.0.8" - +gem "rails-i18n", "~> 7.0", ">= 7.0.10" # Use postgresql as the database for Active Record gem "pg", "~> 1.1" diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index d8dacf95d..8deecfce4 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -176,6 +176,9 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) + rails-i18n (7.0.10) + i18n (>= 0.7, < 2) + railties (>= 6.0.0, < 8) railties (7.0.8) actionpack (= 7.0.8) activesupport (= 7.0.8) @@ -275,6 +278,7 @@ DEPENDENCIES rack-timeout (~> 0.7.0) rails (~> 7.0.8) rails-erd (~> 1.7, >= 1.7.2) + rails-i18n (~> 7.0, >= 7.0.10) rspec-openapi rspec-rails rubocop-rails-omakase diff --git a/serverside_challenge_2/challenge/app/models/measured_rate.rb b/serverside_challenge_2/challenge/app/models/measured_rate.rb index d20b06349..31c2416dc 100644 --- a/serverside_challenge_2/challenge/app/models/measured_rate.rb +++ b/serverside_challenge_2/challenge/app/models/measured_rate.rb @@ -2,6 +2,7 @@ class MeasuredRate < ApplicationRecord MAX_SMALL_INT_VALUE = 32767 + ERR_MESS_INVALID_ELECTRICITY_USAGE = "電気使用量の範囲が重複しています".freeze belongs_to :plan @@ -53,7 +54,7 @@ def validate_max_greater_than_min return if electricity_usage_max.nil? || electricity_usage_min.nil? if electricity_usage_max < electricity_usage_min - errors.add(:electricity_usage_max, "must be greater than or equal to electricity_usage_min") + errors.add(:electricity_usage_max, "電気使用量の上限値を下限値より大きくしてください") end end @@ -68,19 +69,19 @@ def validate_electricity_usage def validate_electricity_usage_min(rates) if rates.find { |rate| rate.electricity_usage_min <= electricity_usage_min && electricity_usage_min <= rate.electricity_usage_max }.present? - errors.add(:electricity_usage_min, "range overlaps with an existing range") + errors.add(:electricity_usage_min, ERR_MESS_INVALID_ELECTRICITY_USAGE) end end def validate_electricity_usage_max(rates) if rates.find { |rate| rate.electricity_usage_min <= electricity_usage_max && electricity_usage_max <= rate.electricity_usage_max }.present? - errors.add(:electricity_usage_max, "range overlaps with an existing range") + errors.add(:electricity_usage_max, ERR_MESS_INVALID_ELECTRICITY_USAGE) end end def validate_electricity_usage_contain(rates) if rates.find { |rate| electricity_usage_min <= rate.electricity_usage_min && rate.electricity_usage_max <= electricity_usage_max }.present? - errors.add(:electricity_usage_max, "range overlaps with an existing range") + errors.add(:electricity_usage_max, ERR_MESS_INVALID_ELECTRICITY_USAGE) end end end diff --git a/serverside_challenge_2/challenge/config/application.rb b/serverside_challenge_2/challenge/config/application.rb index ccfa69e6d..0f9f3e46e 100644 --- a/serverside_challenge_2/challenge/config/application.rb +++ b/serverside_challenge_2/challenge/config/application.rb @@ -15,6 +15,11 @@ class Application < Rails::Application config.time_zone = "Tokyo" config.active_record.default_timezone = :local + # i18n + config.i18n.default_locale = :ja + config.i18n.available_locales = [ :ja ] + config.i18n.load_path += Dir[Rails.root.join("config/locales/**/*.{rb,yml}").to_s] + # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files diff --git a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb index f32adbefe..fbcee1125 100644 --- a/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/basic_price_spec.rb @@ -22,25 +22,25 @@ it 'nilの場合無効であること' do instance = build(:basic_price, plan: plan, amperage: nil) expect(instance).to be_invalid - instance.errors[:amperage].include?("can't be blank") + expect(instance.errors[:amperage]).to include("は一覧にありません") end it '空文字の場合無効であること' do instance = build(:basic_price, plan: plan, amperage: '') expect(instance).to be_invalid - instance.errors[:amperage].include?("can't be blank") + expect(instance.errors[:amperage]).to include("は一覧にありません") end it '文字列の場合無効であること' do instance = build(:basic_price, plan: plan, amperage: 'a') expect(instance).to be_invalid - instance.errors[:amperage].include?("is not a number") + expect(instance.errors[:amperage]).to include("は一覧にありません") end it 'AMPERAGE_LISTに定義されない値の場合無効であること' do instance = build(:basic_price, plan: plan, amperage: 1) expect(instance).to be_invalid - instance.errors[:amperage].include?("is not included in the list") + expect(instance.errors[:amperage]).to include("は一覧にありません") end context 'uniqueness' do @@ -57,7 +57,7 @@ instance = build(:basic_price, plan: plan, amperage: 10) expect(instance).to be_invalid - instance.errors[:plan].include?("has already been taken") + expect(instance.errors[:plan]).to include("はすでに存在します") end end end @@ -66,19 +66,19 @@ it 'nilの場合無効であること' do instance = build(:basic_price, plan: plan, price: nil) expect(instance).to be_invalid - instance.errors[:price].include?("can't be blank") + expect(instance.errors[:price]).to include("は数値で入力してください") end it '空文字の場合無効であること' do instance = build(:basic_price, plan: plan, price: '') expect(instance).to be_invalid - instance.errors[:price].include?("is not a number") + expect(instance.errors[:price]).to include("は数値で入力してください") end it '文字列の場合無効であること' do instance = build(:basic_price, plan: plan, price: 'a') expect(instance).to be_invalid - instance.errors[:price].include?("is not a number") + expect(instance.errors[:price]).to include("は数値で入力してください") end it '0の場合有効であること' do @@ -94,7 +94,7 @@ it '100000.00の場合無効であること' do instance = build(:basic_price, plan: plan, price: 100000.00) expect(instance).to be_invalid - instance.errors[:price].include?("must be less than or equal to 99999.99") + expect(instance.errors[:price]).to include("は99999.99以下の値にしてください") end end @@ -102,7 +102,7 @@ it 'nilの場合無効であること' do instance = build(:basic_price, plan: nil) expect(instance).to be_invalid - instance.errors[:plan].include?("must exist") + expect(instance.errors[:plan]).to include("を入力してください") end end end diff --git a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb index 751c79c47..694bf2e5a 100644 --- a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb @@ -15,25 +15,26 @@ it 'nilの場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_min: nil) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_min]).to include("is not a number") + + expect(instance.errors[:electricity_usage_min]).to eq [ "は数値で入力してください" ] end it 'マイナス値の場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_min: -1) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_min]).to include("must be greater than or equal to 0") + expect(instance.errors[:electricity_usage_min]).to eq [ 'は0以上の値にしてください' ] end it '空文字の場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_min: '') expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_min]).to include("is not a number") + expect(instance.errors[:electricity_usage_min]).to eq [ 'は数値で入力してください' ] end it '文字列の場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_min: 'a') expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_min]).to include("is not a number") + expect(instance.errors[:electricity_usage_min]).to eq [ 'は数値で入力してください' ] end it '1以上の場合有効であること' do @@ -53,7 +54,7 @@ electricity_usage_min: MeasuredRate::MAX_SMALL_INT_VALUE + 1, electricity_usage_max: MeasuredRate::MAX_SMALL_INT_VALUE + 1) expect(instance).not_to be_valid - expect(instance.errors[:electricity_usage_min]).to include("must be less than or equal to 32767") + expect(instance.errors[:electricity_usage_min]).to eq [ 'は32767以下の値にしてください' ] end end @@ -67,19 +68,19 @@ it 'マイナス値の場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_max: -1) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include("must be greater than or equal to 1") + expect(instance.errors[:electricity_usage_max]).to include('は1以上の値にしてください') end it '空文字の場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_max: '') expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include("is not a number") + expect(instance.errors[:electricity_usage_max]).to eq [ 'は数値で入力してください' ] end it '文字列の場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_max: 'a') expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include("is not a number") + expect(instance.errors[:electricity_usage_max]).to eq [ 'は数値で入力してください' ] end it '1以上の場合有効であること' do @@ -99,7 +100,7 @@ electricity_usage_min: MeasuredRate::MAX_SMALL_INT_VALUE + 1, electricity_usage_max: MeasuredRate::MAX_SMALL_INT_VALUE + 1) expect(instance).not_to be_valid - expect(instance.errors[:electricity_usage_max]).to include("must be less than or equal to 32767") + expect(instance.errors[:electricity_usage_max]).to eq [ 'は32767以下の値にしてください' ] end end @@ -107,7 +108,7 @@ it 'electricity_usage_max < electricity_usage_minの場合無効であること' do instance = build(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 1) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include("must be greater than or equal to electricity_usage_min") + expect(instance.errors[:electricity_usage_max]).to include("電気使用量の上限値を下限値より大きくしてください") end end @@ -119,7 +120,7 @@ instance = build(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_min]).to include("range overlaps with an existing range") + expect(instance.errors[:electricity_usage_min]).to include('電気使用量の範囲が重複しています') end end @@ -129,7 +130,7 @@ instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include("range overlaps with an existing range") + expect(instance.errors[:electricity_usage_max]).to include('電気使用量の範囲が重複しています') end end @@ -139,7 +140,7 @@ instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 4) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include("range overlaps with an existing range") + expect(instance.errors[:electricity_usage_max]).to include('電気使用量の範囲が重複しています') end end end @@ -181,19 +182,19 @@ it 'nilの場合無効であること' do instance = build(:measured_rate, plan: plan, price: nil) expect(instance).to be_invalid - instance.errors[:price].include?("can't be blank") + expect(instance.errors[:price]).to include("は数値で入力してください") end it '空文字の場合無効であること' do instance = build(:measured_rate, plan: plan, price: '') expect(instance).to be_invalid - instance.errors[:price].include?("is not a number") + expect(instance.errors[:price]).to include("は数値で入力してください") end it '文字列の場合無効であること' do instance = build(:measured_rate, plan: plan, price: 'a') expect(instance).to be_invalid - instance.errors[:price].include?("is not a number") + expect(instance.errors[:price]).to include("は数値で入力してください") end it '0の場合有効であること' do @@ -209,7 +210,7 @@ it '100000.00の場合無効であること' do instance = build(:measured_rate, plan: plan, price: 100000.00) expect(instance).to be_invalid - instance.errors[:price].include?("must be less than or equal to 99999.99") + expect(instance.errors[:price]).to include("は99999.99以下の値にしてください") end end @@ -217,7 +218,7 @@ it 'nilの場合無効であること' do instance = build(:measured_rate, plan: nil) expect(instance).to be_invalid - instance.errors[:plan].include?("must exist") + expect(instance.errors[:plan]).to include("を入力してください") end end end diff --git a/serverside_challenge_2/challenge/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/spec/models/plan_spec.rb index 674899dd4..fe76765a1 100644 --- a/serverside_challenge_2/challenge/spec/models/plan_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/plan_spec.rb @@ -24,19 +24,19 @@ it 'nilの場合無効であること' do instance = build(:plan, provider: provider, name: nil) expect(instance).to be_invalid - instance.errors[:name].include?("can't be blank") + expect(instance.errors[:name]).to include("を入力してください") end it '空文字の場合無効であること' do instance = build(:plan, provider: provider, name: '') expect(instance).to be_invalid - instance.errors[:name].include?("can't be blank") + expect(instance.errors[:name]).to include("を入力してください") end it '256文字の場合無効であること' do instance = build(:plan, provider: provider, name: 'a' * 256) expect(instance).to be_invalid - instance.errors[:name].include?("is too long (maximum is 255 characters)") + expect(instance.errors[:name]).to include("は255文字以内で入力してください") end context 'uniqueness' do @@ -53,7 +53,7 @@ instance = build(:plan, provider: provider, name: 'プラン') expect(instance).to be_invalid - instance.errors[:name].include?("has already been taken") + expect(instance.errors[:provider]).to include("はすでに存在します") end end end @@ -62,7 +62,7 @@ it 'nilの場合無効であること' do instance = build(:plan, provider: nil) expect(instance).to be_invalid - instance.errors[:provider].include?("must exist") + expect(instance.errors[:provider]).to include("を入力してください") end end end diff --git a/serverside_challenge_2/challenge/spec/models/provider_spec.rb b/serverside_challenge_2/challenge/spec/models/provider_spec.rb index 15cb6217e..a697c50d0 100644 --- a/serverside_challenge_2/challenge/spec/models/provider_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/provider_spec.rb @@ -22,19 +22,19 @@ it 'nilの場合無効であること' do instance = build(:provider, name: nil) expect(instance).to be_invalid - instance.errors[:name].include?("can't be blank") + expect(instance.errors[:name]).to include("を入力してください") end it '空文字の場合無効であること' do instance = build(:provider, name: '') expect(instance).to be_invalid - instance.errors[:name].include?("can't be blank") + expect(instance.errors[:name]).to include("を入力してください") end it '256文字の場合無効であること' do instance = build(:provider, name: 'a' * 256) expect(instance).to be_invalid - instance.errors[:name].include?("is too long (maximum is 255 characters)") + expect(instance.errors[:name]).to include("は255文字以内で入力してください") end it '重複する場合無効であること' do @@ -42,7 +42,7 @@ instance = build(:provider, name: '電力会社') expect(instance).to be_invalid - instance.errors[:name].include?("has already been taken") + expect(instance.errors[:name]).to include("はすでに存在します") end end end From 06bec359b42589233dfc906ab93ed38559940c67 Mon Sep 17 00:00:00 2001 From: kuni Date: Mon, 11 Nov 2024 22:48:15 +0900 Subject: [PATCH 61/63] =?UTF-8?q?fix:=20=E9=9B=BB=E6=B0=97=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E9=87=8F=E3=81=AE=E7=AF=84=E5=9B=B2=E3=83=81=E3=82=A7?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=81=AE=E7=B0=A1=E7=95=A5=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/app/models/measured_rate.rb | 24 ++-------- .../spec/models/measure_rate_spec.rb | 46 +++++++++++++++---- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/serverside_challenge_2/challenge/app/models/measured_rate.rb b/serverside_challenge_2/challenge/app/models/measured_rate.rb index 31c2416dc..427ddb69c 100644 --- a/serverside_challenge_2/challenge/app/models/measured_rate.rb +++ b/serverside_challenge_2/challenge/app/models/measured_rate.rb @@ -62,26 +62,12 @@ def validate_electricity_usage return if electricity_usage_max.nil? || electricity_usage_min.nil? rates = self.class.where(plan: plan).where.not(id: id) - validate_electricity_usage_min(rates) - validate_electricity_usage_max(rates) - validate_electricity_usage_contain(rates) - end - - def validate_electricity_usage_min(rates) - if rates.find { |rate| rate.electricity_usage_min <= electricity_usage_min && electricity_usage_min <= rate.electricity_usage_max }.present? - errors.add(:electricity_usage_min, ERR_MESS_INVALID_ELECTRICITY_USAGE) - end - end - - def validate_electricity_usage_max(rates) - if rates.find { |rate| rate.electricity_usage_min <= electricity_usage_max && electricity_usage_max <= rate.electricity_usage_max }.present? - errors.add(:electricity_usage_max, ERR_MESS_INVALID_ELECTRICITY_USAGE) - end - end - def validate_electricity_usage_contain(rates) - if rates.find { |rate| electricity_usage_min <= rate.electricity_usage_min && rate.electricity_usage_max <= electricity_usage_max }.present? - errors.add(:electricity_usage_max, ERR_MESS_INVALID_ELECTRICITY_USAGE) + a = electricity_usage_min..electricity_usage_max + is_error = rates.any? do |rate| + b = rate.electricity_usage_min..rate.electricity_usage_max + b.include?(a.min) || b.include?(a.max) || a.include?(b.min) || a.include?(b.max) end + errors.add(:electricity_usage_min, ERR_MESS_INVALID_ELECTRICITY_USAGE) if is_error end end diff --git a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb index 694bf2e5a..b10d3fa34 100644 --- a/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb +++ b/serverside_challenge_2/challenge/spec/models/measure_rate_spec.rb @@ -114,33 +114,59 @@ context 'uniqueness' do context 'create時' do - context 'electricity_usage_min' do - it '他のrateに重複する場合無効であること' do + context '電気使用量の範囲重複' do + it 'A.electricity_usage_minとB.electricity_usage_maxの数値が一致しない場合、有効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 3, electricity_usage_max: 4) + expect(instance).to be_valid + end + + it 'A.electricity_usage_maxとB.electricity_usage_minの数値が一致しない場合、有効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 4, electricity_usage_max: 5) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 3) + expect(instance).to be_valid + end + + it 'A.electricity_usage_minがBの範囲に含まれる場合、無効であること' do create(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) instance = build(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) expect(instance).to be_invalid expect(instance.errors[:electricity_usage_min]).to include('電気使用量の範囲が重複しています') end - end - context 'electricity_usage_max' do - it '他のrateに重複する場合無効であること' do + it 'A.electricity_usage_maxがBの範囲に含まれる場合、無効であること' do create(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 2) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include('電気使用量の範囲が重複しています') + expect(instance.errors[:electricity_usage_min]).to include('電気使用量の範囲が重複しています') end - end - context 'electricity_usage_min, electricity_usage_maxのoverlap' do - it '他のrateに重複する場合無効であること' do + it 'Aの範囲内にBのmin,maxが含まれる場合、無効であること' do create(:measured_rate, plan: plan, electricity_usage_min: 2, electricity_usage_max: 3) instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 4) expect(instance).to be_invalid - expect(instance.errors[:electricity_usage_max]).to include('電気使用量の範囲が重複しています') + expect(instance.errors[:electricity_usage_min]).to include('電気使用量の範囲が重複しています') + end + + it 'Bの範囲内にAのminが含まれる場合、無効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 3) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 4) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include('電気使用量の範囲が重複しています') + end + + it 'Bの範囲内にAのmaxが含まれる場合、無効であること' do + create(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 3) + + instance = build(:measured_rate, plan: plan, electricity_usage_min: 1, electricity_usage_max: 4) + expect(instance).to be_invalid + expect(instance.errors[:electricity_usage_min]).to include('電気使用量の範囲が重複しています') end end end From 7a9e3ecd28459d7db61c7b9f68c20f5475eba930 Mon Sep 17 00:00:00 2001 From: kuni Date: Tue, 12 Nov 2024 21:08:25 +0900 Subject: [PATCH 62/63] =?UTF-8?q?fix:=20=E3=80=8C=E3=81=AE=E3=81=84?= =?UTF-8?q?=E3=81=9A=E3=82=8C=E3=81=8B=E3=82=92=E6=8C=87=E5=AE=9A=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=8F=E3=81=A0=E3=81=95=E3=81=84=E3=80=82=E3=80=8D?= =?UTF-8?q?=E3=81=AE=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E6=AF=94?= =?UTF-8?q?=E8=BC=83=E3=81=8C=E4=BB=96=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=A8=E7=B5=B1=E4=B8=80=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=AA=E3=81=84test=E3=81=AE=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/spec/requests/api/electricity/calculate_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb index 963d3a62a..6924bc03e 100644 --- a/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb +++ b/serverside_challenge_2/challenge/spec/requests/api/electricity/calculate_spec.rb @@ -151,7 +151,7 @@ expect(body[:details].size).to eq 2 expect(body[:details][0][:field]).to eq 'amperage' - expect(body[:details][0][:message]).to include('いずれかを指定してください。') + expect(body[:details][0][:message]).to eq BasicPrice::ERR_MESS_INVALID_AMPERAGE expect(body[:details][1][:field]).to eq 'electricity_usage_kwh' expect(body[:details][1][:message]).to eq '整数を指定してください。' end From 8714e1bb4ca7d6e511c058c48c239c79806b94b8 Mon Sep 17 00:00:00 2001 From: kuni Date: Tue, 12 Nov 2024 21:40:42 +0900 Subject: [PATCH 63/63] =?UTF-8?q?fix:=20Frontend=E3=81=AE=E8=A8=88?= =?UTF-8?q?=E7=AE=97=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E6=9C=89=E5=8A=B9?= =?UTF-8?q?=E7=8A=B6=E6=85=8B=E3=81=AE=E5=88=A4=E5=AE=9A=E3=82=92=3D=3D?= =?UTF-8?q?=E3=81=8B=E3=82=89=3D=3D=3D=E3=81=B8=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serverside_challenge_2/challenge/frontend/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverside_challenge_2/challenge/frontend/src/App.tsx b/serverside_challenge_2/challenge/frontend/src/App.tsx index 9be2daf0e..1d3550b21 100644 --- a/serverside_challenge_2/challenge/frontend/src/App.tsx +++ b/serverside_challenge_2/challenge/frontend/src/App.tsx @@ -79,7 +79,7 @@ function App() { />
- +