add batch endpoints for collection items

POST /collection/{characters,weapons,summons}/batch
This commit is contained in:
Justin Edmund 2025-12-03 09:03:37 -08:00
parent 4a471dd273
commit 99292f20ef
4 changed files with 210 additions and 15 deletions

View file

@ -7,7 +7,7 @@ module Api
before_action :set_collection_character_for_read, only: %i[show]
# Write actions: require auth, use current_user
before_action :restrict_access, only: %i[create update destroy]
before_action :restrict_access, only: %i[create update destroy batch]
before_action :set_collection_character_for_write, only: %i[update destroy]
def index
@ -74,6 +74,45 @@ module Api
head :no_content
end
# POST /collection/characters/batch
# Creates multiple collection characters in a single request
def batch
items = batch_character_params[:collection_characters] || []
created = []
skipped = []
errors = []
ActiveRecord::Base.transaction do
items.each_with_index do |item_params, index|
# Check if already exists (skip duplicates)
if current_user.collection_characters.exists?(character_id: item_params[:character_id])
skipped << { index: index, character_id: item_params[:character_id], reason: 'already_exists' }
next
end
collection_character = current_user.collection_characters.build(item_params)
if collection_character.save
created << collection_character
else
errors << {
index: index,
character_id: item_params[:character_id],
error: collection_character.errors.full_messages.join(', ')
}
end
end
end
status = errors.any? ? :multi_status : :created
render json: Api::V1::CollectionCharacterBlueprint.render(
created,
root: :characters,
meta: { created: created.size, skipped: skipped.size, skipped_items: skipped, errors: errors }
), status: status
end
private
def set_target_user
@ -112,6 +151,18 @@ module Api
earring: %i[modifier strength]
)
end
def batch_character_params
params.permit(collection_characters: [
:character_id, :uncap_level, :transcendence_step, :perpetuity,
:awakening_id, :awakening_level,
ring1: %i[modifier strength],
ring2: %i[modifier strength],
ring3: %i[modifier strength],
ring4: %i[modifier strength],
earring: %i[modifier strength]
])
end
end
end
end

View file

@ -1,11 +1,17 @@
module Api
module V1
class CollectionSummonsController < ApiController
before_action :restrict_access
before_action :set_collection_summon, only: %i[show update destroy]
# Read actions: look up user from params, check privacy
before_action :set_target_user, only: %i[index show]
before_action :check_collection_access, only: %i[index show]
before_action :set_collection_summon_for_read, only: %i[show]
# Write actions: require auth, use current_user
before_action :restrict_access, only: %i[create update destroy batch]
before_action :set_collection_summon_for_write, only: %i[update destroy]
def index
@collection_summons = current_user.collection_summons
@collection_summons = @target_user.collection_summons
.includes(:summon)
@collection_summons = @collection_summons.by_summon(params[:summon_id]) if params[:summon_id]
@ -16,7 +22,7 @@ module Api
render json: Api::V1::CollectionSummonBlueprint.render(
@collection_summons,
root: :collection_summons,
root: :summons,
meta: pagination_meta(@collection_summons)
)
end
@ -57,9 +63,61 @@ module Api
head :no_content
end
# POST /collection/summons/batch
# Creates multiple collection summons in a single request
# Unlike characters, summons can have duplicates (user can own multiple copies)
def batch
items = batch_summon_params[:collection_summons] || []
created = []
errors = []
ActiveRecord::Base.transaction do
items.each_with_index do |item_params, index|
collection_summon = current_user.collection_summons.build(item_params)
if collection_summon.save
created << collection_summon
else
errors << {
index: index,
summon_id: item_params[:summon_id],
error: collection_summon.errors.full_messages.join(', ')
}
end
end
end
status = errors.any? ? :multi_status : :created
render json: Api::V1::CollectionSummonBlueprint.render(
created,
root: :summons,
meta: { created: created.size, errors: errors }
), status: status
end
private
def set_collection_summon
def set_target_user
@target_user = User.find(params[:user_id])
rescue ActiveRecord::RecordNotFound
render json: { error: "User not found" }, status: :not_found
end
def check_collection_access
return if @target_user.nil? # Already handled by set_target_user
unless @target_user.collection_viewable_by?(current_user)
render json: { error: "You do not have permission to view this collection" }, status: :forbidden
end
end
def set_collection_summon_for_read
@collection_summon = @target_user.collection_summons.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise CollectionErrors::CollectionItemNotFound.new('summon', params[:id])
end
def set_collection_summon_for_write
@collection_summon = current_user.collection_summons.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise CollectionErrors::CollectionItemNotFound.new('summon', params[:id])
@ -70,6 +128,12 @@ module Api
:summon_id, :uncap_level, :transcendence_step
)
end
def batch_summon_params
params.permit(collection_summons: [
:summon_id, :uncap_level, :transcendence_step
])
end
end
end
end

View file

@ -1,11 +1,17 @@
module Api
module V1
class CollectionWeaponsController < ApiController
before_action :restrict_access
before_action :set_collection_weapon, only: [:show, :update, :destroy]
# Read actions: look up user from params, check privacy
before_action :set_target_user, only: %i[index show]
before_action :check_collection_access, only: %i[index show]
before_action :set_collection_weapon_for_read, only: %i[show]
# Write actions: require auth, use current_user
before_action :restrict_access, only: %i[create update destroy batch]
before_action :set_collection_weapon_for_write, only: %i[update destroy]
def index
@collection_weapons = current_user.collection_weapons
@collection_weapons = @target_user.collection_weapons
.includes(:weapon, :awakening,
:weapon_key1, :weapon_key2,
:weapon_key3, :weapon_key4)
@ -18,7 +24,7 @@ module Api
render json: Api::V1::CollectionWeaponBlueprint.render(
@collection_weapons,
root: :collection_weapons,
root: :weapons,
meta: pagination_meta(@collection_weapons)
)
end
@ -59,9 +65,61 @@ module Api
head :no_content
end
# POST /collection/weapons/batch
# Creates multiple collection weapons in a single request
# Unlike characters, weapons can have duplicates (user can own multiple copies)
def batch
items = batch_weapon_params[:collection_weapons] || []
created = []
errors = []
ActiveRecord::Base.transaction do
items.each_with_index do |item_params, index|
collection_weapon = current_user.collection_weapons.build(item_params)
if collection_weapon.save
created << collection_weapon
else
errors << {
index: index,
weapon_id: item_params[:weapon_id],
error: collection_weapon.errors.full_messages.join(', ')
}
end
end
end
status = errors.any? ? :multi_status : :created
render json: Api::V1::CollectionWeaponBlueprint.render(
created,
root: :weapons,
meta: { created: created.size, errors: errors }
), status: status
end
private
def set_collection_weapon
def set_target_user
@target_user = User.find(params[:user_id])
rescue ActiveRecord::RecordNotFound
render json: { error: "User not found" }, status: :not_found
end
def check_collection_access
return if @target_user.nil? # Already handled by set_target_user
unless @target_user.collection_viewable_by?(current_user)
render json: { error: "You do not have permission to view this collection" }, status: :forbidden
end
end
def set_collection_weapon_for_read
@collection_weapon = @target_user.collection_weapons.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise CollectionErrors::CollectionItemNotFound.new('weapon', params[:id])
end
def set_collection_weapon_for_write
@collection_weapon = current_user.collection_weapons.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise CollectionErrors::CollectionItemNotFound.new('weapon', params[:id])
@ -76,6 +134,16 @@ module Api
:element
)
end
def batch_weapon_params
params.permit(collection_weapons: [
:weapon_id, :uncap_level, :transcendence_step,
:weapon_key1_id, :weapon_key2_id, :weapon_key3_id, :weapon_key4_id,
:awakening_id, :awakening_level,
:ax_modifier1, :ax_strength1, :ax_modifier2, :ax_strength2,
:element
])
end
end
end
end
end

View file

@ -137,9 +137,21 @@ Rails.application.routes.draw do
# Writing to collections - requires auth, operates on current_user
namespace :collection do
resources :characters, only: [:create, :update, :destroy], controller: '/api/v1/collection_characters'
resources :weapons, only: [:create, :update, :destroy], controller: '/api/v1/collection_weapons'
resources :summons, only: [:create, :update, :destroy], controller: '/api/v1/collection_summons'
resources :characters, only: [:create, :update, :destroy], controller: '/api/v1/collection_characters' do
collection do
post :batch
end
end
resources :weapons, only: [:create, :update, :destroy], controller: '/api/v1/collection_weapons' do
collection do
post :batch
end
end
resources :summons, only: [:create, :update, :destroy], controller: '/api/v1/collection_summons' do
collection do
post :batch
end
end
resources :job_accessories, controller: '/api/v1/collection_job_accessories',
only: [:index, :show, :create, :destroy]
end