本番デプロイ設定: SSL, Kamal, API環境変数化, テスト修正

This commit is contained in:
2026-02-21 23:45:12 +09:00
parent e90ea88758
commit 9626db3dcf
21 changed files with 226 additions and 78 deletions

View File

@@ -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

View File

@@ -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?

View File

@@ -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)

View File

@@ -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>

View File

@@ -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")

View File

@@ -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

View File

@@ -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|

View File

@@ -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? %>

View File

@@ -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 %>

View File

@@ -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>

View File

@@ -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? %>