unify collection api: single endpoint for all users

- restructure routes: read via /users/:id/collection/*, write via /collection/*
- add user lookup + privacy check to collection_characters_controller
- add race, proficiency, gender scopes to model
- delete old collection_controller
This commit is contained in:
Justin Edmund 2025-12-02 15:31:39 -08:00
parent 301f323ee1
commit 5bc179afa8
4 changed files with 51 additions and 89 deletions

View file

@ -1,16 +1,25 @@
module Api
module V1
class CollectionCharactersController < ApiController
before_action :restrict_access
before_action :set_collection_character, 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_character_for_read, only: %i[show]
# Write actions: require auth, use current_user
before_action :restrict_access, only: %i[create update destroy]
before_action :set_collection_character_for_write, only: %i[update destroy]
def index
@collection_characters = current_user.collection_characters
@collection_characters = @target_user.collection_characters
.includes(:character, :awakening)
# Apply filters
@collection_characters = @collection_characters.by_element(params[:element]) if params[:element]
@collection_characters = @collection_characters.by_rarity(params[:rarity]) if params[:rarity]
@collection_characters = @collection_characters.by_race(params[:race]) if params[:race]
@collection_characters = @collection_characters.by_proficiency(params[:proficiency]) if params[:proficiency]
@collection_characters = @collection_characters.by_gender(params[:gender]) if params[:gender]
# Apply pagination
@collection_characters = @collection_characters.paginate(page: params[:page], per_page: params[:limit] || 50)
@ -64,7 +73,26 @@ module Api
private
def set_collection_character
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_character_for_read
@collection_character = @target_user.collection_characters.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise CollectionErrors::CollectionItemNotFound.new('character', params[:id])
end
def set_collection_character_for_write
@collection_character = current_user.collection_characters.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise CollectionErrors::CollectionItemNotFound.new('character', params[:id])

View file

@ -1,79 +0,0 @@
module Api
module V1
class CollectionController < ApiController
before_action :set_user
before_action :check_collection_access
# GET /api/v1/users/:user_id/collection
# GET /api/v1/users/:user_id/collection?type=weapons
def show
collection = case params[:type]
when 'characters'
{
characters: Api::V1::CollectionCharacterBlueprint.render_as_hash(
@user.collection_characters.includes(:character, :awakening),
view: :full
)
}
when 'weapons'
{
weapons: Api::V1::CollectionWeaponBlueprint.render_as_hash(
@user.collection_weapons.includes(:weapon, :awakening, :weapon_key1,
:weapon_key2, :weapon_key3, :weapon_key4),
view: :full
)
}
when 'summons'
{
summons: Api::V1::CollectionSummonBlueprint.render_as_hash(
@user.collection_summons.includes(:summon),
view: :full
)
}
when 'job_accessories'
{
job_accessories: Api::V1::CollectionJobAccessoryBlueprint.render_as_hash(
@user.collection_job_accessories.includes(job_accessory: :job)
)
}
else
# Return complete collection
{
characters: Api::V1::CollectionCharacterBlueprint.render_as_hash(
@user.collection_characters.includes(:character, :awakening),
view: :full
),
weapons: Api::V1::CollectionWeaponBlueprint.render_as_hash(
@user.collection_weapons.includes(:weapon, :awakening, :weapon_key1,
:weapon_key2, :weapon_key3, :weapon_key4),
view: :full
),
summons: Api::V1::CollectionSummonBlueprint.render_as_hash(
@user.collection_summons.includes(:summon),
view: :full
),
job_accessories: Api::V1::CollectionJobAccessoryBlueprint.render_as_hash(
@user.collection_job_accessories.includes(job_accessory: :job)
)
}
end
render json: collection
end
private
def set_user
@user = User.find(params[:user_id])
rescue ActiveRecord::RecordNotFound
render json: { error: "User not found" }, status: :not_found
end
def check_collection_access
unless @user.collection_viewable_by?(current_user)
render json: { error: "You do not have permission to view this collection" }, status: :forbidden
end
end
end
end
end

View file

@ -16,6 +16,13 @@ class CollectionCharacter < ApplicationRecord
scope :by_element, ->(element) { joins(:character).where(characters: { element: element }) }
scope :by_rarity, ->(rarity) { joins(:character).where(characters: { rarity: rarity }) }
scope :by_race, ->(races) {
joins(:character).where('characters.race1 IN (?) OR characters.race2 IN (?)', races, races)
}
scope :by_proficiency, ->(proficiencies) {
joins(:character).where('characters.proficiency1 IN (?) OR characters.proficiency2 IN (?)', proficiencies, proficiencies)
}
scope :by_gender, ->(genders) { joins(:character).where(characters: { gender: genders }) }
scope :transcended, -> { where('transcendence_step > 0') }
scope :with_awakening, -> { where.not(awakening_id: nil) }

View file

@ -126,14 +126,20 @@ Rails.application.routes.draw do
delete 'favorites', to: 'favorites#destroy'
# User collection viewing (respects privacy settings)
get 'users/:user_id/collection', to: 'collection#show'
# Reading collections - works for any user with privacy check
scope 'users/:user_id' do
namespace :collection do
resources :characters, only: [:index, :show], controller: '/api/v1/collection_characters'
resources :weapons, only: [:index, :show], controller: '/api/v1/collection_weapons'
resources :summons, only: [:index, :show], controller: '/api/v1/collection_summons'
end
end
# Collection management for current user
# Writing to collections - requires auth, operates on current_user
namespace :collection do
resources :characters, controller: '/api/v1/collection_characters'
resources :weapons, controller: '/api/v1/collection_weapons'
resources :summons, controller: '/api/v1/collection_summons'
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 :job_accessories, controller: '/api/v1/collection_job_accessories',
only: [:index, :show, :create, :destroy]
end