- <% if @board.member?(@current_participant) && !@board.history_mode? %>
+ <% if @board.member?(@current_participant) && !@board.history_mode? && @game.status.in?(%w[power_selection in_progress]) %>
<%= form_with model: [@game, @board, @new_post], local: true, class: "flex items-end space-x-2" do |f| %>
@@ -200,7 +200,7 @@
- <%= membership.participant.power %>
+ <%= membership.participant.display_name %>
<% if membership.participant.user == current_user %>
(YOU)
diff --git a/app/views/games/show.html.erb b/app/views/games/show.html.erb
index 3ddd5cf..2719179 100644
--- a/app/views/games/show.html.erb
+++ b/app/views/games/show.html.erb
@@ -189,6 +189,22 @@
+
+
+
+
+
+
+
国選択を相談しませんか?
+
掲示板で他のプレイヤーと相談できます
+
+
+ <%= link_to game_boards_path(@game), class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
+ 掲示板へ
+ <% end %>
+
+
+
<% if (current_user&.admin? || @game.administrator == current_user) && @game.all_powers_assigned? %>
@@ -475,6 +491,11 @@
<%= status[:power] %>
+ <% if status[:participant] %>
+
+ (<%= status[:participant].user.username %>)
+
+ <% end %>
<% if status[:is_user] %>
YOU
<% elsif status[:participant].nil? %>
diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc
index a4a3fcc..c34d942 100644
--- a/config/credentials.yml.enc
+++ b/config/credentials.yml.enc
@@ -1 +1 @@
-7mDalwYZdLi7w0m1MvGVhygx/O0YiNIiiVA+tj/LBukGJInb/cy6jHV7xg1oGIhzzQ1nl0sUbuMHtitmdkb6QWOUiSPAjkpiqfx5sbnE+5Q7U5CPjg0szp7CrNbBhL4ojibKYRPI5Js78x6eBSr9L1vmoWirFZS/ar3B2TwGQ0yNPxghxN7kQ0pEVoiFZUmuXmErih/nTUy5patG1zPPGsXUiAkMGYXztn6n+cIahX6lgFpV79HzI/c01VeMcOV/pZRs/RrQrzfTlnSX2UQlsHqLTQyXx4O6yDUNF8Mii7g6N57jyd26Osi5OIUAsBkrZOhfSSXmd3ceBlt+oSlMXtKaT+qfg+vywqI026eDlsKyEYbCyIaXp9kn+8VMaJcCI+qSGlewfxmxFKVgq7MyNDBrL1VM51UVO00s6cxXWUZ2W96t1SnLyVwcq/NIuhN530imvLvE6cAOAJJKgCfY1gEmUCZ3kuEMO64OSL5Ynm2wxyMF48cCJrxP--rN5KnyzLPaDv/sop--ouscdd1e738zVM6LolPIQA==
\ No newline at end of file
+NPIjsO0jVJUPWi47JkVirr7vs24Tgxo1+ZUhxCjTTtgGsZjXCHCP1agNcjl1MLlRzjy+2DZgbZuhLGbx33e7veoQ2YMz5kb+AZ7aMzkXla8+TZlqXWYBpQtx3yquV7c7SBHBRy+F+KkvLsyEinWDceoZ3O8kYeu3Fw3QsXBlKENHfDmNEnZ2csm9Yak+jpptNpe+kojuiT/r2F4cD5unOZu8VMluhcGLZ2n2dBev3wFQo/tUgK8CmkFhkd5vSjCFuBhbu6dqwq1jxTrtZdhTG/aia6/RuAnrm/S5MJdxNnXq3dHIa8Wrg0qKp0DBwXxy3okWrRPaPT5udUcZvRA+7DS+to4FnoJDTKI+VanWzOxIbBeqYB9W+kqjKJB1w2Ii2rUoTfSPK9GEcJSb0QI9UNa8Hdjdn/w9MSj9Bkm4LtZch5+HptwLtpZrrKmt4FEIeNhMsVulrbbCMcAANxTRqPxOSbL7Zr5OMMSB/9ReP+atU6u8miWMQkXg--T8JQCM/t7+czYn/u--J/FQEJ3U1dEPM67GCMxR9w==
\ No newline at end of file
diff --git a/config/deploy.yml b/config/deploy.yml
index b08be3f..7e01a44 100644
--- a/config/deploy.yml
+++ b/config/deploy.yml
@@ -7,34 +7,25 @@ image: dip_front
# Deploy to these servers.
servers:
web:
- - 192.168.0.1
+ - 153.127.48.108
# job:
# hosts:
- # - 192.168.0.1
+ # - 153.127.48.108
# cmd: bin/jobs
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
-# If used with Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
-#
-# Using an SSL proxy like this requires turning on config.assume_ssl and config.force_ssl in production.rb!
-#
-# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).
-#
-# proxy:
-# ssl: true
-# host: app.example.com
+# Kamal Proxy will automatically obtain and renew SSL certificates.
+proxy:
+ ssl: true
+ host: diplo.kontei.net
# Where you keep your container images.
+# Using a local registry on the VPS server.
registry:
- # Alternatives: hub.docker.com / registry.digitalocean.com / ghcr.io / ...
server: localhost:5555
-
- # Needed for authenticated registries.
- # username: your-user
-
- # Always use an access token rather than real password when possible.
- # password:
- # - KAMAL_REGISTRY_PASSWORD
+ username: kamal
+ password:
+ - KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .kamal/secrets).
env:
@@ -51,9 +42,8 @@ env:
# Set number of cores available to the application on each server (default: 1).
# WEB_CONCURRENCY: 2
- # Match this to any external database server to configure Active Record correctly
- # Use dip_front-db for a db accessory server on same machine via local kamal docker network.
- # DB_HOST: 192.168.0.2
+ # Diplomacy API URL (dip_api container accessible via Docker host network)
+ DIPLOMACY_API_URL: http://172.17.0.1:8000
# Log everything from Rails
# RAILS_LOG_LEVEL: debug
@@ -77,28 +67,20 @@ volumes:
asset_path: /rails/public/assets
# Configure the image builder.
+# Build on the remote VPS server to avoid insecure registry issues.
builder:
arch: amd64
+ remote: ssh://kontei@153.127.48.108
- # # Build image via remote server (useful for faster amd64 builds on arm64 computers)
- # remote: ssh://docker@docker-builder-server
- #
- # # Pass arguments and secrets to the Docker build process
- # args:
- # RUBY_VERSION: 4.0.1
- # secrets:
- # - GITHUB_TOKEN
- # - RAILS_MASTER_KEY
-
-# Use a different ssh user than root
-# ssh:
-# user: app
+# Use a non-root ssh user
+ssh:
+ user: kontei
# Use accessory services (secrets come from .kamal/secrets).
# accessories:
# db:
# image: mysql:8.0
-# host: 192.168.0.2
+# host: 153.127.48.108
# # Change to 3306 to expose port to the world instead of just local network.
# port: "127.0.0.1:3306:3306"
# env:
@@ -113,7 +95,7 @@ builder:
# - data:/var/lib/mysql
# redis:
# image: valkey/valkey:8
-# host: 192.168.0.2
+# host: 153.127.48.108
# port: 6379
# directories:
# - data:/data
diff --git a/config/environments/production.rb b/config/environments/production.rb
index f5763e0..be0fcce 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -25,13 +25,13 @@ Rails.application.configure do
config.active_storage.service = :local
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
- # config.assume_ssl = true
+ config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
- # config.force_ssl = true
+ config.force_ssl = true
# Skip http-to-https redirect for the default health check endpoint.
- # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
+ config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
# Log to STDOUT with the current request id as a default log tag.
config.log_tags = [ :request_id ]
@@ -58,7 +58,7 @@ Rails.application.configure do
# config.action_mailer.raise_delivery_errors = false
# Set host to be used by links generated in mailer templates.
- config.action_mailer.default_url_options = { host: "example.com" }
+ config.action_mailer.default_url_options = { host: "diplo.kontei.net" }
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
# config.action_mailer.smtp_settings = {
@@ -80,11 +80,11 @@ Rails.application.configure do
config.active_record.attributes_for_inspect = [ :id ]
# Enable DNS rebinding protection and other `Host` header attacks.
- # config.hosts = [
- # "example.com", # Allow requests from example.com
- # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
- # ]
- #
+ config.hosts = [
+ "diplo.kontei.net",
+ /.*\.kontei\.net/
+ ]
+
# Skip DNS rebinding protection for the default health check endpoint.
- # config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
+ config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end
diff --git a/script/deploy_dip_api.sh b/script/deploy_dip_api.sh
new file mode 100644
index 0000000..dbac767
--- /dev/null
+++ b/script/deploy_dip_api.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# ==================================================
+# dip_api (FastAPI) のビルド・起動スクリプト
+# VPS上で実行
+# ==================================================
+
+set -e
+
+# dip_api のソースコードがある場所(VPSにコピー後)
+DIP_API_DIR="${1:-/home/kontei/dip_api}"
+
+echo "=== dip_api Docker イメージのビルド ==="
+cd "$DIP_API_DIR"
+docker build -t dip-api:latest .
+
+echo ""
+echo "=== 既存コンテナの停止・削除(ある場合) ==="
+docker stop dip-api 2>/dev/null || true
+docker rm dip-api 2>/dev/null || true
+
+echo ""
+echo "=== dip_api コンテナの起動 ==="
+docker run -d \
+ --name dip-api \
+ --restart always \
+ -p 8000:8000 \
+ dip-api:latest
+
+echo ""
+echo "=== ヘルスチェック ==="
+sleep 3
+if curl -s http://localhost:8000/debug/heartbeat > /dev/null 2>&1; then
+ echo "✅ dip_api は正常に起動しています"
+else
+ echo "⚠️ dip_api の起動を確認中... (数秒待ってから再確認してください)"
+ echo " docker logs dip-api で確認"
+fi
diff --git a/script/setup_vps.sh b/script/setup_vps.sh
new file mode 100644
index 0000000..9ff84a5
--- /dev/null
+++ b/script/setup_vps.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+# ==================================================
+# さくらVPS 初期セットアップスクリプト
+# 対象: Ubuntu 24.04 LTS
+# 実行: ssh kontei@153.127.48.108 でログイン後に実行
+# ==================================================
+
+set -e
+
+echo "=== 1. Docker のインストール ==="
+# Docker の公式リポジトリを追加
+sudo apt-get update
+sudo apt-get install -y ca-certificates curl
+sudo install -m 0755 -d /etc/apt/keyrings
+sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
+sudo chmod a+r /etc/apt/keyrings/docker.asc
+
+echo \
+ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
+ $(. /etc/os-release && echo "${VERSION_CODENAME}") stable" | \
+ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+
+sudo apt-get update
+sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
+
+# kontei ユーザーを docker グループに追加(sudo なしで docker を使えるようにする)
+sudo usermod -aG docker kontei
+echo "※ docker グループの反映には再ログインが必要です"
+
+echo ""
+echo "=== 2. Docker レジストリの起動 ==="
+# Kamal がイメージをプッシュするためのローカルレジストリ
+sudo docker run -d \
+ -p 5555:5000 \
+ --restart always \
+ --name registry \
+ registry:2
+
+echo ""
+echo "=== 3. ファイアウォール設定 ==="
+# HTTP/HTTPS ポートを開放
+sudo ufw allow 80/tcp
+sudo ufw allow 443/tcp
+# Docker レジストリ(ローカルのみ)
+sudo ufw allow from 127.0.0.1 to any port 5555
+echo "※ UFW が有効でない場合は 'sudo ufw enable' で有効化してください"
+
+echo ""
+echo "=== セットアップ完了 ==="
+echo "次のステップ:"
+echo " 1. 再ログインして docker グループを反映"
+echo " 2. 'docker ps' で registry コンテナが起動していることを確認"
+echo " 3. DNS で diplo.kontei.net → 153.127.48.108 の A レコードを設定"
+echo " 4. ローカルPCから 'bin/kamal setup' を実行"
diff --git a/test/controllers/games_controller_test.rb b/test/controllers/games_controller_test.rb
index 7f0f04a..189c4a4 100644
--- a/test/controllers/games_controller_test.rb
+++ b/test/controllers/games_controller_test.rb
@@ -2,7 +2,11 @@ require "test_helper"
class GamesControllerTest < ActionDispatch::IntegrationTest
setup do
+ @user = users(:one)
+ login_as(@user)
@game = games(:one)
+ # edit/update/destroy には管理者権限が必要
+ @game.participants.create!(user: @user, is_administrator: true) unless @game.participants.exists?(user: @user)
end
test "should get index" do
@@ -17,13 +21,22 @@ class GamesControllerTest < ActionDispatch::IntegrationTest
test "should create game" do
assert_difference("Game.count") do
- post games_url, params: { game: { memo: @game.memo, participants_count: @game.participants_count, title: @game.title } }
+ post games_url, params: { game: {
+ title: "New Test Game",
+ memo: @game.memo,
+ participants_count: @game.participants_count,
+ victory_sc_count: 18,
+ scoring_system: "none"
+ } }
end
assert_redirected_to game_url(Game.last)
end
test "should show game" do
+ # showアクションではgame_stateが必要なため、ターンにgame_stateを設定
+ turn = @game.turns.first
+ turn.update(game_state: { "centers" => {}, "units" => {} }) if turn
get game_url(@game)
assert_response :success
end
@@ -34,7 +47,13 @@ class GamesControllerTest < ActionDispatch::IntegrationTest
end
test "should update game" do
- patch game_url(@game), params: { game: { memo: @game.memo, participants_count: @game.participants_count, title: @game.title } }
+ patch game_url(@game), params: { game: {
+ title: @game.title,
+ memo: @game.memo,
+ auto_order_mode: @game.auto_order_mode,
+ victory_sc_count: 18,
+ scoring_system: "none"
+ } }
assert_redirected_to game_url(@game)
end
diff --git a/test/controllers/turns_controller_test.rb b/test/controllers/turns_controller_test.rb
index f520476..5baf4ef 100644
--- a/test/controllers/turns_controller_test.rb
+++ b/test/controllers/turns_controller_test.rb
@@ -2,6 +2,8 @@ require "test_helper"
class TurnsControllerTest < ActionDispatch::IntegrationTest
setup do
+ @user = users(:one)
+ login_as(@user)
@turn = turns(:one)
end
@@ -17,7 +19,7 @@ class TurnsControllerTest < ActionDispatch::IntegrationTest
test "should create turn" do
assert_difference("Turn.count") do
- post turns_url, params: { turn: { game_id: @turn.game_id, game_stat: @turn.game_stat, number: @turn.number, phase: @turn.phase, svg_date: @turn.svg_date } }
+ post turns_url, params: { turn: { game_id: @turn.game_id, game_state: @turn.game_state, number: @turn.number + 10, phase: @turn.phase, svg_date: @turn.svg_date } }
end
assert_redirected_to turn_url(Turn.last)
@@ -34,7 +36,7 @@ class TurnsControllerTest < ActionDispatch::IntegrationTest
end
test "should update turn" do
- patch turn_url(@turn), params: { turn: { game_id: @turn.game_id, game_stat: @turn.game_stat, number: @turn.number, phase: @turn.phase, svg_date: @turn.svg_date } }
+ patch turn_url(@turn), params: { turn: { game_id: @turn.game_id, game_state: @turn.game_state, number: @turn.number, phase: @turn.phase, svg_date: @turn.svg_date } }
assert_redirected_to turn_url(@turn)
end
diff --git a/test/fixtures/games.yml b/test/fixtures/games.yml
index 7918216..8d1c193 100644
--- a/test/fixtures/games.yml
+++ b/test/fixtures/games.yml
@@ -5,9 +5,13 @@ one:
status: in_progress
turn_schedule: "0,12"
participants_count: 7
+ victory_sc_count: 18
+ scoring_system: none
two:
title: GameTwo
status: recruiting
turn_schedule: "0,12"
participants_count: 1
+ victory_sc_count: 18
+ scoring_system: none
diff --git a/test/services/game_services_test.rb b/test/services/game_services_test.rb
index 4c26d28..ec3cad5 100644
--- a/test/services/game_services_test.rb
+++ b/test/services/game_services_test.rb
@@ -1,19 +1,27 @@
ENV["RAILS_ENV"] ||= "test"
-require_relative "../test/test_helper"
+require_relative "../test_helper"
class RefactoringVerificationTest < ActiveSupport::TestCase
def setup
- @game = Game.create!(title: "Refactor Test #{Time.now.to_r}", status: "recruiting", participants_count: 7, is_solo_mode: true)
+ @game = Game.create!(
+ title: "Refactor Test #{Time.now.to_r}",
+ status: "recruiting",
+ participants_count: 7,
+ is_solo_mode: true,
+ victory_sc_count: 18,
+ scoring_system: "none"
+ )
end
# Mock Client Class
class MockClient
- def initialize(initial_state: {}, possible_orders: {}, render_result: "", process_result: nil, auto_orders: nil)
+ def initialize(initial_state: {}, possible_orders: {}, render_result: "", process_result: nil, auto_orders: nil, validate_result: {})
@initial_state = initial_state
@possible_orders = possible_orders
@render_result = render_result
@process_result = process_result
@auto_orders = auto_orders
+ @validate_result = validate_result
end
def api_game_initial_state(map_name = "standard")
@@ -32,6 +40,10 @@ class RefactoringVerificationTest < ActiveSupport::TestCase
@process_result
end
+ def api_calculate_validate(game_state, orders)
+ @validate_result
+ end
+
def api_calculate_auto_orders(game_state, power_name)
@auto_orders
end