diff --git a/serverside_challenge_2/challenge/.dockerignore b/serverside_challenge_2/challenge/.dockerignore new file mode 100644 index 000000000..bd6887fde --- /dev/null +++ b/serverside_challenge_2/challenge/.dockerignore @@ -0,0 +1,31 @@ +# flyctl launch added from .gitignore +# 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 +fly.toml diff --git a/serverside_challenge_2/challenge/.rspec b/serverside_challenge_2/challenge/.rspec new file mode 100644 index 000000000..c99d2e739 --- /dev/null +++ b/serverside_challenge_2/challenge/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/serverside_challenge_2/challenge/Dockerfile b/serverside_challenge_2/challenge/Dockerfile index 166bd4432..58ce541b7 100644 --- a/serverside_challenge_2/challenge/Dockerfile +++ b/serverside_challenge_2/challenge/Dockerfile @@ -1,8 +1,67 @@ +# ベースイメージ FROM ruby:3.1.2 -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs vim -RUN mkdir /app + +# 必要パッケージ +RUN apt-get update -qq && apt-get install -y \ + build-essential \ + libpq-dev \ + nodejs \ + postgresql-client \ + vim \ + && rm -rf /var/lib/apt/lists/* + +# 作業ディレクトリ WORKDIR /app -ADD Gemfile /app/Gemfile -ADD Gemfile.lock /app/Gemfile.lock -RUN bundle install -ADD . /app \ No newline at end of file + +# Gemfile を先にコピーして bundle install(キャッシュ活用) +COPY Gemfile* ./ +RUN bundle install --jobs 4 --retry 3 + +# アプリ全体コピー +COPY . . + +# PID ファイル削除してサーバー起動 +#CMD ["sh", "-c", "rm -f tmp/pids/server.pid && RAILS_ENV=production bin/rails server -b 0.0.0.0 -p 8080"] +CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "3000"] + + +#FROM ruby:3.1.2 +#RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs vim postgresql-client +#RUN mkdir /app +#WORKDIR /app +#ADD Gemfile /app/Gemfile +#ADD Gemfile.lock /app/Gemfile.lock +#RUN bundle install +#ADD . /app +## Gemfile +#COPY Gemfile* ./ +#RUN bundle install +## アプリコードコピー +#COPY . . +## デフォルトコマンド(Fly.io が起動時に使用) +#CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "8080"] + + +#FROM ruby:3.1.2 +#RUN apt-get update -qq && apt-get install -y \ +# build-essential \ +# libpq-dev \ +# nodejs \ +# yarn \ +# vim +# +## 作業ディレクトリ +#WORKDIR /app +## bundler を最新化(bundle not found 防止) +#RUN gem install bundler +## Gemfile を先にコピーして bundle install +#COPY Gemfile Gemfile.lock ./ +#RUN bundle install +## アプリ全体をコピー +#COPY . . +## ポートを公開 +#EXPOSE 3000 +#ENV RAILS_ENV=production +#ENV RACK_ENV=production +## Rails サーバーを起動 +#CMD ["bin/rails", "server", "-b", "0.0.0.0", "-p", "3000"] \ No newline at end of file diff --git a/serverside_challenge_2/challenge/Gemfile b/serverside_challenge_2/challenge/Gemfile index 43bf67fe3..e35b30e76 100644 --- a/serverside_challenge_2/challenge/Gemfile +++ b/serverside_challenge_2/challenge/Gemfile @@ -36,6 +36,8 @@ gem "bootsnap", require: false # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible # gem "rack-cors" +gem "rails-html-sanitizer" + 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 ] diff --git a/serverside_challenge_2/challenge/Gemfile.lock b/serverside_challenge_2/challenge/Gemfile.lock index a47fb85f5..d053ec8c7 100644 --- a/serverside_challenge_2/challenge/Gemfile.lock +++ b/serverside_challenge_2/challenge/Gemfile.lock @@ -1,90 +1,95 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8) - actionpack (= 7.0.8) - activesupport (= 7.0.8) + actioncable (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8) - actionpack (= 7.0.8) - activejob (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + actionmailbox (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8) - actionpack (= 7.0.8) - actionview (= 7.0.8) - activejob (= 7.0.8) - activesupport (= 7.0.8) + actionmailer (7.0.8.7) + actionpack (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.8) - actionview (= 7.0.8) - activesupport (= 7.0.8) + actionpack (7.0.8.7) + actionview (= 7.0.8.7) + activesupport (= 7.0.8.7) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8) - actionpack (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + actiontext (7.0.8.7) + actionpack (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8) - activesupport (= 7.0.8) + actionview (7.0.8.7) + activesupport (= 7.0.8.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.8) - activesupport (= 7.0.8) + activejob (7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.3.6) - activemodel (7.0.8) - activesupport (= 7.0.8) - activerecord (7.0.8) - activemodel (= 7.0.8) - activesupport (= 7.0.8) - activestorage (7.0.8) - actionpack (= 7.0.8) - activejob (= 7.0.8) - activerecord (= 7.0.8) - activesupport (= 7.0.8) + activemodel (7.0.8.7) + activesupport (= 7.0.8.7) + activerecord (7.0.8.7) + activemodel (= 7.0.8.7) + activesupport (= 7.0.8.7) + activestorage (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activesupport (= 7.0.8.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.8) + activesupport (7.0.8.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - bootsnap (1.18.3) + base64 (0.3.0) + bootsnap (1.18.6) msgpack (~> 1.2) - builder (3.2.4) - concurrent-ruby (1.2.3) + builder (3.3.0) + cgi (0.5.0) + concurrent-ruby (1.3.5) crass (1.0.6) - date (3.3.4) - debug (1.9.1) + date (3.4.1) + debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) - erubi (1.12.0) - globalid (1.2.1) + erb (4.0.4) + cgi (>= 0.3.3) + erubi (1.13.1) + globalid (1.3.0) activesupport (>= 6.1) - i18n (1.14.1) + i18n (1.14.7) concurrent-ruby (~> 1.0) - io-console (0.7.2) - irb (1.11.2) - rdoc + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) reline (>= 0.4.2) - loofah (2.22.0) + loofah (2.24.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -92,79 +97,84 @@ GEM net-imap net-pop net-smtp - marcel (1.0.2) - method_source (1.0.0) + marcel (1.1.0) + method_source (1.1.0) mini_mime (1.1.5) - minitest (5.22.2) - msgpack (1.7.2) - net-imap (0.4.10) + mini_portile2 (2.8.9) + minitest (5.25.5) + msgpack (1.8.0) + net-imap (0.5.10) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0.1) + net-smtp (0.5.1) net-protocol - nio4r (2.7.0) - nokogiri (1.16.2-aarch64-linux) + nio4r (2.7.4) + nokogiri (1.18.10) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.16.2-x86_64-linux) - racc (~> 1.4) - pg (1.5.4) - psych (5.1.2) + pg (1.6.2-x86_64-linux) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + psych (5.2.6) + date stringio - puma (5.6.8) + puma (5.6.9) nio4r (~> 2.0) - racc (1.7.3) - rack (2.2.8) - rack-test (2.1.0) + racc (1.8.1) + rack (2.2.17) + rack-test (2.2.0) rack (>= 1.3) - rails (7.0.8) - actioncable (= 7.0.8) - actionmailbox (= 7.0.8) - actionmailer (= 7.0.8) - actionpack (= 7.0.8) - actiontext (= 7.0.8) - actionview (= 7.0.8) - activejob (= 7.0.8) - activemodel (= 7.0.8) - activerecord (= 7.0.8) - activestorage (= 7.0.8) - activesupport (= 7.0.8) + rails (7.0.8.7) + actioncable (= 7.0.8.7) + actionmailbox (= 7.0.8.7) + actionmailer (= 7.0.8.7) + actionpack (= 7.0.8.7) + actiontext (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activemodel (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) bundler (>= 1.15.0) - railties (= 7.0.8) - rails-dom-testing (2.2.0) + railties (= 7.0.8.7) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) - railties (7.0.8) - actionpack (= 7.0.8) - activesupport (= 7.0.8) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) method_source rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) - rake (13.1.0) - rdoc (6.6.2) + rake (13.3.0) + rdoc (6.14.2) + erb psych (>= 4.0.0) - reline (0.4.2) + reline (0.6.2) io-console (~> 0.5) - stringio (3.1.0) - thor (1.3.0) - timeout (0.4.1) + stringio (3.1.7) + thor (1.4.0) + timeout (0.4.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - websocket-driver (0.7.6) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.6.13) + zeitwerk (2.6.18) PLATFORMS - aarch64-linux x86_64-linux DEPENDENCIES @@ -173,6 +183,7 @@ DEPENDENCIES pg (~> 1.1) puma (~> 5.0) rails (~> 7.0.8) + rails-html-sanitizer tzinfo-data RUBY VERSION diff --git a/serverside_challenge_2/challenge/app/controllers/electricity_api_controller.rb b/serverside_challenge_2/challenge/app/controllers/electricity_api_controller.rb new file mode 100644 index 000000000..0438b0407 --- /dev/null +++ b/serverside_challenge_2/challenge/app/controllers/electricity_api_controller.rb @@ -0,0 +1,74 @@ +class ElectricityApiController < ApplicationController +def get_price + + #リクエストパラメータから契約アンペア数と使用量を取得 + ampere = params[:ampere].to_i + usage_kwh = params[:usage_kwh].to_f + + #設定ファイルからプラン情報を読み込み + plans = EnergyPlan.load_plans + + #各プランごとに料金を計算 + result = plans.map do |plan| + + fee = plan.calculate_fee(ampere, usage_kwh) + + next if fee.nil? # 対象外のアンペア数はスキップ + + + #返却用データを組み立て + #{ + # provider_name: plan.provider_name, + # plan_name: plan.plan_name, + # price: fee + #} + #エラーの場合メッセージを返却 + { + provider_name: plan.provider_name, + plan_name: plan.plan_name, + price: fee.is_a?(Hash) ? ERROR_NO : fee, + message: fee.is_a?(Hash) ? fee[:message] : nil + }.compact + end.compact + + #JSON 形式で結果を返す + render json: { results: result } +end + +def get_price_db + + #リクエストパラメータから契約アンペア数と使用量を取得 + ampere = params[:ampere].to_i + usage_kwh = params[:usage_kwh].to_f + + #設定ファイルからプラン情報を読み込み + plans = EnergyPlanDb.load_plans + + #各プランごとに料金を計算 + result = plans.map do |plan| + + fee = plan.calculate_fee(ampere, usage_kwh) + + next if fee.nil? # 対象外のアンペア数はスキップ + + #返却用データを組み立て + #{ + # provider_name: plan.provider_name, + # plan_name: plan.plan_name, + # price: fee + #} + #エラーの場合メッセージを返却 + { + provider_name: plan.provider_name, + plan_name: plan.plan_name, + price: fee.is_a?(Hash) ? ERROR_NO : fee, + message: fee.is_a?(Hash) ? fee[:message] : nil + }.compact + + end.compact + + #JSON 形式で結果を返す + render json: { results: result } +end + +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/app/models/basic_fee.rb b/serverside_challenge_2/challenge/app/models/basic_fee.rb new file mode 100644 index 000000000..a7441fb72 --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/basic_fee.rb @@ -0,0 +1,3 @@ +class BasicFee < ApplicationRecord + belongs_to :energy_plan_db, class_name: "EnergyPlanDb", foreign_key: :energy_plan_id +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/app/models/energy_plan.rb b/serverside_challenge_2/challenge/app/models/energy_plan.rb new file mode 100644 index 000000000..738dabf82 --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/energy_plan.rb @@ -0,0 +1,65 @@ +# app/models/energy_plan.rb +class EnergyPlan + attr_reader :provider_name, :plan_name, :basic_fee, :unit_fee + + # 初期化 + def initialize(provider_name, plan_name, basic_fee, unit_fee) + @provider_name = provider_name + @plan_name = plan_name + # basic_fee は {アンペア数 => 金額} 形式 + @basic_fee = basic_fee.transform_keys(&:to_i) + # unit_fee は {"0-120" => 19.88} 形式(上限0なら無制限) + @unit_fee = unit_fee + end + + #設定ファイルからプランを読み込み + def self.load_plans + plans_yaml = YAML.load_file(Rails.root.join('config/api_settings.yml'))[Rails.env] + plans_yaml.map do |h| + EnergyPlan.new(h['provider_name'], h['plan_name'], h['basic_fee'], h['unit_fee']) + end + end + + #契約アンペアと使用量から料金計算 + def calculate_fee(ampere, usage_kwh) + + #使用量チェック + if usage_kwh <= 0 || usage_kwh >= MAX_USAGE_KWH + return { error: true, message: "使用量が異常値です" } + end + + # 基本料金を取得(対象外なら -1) + fee = basic_fee[ampere.to_i] + #アンペアチェック + unless fee + return { error: true, message: "対応する基本料金が見つかりません" } + end + + usage_fee = 0.0 + + #使用量に応じて従量料金を加算 + #"0-120"、"121-300"、"301-0"で250kwの場合 + #"0-120":min = 0, max = 120、[250, 120].min = 120、120 - 0 = 120 + #"121-300":min = 121, max = 300[250, 300].min = 250、250 - 121 = 129 + #"301-0":スキップ + unit_fee.each do |range, price| + min, max = range.split('-').map(&:to_f) #"0-120"を-で分解して数値化 + max = Float::INFINITY if max.zero? #上限が0の場合は無制限 + + if usage_kwh > min + # 区間内での使用量を計算 + kwh = [usage_kwh, max].min - min + usage_fee += kwh * price + end + end + + # 基本料金 + 従量料金 を小数点2桁で丸めて返す + (fee + usage_fee).round(2) + + rescue => e + # 計算中に例外が起きた場合はログ出力して -1 を返す + Rails.logger.error("Error calculating fee: #{e.message}") + #例外発生時のエラー + { error: true, message: "料金計算中にエラーが発生しました" } + end +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/app/models/energy_plan_db.rb b/serverside_challenge_2/challenge/app/models/energy_plan_db.rb new file mode 100644 index 000000000..f587d78f2 --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/energy_plan_db.rb @@ -0,0 +1,60 @@ +# app/models/energy_plan_db.rb +class EnergyPlanDb < ApplicationRecord + # energy_plans テーブルと対応 + self.table_name = 'energy_plans' + + # 関連テーブルの設定 + has_many :basic_fees, foreign_key: :energy_plan_id + has_many :unit_fees, foreign_key: :energy_plan_id + + # 利用可能なプランのみ取得 + def self.load_plans + includes(:basic_fees, :unit_fees) # N+1対策で関連テーブルをまとめて取得 + .where(active: true) # activeフラグがtrueのプランのみ + rescue => e + Rails.logger.error("Error loading plans via DB: #{e.message}") + [] # エラー時は空配列 + end + + # 契約アンペアと使用量から料金計算 + def calculate_fee(ampere, usage_kwh) + #使用量チェック + if usage_kwh <= 0 || usage_kwh >= MAX_USAGE_KWH + return { error: true, message: "使用量が異常値です" } + end + # 契約アンペアに対応する基本料金を取得 + fee_record = basic_fees.find { |bf| bf.ampere == ampere.to_i } + #アンペアチェック + unless fee_record + return { error: true, message: "対応する基本料金が見つかりません" } + end + + # 見つかった基本料金の金額を取得(float に変換) + fee = fee_record.price.to_f + + # 従量料金を加算 + usage_fee = 0.0 + # unit_fees テーブルに登録されている各従量料金設定を処理 + unit_fees.each do |uf| + # この従量料金が適用される最小使用量 (kWh) + min = uf.min_kwh.to_f + # 最大使用量が未設定(nil) または 0 の場合は「上限なし」として扱う + max = uf.max_kwh.nil? || uf.max_kwh.zero? ? Float::INFINITY : uf.max_kwh.to_f + + # 実際の使用量が min を超えている場合にのみ、この従量料金が適用される + if usage_kwh > min + # この従量料金帯に該当する使用量(min を超えて max までの範囲) + kwh = [usage_kwh, max].min - min + # 該当する使用量 × 単価を従量料金に加算 + usage_fee += kwh * uf.price.to_f + end + end + + # 合計料金を計算(基本料金 + 従量料金)、小数点以下2桁に四捨五入 + (fee + usage_fee).round(2) + rescue => e + Rails.logger.error("Error calculating fee: #{e.message}") + #例外発生時のエラー + { error: true, message: "料金計算中にエラーが発生しました" } + end +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/app/models/unit_fee.rb b/serverside_challenge_2/challenge/app/models/unit_fee.rb new file mode 100644 index 000000000..020d83f01 --- /dev/null +++ b/serverside_challenge_2/challenge/app/models/unit_fee.rb @@ -0,0 +1,3 @@ +class UnitFee < ApplicationRecord + belongs_to :energy_plan_db, class_name: "EnergyPlanDb", foreign_key: :energy_plan_id +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/app/views/layouts/application.html.erb b/serverside_challenge_2/challenge/app/views/layouts/application.html.erb new file mode 100644 index 000000000..a5ea23f4d --- /dev/null +++ b/serverside_challenge_2/challenge/app/views/layouts/application.html.erb @@ -0,0 +1 @@ +
<%= yield %> \ No newline at end of file diff --git a/serverside_challenge_2/challenge/bin/rails b/serverside_challenge_2/challenge/bin/rails index efc037749..f2a9ce786 100755 --- a/serverside_challenge_2/challenge/bin/rails +++ b/serverside_challenge_2/challenge/bin/rails @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +require "logger" APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../config/boot" require "rails/commands" diff --git a/serverside_challenge_2/challenge/config/api_settings.yml b/serverside_challenge_2/challenge/config/api_settings.yml new file mode 100644 index 000000000..81846ac9e --- /dev/null +++ b/serverside_challenge_2/challenge/config/api_settings.yml @@ -0,0 +1,125 @@ +development: + - provider_name: 東京電力エナジーパートナー + plan_name: 従量電灯B + basic_fee: + 10: 286.0 + 15: 429.0 + 20: 572.0 + 30: 858.0 + 40: 1144.0 + 50: 1430.0 + 60: 1716.0 + unit_fee: + "0-120": 19.88 + "121-300": 26.48 + "301-0": 30.57 + + - provider_name: 東京電力エナジーパートナー + plan_name: スタンダードS + basic_fee: + 10: 311.75 + 15: 467.63 + 20: 623.50 + 30: 935.25 + 40: 1247.00 + 50: 1558.75 + 60: 1870.50 + unit_fee: + "0-120": 29.80 + "121-300": 36.40 + "301-0": 40.49 + + - provider_name: 東京ガス + plan_name: ずっとも電気1 + basic_fee: + 30: 858.0 + 40: 1144.0 + 50: 1430.0 + 60: 1716.0 + unit_fee: + "0-140": 23.67 + "141-350": 23.88 + "351-0": 26.41 + + - provider_name: Looopでんき + plan_name: おうちプラン + basic_fee: + 10: 0.0 + 15: 0.0 + 20: 0.0 + 30: 0.0 + 40: 0.0 + 50: 0.0 + 60: 0.0 + unit_fee: + "0-0": 28.8 + + - provider_name: YAMLtest + plan_name: テスト + basic_fee: + 10: 0.0 + unit_fee: + "0-0": 10.0 + +production: + - provider_name: 東京電力エナジーパートナー + plan_name: 従量電灯B + basic_fee: + 10: 286.0 + 15: 429.0 + 20: 572.0 + 30: 858.0 + 40: 1144.0 + 50: 1430.0 + 60: 1716.0 + unit_fee: + "0-120": 19.88 + "121-300": 26.48 + "301-0": 30.57 + + - provider_name: 東京電力エナジーパートナー + plan_name: スタンダードS + basic_fee: + 10: 311.75 + 15: 467.63 + 20: 623.50 + 30: 935.25 + 40: 1247.00 + 50: 1558.75 + 60: 1870.50 + unit_fee: + "0-120": 29.80 + "121-300": 36.40 + "301-0": 40.49 + + - provider_name: 東京ガス + plan_name: ずっとも電気1 + basic_fee: + 30: 858.0 + 40: 1144.0 + 50: 1430.0 + 60: 1716.0 + unit_fee: + "0-140": 23.67 + "141-350": 23.88 + "351-0": 26.41 + + - provider_name: Looopでんき + plan_name: おうちプラン + basic_fee: + 10: 0.0 + 15: 0.0 + 20: 0.0 + 30: 0.0 + 40: 0.0 + 50: 0.0 + 60: 0.0 + unit_fee: + "0-0": 28.8 + + - provider_name: YAMLtest + plan_name: テスト + basic_fee: + 10: 0.0 + unit_fee: + "0-0": 10.0 diff --git a/serverside_challenge_2/challenge/config/application.rb b/serverside_challenge_2/challenge/config/application.rb index b39913bf7..d2a091b65 100644 --- a/serverside_challenge_2/challenge/config/application.rb +++ b/serverside_challenge_2/challenge/config/application.rb @@ -1,7 +1,7 @@ require_relative "boot" require "rails/all" - +require "logger" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) @@ -23,5 +23,11 @@ class Application < Rails::Application # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. config.api_only = true + + # API モードから復帰させるミドルウェアを明示的に追加 + config.middleware.use ActionDispatch::Cookies + config.middleware.use ActionDispatch::Session::CookieStore + config.middleware.use ActionDispatch::Flash + end end diff --git a/serverside_challenge_2/challenge/config/database.yml b/serverside_challenge_2/challenge/config/database.yml index 9c66b2942..69e556830 100644 --- a/serverside_challenge_2/challenge/config/database.yml +++ b/serverside_challenge_2/challenge/config/database.yml @@ -1,87 +1,17 @@ -# PostgreSQL. Versions 9.3 and up are supported. -# -# Install the pg driver: -# gem install pg -# On macOS with Homebrew: -# gem install pg -- --with-pg-config=/usr/local/bin/pg_config -# On macOS with MacPorts: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# -# Configure Using Gemfile -# gem "pg" -# default: &default adapter: postgresql - encoding: unicode - # For details on connection pooling, see Rails configuration guide - # https://guides.rubyonrails.org/configuring.html#database-pooling - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - host: db username: postgres password: password + encoding: unicode + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default - database: app_development - - # The specified database role being used to connect to postgres. - # To create additional roles in postgres see `$ createuser --help`. - # When left blank, postgres will use the default role. This is - # the same name as the operating system user running Rails. - #username: app - - # The password associated with the postgres role (username). - #password: - # Connect on a TCP socket. Omitted by default since the client uses a - # domain socket that doesn't need configuration. Windows does not have - # domain sockets, so uncomment these lines. - #host: localhost - - # The TCP port the server listens on. Defaults to 5432. - # If your server runs on a different port number, change accordingly. - #port: 5432 - - # Schema search path. The server defaults to $user,public - #schema_search_path: myapp,sharedapp,public - - # Minimum log levels, in increasing order: - # debug5, debug4, debug3, debug2, debug1, - # log, notice, warning, error, fatal, and panic - # Defaults to warning. - #min_messages: notice - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <<: *default - database: app_test + host: db + port: 5432 + database: challenge_db -# As with config/credentials.yml, you never want to store sensitive information, -# like your database password, in your source code. If your source code is -# ever seen by anyone, they now have access to your database. -# -# Instead, provide the password or a full connection URL as an environment -# variable when you boot the app. For example: -# -# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" -# -# If the connection URL is provided in the special DATABASE_URL environment -# variable, Rails will automatically merge its configuration values on top of -# the values provided in this file. Alternatively, you can specify a connection -# URL environment variable explicitly: -# -# production: -# url: <%= ENV["MY_APP_DATABASE_URL"] %> -# -# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database -# for a full overview on how database connection configuration can be specified. -# production: <<: *default - database: app_production + url: <%= ENV['DATABASE_URL'] %> \ No newline at end of file diff --git a/serverside_challenge_2/challenge/config/environments/development.rb b/serverside_challenge_2/challenge/config/environments/development.rb index 3d6b07360..f261ed8d8 100644 --- a/serverside_challenge_2/challenge/config/environments/development.rb +++ b/serverside_challenge_2/challenge/config/environments/development.rb @@ -2,7 +2,7 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - +# config.hosts << "challenge-falling-haze-6654.fly.dev" # In the development environment your application's code is reloaded any time # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. diff --git a/serverside_challenge_2/challenge/config/environments/production.rb b/serverside_challenge_2/challenge/config/environments/production.rb index 0376660ba..93eeb8135 100644 --- a/serverside_challenge_2/challenge/config/environments/production.rb +++ b/serverside_challenge_2/challenge/config/environments/production.rb @@ -1,86 +1,29 @@ -require "active_support/core_ext/integer/time" - Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. config.cache_classes = true - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. config.eager_load = true + config.consider_all_requests_local = false - # Full error reports are disabled and caching is turned on. - 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). - # config.require_master_key = true - - # Disable serving static files from the `/public` folder by default since - # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.asset_host = "http://assets.example.com" - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache - # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX - - # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local - - # Mount Action Cable outside main process or domain. - # config.action_cable.mount_path = nil - # config.action_cable.url = "wss://example.com/cable" - # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + # Fly.io のホスト名を許可 + config.hosts << "challenge-falling-haze-6654.fly.dev" - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true + # 静的ファイルを ENV で有効化(必要なら true に) + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? - # 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 + config.log_tags = [:request_id] - # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "app_production" - - config.action_mailer.perform_caching = false - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false + config.active_storage.service = :local + config.active_job.queue_adapter = :async - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true - - # Don't log any deprecations. config.active_support.report_deprecations = false - - # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new - # Use a different logger for distributed setups. - # require "syslog/logger" - # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") - if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end - # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end diff --git a/serverside_challenge_2/challenge/config/initializers/constants.rb b/serverside_challenge_2/challenge/config/initializers/constants.rb new file mode 100644 index 000000000..37c1a87bc --- /dev/null +++ b/serverside_challenge_2/challenge/config/initializers/constants.rb @@ -0,0 +1,2 @@ + MAX_USAGE_KWH = 99_999_999 #使用料最大 + ERROR_NO = -1 #エラー返却値 diff --git a/serverside_challenge_2/challenge/config/initializers/load_settings.rb b/serverside_challenge_2/challenge/config/initializers/load_settings.rb new file mode 100644 index 000000000..ed373864e --- /dev/null +++ b/serverside_challenge_2/challenge/config/initializers/load_settings.rb @@ -0,0 +1,10 @@ +# Rails 起動時に YAML ファイルを読み込む +require 'yaml' + +ELECTRICITY_SETTINGS = begin + yaml_file = Rails.root.join("config/api_settings.yml") + raw_data = YAML.load_file(yaml_file) + raw_data.fetch(Rails.env.to_s, []) +rescue Errno::ENOENT + [] # ファイルがない場合は空配列 +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/config/puma.rb b/serverside_challenge_2/challenge/config/puma.rb index daaf03699..5e5c90bb9 100644 --- a/serverside_challenge_2/challenge/config/puma.rb +++ b/serverside_challenge_2/challenge/config/puma.rb @@ -15,6 +15,7 @@ # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # +#port ENV.fetch("PORT") { ENV["PORT"] } port ENV.fetch("PORT") { 3000 } # Specifies the `environment` that Puma will run in. diff --git a/serverside_challenge_2/challenge/config/routes.rb b/serverside_challenge_2/challenge/config/routes.rb index 262ffd547..4312d1bf9 100644 --- a/serverside_challenge_2/challenge/config/routes.rb +++ b/serverside_challenge_2/challenge/config/routes.rb @@ -3,4 +3,6 @@ # Defines the root path route ("/") # root "articles#index" + get "api/get_price", to: "electricity_api#get_price" # JSON API + get "api/get_price_db", to: "electricity_api#get_price_db" # JSON API end diff --git a/serverside_challenge_2/challenge/config/secrets.yml b/serverside_challenge_2/challenge/config/secrets.yml new file mode 100644 index 000000000..41b5a4ed1 --- /dev/null +++ b/serverside_challenge_2/challenge/config/secrets.yml @@ -0,0 +1,8 @@ +development: + secret_key_base: 022eeea32ce3537c8caa3d494d34cbb5ea55bc5ff97a6f656a329973dd27c408f335ffca6d39184a8ab695062aa1f3cb5a64eb8c4cea22c39d12c651d0590212 + +test: + secret_key_base: 022eeea32ce3537c8caa3d494d34cbb5ea55bc5ff97a6f656a329973dd27c408f335ffca6d39184a8ab695062aa1f3cb5a64eb8c4cea22c39d12c651d0590212 + +production: + secret_key_base: 022eeea32ce3537c8caa3d494d34cbb5ea55bc5ff97a6f656a329973dd27c408f335ffca6d39184a8ab695062aa1f3cb5a64eb8c4cea22c39d12c651d0590212 \ No newline at end of file diff --git a/serverside_challenge_2/challenge/db/migrate/20251001121000_create_energy_plans.rb b/serverside_challenge_2/challenge/db/migrate/20251001121000_create_energy_plans.rb new file mode 100644 index 000000000..4a5203980 --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20251001121000_create_energy_plans.rb @@ -0,0 +1,11 @@ +class CreateEnergyPlans < ActiveRecord::Migration[7.0] + def change + create_table :energy_plans do |t| + t.string :provider_name, null: false + t.string :plan_name, null: false + t.boolean :active, default: true, null: false + + t.timestamps + end + end +end \ No newline at end of file diff --git a/serverside_challenge_2/challenge/db/migrate/20251001121010_create_basic_fees.rb b/serverside_challenge_2/challenge/db/migrate/20251001121010_create_basic_fees.rb new file mode 100644 index 000000000..c5fe07952 --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20251001121010_create_basic_fees.rb @@ -0,0 +1,12 @@ +class CreateBasicFees < ActiveRecord::Migration[7.0] + def change + create_table :basic_fees do |t| + t.references :energy_plan, null: false, foreign_key: true + t.integer :ampere, null: false + t.float :price, null: false + t.boolean :active, default: true, null: false + + t.timestamps + end + end +end diff --git a/serverside_challenge_2/challenge/db/migrate/20251001121020_create_unit_fees.rb b/serverside_challenge_2/challenge/db/migrate/20251001121020_create_unit_fees.rb new file mode 100644 index 000000000..935981f5f --- /dev/null +++ b/serverside_challenge_2/challenge/db/migrate/20251001121020_create_unit_fees.rb @@ -0,0 +1,12 @@ +class CreateUnitFees < ActiveRecord::Migration[7.0] + def change + create_table :unit_fees do |t| + t.references :energy_plan, null: false, foreign_key: true + t.float :min_kwh, null: false + t.float :max_kwh + t.float :price, null: false + + t.timestamps + end + end +end diff --git a/serverside_challenge_2/challenge/fly.toml b/serverside_challenge_2/challenge/fly.toml new file mode 100644 index 000000000..5f4cb2319 --- /dev/null +++ b/serverside_challenge_2/challenge/fly.toml @@ -0,0 +1,27 @@ +# fly.toml app configuration file generated for challenge-db-prod on 2025-10-02T13:54:08+09:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'challenge-db-prod' +primary_region = 'nrt' + +[build] + dockerfile = 'Dockerfile' + +[[services]] + protocol = 'tcp' + internal_port = 8080 + + [[services.ports]] + port = 80 + handlers = ['http'] + + [[services.ports]] + port = 443 + handlers = ['tls', 'http'] + +[[vm]] + memory = '1gb' + cpu_kind = 'shared' + cpus = 1 diff --git a/serverside_challenge_2/challenge/public/index.html b/serverside_challenge_2/challenge/public/index.html new file mode 100644 index 000000000..340613473 --- /dev/null +++ b/serverside_challenge_2/challenge/public/index.html @@ -0,0 +1,69 @@ + + + + +-- 結果 ----+