diff --git a/app/blueprints/api/v1/gw_event_blueprint.rb b/app/blueprints/api/v1/gw_event_blueprint.rb index 2861100..e90d205 100644 --- a/app/blueprints/api/v1/gw_event_blueprint.rb +++ b/app/blueprints/api/v1/gw_event_blueprint.rb @@ -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? diff --git a/app/controllers/api/v1/crew_gw_participations_controller.rb b/app/controllers/api/v1/crew_gw_participations_controller.rb index f64a5d4..edf0b3b 100644 --- a/app/controllers/api/v1/crew_gw_participations_controller.rb +++ b/app/controllers/api/v1/crew_gw_participations_controller.rb @@ -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]) diff --git a/app/controllers/api/v1/gw_events_controller.rb b/app/controllers/api/v1/gw_events_controller.rb index f2aadcb..33a158f 100644 --- a/app/controllers/api/v1/gw_events_controller.rb +++ b/app/controllers/api/v1/gw_events_controller.rb @@ -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! diff --git a/app/models/gw_event.rb b/app/models/gw_event.rb index 3f541fa..6050cf2 100644 --- a/app/models/gw_event.rb +++ b/app/models/gw_event.rb @@ -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 diff --git a/db/migrate/20251204093029_change_gw_events_name_nullable.rb b/db/migrate/20251204093029_change_gw_events_name_nullable.rb new file mode 100644 index 0000000..8fdf645 --- /dev/null +++ b/db/migrate/20251204093029_change_gw_events_name_nullable.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ChangeGwEventsNameNullable < ActiveRecord::Migration[8.0] + def change + remove_column :gw_events, :name, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 79d2f6a..1c0478f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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)" diff --git a/spec/factories/gw_events.rb b/spec/factories/gw_events.rb index bce003c..5361e80 100644 --- a/spec/factories/gw_events.rb +++ b/spec/factories/gw_events.rb @@ -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 } diff --git a/spec/requests/api/v1/crew_gw_participations_spec.rb b/spec/requests/api/v1/crew_gw_participations_spec.rb index a6ac128..dbba905 100644 --- a/spec/requests/api/v1/crew_gw_participations_spec.rb +++ b/spec/requests/api/v1/crew_gw_participations_spec.rb @@ -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) } diff --git a/spec/requests/api/v1/gw_events_spec.rb b/spec/requests/api/v1/gw_events_spec.rb index fad989d..da51d89 100644 --- a/spec/requests/api/v1/gw_events_spec.rb +++ b/spec/requests/api/v1/gw_events_spec.rb @@ -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