本番デプロイ設定: SSL, Kamal, API環境変数化, テスト修正
This commit is contained in:
@@ -18,3 +18,6 @@
|
||||
|
||||
# Improve security by using a password manager. Never check config/master.key into git!
|
||||
RAILS_MASTER_KEY=$(cat config/master.key)
|
||||
|
||||
# Local registry does not require auth, but Kamal needs a value
|
||||
KAMAL_REGISTRY_PASSWORD=unused
|
||||
|
||||
@@ -7,8 +7,8 @@ class BoardPostsController < ApplicationController
|
||||
before_action :set_current_participant
|
||||
|
||||
def create
|
||||
# 参加チェック
|
||||
unless @board.member?(@current_participant) && @game.status == "in_progress"
|
||||
# 参加チェック(国選択フェーズでも共通掲示板への投稿を許可)
|
||||
unless @board.member?(@current_participant) && @game.status.in?(%w[power_selection in_progress])
|
||||
return redirect_to game_board_path(@game, @board), alert: "投稿権限がありません"
|
||||
end
|
||||
|
||||
|
||||
@@ -22,14 +22,19 @@ class BoardsController < ApplicationController
|
||||
else
|
||||
return redirect_to game_path(@game), alert: "参加者ではないためアクセスできません" unless @current_participant
|
||||
|
||||
# 進行中:参加中の掲示板のみ(eager load で N+1 防止)
|
||||
@boards = @current_participant.boards.includes(:participants, :board_posts, :board_memberships).order(created_at: :desc)
|
||||
# 共通掲示板のフォールバック
|
||||
global_board = @game.boards.global.first
|
||||
if global_board && !@boards.include?(global_board)
|
||||
@boards = [ global_board ] + @boards
|
||||
if @game.status == "power_selection"
|
||||
# 国選択フェーズ:共通掲示板のみ表示
|
||||
@boards = @game.boards.global.includes(:participants, :board_posts, :board_memberships).to_a
|
||||
else
|
||||
# 進行中:参加中の掲示板のみ(eager load で N+1 防止)
|
||||
@boards = @current_participant.boards.includes(:participants, :board_posts, :board_memberships).order(created_at: :desc)
|
||||
# 共通掲示板のフォールバック
|
||||
global_board = @game.boards.global.first
|
||||
if global_board && !@boards.include?(global_board)
|
||||
@boards = [ global_board ] + @boards
|
||||
end
|
||||
@boards = @boards.sort_by { |b| b.global? ? 0 : 1 }
|
||||
end
|
||||
@boards = @boards.sort_by { |b| b.global? ? 0 : 1 }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,11 +75,11 @@ class BoardsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
@posts = @board.board_posts.includes(:participant).order(created_at: :desc)
|
||||
@posts = @board.board_posts.includes(participant: :user).order(created_at: :desc)
|
||||
@proposals = @board.board_proposals.includes(:proposer).order(created_at: :desc)
|
||||
@new_post = BoardPost.new
|
||||
@new_proposal = BoardProposal.new
|
||||
@active_members = @board.active_memberships.includes(:participant).map(&:participant)
|
||||
@active_members = @board.active_memberships.includes(participant: :user).map(&:participant)
|
||||
|
||||
# メンバー追加用:招待可能なプレイヤー一覧
|
||||
if @board.negotiation? && !@board.history_mode?
|
||||
|
||||
@@ -75,8 +75,11 @@ class GamesController < ApplicationController
|
||||
# 全7カ国(固定)
|
||||
powers = %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY]
|
||||
|
||||
# N+1 防止のためparticipantsをキャッシュ
|
||||
participants_cache = @game.participants.includes(:user).index_by(&:power)
|
||||
|
||||
@country_statuses = powers.map do |power|
|
||||
participant = @game.participants.find_by(power: power)
|
||||
participant = participants_cache[power]
|
||||
# 終了済みなら全員完了扱い、そうでなければターンごとの提出状況
|
||||
submitted = @game_finished ? true : @display_turn.orders_submitted_for?(power)
|
||||
|
||||
|
||||
@@ -38,15 +38,15 @@ export default class extends Controller {
|
||||
<div class="flex ${isMe ? 'justify-end' : 'justify-start'} mb-4 message-item" id="post_${data.post_id}">
|
||||
${!isMe ? `
|
||||
<div class="flex-shrink-0 mr-3">
|
||||
<span class="inline-flex items-center justify-center h-8 w-8 rounded-full text-xs font-bold border border-gray-300 bg-white text-gray-700 shadow-sm" title="${data.power}">
|
||||
${data.power ? data.power.substring(0, 2) : '?'}
|
||||
<span class="inline-flex items-center justify-center h-8 w-8 rounded-full text-xs font-bold border border-gray-300 bg-white text-gray-700 shadow-sm" title="${data.display_name || data.power || '?'}">
|
||||
${(data.display_name || data.power || '?').substring(0, 2)}
|
||||
</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="max-w-lg ${isMe ? 'order-1' : 'order-2'}">
|
||||
<div class="flex items-baseline space-x-2 mb-1 ${isMe ? 'justify-end' : 'justify-start'}">
|
||||
${!isMe ? `<span class="text-xs font-bold text-gray-900">${data.power}</span>` : ''}
|
||||
${!isMe ? `<span class="text-xs font-bold text-gray-900">${data.display_name || data.power || '?'}</span>` : ''}
|
||||
<span class="text-xs text-gray-500">${data.created_at}</span>
|
||||
${data.phase ? `<span class="text-xs bg-gray-100 px-1 rounded text-gray-600 border border-gray-200">${data.phase}</span>` : ''}
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ class BoardPost < ApplicationRecord
|
||||
post_id: id,
|
||||
participant_id: participant.id,
|
||||
power: participant.power, # 国名
|
||||
display_name: participant.display_name, # 国名 or ユーザー名
|
||||
body: body,
|
||||
phase: phase, # フェーズ情報
|
||||
created_at: created_at.in_time_zone("Asia/Tokyo").strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
@@ -21,4 +21,9 @@ class Participant < ApplicationRecord
|
||||
in: %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY],
|
||||
message: "無効な国です"
|
||||
}, allow_nil: true
|
||||
|
||||
# 表示名:国名が設定済みなら国名、未設定ならユーザー名
|
||||
def display_name
|
||||
power.present? ? power : user.username
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ require "faraday"
|
||||
require "json"
|
||||
|
||||
class GameApiClient
|
||||
BASE_URL = "http://0.0.0.0:8000"
|
||||
BASE_URL = ENV.fetch("DIPLOMACY_API_URL", "http://0.0.0.0:8000")
|
||||
|
||||
def initialize
|
||||
@connection = Faraday.new(url: BASE_URL) do |f|
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<% unless is_me %>
|
||||
<div class="flex-shrink-0 mr-3">
|
||||
<!-- アバター代わりの国名バッジ -->
|
||||
<span class="inline-flex items-center justify-center h-8 w-8 rounded-full text-xs font-bold border border-gray-300 bg-white text-gray-700 shadow-sm" title="<%= post.participant.power %>">
|
||||
<%= post.participant.power ? post.participant.power[0..1] : '?' %>
|
||||
<span class="inline-flex items-center justify-center h-8 w-8 rounded-full text-xs font-bold border border-gray-300 bg-white text-gray-700 shadow-sm" title="<%= post.participant.display_name %>">
|
||||
<%= post.participant.display_name[0..1] %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="max-w-lg <%= is_me ? 'order-1' : 'order-2' %>">
|
||||
<div class="flex items-baseline space-x-2 mb-1 <%= is_me ? 'justify-end' : 'justify-start' %>">
|
||||
<% unless is_me %>
|
||||
<span class="text-xs font-bold text-gray-900"><%= post.participant.power %></span>
|
||||
<span class="text-xs font-bold text-gray-900"><%= post.participant.display_name %></span>
|
||||
<% end %>
|
||||
<span class="text-xs text-gray-500"><%= l post.created_at, format: :short %></span>
|
||||
<% if post.phase.present? %>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<% if last_post %>
|
||||
<i class="fa-solid fa-comment-dots flex-shrink-0 mr-1.5 text-gray-400"></i>
|
||||
<span class="truncate max-w-xs"><%= last_post.body.truncate(30) %></span>
|
||||
<span class="ml-2 text-xs text-gray-400">- <%= last_post.participant.power %></span>
|
||||
<span class="ml-2 text-xs text-gray-400">- <%= last_post.participant.display_name %></span>
|
||||
<% else %>
|
||||
<span class="text-gray-400">投稿なし</span>
|
||||
<% end %>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
メンバー:
|
||||
<% @active_members.each do |m| %>
|
||||
<span class="ml-1 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border <%= power_color_class(m.power) %>">
|
||||
<%= m.power %>
|
||||
<%= m.display_name %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
<div class="flex-1 flex flex-col bg-white border border-gray-200 rounded-lg shadow-sm mr-4 overflow-hidden">
|
||||
|
||||
<!-- 投稿フォーム (上部に移動) -->
|
||||
<% if @board.member?(@current_participant) && !@board.history_mode? %>
|
||||
<% if @board.member?(@current_participant) && !@board.history_mode? && @game.status.in?(%w[power_selection in_progress]) %>
|
||||
<div class="p-4 bg-gray-50 border-b border-gray-200">
|
||||
<%= form_with model: [@game, @board, @new_post], local: true, class: "flex items-end space-x-2" do |f| %>
|
||||
<div class="flex-1">
|
||||
@@ -200,7 +200,7 @@
|
||||
<li class="px-3 py-2 flex items-center justify-between text-sm">
|
||||
<div class="flex items-center">
|
||||
<span class="<%= 'line-through text-gray-400' if membership.left_at %>">
|
||||
<%= membership.participant.power %>
|
||||
<%= membership.participant.display_name %>
|
||||
</span>
|
||||
<% if membership.participant.user == current_user %>
|
||||
<span class="ml-1 text-xs text-indigo-500">(YOU)</span>
|
||||
|
||||
@@ -189,6 +189,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 掲示板リンク(国選択の相談用) -->
|
||||
<div class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<i class="fa-solid fa-comments text-blue-600 text-lg mr-3"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-blue-800">国選択を相談しませんか?</p>
|
||||
<p class="text-xs text-blue-600">掲示板で他のプレイヤーと相談できます</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= 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 %>
|
||||
<i class="fa-solid fa-comment-dots mr-2"></i> 掲示板へ
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ゲーム管理者用コントロール -->
|
||||
<% if (current_user&.admin? || @game.administrator == current_user) && @game.all_powers_assigned? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
@@ -475,6 +491,11 @@
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-sm font-bold border border-gray-200 shadow-sm <%= power_color_class(status[:power]) %>">
|
||||
<%= status[:power] %>
|
||||
</span>
|
||||
<% if status[:participant] %>
|
||||
<span class="ml-2 text-xs text-gray-500">
|
||||
(<%= status[:participant].user.username %>)
|
||||
</span>
|
||||
<% end %>
|
||||
<% if status[:is_user] %>
|
||||
<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-800">YOU</span>
|
||||
<% elsif status[:participant].nil? %>
|
||||
|
||||
@@ -1 +1 @@
|
||||
7mDalwYZdLi7w0m1MvGVhygx/O0YiNIiiVA+tj/LBukGJInb/cy6jHV7xg1oGIhzzQ1nl0sUbuMHtitmdkb6QWOUiSPAjkpiqfx5sbnE+5Q7U5CPjg0szp7CrNbBhL4ojibKYRPI5Js78x6eBSr9L1vmoWirFZS/ar3B2TwGQ0yNPxghxN7kQ0pEVoiFZUmuXmErih/nTUy5patG1zPPGsXUiAkMGYXztn6n+cIahX6lgFpV79HzI/c01VeMcOV/pZRs/RrQrzfTlnSX2UQlsHqLTQyXx4O6yDUNF8Mii7g6N57jyd26Osi5OIUAsBkrZOhfSSXmd3ceBlt+oSlMXtKaT+qfg+vywqI026eDlsKyEYbCyIaXp9kn+8VMaJcCI+qSGlewfxmxFKVgq7MyNDBrL1VM51UVO00s6cxXWUZ2W96t1SnLyVwcq/NIuhN530imvLvE6cAOAJJKgCfY1gEmUCZ3kuEMO64OSL5Ynm2wxyMF48cCJrxP--rN5KnyzLPaDv/sop--ouscdd1e738zVM6LolPIQA==
|
||||
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==
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
37
script/deploy_dip_api.sh
Normal file
37
script/deploy_dip_api.sh
Normal file
@@ -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
|
||||
54
script/setup_vps.sh
Normal file
54
script/setup_vps.sh
Normal file
@@ -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' を実行"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
4
test/fixtures/games.yml
vendored
4
test/fixtures/games.yml
vendored
@@ -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
|
||||
|
||||
@@ -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: "<svg>...</svg>", process_result: nil, auto_orders: nil)
|
||||
def initialize(initial_state: {}, possible_orders: {}, render_result: "<svg>...</svg>", 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
|
||||
|
||||
Reference in New Issue
Block a user