Add batch grid update endpoint
- POST /parties/:id/grid_update for atomic multi-operations - Support move, swap, and remove operations - Validate all operations before executing - Use transaction for atomicity - Optional character sequence maintenance
This commit is contained in:
parent
311e8254d0
commit
8c05cd838c
1 changed files with 167 additions and 3 deletions
|
|
@ -32,9 +32,9 @@ module Api
|
|||
# Default maximum clear time in seconds
|
||||
DEFAULT_MAX_CLEAR_TIME = 5400
|
||||
|
||||
before_action :set_from_slug, except: %w[create destroy update index favorites]
|
||||
before_action :set, only: %w[update destroy]
|
||||
before_action :authorize_party!, only: %w[update destroy]
|
||||
before_action :set_from_slug, except: %w[create destroy update index favorites grid_update]
|
||||
before_action :set, only: %w[update destroy grid_update]
|
||||
before_action :authorize_party!, only: %w[update destroy grid_update]
|
||||
|
||||
# Primary CRUD Actions
|
||||
|
||||
|
|
@ -103,6 +103,44 @@ module Api
|
|||
end
|
||||
end
|
||||
|
||||
# Batch updates grid items (weapons, characters, summons) atomically.
|
||||
def grid_update
|
||||
operations = grid_update_params[:operations]
|
||||
options = grid_update_params[:options] || {}
|
||||
|
||||
# Validate all operations first
|
||||
validation_errors = validate_grid_operations(operations)
|
||||
if validation_errors.any?
|
||||
return render_unprocessable_entity_response(
|
||||
Api::V1::GranblueError.new("Validation failed: #{validation_errors.join(', ')}")
|
||||
)
|
||||
end
|
||||
|
||||
changes = []
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
operations.each do |operation|
|
||||
change = apply_grid_operation(operation)
|
||||
changes << change if change
|
||||
end
|
||||
|
||||
# Compact character positions if needed
|
||||
if options[:maintain_character_sequence]
|
||||
compact_party_character_positions
|
||||
end
|
||||
end
|
||||
|
||||
render json: {
|
||||
party: PartyBlueprint.render_as_hash(@party.reload, view: :full),
|
||||
operations_applied: changes.count,
|
||||
changes: changes
|
||||
}, status: :ok
|
||||
rescue StandardError => e
|
||||
render_unprocessable_entity_response(
|
||||
Api::V1::GranblueError.new("Grid update failed: #{e.message}")
|
||||
)
|
||||
end
|
||||
|
||||
# Lists parties based on query parameters.
|
||||
def index
|
||||
query = build_filtered_query(build_common_base_query)
|
||||
|
|
@ -195,6 +233,132 @@ module Api
|
|||
weapons_attributes: %i[id party_id weapon_id position mainhand uncap_level transcendence_step element weapon_key1_id weapon_key2_id weapon_key3_id ax_modifier1 ax_modifier2 ax_strength1 ax_strength2 awakening_id awakening_level]
|
||||
)
|
||||
end
|
||||
|
||||
# Permits parameters for grid update operation.
|
||||
def grid_update_params
|
||||
params.permit(
|
||||
operations: [:type, :entity, :id, :source_id, :target_id, :position, :container],
|
||||
options: [:maintain_character_sequence, :validate_before_execute]
|
||||
)
|
||||
end
|
||||
|
||||
# Validates grid operations before executing.
|
||||
def validate_grid_operations(operations)
|
||||
errors = []
|
||||
|
||||
operations.each_with_index do |op, index|
|
||||
case op[:type]
|
||||
when 'move'
|
||||
errors << "Operation #{index}: missing id" unless op[:id].present?
|
||||
errors << "Operation #{index}: missing position" unless op[:position].present?
|
||||
when 'swap'
|
||||
errors << "Operation #{index}: missing source_id" unless op[:source_id].present?
|
||||
errors << "Operation #{index}: missing target_id" unless op[:target_id].present?
|
||||
when 'remove'
|
||||
errors << "Operation #{index}: missing id" unless op[:id].present?
|
||||
else
|
||||
errors << "Operation #{index}: unknown operation type #{op[:type]}"
|
||||
end
|
||||
|
||||
unless %w[weapon character summon].include?(op[:entity])
|
||||
errors << "Operation #{index}: invalid entity type #{op[:entity]}"
|
||||
end
|
||||
end
|
||||
|
||||
errors
|
||||
end
|
||||
|
||||
# Applies a single grid operation.
|
||||
def apply_grid_operation(operation)
|
||||
case operation[:type]
|
||||
when 'move'
|
||||
apply_move_operation(operation)
|
||||
when 'swap'
|
||||
apply_swap_operation(operation)
|
||||
when 'remove'
|
||||
apply_remove_operation(operation)
|
||||
end
|
||||
end
|
||||
|
||||
# Applies a move operation.
|
||||
def apply_move_operation(operation)
|
||||
model_class = grid_model_for_entity(operation[:entity])
|
||||
item = model_class.find_by(id: operation[:id], party_id: @party.id)
|
||||
|
||||
return nil unless item
|
||||
|
||||
old_position = item.position
|
||||
item.update!(position: operation[:position])
|
||||
|
||||
{
|
||||
entity: operation[:entity],
|
||||
id: operation[:id],
|
||||
action: 'moved',
|
||||
from: old_position,
|
||||
to: operation[:position]
|
||||
}
|
||||
end
|
||||
|
||||
# Applies a swap operation.
|
||||
def apply_swap_operation(operation)
|
||||
model_class = grid_model_for_entity(operation[:entity])
|
||||
source = model_class.find_by(id: operation[:source_id], party_id: @party.id)
|
||||
target = model_class.find_by(id: operation[:target_id], party_id: @party.id)
|
||||
|
||||
return nil unless source && target
|
||||
|
||||
source_pos = source.position
|
||||
target_pos = target.position
|
||||
|
||||
# Use a temporary position to avoid conflicts
|
||||
source.update!(position: -999)
|
||||
target.update!(position: source_pos)
|
||||
source.update!(position: target_pos)
|
||||
|
||||
{
|
||||
entity: operation[:entity],
|
||||
id: operation[:source_id],
|
||||
action: 'swapped',
|
||||
with: operation[:target_id]
|
||||
}
|
||||
end
|
||||
|
||||
# Applies a remove operation.
|
||||
def apply_remove_operation(operation)
|
||||
model_class = grid_model_for_entity(operation[:entity])
|
||||
item = model_class.find_by(id: operation[:id], party_id: @party.id)
|
||||
|
||||
return nil unless item
|
||||
|
||||
item.destroy
|
||||
|
||||
{
|
||||
entity: operation[:entity],
|
||||
id: operation[:id],
|
||||
action: 'removed'
|
||||
}
|
||||
end
|
||||
|
||||
# Returns the model class for a given entity type.
|
||||
def grid_model_for_entity(entity)
|
||||
case entity
|
||||
when 'weapon'
|
||||
GridWeapon
|
||||
when 'character'
|
||||
GridCharacter
|
||||
when 'summon'
|
||||
GridSummon
|
||||
end
|
||||
end
|
||||
|
||||
# Compacts character positions to maintain sequential filling.
|
||||
def compact_party_character_positions
|
||||
main_characters = @party.characters.where(position: 0..4).order(:position)
|
||||
|
||||
main_characters.each_with_index do |char, index|
|
||||
char.update!(position: index) if char.position != index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue