gw event improvements: status field, members_during_event endpoint

This commit is contained in:
Justin Edmund 2025-12-04 03:02:35 -08:00
parent 7d27d3c8b1
commit 26718b5a3e
9 changed files with 116 additions and 14 deletions

View file

@ -3,7 +3,11 @@
module Api
module V1
class GwEventBlueprint < ApiBlueprint
fields :name, :element, :start_date, :end_date, :event_number
fields :start_date, :end_date, :event_number
field :element do |event|
GwEvent.elements[event.element]
end
field :status do |event|
if event.active?

View file

@ -22,6 +22,39 @@ module Api
render json: CrewGwParticipationBlueprint.render(@participation, view: :with_individual_scores, root: :crew_gw_participation)
end
# GET /crew/gw_participations/by_event/:event_id
def by_event
# Support lookup by event_id (UUID) or event_number (integer)
event = if params[:event_id].match?(/\A\d+\z/)
GwEvent.find_by(event_number: params[:event_id])
else
GwEvent.find_by(id: params[:event_id])
end
return render json: { gw_event: nil, crew_gw_participation: nil, members_during_event: [] } unless event
participation = @crew.crew_gw_participations
.includes(:gw_event, gw_individual_scores: [:crew_membership, :phantom_player])
.find_by(gw_event: event)
# Get all members who were active during the event (includes retired members who left after event started)
# Also include all currently active members for score entry purposes
# Uses joined_at (editable) for historical accuracy
members_during_event = @crew.crew_memberships
.includes(:user)
.active_during(event.start_date, event.end_date)
# Get all phantom players who were active during the event or currently active
phantom_players = @crew.phantom_players.active_during(event.start_date, event.end_date)
render json: {
gw_event: GwEventBlueprint.render_as_hash(event),
crew_gw_participation: participation ? CrewGwParticipationBlueprint.render_as_hash(participation, view: :with_individual_scores) : nil,
members_during_event: CrewMembershipBlueprint.render_as_hash(members_during_event, view: :with_user),
phantom_players: PhantomPlayerBlueprint.render_as_hash(phantom_players)
}
end
# POST /gw_events/:id/participations
def create
event = GwEvent.find(params[:id])

View file

@ -46,7 +46,7 @@ module Api
end
def event_params
params.require(:gw_event).permit(:name, :element, :start_date, :end_date, :event_number)
params.require(:gw_event).permit(:element, :start_date, :end_date, :event_number)
end
def require_admin!

View file

@ -8,7 +8,6 @@ class GwEvent < ApplicationRecord
enum :element, ELEMENTS
validates :name, presence: true
validates :element, presence: true
validates :start_date, presence: true
validates :end_date, presence: true

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class ChangeGwEventsNameNullable < ActiveRecord::Migration[8.0]
def change
remove_column :gw_events, :name, :string
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_12_04_075226) do
ActiveRecord::Schema[8.0].define(version: 2025_12_04_102935) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "pg_catalog.plpgsql"
@ -269,6 +269,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_04_075226) do
t.datetime "retired_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "joined_at"
t.index ["crew_id", "role"], name: "index_crew_memberships_on_crew_id_and_role"
t.index ["crew_id", "user_id"], name: "index_crew_memberships_on_crew_id_and_user_id", unique: true
t.index ["crew_id"], name: "index_crew_memberships_on_crew_id"
@ -462,7 +463,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_04_075226) do
end
create_table "gw_events", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name", null: false
t.integer "element", null: false
t.date "start_date", null: false
t.date "end_date", null: false
@ -664,6 +664,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_04_075226) do
t.boolean "claim_confirmed", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "retired", default: false, null: false
t.datetime "retired_at"
t.datetime "joined_at"
t.index ["claimed_by_id"], name: "index_phantom_players_on_claimed_by_id"
t.index ["claimed_from_membership_id"], name: "index_phantom_players_on_claimed_from_membership_id"
t.index ["crew_id", "granblue_id"], name: "index_phantom_players_on_crew_id_and_granblue_id", unique: true, where: "(granblue_id IS NOT NULL)"

View file

@ -1,6 +1,5 @@
FactoryBot.define do
factory :gw_event do
sequence(:name) { |n| "Unite and Fight ##{n}" }
element { %i[Fire Water Earth Wind Light Dark].sample }
start_date { 1.week.from_now.to_date }
end_date { 2.weeks.from_now.to_date }

View file

@ -91,6 +91,65 @@ RSpec.describe 'Api::V1::CrewGwParticipations', type: :request do
end
end
describe 'GET /api/v1/crew/gw_participations/by_event/:event_id' do
context 'when participating in the event' do
let!(:participation) { create(:crew_gw_participation, crew: crew, gw_event: gw_event) }
it 'returns the event and participation by event ID' do
get "/api/v1/crew/gw_participations/by_event/#{gw_event.id}", headers: auth_headers
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json['gw_event']['id']).to eq(gw_event.id)
expect(json['crew_gw_participation']['id']).to eq(participation.id)
expect(json['members_during_event']).to be_an(Array)
end
it 'returns the event and participation by event number' do
get "/api/v1/crew/gw_participations/by_event/#{gw_event.event_number}", headers: auth_headers
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json['gw_event']['id']).to eq(gw_event.id)
expect(json['crew_gw_participation']['id']).to eq(participation.id)
end
it 'includes members who were active during the event' do
get "/api/v1/crew/gw_participations/by_event/#{gw_event.id}", headers: auth_headers
json = JSON.parse(response.body)
member_ids = json['members_during_event'].map { |m| m['id'] }
expect(member_ids).to include(membership.id)
end
end
context 'when not participating in the event' do
it 'returns event but null participation' do
get "/api/v1/crew/gw_participations/by_event/#{gw_event.id}", headers: auth_headers
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json['gw_event']['id']).to eq(gw_event.id)
expect(json['crew_gw_participation']).to be_nil
end
end
context 'when event does not exist' do
it 'returns null for both' do
get '/api/v1/crew/gw_participations/by_event/99999', headers: auth_headers
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body)
expect(json['gw_event']).to be_nil
expect(json['crew_gw_participation']).to be_nil
end
end
context 'without a crew' do
let!(:membership) { nil }
it 'returns unprocessable_entity' do
get "/api/v1/crew/gw_participations/by_event/#{gw_event.id}", headers: auth_headers
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
describe 'PUT /api/v1/crew/gw_participations/:id' do
let!(:participation) { create(:crew_gw_participation, crew: crew, gw_event: gw_event) }

View file

@ -31,8 +31,7 @@ RSpec.describe 'Api::V1::GwEvents', type: :request do
get "/api/v1/gw_events/#{event.id}"
expect(response).to have_http_status(:ok)
expect(json_response['gw_event']['id']).to eq(event.id)
expect(json_response['gw_event']['name']).to eq(event.name)
expect(json_response['gw_event']['element']).to eq(event.element)
expect(json_response['gw_event']['element']).to eq(GwEvent.elements[event.element])
end
it 'returns 404 for non-existent event' do
@ -45,7 +44,6 @@ RSpec.describe 'Api::V1::GwEvents', type: :request do
let(:valid_params) do
{
gw_event: {
name: 'Unite and Fight #50',
element: 'Fire',
start_date: 1.week.from_now.to_date,
end_date: 2.weeks.from_now.to_date,
@ -61,12 +59,12 @@ RSpec.describe 'Api::V1::GwEvents', type: :request do
}.to change(GwEvent, :count).by(1)
expect(response).to have_http_status(:created)
expect(json_response['gw_event']['name']).to eq('Unite and Fight #50')
expect(json_response['gw_event']['element']).to eq('Fire')
expect(json_response['gw_event']['element']).to eq(GwEvent.elements['Fire'])
expect(json_response['gw_event']['event_number']).to eq(50)
end
it 'returns errors for invalid params' do
post '/api/v1/gw_events', params: { gw_event: { name: '' } }, headers: admin_headers
post '/api/v1/gw_events', params: { gw_event: { element: '' } }, headers: admin_headers
expect(response).to have_http_status(:unprocessable_entity)
end
end
@ -88,13 +86,13 @@ RSpec.describe 'Api::V1::GwEvents', type: :request do
describe 'PUT /api/v1/gw_events/:id' do
let!(:event) { create(:gw_event) }
let(:update_params) { { gw_event: { name: 'Updated Event Name' } } }
let(:update_params) { { gw_event: { event_number: 99 } } }
context 'as admin' do
it 'updates the event' do
put "/api/v1/gw_events/#{event.id}", params: update_params, headers: admin_headers
expect(response).to have_http_status(:ok)
expect(json_response['gw_event']['name']).to eq('Updated Event Name')
expect(json_response['gw_event']['event_number']).to eq(99)
end
end