diff --git a/serverside_challenge_2/challenge/.gitignore b/serverside_challenge_2/challenge/.gitignore index 88381219c..a1c1bd13b 100644 --- a/serverside_challenge_2/challenge/.gitignore +++ b/serverside_challenge_2/challenge/.gitignore @@ -1,29 +1 @@ -# 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 +/copilot/* diff --git a/serverside_challenge_2/challenge/README.md b/serverside_challenge_2/challenge/README.md index 7db80e4ca..bce49568c 100644 --- a/serverside_challenge_2/challenge/README.md +++ b/serverside_challenge_2/challenge/README.md @@ -1,24 +1,223 @@ -# 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 +- バックエンド + - Ruby `3.1.2` + - Rails `7.0.8` +- フロントエンド + - vue `3.5.13` + - vite `6.2.0` -* System dependencies +# ファイル構成 -* Configuration +``` +. +├── docker-compose.override.yml # 開発環境の時に上書きで利用するDocker設定 +├── docker-compose.yml # 開発・本番環境のサービス定義とコンテナ設定 +├── backend +│ └── Dockerfile # バックエンド (Rails) アプリケーションのDockerfile +└── frontend + ├── Dockerfile # フロントエンド (Vue.js) アプリケーションの本番用Dockerfile + └── Dockerfile.dev # フロントエンド (Vue.js) アプリケーションの開発用Dockerfile +``` -* Database creation +**※ バックエンド(Rails)のDockerfileは本番・開発用共通です** -* Database initialization +# ローカル環境構築手順 -* How to run the test suite +1. Dockerコンテナを起動 -* Services (job queues, cache servers, search engines, etc.) +```bash +docker compose up --build +``` -* Deployment instructions +2. データベースを作成 -* ... +```bash +docker compose run web rails db:create +``` + +3. マイグレーション実行 + + +```bash +docker compose run web rails db:migrate +``` + +4. シードデータの投入 + +```bash +docker compose run web rails db:seed +``` + +# 本番デプロイ手順(aws) + +1. Copilotのインストール + +以下を参考に環境に合わせて実施 +https://docs.aws.amazon.com/AmazonECS/latest/developerguide/copilot-install.html + +2. AWS環境の初期設定 + +```bash +aws configure +``` + +3. フロントエンドのデプロイ + +```bash +copilot svc deploy --name frontend --env prod +``` + +4. バックエンドのデプロイ + +```bash +copilot svc deploy --name web --env prod +``` + +**※ `copilot/` ディレクトリが必要ですが、機密情報が含まれるためコミット対象に含めておりません** +**そのため、本手順は参考としてご確認していただけたら幸いです。** +**どうするのが正解かは分かりませんが、本当の運用であれば機密情報の管理はAWS Systems Manager 等で管理すべきかと思います。** + +# アプリケーションへのアクセス + +http://localhost:5173 + +# アプリケーションの利用方法 + +- 以下項目を入力する + - 契約アンペア数(A) + - 1ヶ月の使用量(kWh) +- 計算するを押下する +- 正常な値を入力した場合 + - 画面上に電力会社、プラン、料金(円)が表示される +- 不正な値を入力した場合 + - エラーが表示される + +# データベース設計 + +## テーブル一覧 +- `providers` (電力会社情報) +- `plans` (電力会社ごとのプラン) +- `electricity_charges_basic_rates` (プランごとの基本料金) +- `electricity_charges_usage_rates` (プランごとの従量料金) + +## テーブル詳細 + +### `providers` (電力会社情報) +| カラム名 | 型 | 制約 | 説明 | +|:---|:---|:---|:---| +| `id` | `bigint` | PK, NOT NULL | ID (主キー) | +| `name` | `string` | NOT NULL, UNIQUE | 会社名 | +| `created_at` | `datetime` | NOT NULL | 作成日時 | +| `updated_at` | `datetime` | NOT NULL | 更新日時 | + +--- + +### `plans` (電力会社ごとのプラン) +| カラム名 | 型 | 制約 | 説明 | +|:---|:---|:---|:---| +| `id` | `bigint` | PK, NOT NULL | ID (主キー) | +| `provider_id` | `bigint` | FK, NOT NULL | 電力会社ID (外部キー) | +| `name` | `string` | NOT NULL, UNIQUE | プラン名 | +| `created_at` | `datetime` | NOT NULL | 作成日時 | +| `updated_at` | `datetime` | NOT NULL | 更新日時 | + +--- + +### `electricity_charges_basic_rates` (プランごとの基本料金) +| カラム名 | 型 | 制約 | 説明 | +|:---|:---|:---|:---| +| `id` | `bigint` | PK, NOT NULL | ID (主キー) | +| `plan_id` | `bigint` | FK, NOT NULL, ampereとの複合UNIQUE | プランID (外部キー) | +| `ampere` | `integer` | NOT NULL, planとの複合UNIQUE | 契約アンペア数(A) | +| `basic_rate` | `decimal` | NOT NULL | 基本料金 (円) | +| `created_at` | `datetime` | NOT NULL | 作成日時 | +| `updated_at` | `datetime` | NOT NULL | 更新日時 | + +--- + +### `electricity_charges_usage_rates` (プランごとの従量料金) +| カラム名 | 型 | 制約 | 説明 | +|:---|:---|:---|:---| +| `id` | `bigint` | PK, NOT NULL | ID (主キー) | +| `plan_id` | `bigint` | FK, NOT NULL | プランID (外部キー) | +| `min_usage` | `integer` | NOT NULL | 電気使用量(kWh)の下限値 (境界値を含まない) | +| `max_usage` | `integer` | NULL許可 | 電気使用量(kWh)の上限値 (境界値を含む) | +| `unit_rate` | `decimal` | NOT NULL | 従量料金単価 (円/kWh) | +| `created_at` | `datetime` | NOT NULL | 作成日時 | +| `updated_at` | `datetime` | NOT NULL | 更新日時 | + +# API + +## 電力料金計算API + +### エンドポイント + +``` +GET /electricity_prices +``` + +### リクエストパラメータ + +| パラメータ | 必須 | 説明 | 例 | +| -- | -- | -- | -- | +| ampere | ○ | 契約アンペア数 (10/15/20/30/40/50/60のいずれか) | 30 | +| usage | ○ | 使用量 (0以上の整数) | 200 | + +### レスポンス + +#### データ形式 +- レスポンス形式: JSON +- データの並び順: ID の昇順で返却 + +--- + +#### 各項目の説明 +- `provider_name` : 電力会社の名称 +- `plan_name`: 電気料金プランの名称 +- `price`: 料金 (円) 小数点以下は切り捨て + +--- + +#### レスポンス例 (成功時) + +```json +[ + { + "provider_name": "東京電力エナジーパートナー", + "plan_name": "従量電灯B", + "price": "5648" + }, + { + "provider_name": "東京ガス", + "plan_name": "ずっとも電気1", + "price": "5890" + } +] +``` + +#### レスポンス例 (エラー時) + +```json +{ + "error": "アンペア数(ampere)は 10/15/20/30/40/50/60 のいずれかを指定してください" +} + +``` + +# テストの実行 + +## フロントエンド +未実装 + +## バックエンド + +rspecを導入しており、以下のコマンドでテスト実行できます + +``` +docker compose run web rspec +``` diff --git a/serverside_challenge_2/challenge/app/controllers/application_controller.rb b/serverside_challenge_2/challenge/app/controllers/application_controller.rb deleted file mode 100644 index 4ac8823b0..000000000 --- a/serverside_challenge_2/challenge/app/controllers/application_controller.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ApplicationController < ActionController::API -end diff --git a/serverside_challenge_2/challenge/app/mailers/application_mailer.rb b/serverside_challenge_2/challenge/app/mailers/application_mailer.rb deleted file mode 100644 index 3c34c8148..000000000 --- a/serverside_challenge_2/challenge/app/mailers/application_mailer.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" - layout "mailer" -end diff --git a/serverside_challenge_2/challenge/backend/.gitignore b/serverside_challenge_2/challenge/backend/.gitignore new file mode 100644 index 000000000..88381219c --- /dev/null +++ b/serverside_challenge_2/challenge/backend/.gitignore @@ -0,0 +1,29 @@ +# 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 diff --git a/serverside_challenge_2/challenge/backend/.rspec b/serverside_challenge_2/challenge/backend/.rspec new file mode 100644 index 000000000..5be63fcb0 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/.rspec @@ -0,0 +1,2 @@ +--require spec_helper +--format documentation diff --git a/serverside_challenge_2/challenge/backend/.rubocop.yml b/serverside_challenge_2/challenge/backend/.rubocop.yml new file mode 100644 index 000000000..a056b9148 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/.rubocop.yml @@ -0,0 +1,8 @@ +Style/Documentation: + Enabled: false +Metrics/MethodLength: + Enabled: false +Metrics/BlockLength: + Enabled: false +Layout/FirstHashElementIndentation: + Enabled: false diff --git a/serverside_challenge_2/challenge/.ruby-version b/serverside_challenge_2/challenge/backend/.ruby-version similarity index 100% rename from serverside_challenge_2/challenge/.ruby-version rename to serverside_challenge_2/challenge/backend/.ruby-version diff --git a/serverside_challenge_2/challenge/Dockerfile b/serverside_challenge_2/challenge/backend/Dockerfile similarity index 66% rename from serverside_challenge_2/challenge/Dockerfile rename to serverside_challenge_2/challenge/backend/Dockerfile index 166bd4432..30f525208 100644 --- a/serverside_challenge_2/challenge/Dockerfile +++ b/serverside_challenge_2/challenge/backend/Dockerfile @@ -5,4 +5,5 @@ 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 ["bash", "-c", "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"] diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/backend/Gemfile similarity index 77% rename from serverside_challenge_2/challenge/Gemfile rename to serverside_challenge_2/challenge/backend/Gemfile index 43bf67fe3..15782da93 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/backend/Gemfile @@ -1,16 +1,18 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "3.1.2" +ruby '3.1.2' # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" -gem "rails", "~> 7.0.8" +gem 'rails', '~> 7.0.8' # Use postgresql as the database for Active Record -gem "pg", "~> 1.1" +gem 'pg', '~> 1.1' # Use the Puma web server [https://github.com/puma/puma] -gem "puma", "~> 5.0" +gem 'puma', '~> 5.0' # Build JSON APIs with ease [https://github.com/rails/jbuilder] # gem "jbuilder" @@ -25,24 +27,26 @@ gem "puma", "~> 5.0" # gem "bcrypt", "~> 3.1.7" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] +gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] # Reduces boot times through caching; required in config/boot.rb -gem "bootsnap", require: false +gem 'bootsnap', require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # 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' 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 'debug', platforms: %i[mri mingw x64_mingw] + gem 'factory_bot_rails' + gem 'rspec-rails', '~> 7.0' end group :development do # Speed up commands on slow machines / big apps [https://github.com/rails/spring] # gem "spring" + gem 'rubocop-rails', require: false end - diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/backend/Gemfile.lock similarity index 70% rename from serverside_challenge_2/challenge/Gemfile.lock rename to serverside_challenge_2/challenge/backend/Gemfile.lock index a47fb85f5..549168fe2 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/backend/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) @@ -75,7 +76,13 @@ GEM debug (1.9.1) irb (~> 1.10) reline (>= 0.3.8) + diff-lcs (1.6.0) erubi (1.12.0) + factory_bot (6.5.1) + activesupport (>= 6.1.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) @@ -84,6 +91,9 @@ GEM irb (1.11.2) rdoc reline (>= 0.4.2) + json (2.10.1) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -111,6 +121,10 @@ GEM racc (~> 1.4) nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) + parallel (1.26.3) + parser (3.3.7.1) + ast (~> 2.4.1) + racc pg (1.5.4) psych (5.1.2) stringio @@ -118,6 +132,8 @@ 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) rails (7.0.8) @@ -148,16 +164,58 @@ 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.10.0) reline (0.4.2) io-console (~> 0.5) + rspec-core (3.13.3) + 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.1.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.2) + rubocop (1.73.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.38.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.38.1) + parser (>= 3.3.1.0) + rubocop-rails (2.30.3) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + 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 (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -170,9 +228,13 @@ PLATFORMS DEPENDENCIES bootsnap debug + factory_bot_rails pg (~> 1.1) puma (~> 5.0) + rack-cors rails (~> 7.0.8) + rspec-rails (~> 7.0) + rubocop-rails tzinfo-data RUBY VERSION diff --git a/serverside_challenge_2/challenge/Rakefile b/serverside_challenge_2/challenge/backend/Rakefile similarity index 73% rename from serverside_challenge_2/challenge/Rakefile rename to serverside_challenge_2/challenge/backend/Rakefile index 9a5ea7383..488c551fe 100644 --- a/serverside_challenge_2/challenge/Rakefile +++ b/serverside_challenge_2/challenge/backend/Rakefile @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require_relative "config/application" +require_relative 'config/application' Rails.application.load_tasks diff --git a/serverside_challenge_2/challenge/app/channels/application_cable/channel.rb b/serverside_challenge_2/challenge/backend/app/channels/application_cable/channel.rb similarity index 71% rename from serverside_challenge_2/challenge/app/channels/application_cable/channel.rb rename to serverside_challenge_2/challenge/backend/app/channels/application_cable/channel.rb index d67269728..9aec23053 100644 --- a/serverside_challenge_2/challenge/app/channels/application_cable/channel.rb +++ b/serverside_challenge_2/challenge/backend/app/channels/application_cable/channel.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationCable class Channel < ActionCable::Channel::Base end diff --git a/serverside_challenge_2/challenge/app/channels/application_cable/connection.rb b/serverside_challenge_2/challenge/backend/app/channels/application_cable/connection.rb similarity index 73% rename from serverside_challenge_2/challenge/app/channels/application_cable/connection.rb rename to serverside_challenge_2/challenge/backend/app/channels/application_cable/connection.rb index 0ff5442f4..8d6c2a1bf 100644 --- a/serverside_challenge_2/challenge/app/channels/application_cable/connection.rb +++ b/serverside_challenge_2/challenge/backend/app/channels/application_cable/connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationCable class Connection < ActionCable::Connection::Base end diff --git a/serverside_challenge_2/challenge/backend/app/controllers/application_controller.rb b/serverside_challenge_2/challenge/backend/app/controllers/application_controller.rb new file mode 100644 index 000000000..f0661b705 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/controllers/application_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class ApplicationController < ActionController::API + rescue_from StandardError, with: :handle_unexpected_error + + private + + def handle_unexpected_error(exception) + Rails.logger.error "予期しないエラーが発生しました: #{exception.message}" + Rails.logger.error exception.backtrace.join('\n') + render json: { error: '予期しないエラーが発生しました' }, status: :internal_server_error + end +end diff --git a/serverside_challenge_2/challenge/app/controllers/concerns/.keep b/serverside_challenge_2/challenge/backend/app/controllers/concerns/.keep similarity index 100% rename from serverside_challenge_2/challenge/app/controllers/concerns/.keep rename to serverside_challenge_2/challenge/backend/app/controllers/concerns/.keep diff --git a/serverside_challenge_2/challenge/backend/app/controllers/electricity_prices_controller.rb b/serverside_challenge_2/challenge/backend/app/controllers/electricity_prices_controller.rb new file mode 100644 index 000000000..89d8cecc2 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/controllers/electricity_prices_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class ElectricityPricesController < ApplicationController + ALLOWED_AMPERES = [10, 15, 20, 30, 40, 50, 60].freeze + + def index + ampere = Integer(index_params[:ampere]) + usage = Integer(index_params[:usage]) + + unless ALLOWED_AMPERES.include?(ampere) + render json: { error: "アンペア数(ampere)は #{ALLOWED_AMPERES.join('/')} のいずれかを指定してください" }, status: :bad_request + return + end + + if usage.negative? + render json: { error: '使用量(usage)は 0 以上の整数を指定してください' }, status: :bad_request + return + end + + plans = ElectricityPriceCalculateService.new(ampere, usage).calc + render json: plans + rescue TypeError + render json: { error: 'アンペア数(ampere)と使用量(usage)の両方が指定されていません' }, status: :bad_request + rescue ArgumentError + render json: { error: 'アンペア数(ampere)と使用量(usage)を整数で指定してください' }, status: :bad_request + end + + private + + def index_params + params.permit(:ampere, :usage) + end +end diff --git a/serverside_challenge_2/challenge/app/jobs/application_job.rb b/serverside_challenge_2/challenge/backend/app/jobs/application_job.rb similarity index 89% rename from serverside_challenge_2/challenge/app/jobs/application_job.rb rename to serverside_challenge_2/challenge/backend/app/jobs/application_job.rb index d394c3d10..bef395997 100644 --- a/serverside_challenge_2/challenge/app/jobs/application_job.rb +++ b/serverside_challenge_2/challenge/backend/app/jobs/application_job.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationJob < ActiveJob::Base # Automatically retry jobs that encountered a deadlock # retry_on ActiveRecord::Deadlocked diff --git a/serverside_challenge_2/challenge/backend/app/mailers/application_mailer.rb b/serverside_challenge_2/challenge/backend/app/mailers/application_mailer.rb new file mode 100644 index 000000000..d84cb6e71 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/mailers/application_mailer.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/serverside_challenge_2/challenge/app/models/application_record.rb b/serverside_challenge_2/challenge/backend/app/models/application_record.rb similarity index 70% rename from serverside_challenge_2/challenge/app/models/application_record.rb rename to serverside_challenge_2/challenge/backend/app/models/application_record.rb index b63caeb8a..08dc53798 100644 --- a/serverside_challenge_2/challenge/app/models/application_record.rb +++ b/serverside_challenge_2/challenge/backend/app/models/application_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationRecord < ActiveRecord::Base primary_abstract_class end diff --git a/serverside_challenge_2/challenge/app/models/concerns/.keep b/serverside_challenge_2/challenge/backend/app/models/concerns/.keep similarity index 100% rename from serverside_challenge_2/challenge/app/models/concerns/.keep rename to serverside_challenge_2/challenge/backend/app/models/concerns/.keep diff --git a/serverside_challenge_2/challenge/backend/app/models/electricity_charges_basic_rate.rb b/serverside_challenge_2/challenge/backend/app/models/electricity_charges_basic_rate.rb new file mode 100644 index 000000000..74b44d1ac --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/models/electricity_charges_basic_rate.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class ElectricityChargesBasicRate < ApplicationRecord + belongs_to :plan + + validates :basic_rate, presence: true + validates :ampere, presence: true, uniqueness: { scope: :plan_id } +end diff --git a/serverside_challenge_2/challenge/backend/app/models/electricity_charges_usage_rate.rb b/serverside_challenge_2/challenge/backend/app/models/electricity_charges_usage_rate.rb new file mode 100644 index 000000000..757a08cc7 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/models/electricity_charges_usage_rate.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class ElectricityChargesUsageRate < ApplicationRecord + belongs_to :plan + + validates :min_usage, :unit_rate, presence: true + validates :max_usage, numericality: { greater_than: :min_usage }, allow_nil: true + + validate :validate_usage_ranges + + private + + def validate_usage_ranges + # usageがplan内で重複しないかを確認する + usage_ranges = fetch_existing_usage_ranges + [{ min: min_usage, max: max_usage }] + usage_ranges.sort_by { |usage_range| usage_range[:min] } + # puts usage_ranges + + # 使用量範囲の重複チェック + validate_no_overlap_in_usage_ranges(usage_ranges) + + # 上限なしの範囲が複数存在しないかチェック + validate_single_unbounded_range(usage_ranges) + end + + def fetch_existing_usage_ranges + ElectricityChargesUsageRate + .where(plan: plan) + .order(:min_usage) + .pluck(:min_usage, :max_usage) + .map { |min, max| { min: min, max: max } } + end + + def validate_no_overlap_in_usage_ranges(usage_ranges) + previous_max_usage = nil + + usage_ranges.each do |usage_range| + if previous_max_usage.present? && usage_range[:min] < previous_max_usage + errors.add(:base, '同一プラン内で使用量の範囲が他のレコードと重複しています') + break + end + + previous_max_usage = usage_range[:max] + end + end + + def validate_single_unbounded_range(usage_ranges) + unbounded_count = usage_ranges.count { |usage_range| usage_range[:max].nil? } + + return unless unbounded_count > 1 + + errors.add(:base, '同一プラン内で使用量上限がない項目が2つ以上存在します') + end +end diff --git a/serverside_challenge_2/challenge/backend/app/models/plan.rb b/serverside_challenge_2/challenge/backend/app/models/plan.rb new file mode 100644 index 000000000..114d2eb6a --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/models/plan.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class Plan < ApplicationRecord + belongs_to :provider + has_many :electricity_charges_basic_rates, dependent: :destroy + has_many :electricity_charges_usage_rates, dependent: :destroy + + validates :name, presence: true, uniqueness: { scope: :provider_id } + + def electricity_price(ampere, usage) + # アンペア数に対応する基本料金がなければ計算不能のためnilを返却する + electricity_charges_basic_rate = electricity_charges_basic_rates.find_by(ampere: ampere) + return nil if electricity_charges_basic_rate.nil? + + # 基本料金 + basic_price = electricity_charges_basic_rate.basic_rate + + # 従量料金 + usage_price = calc_usage_price(usage) + + # 電気料金(基本料金 + 従量料金)を返却 + (basic_price + usage_price).floor + end + + private + + def calc_usage_price(usage) + usage_rates = electricity_charges_usage_rates.order(:min_usage) + + usage_price = 0 + remaining_usage = usage + + usage_rates.each do |rate| + # 使用量の上限がない場合、すべてその単価で計算 + if rate.max_usage.nil? + usage_price += remaining_usage * rate.unit_rate + break + end + + # 範囲内の使用量のみ計算 + range_usage = [remaining_usage, rate.max_usage - rate.min_usage].min + usage_price += range_usage * rate.unit_rate + remaining_usage -= range_usage + + # 使用量が計算し終わったら終了 + break if remaining_usage <= 0 + end + + usage_price + end +end diff --git a/serverside_challenge_2/challenge/backend/app/models/provider.rb b/serverside_challenge_2/challenge/backend/app/models/provider.rb new file mode 100644 index 000000000..491baad6d --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/models/provider.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class Provider < ApplicationRecord + has_many :plans, dependent: :destroy + + validates :name, presence: true, uniqueness: true + + def electricity_prices(ampere, usage) + plans.map do |plan| + price = plan.electricity_price(ampere, usage) + next if price.nil? + + { + provider_name: name, + plan_name: plan.name, + price: price + } + end.compact + end +end diff --git a/serverside_challenge_2/challenge/backend/app/services/electricity_price_calculate_service.rb b/serverside_challenge_2/challenge/backend/app/services/electricity_price_calculate_service.rb new file mode 100644 index 000000000..74399dd01 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/app/services/electricity_price_calculate_service.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ElectricityPriceCalculateService + def initialize(ampere, usage) + @ampere = ampere + @usage = usage + end + + def calc + Provider.order(:id).map { |provider| provider.electricity_prices(@ampere, @usage) }.flatten + end +end diff --git a/serverside_challenge_2/challenge/app/views/layouts/mailer.html.erb b/serverside_challenge_2/challenge/backend/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/backend/app/views/layouts/mailer.html.erb diff --git a/serverside_challenge_2/challenge/app/views/layouts/mailer.text.erb b/serverside_challenge_2/challenge/backend/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/backend/app/views/layouts/mailer.text.erb diff --git a/serverside_challenge_2/challenge/backend/bin/rails b/serverside_challenge_2/challenge/backend/bin/rails new file mode 100755 index 000000000..a31728ab9 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/bin/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/serverside_challenge_2/challenge/backend/bin/rake b/serverside_challenge_2/challenge/backend/bin/rake new file mode 100755 index 000000000..c19995500 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/bin/rake @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/serverside_challenge_2/challenge/bin/setup b/serverside_challenge_2/challenge/backend/bin/setup similarity index 67% rename from serverside_challenge_2/challenge/bin/setup rename to serverside_challenge_2/challenge/backend/bin/setup index ec47b79b3..516b651e3 100755 --- a/serverside_challenge_2/challenge/bin/setup +++ b/serverside_challenge_2/challenge/backend/bin/setup @@ -1,8 +1,10 @@ #!/usr/bin/env ruby -require "fileutils" +# frozen_string_literal: true + +require 'fileutils' # path to your application root. -APP_ROOT = File.expand_path("..", __dir__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -13,9 +15,9 @@ FileUtils.chdir APP_ROOT do # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. - puts "== Installing dependencies ==" - system! "gem install bundler --conservative" - system("bundle check") || system!("bundle install") + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") @@ -23,11 +25,11 @@ FileUtils.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system! "bin/rails db:prepare" + system! 'bin/rails db:prepare' puts "\n== Removing old logs and tempfiles ==" - system! "bin/rails log:clear tmp:clear" + system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" - system! "bin/rails restart" + system! 'bin/rails restart' end diff --git a/serverside_challenge_2/challenge/config.ru b/serverside_challenge_2/challenge/backend/config.ru similarity index 63% rename from serverside_challenge_2/challenge/config.ru rename to serverside_challenge_2/challenge/backend/config.ru index 4a3c09a68..6dc832180 100644 --- a/serverside_challenge_2/challenge/config.ru +++ b/serverside_challenge_2/challenge/backend/config.ru @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. -require_relative "config/environment" +require_relative 'config/environment' run Rails.application Rails.application.load_server diff --git a/serverside_challenge_2/challenge/config/application.rb b/serverside_challenge_2/challenge/backend/config/application.rb similarity index 92% rename from serverside_challenge_2/challenge/config/application.rb rename to serverside_challenge_2/challenge/backend/config/application.rb index b39913bf7..9727c90fb 100644 --- a/serverside_challenge_2/challenge/config/application.rb +++ b/serverside_challenge_2/challenge/backend/config/application.rb @@ -1,6 +1,8 @@ -require_relative "boot" +# frozen_string_literal: true -require "rails/all" +require_relative 'boot' + +require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. diff --git a/serverside_challenge_2/challenge/backend/config/boot.rb b/serverside_challenge_2/challenge/backend/config/boot.rb new file mode 100644 index 000000000..c04863fa7 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/config/boot.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/serverside_challenge_2/challenge/config/cable.yml b/serverside_challenge_2/challenge/backend/config/cable.yml similarity index 100% rename from serverside_challenge_2/challenge/config/cable.yml rename to serverside_challenge_2/challenge/backend/config/cable.yml diff --git a/serverside_challenge_2/challenge/config/credentials.yml.enc b/serverside_challenge_2/challenge/backend/config/credentials.yml.enc similarity index 100% rename from serverside_challenge_2/challenge/config/credentials.yml.enc rename to serverside_challenge_2/challenge/backend/config/credentials.yml.enc diff --git a/serverside_challenge_2/challenge/config/database.yml b/serverside_challenge_2/challenge/backend/config/database.yml similarity index 94% rename from serverside_challenge_2/challenge/config/database.yml rename to serverside_challenge_2/challenge/backend/config/database.yml index 9c66b2942..9a92863d7 100644 --- a/serverside_challenge_2/challenge/config/database.yml +++ b/serverside_challenge_2/challenge/backend/config/database.yml @@ -84,4 +84,8 @@ test: # production: <<: *default - database: app_production + database: <%= ENV['DB_NAME'] %> + host: <%= ENV['DATABASE_HOST'] %> + username: <%= ENV['POSTGRES_USER'] %> + password: <%= ENV['POSTGRES_PASSWORD'] %> + port: <%= ENV['DB_PORT'] %> diff --git a/serverside_challenge_2/challenge/config/environment.rb b/serverside_challenge_2/challenge/backend/config/environment.rb similarity index 61% rename from serverside_challenge_2/challenge/config/environment.rb rename to serverside_challenge_2/challenge/backend/config/environment.rb index cac531577..d5abe5580 100644 --- a/serverside_challenge_2/challenge/config/environment.rb +++ b/serverside_challenge_2/challenge/backend/config/environment.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Load the Rails application. -require_relative "application" +require_relative 'application' # Initialize the Rails application. Rails.application.initialize! diff --git a/serverside_challenge_2/challenge/config/environments/development.rb b/serverside_challenge_2/challenge/backend/config/environments/development.rb similarity index 89% rename from serverside_challenge_2/challenge/config/environments/development.rb rename to serverside_challenge_2/challenge/backend/config/environments/development.rb index 3d6b07360..5a23d6a20 100644 --- a/serverside_challenge_2/challenge/config/environments/development.rb +++ b/serverside_challenge_2/challenge/backend/config/environments/development.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -19,10 +21,10 @@ # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join("tmp/caching-dev.txt").exist? + if Rails.root.join('tmp/caching-dev.txt').exist? config.cache_store = :memory_store config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{2.days.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -40,6 +42,7 @@ # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log + config.log_formatter = ::Logger::Formatter.new # Raise exceptions for disallowed deprecations. config.active_support.disallowed_deprecation = :raise @@ -53,7 +56,6 @@ # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true - # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/serverside_challenge_2/challenge/config/environments/production.rb b/serverside_challenge_2/challenge/backend/config/environments/production.rb similarity index 90% rename from serverside_challenge_2/challenge/config/environments/production.rb rename to serverside_challenge_2/challenge/backend/config/environments/production.rb index 0376660ba..70d949261 100644 --- a/serverside_challenge_2/challenge/config/environments/production.rb +++ b/serverside_challenge_2/challenge/backend/config/environments/production.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -13,7 +15,7 @@ config.eager_load = true # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false + config.consider_all_requests_local = false # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). @@ -21,7 +23,7 @@ # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" @@ -39,14 +41,14 @@ # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true + config.force_ssl = false # Include generic and useful information about system operation, but avoid logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). config.log_level = :info # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -75,8 +77,8 @@ # require "syslog/logger" # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") - if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new(STDOUT) + if ENV['RAILS_LOG_TO_STDOUT'].present? + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/serverside_challenge_2/challenge/config/environments/test.rb b/serverside_challenge_2/challenge/backend/config/environments/test.rb similarity index 92% rename from serverside_challenge_2/challenge/config/environments/test.rb rename to serverside_challenge_2/challenge/backend/config/environments/test.rb index 6ea4d1e70..8f3f63ce7 100644 --- a/serverside_challenge_2/challenge/config/environments/test.rb +++ b/serverside_challenge_2/challenge/backend/config/environments/test.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -14,12 +16,12 @@ # Eager loading loads your whole application. When running a single test locally, # this probably isn't necessary. It's a good idea to do in a continuous integration # system, or in some way before deploying your code. - config.eager_load = ENV["CI"].present? + config.eager_load = ENV['CI'].present? # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{1.hour.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. diff --git a/serverside_challenge_2/challenge/backend/config/initializers/cors.rb b/serverside_challenge_2/challenge/backend/config/initializers/cors.rb new file mode 100644 index 000000000..d035be3c1 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/config/initializers/cors.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins 'localhost:5173' + + resource '*', + headers: :any, + # methods: [:get, :post, :put, :patch, :delete, :options, :head] + methods: [:get] + end +end diff --git a/serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb b/serverside_challenge_2/challenge/backend/config/initializers/filter_parameter_logging.rb similarity index 66% rename from serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb rename to serverside_challenge_2/challenge/backend/config/initializers/filter_parameter_logging.rb index adc6568ce..3df77c5be 100644 --- a/serverside_challenge_2/challenge/config/initializers/filter_parameter_logging.rb +++ b/serverside_challenge_2/challenge/backend/config/initializers/filter_parameter_logging.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Configure parameters to be filtered from the log file. Use this to limit dissemination of # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported # notations and behaviors. -Rails.application.config.filter_parameters += [ - :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +Rails.application.config.filter_parameters += %i[ + passw secret token _key crypt salt certificate otp ssn ] diff --git a/serverside_challenge_2/challenge/config/initializers/inflections.rb b/serverside_challenge_2/challenge/backend/config/initializers/inflections.rb similarity index 95% rename from serverside_challenge_2/challenge/config/initializers/inflections.rb rename to serverside_challenge_2/challenge/backend/config/initializers/inflections.rb index 3860f659e..9e049dcc9 100644 --- a/serverside_challenge_2/challenge/config/initializers/inflections.rb +++ b/serverside_challenge_2/challenge/backend/config/initializers/inflections.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/serverside_challenge_2/challenge/config/locales/en.yml b/serverside_challenge_2/challenge/backend/config/locales/en.yml similarity index 100% rename from serverside_challenge_2/challenge/config/locales/en.yml rename to serverside_challenge_2/challenge/backend/config/locales/en.yml diff --git a/serverside_challenge_2/challenge/config/puma.rb b/serverside_challenge_2/challenge/backend/config/puma.rb similarity index 80% rename from serverside_challenge_2/challenge/config/puma.rb rename to serverside_challenge_2/challenge/backend/config/puma.rb index daaf03699..1713441e5 100644 --- a/serverside_challenge_2/challenge/config/puma.rb +++ b/serverside_challenge_2/challenge/backend/config/puma.rb @@ -1,28 +1,30 @@ +# frozen_string_literal: true + # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } -min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) +min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } threads min_threads_count, max_threads_count # Specifies the `worker_timeout` threshold that Puma will use to wait before # terminating a worker in development environments. # -worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" +worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development' # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT', 3000) # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV', 'development') # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid') # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together diff --git a/serverside_challenge_2/challenge/config/routes.rb b/serverside_challenge_2/challenge/backend/config/routes.rb similarity index 60% rename from serverside_challenge_2/challenge/config/routes.rb rename to serverside_challenge_2/challenge/backend/config/routes.rb index 262ffd547..5d8704941 100644 --- a/serverside_challenge_2/challenge/config/routes.rb +++ b/serverside_challenge_2/challenge/backend/config/routes.rb @@ -1,6 +1,11 @@ +# frozen_string_literal: true + 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" + + get '/health', to: proc { [200, {}, ['OK']] } + resources :electricity_prices, only: [:index] end diff --git a/serverside_challenge_2/challenge/config/storage.yml b/serverside_challenge_2/challenge/backend/config/storage.yml similarity index 100% rename from serverside_challenge_2/challenge/config/storage.yml rename to serverside_challenge_2/challenge/backend/config/storage.yml diff --git a/serverside_challenge_2/challenge/backend/db/migrate/20250311091328_create_providers.rb b/serverside_challenge_2/challenge/backend/db/migrate/20250311091328_create_providers.rb new file mode 100644 index 000000000..4db41f597 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/migrate/20250311091328_create_providers.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class CreateProviders < ActiveRecord::Migration[7.0] + def change + create_table :providers, comment: '電力会社を格納する' do |t| + t.string :name, null: false, comment: '会社名' + + t.timestamps + end + + add_index :providers, :name, unique: true + end +end diff --git a/serverside_challenge_2/challenge/backend/db/migrate/20250311091421_create_plans.rb b/serverside_challenge_2/challenge/backend/db/migrate/20250311091421_create_plans.rb new file mode 100644 index 000000000..b02fe538d --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/migrate/20250311091421_create_plans.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreatePlans < ActiveRecord::Migration[7.0] + def change + create_table :plans, comment: '各電力会社毎のプランを格納する' do |t| + t.references :provider, null: false, foreign_key: true + t.string :name, null: false, comment: 'プラン名' + + t.timestamps + end + + add_index :plans, %i[provider_id name], unique: true + end +end diff --git a/serverside_challenge_2/challenge/backend/db/migrate/20250311091545_create_electricity_charges_basic_rates.rb b/serverside_challenge_2/challenge/backend/db/migrate/20250311091545_create_electricity_charges_basic_rates.rb new file mode 100644 index 000000000..a4e7c1dc3 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/migrate/20250311091545_create_electricity_charges_basic_rates.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class CreateElectricityChargesBasicRates < ActiveRecord::Migration[7.0] + def change + create_table :electricity_charges_basic_rates, comment: 'プラン毎の電気基本料金を格納する' do |t| + t.references :plan, null: false, foreign_key: true + t.integer :ampere, null: false, comment: '契約アンペア数(A)' + t.decimal :basic_rate, precision: 10, scale: 2, null: false, comment: '基本料金(円)' + + t.timestamps + end + + add_index :electricity_charges_basic_rates, %i[plan_id ampere], unique: true + end +end diff --git a/serverside_challenge_2/challenge/backend/db/migrate/20250311091629_create_electricity_charges_usage_rates.rb b/serverside_challenge_2/challenge/backend/db/migrate/20250311091629_create_electricity_charges_usage_rates.rb new file mode 100644 index 000000000..dfc8bdf8d --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/migrate/20250311091629_create_electricity_charges_usage_rates.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateElectricityChargesUsageRates < ActiveRecord::Migration[7.0] + def change + create_table :electricity_charges_usage_rates, comment: 'プラン毎の電気従量料金を格納する' do |t| + t.references :plan, null: false, foreign_key: true + t.integer :min_usage, null: false, comment: '電気使用量(kWh)の下限値(境界値を含まない)' + t.integer :max_usage, comment: '電気使用量(kWh)の上限値(境界値を含む)' + t.decimal :unit_rate, precision: 10, scale: 2, null: false, comment: '従量料金単価(円/kWh)' + + t.timestamps + end + end +end diff --git a/serverside_challenge_2/challenge/backend/db/schema.rb b/serverside_challenge_2/challenge/backend/db/schema.rb new file mode 100644 index 000000000..95133fcb3 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/schema.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# 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: 20_250_311_091_629) do + # These are extensions that must be enabled in order to support this database + enable_extension 'plpgsql' + + create_table 'electricity_charges_basic_rates', comment: 'プラン毎の電気基本料金を格納する', force: :cascade do |t| + t.bigint 'plan_id', null: false + t.integer 'ampere', null: false, comment: '契約アンペア数(A)' + t.decimal 'basic_rate', precision: 10, scale: 2, null: false, comment: '基本料金(円)' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index %w[plan_id ampere], name: 'index_electricity_charges_basic_rates_on_plan_id_and_ampere', unique: true + t.index ['plan_id'], name: 'index_electricity_charges_basic_rates_on_plan_id' + end + + create_table 'electricity_charges_usage_rates', comment: 'プラン毎の電気従量料金を格納する', force: :cascade do |t| + t.bigint 'plan_id', null: false + t.integer 'min_usage', null: false, comment: '電気使用量(kWh)の下限値(境界値を含まない)' + t.integer 'max_usage', comment: '電気使用量(kWh)の上限値(境界値を含む)' + t.decimal 'unit_rate', precision: 10, scale: 2, null: false, comment: '従量料金単価(円/kWh)' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['plan_id'], name: 'index_electricity_charges_usage_rates_on_plan_id' + end + + create_table 'plans', comment: '各電力会社毎のプランを格納する', force: :cascade do |t| + t.bigint 'provider_id', null: false + t.string 'name', null: false, comment: 'プラン名' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index %w[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', comment: '電力会社を格納する', force: :cascade do |t| + t.string 'name', null: false, comment: '会社名' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['name'], name: 'index_providers_on_name', unique: true + end + + add_foreign_key 'electricity_charges_basic_rates', 'plans' + add_foreign_key 'electricity_charges_usage_rates', 'plans' + add_foreign_key 'plans', 'providers' +end diff --git a/serverside_challenge_2/challenge/db/seeds.rb b/serverside_challenge_2/challenge/backend/db/seeds.rb similarity index 82% rename from serverside_challenge_2/challenge/db/seeds.rb rename to serverside_challenge_2/challenge/backend/db/seeds.rb index bc25fce30..dcfb8c629 100644 --- a/serverside_challenge_2/challenge/db/seeds.rb +++ b/serverside_challenge_2/challenge/backend/db/seeds.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # 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). # @@ -5,3 +7,5 @@ # # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) # Character.create(name: "Luke", movie: movies.first) + +require_relative 'seeds/provider_plan_rates' diff --git a/serverside_challenge_2/challenge/backend/db/seeds/provider_plan_rates.rb b/serverside_challenge_2/challenge/backend/db/seeds/provider_plan_rates.rb new file mode 100644 index 000000000..d3aac8a41 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/seeds/provider_plan_rates.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'yaml' + +def create_provider_and_plans(provider_name, plans_data) + provider = Provider.find_or_create_by!(name: provider_name) + + plans_data.each do |plan_data| + plan = Plan.find_or_create_by!(provider: provider, name: plan_data['name']) + + create_basic_rates(plan, plan_data['basic_rates']) + create_usage_rates(plan, plan_data['usage_rates']) + end +end + +def create_basic_rates(plan, rates) + rates.each do |rate_data| + ElectricityChargesBasicRate.find_or_create_by!( + plan: plan, + ampere: rate_data['ampere'] + ) do |rate| + rate.basic_rate = rate_data['basic_rate'] + end + end +end + +def create_usage_rates(plan, rates) + rates.each do |rate_data| + ElectricityChargesUsageRate.find_or_create_by!( + plan: plan, + min_usage: rate_data['min_usage'], + max_usage: rate_data['max_usage'] + ) do |rate| + rate.unit_rate = rate_data['unit_rate'] + end + end +end + +seed_data = YAML.load_file(Rails.root.join('db/seeds/provider_plan_rates.yml')) +seed_data['providers'].each do |provider_data| + create_provider_and_plans(provider_data['name'], provider_data['plans']) +end + +puts '各電力会社と、そのプラン作成が完了しました' diff --git a/serverside_challenge_2/challenge/backend/db/seeds/provider_plan_rates.yml b/serverside_challenge_2/challenge/backend/db/seeds/provider_plan_rates.yml new file mode 100644 index 000000000..7daf50357 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/db/seeds/provider_plan_rates.yml @@ -0,0 +1,57 @@ +providers: + - name: "東京電力エナジーパートナー" + plans: + - name: "従量電灯B" + basic_rates: + - { ampere: 10, basic_rate: 286.00 } + - { ampere: 15, basic_rate: 429.00 } + - { ampere: 20, basic_rate: 572.00 } + - { ampere: 30, basic_rate: 858.00 } + - { ampere: 40, basic_rate: 1144.00 } + - { ampere: 50, basic_rate: 1430.00 } + - { ampere: 60, basic_rate: 1716.00 } + usage_rates: + - { min_usage: 0, max_usage: 120, unit_rate: 19.88 } + - { min_usage: 120, max_usage: 300, unit_rate: 26.48 } + - { min_usage: 300, max_usage: null, unit_rate: 30.57 } + + - name: "スタンダードS" + basic_rates: + - { ampere: 10, basic_rate: 311.75 } + - { ampere: 15, basic_rate: 467.63 } + - { ampere: 20, basic_rate: 623.50 } + - { ampere: 30, basic_rate: 935.25 } + - { ampere: 40, basic_rate: 1247.00 } + - { ampere: 50, basic_rate: 1558.75 } + - { ampere: 60, basic_rate: 1870.50 } + usage_rates: + - { min_usage: 0, max_usage: 120, unit_rate: 29.80 } + - { min_usage: 120, max_usage: 300, unit_rate: 36.40 } + - { min_usage: 300, max_usage: null, unit_rate: 40.49 } + + - name: "東京ガス" + plans: + - name: "ずっとも電気1" + basic_rates: + - { ampere: 30, basic_rate: 858.00 } + - { ampere: 40, basic_rate: 1144.00 } + - { ampere: 50, basic_rate: 1430.00 } + - { ampere: 60, basic_rate: 1716.00 } + usage_rates: + - { min_usage: 0, max_usage: 140, unit_rate: 23.67 } + - { min_usage: 140, max_usage: 350, unit_rate: 23.88 } + - { min_usage: 350, max_usage: null, unit_rate: 26.41 } + + - name: "Looopでんき" + plans: + - name: "おうちプラン" + basic_rates: + - { ampere: 10, basic_rate: 0.00 } + - { ampere: 15, basic_rate: 0.00 } + - { ampere: 20, basic_rate: 0.00 } + - { ampere: 30, basic_rate: 0.00 } + - { ampere: 40, basic_rate: 0.00 } + - { ampere: 50, basic_rate: 0.00 } + - { ampere: 60, basic_rate: 0.00 } + usage_rates: + - { min_usage: 0, max_usage: null, unit_rate: 28.8 } diff --git a/serverside_challenge_2/challenge/lib/tasks/.keep b/serverside_challenge_2/challenge/backend/lib/tasks/.keep similarity index 100% rename from serverside_challenge_2/challenge/lib/tasks/.keep rename to serverside_challenge_2/challenge/backend/lib/tasks/.keep diff --git a/serverside_challenge_2/challenge/log/.keep b/serverside_challenge_2/challenge/backend/log/.keep similarity index 100% rename from serverside_challenge_2/challenge/log/.keep rename to serverside_challenge_2/challenge/backend/log/.keep diff --git a/serverside_challenge_2/challenge/public/robots.txt b/serverside_challenge_2/challenge/backend/public/robots.txt similarity index 100% rename from serverside_challenge_2/challenge/public/robots.txt rename to serverside_challenge_2/challenge/backend/public/robots.txt diff --git a/serverside_challenge_2/challenge/backend/spec/factories/electricity_charges_basic_rates.rb b/serverside_challenge_2/challenge/backend/spec/factories/electricity_charges_basic_rates.rb new file mode 100644 index 000000000..48960f599 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/factories/electricity_charges_basic_rates.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :electricity_charges_basic_rate do + association :plan + ampere { 10 } + basic_rate { BigDecimal('100.00') } + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/factories/electricity_charges_usage_rates.rb b/serverside_challenge_2/challenge/backend/spec/factories/electricity_charges_usage_rates.rb new file mode 100644 index 000000000..a5461de9c --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/factories/electricity_charges_usage_rates.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :electricity_charges_usage_rate do + association :plan + min_usage { 0 } + max_usage { 120 } + unit_rate { BigDecimal('10.00') } + + trait :low_usage do + min_usage { 0 } + max_usage { 120 } + unit_rate { 20.0 } + end + + trait :mid_usage do + min_usage { 121 } + max_usage { 300 } + unit_rate { 25.0 } + end + + trait :high_usage do + min_usage { 301 } + max_usage { nil } + unit_rate { 30.0 } + end + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/factories/plan.rb b/serverside_challenge_2/challenge/backend/spec/factories/plan.rb new file mode 100644 index 000000000..11973447a --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/factories/plan.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :plan do + association :provider + sequence(:name) { |n| "プラン#{n}" } + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/factories/providers.rb b/serverside_challenge_2/challenge/backend/spec/factories/providers.rb new file mode 100644 index 000000000..9fe3ad2f2 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/factories/providers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :provider do + sequence(:name) { |n| "プロバイダー#{n}" } + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/models/electricity_charges_usage_rate_spec.rb b/serverside_challenge_2/challenge/backend/spec/models/electricity_charges_usage_rate_spec.rb new file mode 100644 index 000000000..60c9d3e32 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/models/electricity_charges_usage_rate_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ElectricityChargesUsageRate, type: :model do + let(:provider) { create(:provider) } + let(:plan) { create(:plan, provider: provider) } + + describe 'バリデーション' do + describe '正常系' do + context '使用量の範囲が他と重複していない場合' do + it 'バリデーションが通る' do + rate1 = build(:electricity_charges_usage_rate, plan: plan, min_usage: 0, max_usage: 120) + rate2 = build(:electricity_charges_usage_rate, plan: plan, min_usage: 121, max_usage: 300) + + expect(rate1).to be_valid + expect(rate2).to be_valid + end + end + + context '使用量の上限がnilで、他のデータと重複していない場合' do + it 'バリデーションが通る' do + rate1 = build(:electricity_charges_usage_rate, plan: plan, min_usage: 0, max_usage: 120) + rate2 = build(:electricity_charges_usage_rate, plan: plan, min_usage: 121, max_usage: nil) + + expect(rate1).to be_valid + expect(rate2).to be_valid + end + end + end + + describe '異常系' do + context '同一プラン内で使用量の範囲が重複する場合' do + before do + create(:electricity_charges_usage_rate, plan: plan, min_usage: 0, max_usage: 120) + end + + it 'バリデーションエラーが発生する' do + overlapping_rate = build(:electricity_charges_usage_rate, plan: plan, min_usage: 100, max_usage: 200) + expect(overlapping_rate).to be_invalid + expect(overlapping_rate.errors[:base]).to include('同一プラン内で使用量の範囲が他のレコードと重複しています') + end + end + + context '同一プラン内で使用量上限がnilのデータが複数存在する場合' do + before do + create(:electricity_charges_usage_rate, plan: plan, min_usage: 121, max_usage: nil) + end + + it 'バリデーションエラーが発生する' do + duplicate_max_nil = build(:electricity_charges_usage_rate, plan: plan, min_usage: 301, max_usage: nil) + expect(duplicate_max_nil).to be_invalid + expect(duplicate_max_nil.errors[:base]).to include('同一プラン内で使用量上限がない項目が2つ以上存在します') + end + end + end + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/models/plan_spec.rb b/serverside_challenge_2/challenge/backend/spec/models/plan_spec.rb new file mode 100644 index 000000000..63ca38c17 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/models/plan_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Plan, type: :model do + describe '#electricity_price' do + let(:provider) { create(:provider) } + let(:plan) do + create(:plan, provider: provider, electricity_charges_basic_rates: [], electricity_charges_usage_rates: []) + end + + let!(:basic_rate_20A) do + create(:electricity_charges_basic_rate, plan: plan, ampere: 20, basic_rate: 572.00) + end + + let!(:basic_rate_30A) do + create(:electricity_charges_basic_rate, plan: plan, ampere: 30, basic_rate: 858.00) + end + + let!(:usage_rate_first) do + create(:electricity_charges_usage_rate, plan: plan, min_usage: 0, max_usage: 120, unit_rate: 19.88) + end + + let!(:usage_rate_second) do + create(:electricity_charges_usage_rate, plan: plan, min_usage: 120, max_usage: 300, unit_rate: 26.48) + end + + let!(:usage_rate_third) do + create(:electricity_charges_usage_rate, plan: plan, min_usage: 300, max_usage: nil, unit_rate: 30.57) + end + + describe '正常系' do + context '使用量が0の場合' do + let(:ampere) { 30 } + let(:usage) { 0 } + + it '基本料金を小数点以下切り捨てで返す' do + expect(plan.electricity_price(ampere, usage)).to eq(basic_rate_30A.basic_rate) + end + end + + context '使用量が第一段階内の場合' do + let(:ampere) { 20 } + let(:usage) { 100 } + + it '基本料金と1段階目までの従量料金の合計を小数点以下切り捨てで返す' do + expect( + plan.electricity_price(ampere, usage) + ).to eq((basic_rate_20A.basic_rate + (usage * usage_rate_first.unit_rate)).floor) + end + end + + context '使用量が第一段階上限の場合' do + let(:ampere) { 30 } + let(:usage) { 120 } + + it '基本料金と1段階目までの従量料金の合計を小数点以下切り捨てで返す' do + expect( + plan.electricity_price(ampere, usage) + ).to eq((basic_rate_30A.basic_rate + (usage * usage_rate_first.unit_rate)).floor) + end + end + + context '使用量が第二段階内の場合' do + let(:ampere) { 30 } + let(:usage) { 201 } + + it '基本料金と2段階目までの従量料金の合計を小数点以下切り捨てで返す' do + usage_first_price = usage_rate_first.max_usage * usage_rate_first.unit_rate + usage_second_price = (usage - usage_rate_first.max_usage) * usage_rate_second.unit_rate + + expect( + plan.electricity_price(ampere, usage) + ).to eq((basic_rate_30A.basic_rate + usage_first_price + usage_second_price).floor) + end + end + + context '使用量が上限の無い(max_usage: nil)レート内の場合' do + let(:ampere) { 30 } + let(:usage) { 352 } + + it '基本料金と従量料金の合計を小数点以下切り捨てで返す' do + usage_first_price = usage_rate_first.max_usage * usage_rate_first.unit_rate + usage_second_price = (usage_rate_second.max_usage - usage_rate_first.max_usage) * usage_rate_second.unit_rate + usage_third_price = (usage - usage_rate_second.max_usage) * usage_rate_third.unit_rate + + expect( + plan.electricity_price(ampere, usage) + ).to eq((basic_rate_30A.basic_rate + usage_first_price + usage_second_price + usage_third_price).floor) + end + end + end + + describe '異常系' do + context '指定したアンペア数に対応する基本料金が存在しない場合' do + let(:ampere) { 40 } + let(:usage) { 150 } + + it 'nilを返す' do + expect(plan.electricity_price(ampere, usage)).to be_nil + end + end + end + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/models/provider_spec.rb b/serverside_challenge_2/challenge/backend/spec/models/provider_spec.rb new file mode 100644 index 000000000..47129eaa4 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/models/provider_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Provider, type: :model do + describe '#electricity_prices' do + let(:provider) { create(:provider, name: '東京電力エナジーパートナー') } + + context '対象アンペアが全プランで存在する場合' do + let(:ampere) { 20 } + let(:usage) { 100 } + let!(:plan1) { create(:plan, provider: provider, name: '従量電灯B') } + let!(:plan2) { create(:plan, provider: provider, name: 'スタンダードS') } + + before do + # plan1 20Aの価格設定あり + create(:electricity_charges_basic_rate, plan: plan1, ampere: 20, basic_rate: 572.00) + create(:electricity_charges_usage_rate, plan: plan1, min_usage: 0, max_usage: 120, unit_rate: 19.88) + + # plan2 20Aの価格設定あり + create(:electricity_charges_basic_rate, plan: plan2, ampere: 20, basic_rate: 672.00) + create(:electricity_charges_usage_rate, plan: plan2, min_usage: 0, max_usage: 120, unit_rate: 29.88) + end + + it 'すべてのプランが返る' do + result = provider.electricity_prices(ampere, usage) + + expect(result).to eq([ + { + provider_name: '東京電力エナジーパートナー', + plan_name: '従量電灯B', + price: plan1.electricity_price(ampere, usage) + }, + { + provider_name: '東京電力エナジーパートナー', + plan_name: 'スタンダードS', + price: plan2.electricity_price(ampere, usage) + } + ]) + end + end + + context '対象アンペアが存在するプランとしないプランで混在する場合' do + let(:ampere) { 20 } + let(:usage) { 100 } + let!(:plan1) { create(:plan, provider: provider, name: '従量電灯B') } + let!(:plan2) { create(:plan, provider: provider, name: 'スタンダードS') } + + before do + # plan1 20Aの価格設定あり + create(:electricity_charges_basic_rate, plan: plan1, ampere: 20, basic_rate: 572.00) + create(:electricity_charges_usage_rate, plan: plan1, min_usage: 0, max_usage: 120, unit_rate: 19.88) + + # plan2 30Aの価格設定あり + create(:electricity_charges_basic_rate, plan: plan2, ampere: 30, basic_rate: 672.00) + create(:electricity_charges_usage_rate, plan: plan2, min_usage: 0, max_usage: 120, unit_rate: 29.88) + end + + it '対象アンペアが存在するプランのみが返る' do + result = provider.electricity_prices(ampere, usage) + + expect(result).to eq([ + { + provider_name: '東京電力エナジーパートナー', + plan_name: '従量電灯B', + price: plan1.electricity_price(ampere, usage) + } + ]) + end + end + + context '対象アンペアが全プランで存在しない場合' do + let(:ampere) { 11 } + let(:usage) { 100 } + let!(:plan1) { create(:plan, provider: provider, name: '従量電灯B') } + let!(:plan2) { create(:plan, provider: provider, name: 'スタンダードS') } + + before do + # plan1 20Aの価格設定あり + create(:electricity_charges_basic_rate, plan: plan1, ampere: 20, basic_rate: 572.00) + create(:electricity_charges_usage_rate, plan: plan1, min_usage: 0, max_usage: 120, unit_rate: 19.88) + + # plan2 30Aの価格設定なし + create(:electricity_charges_basic_rate, plan: plan2, ampere: 30, basic_rate: 672.00) + create(:electricity_charges_usage_rate, plan: plan2, min_usage: 0, max_usage: 120, unit_rate: 29.88) + end + + it '対象アンペアが存在するプランのみが返る' do + result = provider.electricity_prices(ampere, usage) + + expect(result).to eq([]) + end + end + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/rails_helper.rb b/serverside_challenge_2/challenge/backend/spec/rails_helper.rb new file mode 100644 index 000000000..aa65ee868 --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/rails_helper.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +# 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 uses metadata to mix in different behaviours to your tests, + # for example enabling you to call `get` and `post` in request specs. e.g.: + # + # RSpec.describe UsersController, type: :request do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://rspec.info/features/7-1/rspec-rails + # + # You can also this infer these behaviours automatically by location, e.g. + # /spec/models would pull in the same behaviour as `type: :model` but this + # behaviour is considered legacy and will be removed in a future version. + # + # To enable this behaviour uncomment the line below. + # config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") + + config.include FactoryBot::Syntax::Methods +end diff --git a/serverside_challenge_2/challenge/backend/spec/requests/electricity_prices_controller_spec.rb b/serverside_challenge_2/challenge/backend/spec/requests/electricity_prices_controller_spec.rb new file mode 100644 index 000000000..6114881ad --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/requests/electricity_prices_controller_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'ElectricityPrices', type: :request do + describe 'GET /electricity_prices' do + let(:valid_params) { { ampere: 30, usage: 200 } } + let(:invalid_ampere_params) { { ampere: 100, usage: 200 } } + let(:negative_usage_params) { { ampere: 30, usage: -50 } } + let(:string_ampere_params) { { ampere: 'abc', usage: 200 } } + let(:string_usage_params) { { ampere: 30, usage: 'xyz' } } + let(:missing_ampere_params) { { usage: 200 } } + let(:missing_usage_params) { { ampere: 30 } } + + # 正常系 + describe '正常系' do + context '正しいパラメータが指定された場合' do + let(:expected_response) do + [ + { + 'company_name' => '東京電力エナジーパートナー', + 'plan_name' => '従量電灯B', + 'price' => '2274.0' + }, + { + 'company_name' => '東京電力エナジーパートナー', + 'plan_name' => 'スタンダードS', + 'price' => '3291.75' + } + ] + end + + before do + allow(ElectricityPriceCalculateService).to receive(:new) + .and_return(double(calc: expected_response)) + end + + it '成功の応答を返し、レスポンスのボディにサービスで計算された結果が入っている' do + get electricity_prices_url, params: valid_params + expect(response).to have_http_status(:success) + expect(JSON.parse(response.body)).to eq(expected_response) + end + end + end + + # 異常系 + describe '異常系' do + context '無効なアンペア数が指定された場合' do + it 'それに対応するエラー応答を返す' do + get electricity_prices_url, params: invalid_ampere_params + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ + 'error' => "アンペア数(ampere)は #{ElectricityPricesController::ALLOWED_AMPERES.join('/')} のいずれかを指定してください" + }) + end + end + + context '使用量が負の数の場合' do + it 'それに対応するエラー応答を返す' do + get electricity_prices_url, params: negative_usage_params + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => '使用量(usage)は 0 以上の整数を指定してください' }) + end + end + + context 'アンペア数が文字列の場合' do + it 'それに対応するエラー応答を返す' do + get electricity_prices_url, params: string_ampere_params + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => 'アンペア数(ampere)と使用量(usage)を整数で指定してください' }) + end + end + + context '使用量が文字列の場合' do + it 'それに対応するエラー応答を返す' do + get electricity_prices_url, params: string_usage_params + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => 'アンペア数(ampere)と使用量(usage)を整数で指定してください' }) + end + end + + context 'アンペア数が指定されていない場合' do + it 'それに対応するエラー応答を返す' do + get electricity_prices_url, params: missing_ampere_params + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => 'アンペア数(ampere)と使用量(usage)の両方が指定されていません' }) + end + end + + context '使用量が指定されていない場合' do + it 'それに対応するエラー応答を返す' do + get electricity_prices_url, params: missing_usage_params + expect(response).to have_http_status(:bad_request) + expect(JSON.parse(response.body)).to eq({ 'error' => 'アンペア数(ampere)と使用量(usage)の両方が指定されていません' }) + end + end + + context 'サービス内でエラーが発生した場合' do + before do + allow(ElectricityPriceCalculateService).to receive(:new).and_raise(StandardError) + end + + it '予期しないエラー応答を返す' do + get electricity_prices_url, params: valid_params + expect(response).to have_http_status(:internal_server_error) + expect(JSON.parse(response.body)).to eq({ 'error' => '予期しないエラーが発生しました' }) + end + end + end + end +end diff --git a/serverside_challenge_2/challenge/backend/spec/spec_helper.rb b/serverside_challenge_2/challenge/backend/spec/spec_helper.rb new file mode 100644 index 000000000..409c64b6c --- /dev/null +++ b/serverside_challenge_2/challenge/backend/spec/spec_helper.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +# 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. + # # 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 diff --git a/serverside_challenge_2/challenge/storage/.keep b/serverside_challenge_2/challenge/backend/storage/.keep similarity index 100% rename from serverside_challenge_2/challenge/storage/.keep rename to serverside_challenge_2/challenge/backend/storage/.keep diff --git a/serverside_challenge_2/challenge/test/controllers/.keep b/serverside_challenge_2/challenge/backend/tmp/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/controllers/.keep rename to serverside_challenge_2/challenge/backend/tmp/.keep diff --git a/serverside_challenge_2/challenge/test/fixtures/files/.keep b/serverside_challenge_2/challenge/backend/tmp/pids/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/fixtures/files/.keep rename to serverside_challenge_2/challenge/backend/tmp/pids/.keep diff --git a/serverside_challenge_2/challenge/test/integration/.keep b/serverside_challenge_2/challenge/backend/tmp/storage/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/integration/.keep rename to serverside_challenge_2/challenge/backend/tmp/storage/.keep diff --git a/serverside_challenge_2/challenge/test/mailers/.keep b/serverside_challenge_2/challenge/backend/vendor/.keep similarity index 100% rename from serverside_challenge_2/challenge/test/mailers/.keep rename to serverside_challenge_2/challenge/backend/vendor/.keep diff --git a/serverside_challenge_2/challenge/bin/rails b/serverside_challenge_2/challenge/bin/rails deleted file mode 100755 index efc037749..000000000 --- a/serverside_challenge_2/challenge/bin/rails +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -APP_PATH = File.expand_path("../config/application", __dir__) -require_relative "../config/boot" -require "rails/commands" diff --git a/serverside_challenge_2/challenge/bin/rake b/serverside_challenge_2/challenge/bin/rake deleted file mode 100755 index 4fbf10b96..000000000 --- a/serverside_challenge_2/challenge/bin/rake +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -require_relative "../config/boot" -require "rake" -Rake.application.run diff --git a/serverside_challenge_2/challenge/config/boot.rb b/serverside_challenge_2/challenge/config/boot.rb deleted file mode 100644 index 988a5ddc4..000000000 --- a/serverside_challenge_2/challenge/config/boot.rb +++ /dev/null @@ -1,4 +0,0 @@ -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) - -require "bundler/setup" # Set up gems listed in the Gemfile. -require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/serverside_challenge_2/challenge/config/initializers/cors.rb b/serverside_challenge_2/challenge/config/initializers/cors.rb deleted file mode 100644 index e5a82f162..000000000 --- a/serverside_challenge_2/challenge/config/initializers/cors.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Avoid CORS issues when API is called from the frontend app. -# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. - -# 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 diff --git a/serverside_challenge_2/challenge/docker-compose.override.yml b/serverside_challenge_2/challenge/docker-compose.override.yml new file mode 100644 index 000000000..686d59e6e --- /dev/null +++ b/serverside_challenge_2/challenge/docker-compose.override.yml @@ -0,0 +1,6 @@ +version: '3' +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile.dev diff --git a/serverside_challenge_2/challenge/docker-compose.yml b/serverside_challenge_2/challenge/docker-compose.yml index 723d14368..96ca14298 100644 --- a/serverside_challenge_2/challenge/docker-compose.yml +++ b/serverside_challenge_2/challenge/docker-compose.yml @@ -9,13 +9,12 @@ services: - postgres_volume:/var/lib/postgresql/data restart: always web: - build: . - command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" + build: ./backend environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password volumes: - - .:/app + - ./backend:/app ports: - "3000:3000" restart: always @@ -23,5 +22,13 @@ services: stdin_open: true depends_on: - db + frontend: + build: ./frontend + ports: + - "5173:5173" + volumes: + - ./frontend:/app + tty: true + stdin_open: true volumes: - postgres_volume: \ No newline at end of file + postgres_volume: diff --git a/serverside_challenge_2/challenge/frontend/.env b/serverside_challenge_2/challenge/frontend/.env new file mode 100644 index 000000000..99cbcd54e --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:3000 diff --git a/serverside_challenge_2/challenge/frontend/.env.production b/serverside_challenge_2/challenge/frontend/.env.production new file mode 100644 index 000000000..a181c8626 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/.env.production @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://enecha-publi-yakvtpzir2fc-1409577172.us-east-2.elb.amazonaws.com 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/.vscode/extensions.json b/serverside_challenge_2/challenge/frontend/.vscode/extensions.json new file mode 100644 index 000000000..a7cea0b06 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/serverside_challenge_2/challenge/frontend/Dockerfile b/serverside_challenge_2/challenge/frontend/Dockerfile new file mode 100644 index 000000000..a5d198129 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/Dockerfile @@ -0,0 +1,10 @@ +FROM node:23-bookworm + +WORKDIR /app + +COPY . . + +RUN yarn install +RUN yarn build + +CMD ["yarn", "preview", "--host", "0.0.0.0", "--port", "5173"] diff --git a/serverside_challenge_2/challenge/frontend/Dockerfile.dev b/serverside_challenge_2/challenge/frontend/Dockerfile.dev new file mode 100644 index 000000000..44d146314 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/Dockerfile.dev @@ -0,0 +1,10 @@ +FROM node:23-bookworm + +WORKDIR /app + +COPY . . + +RUN yarn install +RUN yarn build + +CMD ["yarn", "dev", "--host"] diff --git a/serverside_challenge_2/challenge/frontend/README.md b/serverside_challenge_2/challenge/frontend/README.md new file mode 100644 index 000000000..1511959c2 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/README.md @@ -0,0 +1,5 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + diff --git a/serverside_challenge_2/challenge/frontend/package.json b/serverside_challenge_2/challenge/frontend/package.json new file mode 100644 index 000000000..7692b6798 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.8.3", + "floating-vue": "^5.2.2", + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.2.0" + } +} diff --git a/serverside_challenge_2/challenge/frontend/public/vite.svg b/serverside_challenge_2/challenge/frontend/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/serverside_challenge_2/challenge/frontend/src/App.vue b/serverside_challenge_2/challenge/frontend/src/App.vue new file mode 100644 index 000000000..2f83c662f --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/App.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/serverside_challenge_2/challenge/frontend/src/assets/vue.svg b/serverside_challenge_2/challenge/frontend/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/serverside_challenge_2/challenge/frontend/src/components/ElectricityRateSimulation.vue b/serverside_challenge_2/challenge/frontend/src/components/ElectricityRateSimulation.vue new file mode 100644 index 000000000..890d7e4b8 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/components/ElectricityRateSimulation.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/serverside_challenge_2/challenge/frontend/src/main.js b/serverside_challenge_2/challenge/frontend/src/main.js new file mode 100644 index 000000000..94d34c8ef --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/main.js @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' +import FloatingVue from 'floating-vue' +import 'floating-vue/dist/style.css' + +const app = createApp(App) +app.use(FloatingVue) +app.mount('#app') diff --git a/serverside_challenge_2/challenge/frontend/src/style.css b/serverside_challenge_2/challenge/frontend/src/style.css new file mode 100644 index 000000000..f69131543 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/src/style.css @@ -0,0 +1,79 @@ +:root { + font-family: 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; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@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/vite.config.js b/serverside_challenge_2/challenge/frontend/vite.config.js new file mode 100644 index 000000000..6f4ba42d1 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + base: '/frontend/', + server: { + host: true, + }, + preview: { + host: '0.0.0.0', + port: 5173, + allowedHosts: [ + 'localhost', + 'enecha-publi-yakvtpzir2fc-1409577172.us-east-2.elb.amazonaws.com' + ] + } +}) diff --git a/serverside_challenge_2/challenge/frontend/yarn.lock b/serverside_challenge_2/challenge/frontend/yarn.lock new file mode 100644 index 000000000..ad3447fe0 --- /dev/null +++ b/serverside_challenge_2/challenge/frontend/yarn.lock @@ -0,0 +1,667 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/parser@^7.25.3": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" + integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== + dependencies: + "@babel/types" "^7.26.10" + +"@babel/types@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" + integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@esbuild/aix-ppc64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz#c33cf6bbee34975626b01b80451cbb72b4c6c91d" + integrity sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ== + +"@esbuild/android-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz#ea766015c7d2655164f22100d33d7f0308a28d6d" + integrity sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA== + +"@esbuild/android-arm@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.1.tgz#e84d2bf2fe2e6177a0facda3a575b2139fd3cb9c" + integrity sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q== + +"@esbuild/android-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.1.tgz#58337bee3bc6d78d10425e5500bd11370cfdfbed" + integrity sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw== + +"@esbuild/darwin-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz#a46805c1c585d451aa83be72500bd6e8495dd591" + integrity sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ== + +"@esbuild/darwin-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz#0643e003bb238c63fc93ddbee7d26a003be3cd98" + integrity sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA== + +"@esbuild/freebsd-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz#cff18da5469c09986b93e87979de5d6872fe8f8e" + integrity sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A== + +"@esbuild/freebsd-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz#362fc09c2de14987621c1878af19203c46365dde" + integrity sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww== + +"@esbuild/linux-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz#aa90d5b02efc97a271e124e6d1cea490634f7498" + integrity sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ== + +"@esbuild/linux-arm@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz#dfcefcbac60a20918b19569b4b657844d39db35a" + integrity sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ== + +"@esbuild/linux-ia32@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz#6f9527077ccb7953ed2af02e013d4bac69f13754" + integrity sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ== + +"@esbuild/linux-loong64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz#287d2412a5456e5860c2839d42a4b51284d1697c" + integrity sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg== + +"@esbuild/linux-mips64el@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz#530574b9e1bc5d20f7a4f44c5f045e26f3783d57" + integrity sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg== + +"@esbuild/linux-ppc64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz#5d7e6b283a0b321ea42c6bc0abeb9eb99c1f5589" + integrity sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg== + +"@esbuild/linux-riscv64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz#14fa0cd073c26b4ee2465d18cd1e18eea7859fa8" + integrity sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ== + +"@esbuild/linux-s390x@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz#e677b4b9d1b384098752266ccaa0d52a420dc1aa" + integrity sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ== + +"@esbuild/linux-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz#f1c796b78fff5ce393658313e8c58613198d9954" + integrity sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA== + +"@esbuild/netbsd-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz#0d280b7dfe3973f111b02d5fe9f3063b92796d29" + integrity sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g== + +"@esbuild/netbsd-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz#be663893931a4bb3f3a009c5cc24fa9681cc71c0" + integrity sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA== + +"@esbuild/openbsd-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz#d9021b884233673a05dc1cc26de0bf325d824217" + integrity sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg== + +"@esbuild/openbsd-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz#9f1dc1786ed2e2938c404b06bcc48be9a13250de" + integrity sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw== + +"@esbuild/sunos-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz#89aac24a4b4115959b3f790192cf130396696c27" + integrity sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg== + +"@esbuild/win32-arm64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz#354358647a6ea98ea6d243bf48bdd7a434999582" + integrity sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ== + +"@esbuild/win32-ia32@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz#8cea7340f2647eba951a041dc95651e3908cd4cb" + integrity sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A== + +"@esbuild/win32-x64@0.25.1": + version "0.25.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz#7d79922cb2d88f9048f06393dbf62d2e4accb584" + integrity sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg== + +"@floating-ui/core@^1.1.0": + version "1.6.9" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.9.tgz#64d1da251433019dafa091de9b2886ff35ec14e6" + integrity sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw== + dependencies: + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/dom@~1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.1.1.tgz#66aa747e15894910869bf9144fc54fc7d6e9f975" + integrity sha512-TpIO93+DIujg3g7SykEAGZMDtbJRrmnYRCNYSjJlvIbGhBjRSNTLVbNeDQBrzy9qDgUbiWdc7KA0uZHZ2tJmiw== + dependencies: + "@floating-ui/core" "^1.1.0" + +"@floating-ui/utils@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" + integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== + +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@rollup/rollup-android-arm-eabi@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz#e1d7700735f7e8de561ef7d1fa0362082a180c43" + integrity sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ== + +"@rollup/rollup-android-arm64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz#fa6cdfb1fc9e2c8e227a7f35d524d8f7f90cf4db" + integrity sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA== + +"@rollup/rollup-darwin-arm64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz#6da5a1ddc4f11d4a7ae85ab443824cb6bf614e30" + integrity sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q== + +"@rollup/rollup-darwin-x64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz#25b74ce2d8d3f9ea8e119b01384d44a1c0a0d3ae" + integrity sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q== + +"@rollup/rollup-freebsd-arm64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz#be3d39e3441df5d6e187c83d158c60656c82e203" + integrity sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ== + +"@rollup/rollup-freebsd-x64@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz#cd932d3ec679711efd65ca25821fb318e25b7ce4" + integrity sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw== + +"@rollup/rollup-linux-arm-gnueabihf@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz#d300b74c6f805474225632f185daaeae760ac2bb" + integrity sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg== + +"@rollup/rollup-linux-arm-musleabihf@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz#2caac622380f314c41934ed1e68ceaf6cc380cc3" + integrity sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A== + +"@rollup/rollup-linux-arm64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz#1ec841650b038cc15c194c26326483fd7ebff3e3" + integrity sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A== + +"@rollup/rollup-linux-arm64-musl@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz#2fc70a446d986e27f6101ea74e81746987f69150" + integrity sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg== + +"@rollup/rollup-linux-loongarch64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz#561bd045cd9ce9e08c95f42e7a8688af8c93d764" + integrity sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g== + +"@rollup/rollup-linux-powerpc64le-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz#45d849a0b33813f33fe5eba9f99e0ff15ab5caad" + integrity sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA== + +"@rollup/rollup-linux-riscv64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz#78dde3e6fcf5b5733a97d0a67482d768aa1e83a5" + integrity sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g== + +"@rollup/rollup-linux-s390x-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz#2e34835020f9e03dfb411473a5c2a0e8a9c5037b" + integrity sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw== + +"@rollup/rollup-linux-x64-gnu@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz#4f9774beddc6f4274df57ac99862eb23040de461" + integrity sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA== + +"@rollup/rollup-linux-x64-musl@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz#dfcff2c1aed518b3d23ccffb49afb349d74fb608" + integrity sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg== + +"@rollup/rollup-win32-arm64-msvc@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz#b0b37e2d77041e3aa772f519291309abf4c03a84" + integrity sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg== + +"@rollup/rollup-win32-ia32-msvc@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz#5b5a40e44a743ddc0e06b8e1b3982f856dc9ce0a" + integrity sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw== + +"@rollup/rollup-win32-x64-msvc@4.35.0": + version "4.35.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz#05f25dbc9981bee1ae6e713daab10397044a46ca" + integrity sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw== + +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@vitejs/plugin-vue@^5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz#d1491f678ee3af899f7ae57d9c21dc52a65c7133" + integrity sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ== + +"@vue/compiler-core@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" + integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/shared" "3.5.13" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.0" + +"@vue/compiler-dom@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" + integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA== + dependencies: + "@vue/compiler-core" "3.5.13" + "@vue/shared" "3.5.13" + +"@vue/compiler-sfc@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46" + integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/compiler-core" "3.5.13" + "@vue/compiler-dom" "3.5.13" + "@vue/compiler-ssr" "3.5.13" + "@vue/shared" "3.5.13" + estree-walker "^2.0.2" + magic-string "^0.30.11" + postcss "^8.4.48" + source-map-js "^1.2.0" + +"@vue/compiler-ssr@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz#e771adcca6d3d000f91a4277c972a996d07f43ba" + integrity sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA== + dependencies: + "@vue/compiler-dom" "3.5.13" + "@vue/shared" "3.5.13" + +"@vue/reactivity@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f" + integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg== + dependencies: + "@vue/shared" "3.5.13" + +"@vue/runtime-core@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455" + integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw== + dependencies: + "@vue/reactivity" "3.5.13" + "@vue/shared" "3.5.13" + +"@vue/runtime-dom@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215" + integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog== + dependencies: + "@vue/reactivity" "3.5.13" + "@vue/runtime-core" "3.5.13" + "@vue/shared" "3.5.13" + csstype "^3.1.3" + +"@vue/server-renderer@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7" + integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA== + dependencies: + "@vue/compiler-ssr" "3.5.13" + "@vue/shared" "3.5.13" + +"@vue/shared@3.5.13": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" + integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.3.tgz#9ebccd71c98651d547162a018a1a95a4b4ed4de8" + integrity sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +esbuild@^0.25.0: + version "0.25.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.1.tgz#a16b8d070b6ad4871935277bda6ccfe852e3fa2f" + integrity sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.25.1" + "@esbuild/android-arm" "0.25.1" + "@esbuild/android-arm64" "0.25.1" + "@esbuild/android-x64" "0.25.1" + "@esbuild/darwin-arm64" "0.25.1" + "@esbuild/darwin-x64" "0.25.1" + "@esbuild/freebsd-arm64" "0.25.1" + "@esbuild/freebsd-x64" "0.25.1" + "@esbuild/linux-arm" "0.25.1" + "@esbuild/linux-arm64" "0.25.1" + "@esbuild/linux-ia32" "0.25.1" + "@esbuild/linux-loong64" "0.25.1" + "@esbuild/linux-mips64el" "0.25.1" + "@esbuild/linux-ppc64" "0.25.1" + "@esbuild/linux-riscv64" "0.25.1" + "@esbuild/linux-s390x" "0.25.1" + "@esbuild/linux-x64" "0.25.1" + "@esbuild/netbsd-arm64" "0.25.1" + "@esbuild/netbsd-x64" "0.25.1" + "@esbuild/openbsd-arm64" "0.25.1" + "@esbuild/openbsd-x64" "0.25.1" + "@esbuild/sunos-x64" "0.25.1" + "@esbuild/win32-arm64" "0.25.1" + "@esbuild/win32-ia32" "0.25.1" + "@esbuild/win32-x64" "0.25.1" + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +floating-vue@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/floating-vue/-/floating-vue-5.2.2.tgz#e263932042753f59f3e36e7c1188f3f3e272a539" + integrity sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg== + dependencies: + "@floating-ui/dom" "~1.1.1" + vue-resize "^2.0.0-alpha.1" + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +magic-string@^0.30.11: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +nanoid@^3.3.8: + version "3.3.9" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.9.tgz#e0097d8e026b3343ff053e9ccd407360a03f503a" + integrity sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +postcss@^8.4.48, postcss@^8.5.3: + version "8.5.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +rollup@^4.30.1: + version "4.35.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.35.0.tgz#76c95dba17a579df4c00c3955aed32aa5d4dc66d" + integrity sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.35.0" + "@rollup/rollup-android-arm64" "4.35.0" + "@rollup/rollup-darwin-arm64" "4.35.0" + "@rollup/rollup-darwin-x64" "4.35.0" + "@rollup/rollup-freebsd-arm64" "4.35.0" + "@rollup/rollup-freebsd-x64" "4.35.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.35.0" + "@rollup/rollup-linux-arm-musleabihf" "4.35.0" + "@rollup/rollup-linux-arm64-gnu" "4.35.0" + "@rollup/rollup-linux-arm64-musl" "4.35.0" + "@rollup/rollup-linux-loongarch64-gnu" "4.35.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.35.0" + "@rollup/rollup-linux-riscv64-gnu" "4.35.0" + "@rollup/rollup-linux-s390x-gnu" "4.35.0" + "@rollup/rollup-linux-x64-gnu" "4.35.0" + "@rollup/rollup-linux-x64-musl" "4.35.0" + "@rollup/rollup-win32-arm64-msvc" "4.35.0" + "@rollup/rollup-win32-ia32-msvc" "4.35.0" + "@rollup/rollup-win32-x64-msvc" "4.35.0" + fsevents "~2.3.2" + +source-map-js@^1.2.0, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +vite@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.1.tgz#ae865d4bb93a11844be1bc647c8b2dd1856ea180" + integrity sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q== + dependencies: + esbuild "^0.25.0" + postcss "^8.5.3" + rollup "^4.30.1" + optionalDependencies: + fsevents "~2.3.3" + +vue-resize@^2.0.0-alpha.1: + version "2.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a" + integrity sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg== + +vue@^3.5.13: + version "3.5.13" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a" + integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ== + dependencies: + "@vue/compiler-dom" "3.5.13" + "@vue/compiler-sfc" "3.5.13" + "@vue/runtime-dom" "3.5.13" + "@vue/server-renderer" "3.5.13" + "@vue/shared" "3.5.13" diff --git a/serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb b/serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb deleted file mode 100644 index 800405f15..000000000 --- a/serverside_challenge_2/challenge/test/channels/application_cable/connection_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "test_helper" - -class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase - # test "connects with cookies" do - # cookies.signed[:user_id] = 42 - # - # connect - # - # assert_equal connection.user_id, "42" - # end -end diff --git a/serverside_challenge_2/challenge/test/models/.keep b/serverside_challenge_2/challenge/test/models/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/serverside_challenge_2/challenge/test/test_helper.rb b/serverside_challenge_2/challenge/test/test_helper.rb deleted file mode 100644 index d713e377c..000000000 --- a/serverside_challenge_2/challenge/test/test_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -ENV["RAILS_ENV"] ||= "test" -require_relative "../config/environment" -require "rails/test_help" - -class ActiveSupport::TestCase - # Run tests in parallel with specified workers - parallelize(workers: :number_of_processors) - - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all - - # Add more helper methods to be used by all tests here... -end diff --git a/serverside_challenge_2/challenge/tmp/.keep b/serverside_challenge_2/challenge/tmp/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/serverside_challenge_2/challenge/tmp/pids/.keep b/serverside_challenge_2/challenge/tmp/pids/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/serverside_challenge_2/challenge/tmp/storage/.keep b/serverside_challenge_2/challenge/tmp/storage/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/serverside_challenge_2/challenge/vendor/.keep b/serverside_challenge_2/challenge/vendor/.keep deleted file mode 100644 index e69de29bb..000000000