hensei-api/docs/implementation/collection-tracking-implementation.md

1043 lines
No EOL
30 KiB
Markdown

# Collection Tracking Implementation Guide
## Prerequisites
- Rails 8.0.1 environment set up
- PostgreSQL database running
- Basic understanding of the existing codebase structure
- Familiarity with Rails migrations, models, and controllers
## Step-by-Step Implementation
### Step 1: Create Database Migrations
#### 1.0 Add collection privacy levels to Users table
```bash
rails generate migration AddCollectionPrivacyToUsers
```
```ruby
# db/migrate/xxx_add_collection_privacy_to_users.rb
class AddCollectionPrivacyToUsers < ActiveRecord::Migration[8.0]
def change
# Privacy levels: 0 = public, 1 = crew_only, 2 = private
add_column :users, :collection_privacy, :integer, default: 0, null: false
add_index :users, :collection_privacy
end
end
```
#### 1.1 Create CollectionCharacters migration
```bash
rails generate migration CreateCollectionCharacters
```
```ruby
# db/migrate/xxx_create_collection_characters.rb
class CreateCollectionCharacters < ActiveRecord::Migration[8.0]
def change
create_table :collection_characters, id: :uuid do |t|
t.references :user, type: :uuid, null: false, foreign_key: true
t.references :character, type: :uuid, null: false, foreign_key: true
t.integer :uncap_level, default: 0, null: false
t.integer :transcendence_step, default: 0, null: false
t.boolean :perpetuity, default: false, null: false
t.references :awakening, type: :uuid, foreign_key: true
t.integer :awakening_level, default: 1
t.jsonb :ring1, default: { modifier: nil, strength: nil }, null: false
t.jsonb :ring2, default: { modifier: nil, strength: nil }, null: false
t.jsonb :ring3, default: { modifier: nil, strength: nil }, null: false
t.jsonb :ring4, default: { modifier: nil, strength: nil }, null: false
t.jsonb :earring, default: { modifier: nil, strength: nil }, null: false
t.timestamps
end
add_index :collection_characters, [:user_id, :character_id], unique: true
add_index :collection_characters, :user_id
add_index :collection_characters, :character_id
end
end
```
#### 1.2 Create CollectionWeapons migration
```bash
rails generate migration CreateCollectionWeapons
```
```ruby
# db/migrate/xxx_create_collection_weapons.rb
class CreateCollectionWeapons < ActiveRecord::Migration[8.0]
def change
create_table :collection_weapons, id: :uuid do |t|
t.references :user, type: :uuid, null: false, foreign_key: true
t.references :weapon, type: :uuid, null: false, foreign_key: true
t.integer :uncap_level, default: 0, null: false
t.integer :transcendence_step, default: 0
t.references :weapon_key1, type: :uuid, foreign_key: { to_table: :weapon_keys }
t.references :weapon_key2, type: :uuid, foreign_key: { to_table: :weapon_keys }
t.references :weapon_key3, type: :uuid, foreign_key: { to_table: :weapon_keys }
t.string :weapon_key4_id
t.references :awakening, type: :uuid, foreign_key: true
t.integer :awakening_level, default: 1, null: false
t.integer :ax_modifier1
t.float :ax_strength1
t.integer :ax_modifier2
t.float :ax_strength2
t.integer :element
t.timestamps
end
add_index :collection_weapons, :user_id
add_index :collection_weapons, :weapon_id
add_index :collection_weapons, [:user_id, :weapon_id]
end
end
```
#### 1.3 Create CollectionSummons migration
```bash
rails generate migration CreateCollectionSummons
```
```ruby
# db/migrate/xxx_create_collection_summons.rb
class CreateCollectionSummons < ActiveRecord::Migration[8.0]
def change
create_table :collection_summons, id: :uuid do |t|
t.references :user, type: :uuid, null: false, foreign_key: true
t.references :summon, type: :uuid, null: false, foreign_key: true
t.integer :uncap_level, default: 0, null: false
t.integer :transcendence_step, default: 0, null: false
t.timestamps
end
add_index :collection_summons, :user_id
add_index :collection_summons, :summon_id
add_index :collection_summons, [:user_id, :summon_id]
end
end
```
#### 1.4 Create CollectionJobAccessories migration
```bash
rails generate migration CreateCollectionJobAccessories
```
```ruby
# db/migrate/xxx_create_collection_job_accessories.rb
class CreateCollectionJobAccessories < ActiveRecord::Migration[8.0]
def change
create_table :collection_job_accessories, id: :uuid do |t|
t.references :user, type: :uuid, null: false, foreign_key: true
t.references :job_accessory, type: :uuid, null: false, foreign_key: true
t.timestamps
end
add_index :collection_job_accessories, [:user_id, :job_accessory_id],
unique: true, name: 'idx_collection_job_acc_user_accessory'
add_index :collection_job_accessories, :user_id
add_index :collection_job_accessories, :job_accessory_id
end
end
```
#### 1.5 Run migrations
```bash
rails db:migrate
```
### Step 2: Create Models
#### 2.1 Create CollectionCharacter model
```ruby
# app/models/collection_character.rb
class CollectionCharacter < ApplicationRecord
belongs_to :user
belongs_to :character
belongs_to :awakening, optional: true
validates :character_id, uniqueness: { scope: :user_id,
message: "already exists in your collection" }
validates :uncap_level, inclusion: { in: 0..5 }
validates :transcendence_step, inclusion: { in: 0..10 }
validates :awakening_level, inclusion: { in: 1..10 }
validate :validate_rings
validate :validate_awakening_compatibility
scope :by_element, ->(element) { joins(:character).where(characters: { element: element }) }
scope :by_rarity, ->(rarity) { joins(:character).where(characters: { rarity: rarity }) }
scope :transcended, -> { where('transcendence_step > 0') }
scope :with_awakening, -> { where.not(awakening_id: nil) }
def blueprint
CollectionCharacterBlueprint
end
private
def validate_rings
[ring1, ring2, ring3, ring4, earring].each_with_index do |ring, index|
next unless ring['modifier'].present? || ring['strength'].present?
if ring['modifier'].blank? || ring['strength'].blank?
errors.add(:base, "Ring #{index + 1} must have both modifier and strength")
end
end
end
def validate_awakening_compatibility
return unless awakening.present?
unless awakening.object_type == 'Character'
errors.add(:awakening, "must be a character awakening")
end
end
end
```
#### 2.2 Create CollectionWeapon model
```ruby
# app/models/collection_weapon.rb
class CollectionWeapon < ApplicationRecord
belongs_to :user
belongs_to :weapon
belongs_to :awakening, optional: true
belongs_to :weapon_key1, class_name: 'WeaponKey', optional: true
belongs_to :weapon_key2, class_name: 'WeaponKey', optional: true
belongs_to :weapon_key3, class_name: 'WeaponKey', optional: true
belongs_to :weapon_key4, class_name: 'WeaponKey', optional: true
validates :uncap_level, inclusion: { in: 0..5 }
validates :transcendence_step, inclusion: { in: 0..10 }, allow_nil: true
validates :awakening_level, inclusion: { in: 1..10 }
validate :validate_weapon_keys
validate :validate_ax_skills
validate :validate_element_change
validate :validate_awakening_compatibility
scope :by_weapon, ->(weapon_id) { where(weapon_id: weapon_id) }
scope :by_series, ->(series) { joins(:weapon).where(weapons: { series: series }) }
scope :with_keys, -> { where.not(weapon_key1_id: nil) }
scope :with_ax, -> { where.not(ax_modifier1: nil) }
def blueprint
CollectionWeaponBlueprint
end
def weapon_keys
[weapon_key1, weapon_key2, weapon_key3, weapon_key4].compact
end
private
def validate_weapon_keys
return unless weapon.present?
weapon_keys.each do |key|
unless weapon.compatible_with_key?(key)
errors.add(:weapon_keys, "#{key.name_en} is not compatible with this weapon")
end
end
# Check for duplicate keys
key_ids = [weapon_key1_id, weapon_key2_id, weapon_key3_id, weapon_key4_id].compact
if key_ids.length != key_ids.uniq.length
errors.add(:weapon_keys, "cannot have duplicate keys")
end
end
def validate_ax_skills
return unless weapon.present? && weapon.ax
if (ax_modifier1.present? && ax_strength1.blank?) ||
(ax_modifier1.blank? && ax_strength1.present?)
errors.add(:ax_modifier1, "AX skill 1 must have both modifier and strength")
end
if (ax_modifier2.present? && ax_strength2.blank?) ||
(ax_modifier2.blank? && ax_strength2.present?)
errors.add(:ax_modifier2, "AX skill 2 must have both modifier and strength")
end
end
def validate_element_change
return unless element.present? && weapon.present?
unless Weapon.element_changeable?(weapon.series)
errors.add(:element, "cannot be changed for this weapon series")
end
end
def validate_awakening_compatibility
return unless awakening.present?
unless awakening.object_type == 'Weapon'
errors.add(:awakening, "must be a weapon awakening")
end
end
end
```
#### 2.3 Create CollectionSummon model
```ruby
# app/models/collection_summon.rb
class CollectionSummon < ApplicationRecord
belongs_to :user
belongs_to :summon
validates :uncap_level, inclusion: { in: 0..5 }
validates :transcendence_step, inclusion: { in: 0..10 }
scope :by_summon, ->(summon_id) { where(summon_id: summon_id) }
scope :by_element, ->(element) { joins(:summon).where(summons: { element: element }) }
scope :transcended, -> { where('transcendence_step > 0') }
def blueprint
CollectionSummonBlueprint
end
end
```
#### 2.4 Create CollectionJobAccessory model
```ruby
# app/models/collection_job_accessory.rb
class CollectionJobAccessory < ApplicationRecord
belongs_to :user
belongs_to :job_accessory
validates :job_accessory_id, uniqueness: { scope: :user_id,
message: "already exists in your collection" }
scope :by_job, ->(job_id) { joins(:job_accessory).where(job_accessories: { job_id: job_id }) }
def blueprint
CollectionJobAccessoryBlueprint
end
end
```
#### 2.5 Update User model
```ruby
# app/models/user.rb - Add these associations and methods
# Associations
has_many :collection_characters, dependent: :destroy
has_many :collection_weapons, dependent: :destroy
has_many :collection_summons, dependent: :destroy
has_many :collection_job_accessories, dependent: :destroy
# Note: The crew association will be added when crews feature is implemented
# belongs_to :crew, optional: true
# Enum for collection privacy levels
enum collection_privacy: {
public: 0,
crew_only: 1,
private: 2
}
# Add collection statistics method
def collection_statistics
{
total_characters: collection_characters.count,
total_weapons: collection_weapons.count,
total_summons: collection_summons.count,
total_job_accessories: collection_job_accessories.count,
unique_weapons: collection_weapons.distinct.count(:weapon_id),
unique_summons: collection_summons.distinct.count(:summon_id)
}
end
# Check if collection is viewable by another user
def collection_viewable_by?(viewer)
return true if self == viewer # Owners can always view their own collection
case collection_privacy
when 'public'
true
when 'crew_only'
# Will be implemented when crew feature is added:
# viewer.present? && crew.present? && viewer.crew_id == crew_id
false # For now, crew_only acts like private until crews are implemented
when 'private'
false
else
false
end
end
# Helper method to check if user is in same crew (placeholder for future)
def in_same_crew_as?(other_user)
# Will be implemented when crew feature is added:
# return false unless other_user.present?
# crew.present? && other_user.crew_id == crew_id
false
end
```
### Step 3: Create Blueprints
#### 3.1 CollectionCharacterBlueprint
```ruby
# app/blueprints/api/v1/collection_character_blueprint.rb
module Api
module V1
class CollectionCharacterBlueprint < ApiBlueprint
identifier :id
fields :uncap_level, :transcendence_step, :perpetuity,
:ring1, :ring2, :ring3, :ring4, :earring,
:created_at, :updated_at
field :awakening, if: ->(_, obj, _) { obj.awakening.present? } do |obj|
{
type: AwakeningBlueprint.render_as_hash(obj.awakening),
level: obj.awakening_level
}
end
association :character, blueprint: CharacterBlueprint, view: :nested
view :full do
association :character, blueprint: CharacterBlueprint, view: :full
end
end
end
end
```
#### 3.2 CollectionWeaponBlueprint
```ruby
# app/blueprints/api/v1/collection_weapon_blueprint.rb
module Api
module V1
class CollectionWeaponBlueprint < ApiBlueprint
identifier :id
fields :uncap_level, :transcendence_step, :element,
:created_at, :updated_at
field :ax, if: ->(_, obj, _) { obj.ax_modifier1.present? } do |obj|
[
{ modifier: obj.ax_modifier1, strength: obj.ax_strength1 },
{ modifier: obj.ax_modifier2, strength: obj.ax_strength2 }
].compact_blank
end
field :awakening, if: ->(_, obj, _) { obj.awakening.present? } do |obj|
{
type: AwakeningBlueprint.render_as_hash(obj.awakening),
level: obj.awakening_level
}
end
association :weapon, blueprint: WeaponBlueprint, view: :nested
association :weapon_keys, blueprint: WeaponKeyBlueprint,
if: ->(_, obj, _) { obj.weapon_keys.any? }
view :full do
association :weapon, blueprint: WeaponBlueprint, view: :full
end
end
end
end
```
#### 3.3 CollectionSummonBlueprint
```ruby
# app/blueprints/api/v1/collection_summon_blueprint.rb
module Api
module V1
class CollectionSummonBlueprint < ApiBlueprint
identifier :id
fields :uncap_level, :transcendence_step,
:created_at, :updated_at
association :summon, blueprint: SummonBlueprint, view: :nested
view :full do
association :summon, blueprint: SummonBlueprint, view: :full
end
end
end
end
```
#### 3.4 CollectionJobAccessoryBlueprint
```ruby
# app/blueprints/api/v1/collection_job_accessory_blueprint.rb
module Api
module V1
class CollectionJobAccessoryBlueprint < ApiBlueprint
identifier :id
fields :created_at, :updated_at
association :job_accessory, blueprint: JobAccessoryBlueprint
end
end
end
```
### Step 4: Create Controllers
#### 4.1 Base Collection Controller (with User Collection Viewing and Privacy)
```ruby
# app/controllers/api/v1/collection_controller.rb
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: CollectionCharacterBlueprint.render_as_hash(
@user.collection_characters.includes(:character, :awakening),
view: :full
)
}
when 'weapons'
{
weapons: CollectionWeaponBlueprint.render_as_hash(
@user.collection_weapons.includes(:weapon, :awakening, :weapon_key1,
:weapon_key2, :weapon_key3, :weapon_key4),
view: :full
)
}
when 'summons'
{
summons: CollectionSummonBlueprint.render_as_hash(
@user.collection_summons.includes(:summon),
view: :full
)
}
when 'job_accessories'
{
job_accessories: CollectionJobAccessoryBlueprint.render_as_hash(
@user.collection_job_accessories.includes(job_accessory: :job)
)
}
else
# Return complete collection
{
characters: CollectionCharacterBlueprint.render_as_hash(
@user.collection_characters.includes(:character, :awakening),
view: :full
),
weapons: CollectionWeaponBlueprint.render_as_hash(
@user.collection_weapons.includes(:weapon, :awakening, :weapon_key1,
:weapon_key2, :weapon_key3, :weapon_key4),
view: :full
),
summons: CollectionSummonBlueprint.render_as_hash(
@user.collection_summons.includes(:summon),
view: :full
),
job_accessories: CollectionJobAccessoryBlueprint.render_as_hash(
@user.collection_job_accessories.includes(job_accessory: :job)
)
}
end
render json: collection
end
def statistics
stats = @user.collection_statistics
render json: stats
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
```
#### 4.2 CollectionCharactersController
```ruby
# app/controllers/api/v1/collection_characters_controller.rb
module Api
module V1
class CollectionCharactersController < ApiController
before_action :authenticate_user!
before_action :set_collection_character, only: [:show, :update, :destroy]
def index
@collection_characters = current_user.collection_characters
.includes(:character, :awakening)
.page(params[:page])
.per(params[:limit] || 50)
render json: CollectionCharacterBlueprint.render(
@collection_characters,
root: :collection_characters,
meta: pagination_meta(@collection_characters)
)
end
def show
render json: CollectionCharacterBlueprint.render(
@collection_character,
view: :full
)
end
def create
@collection_character = current_user.collection_characters.build(collection_character_params)
if @collection_character.save
render json: CollectionCharacterBlueprint.render(
@collection_character,
view: :full
), status: :created
else
render_errors(@collection_character.errors)
end
end
def update
if @collection_character.update(collection_character_params)
render json: CollectionCharacterBlueprint.render(
@collection_character,
view: :full
)
else
render_errors(@collection_character.errors)
end
end
def destroy
@collection_character.destroy
head :no_content
end
private
def set_collection_character
@collection_character = current_user.collection_characters.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Collection character not found" }, status: :not_found
end
def collection_character_params
params.require(:collection_character).permit(
:character_id, :uncap_level, :transcendence_step, :perpetuity,
:awakening_id, :awakening_level,
ring1: [:modifier, :strength],
ring2: [:modifier, :strength],
ring3: [:modifier, :strength],
ring4: [:modifier, :strength],
earring: [:modifier, :strength]
)
end
end
end
end
```
#### 4.3 CollectionWeaponsController
```ruby
# app/controllers/api/v1/collection_weapons_controller.rb
module Api
module V1
class CollectionWeaponsController < ApiController
before_action :authenticate_user!
before_action :set_collection_weapon, only: [:show, :update, :destroy]
def index
@collection_weapons = current_user.collection_weapons
.includes(:weapon, :awakening,
:weapon_key1, :weapon_key2,
:weapon_key3, :weapon_key4)
@collection_weapons = @collection_weapons.by_weapon(params[:weapon_id]) if params[:weapon_id]
@collection_weapons = @collection_weapons.page(params[:page]).per(params[:limit] || 50)
render json: CollectionWeaponBlueprint.render(
@collection_weapons,
root: :collection_weapons,
meta: pagination_meta(@collection_weapons)
)
end
def show
render json: CollectionWeaponBlueprint.render(
@collection_weapon,
view: :full
)
end
def create
@collection_weapon = current_user.collection_weapons.build(collection_weapon_params)
if @collection_weapon.save
render json: CollectionWeaponBlueprint.render(
@collection_weapon,
view: :full
), status: :created
else
render_errors(@collection_weapon.errors)
end
end
def update
if @collection_weapon.update(collection_weapon_params)
render json: CollectionWeaponBlueprint.render(
@collection_weapon,
view: :full
)
else
render_errors(@collection_weapon.errors)
end
end
def destroy
@collection_weapon.destroy
head :no_content
end
private
def set_collection_weapon
@collection_weapon = current_user.collection_weapons.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Collection weapon not found" }, status: :not_found
end
def collection_weapon_params
params.require(:collection_weapon).permit(
: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
```
#### 4.4 CollectionSummonsController
```ruby
# app/controllers/api/v1/collection_summons_controller.rb
module Api
module V1
class CollectionSummonsController < ApiController
before_action :authenticate_user!
before_action :set_collection_summon, only: [:show, :update, :destroy]
def index
@collection_summons = current_user.collection_summons
.includes(:summon)
@collection_summons = @collection_summons.by_summon(params[:summon_id]) if params[:summon_id]
@collection_summons = @collection_summons.page(params[:page]).per(params[:limit] || 50)
render json: CollectionSummonBlueprint.render(
@collection_summons,
root: :collection_summons,
meta: pagination_meta(@collection_summons)
)
end
def show
render json: CollectionSummonBlueprint.render(
@collection_summon,
view: :full
)
end
def create
@collection_summon = current_user.collection_summons.build(collection_summon_params)
if @collection_summon.save
render json: CollectionSummonBlueprint.render(
@collection_summon,
view: :full
), status: :created
else
render_errors(@collection_summon.errors)
end
end
def update
if @collection_summon.update(collection_summon_params)
render json: CollectionSummonBlueprint.render(
@collection_summon,
view: :full
)
else
render_errors(@collection_summon.errors)
end
end
def destroy
@collection_summon.destroy
head :no_content
end
private
def set_collection_summon
@collection_summon = current_user.collection_summons.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Collection summon not found" }, status: :not_found
end
def collection_summon_params
params.require(:collection_summon).permit(
:summon_id, :uncap_level, :transcendence_step
)
end
end
end
end
```
#### 4.5 CollectionJobAccessoriesController
```ruby
# app/controllers/api/v1/collection_job_accessories_controller.rb
module Api
module V1
class CollectionJobAccessoriesController < ApiController
before_action :authenticate_user!
before_action :set_collection_job_accessory, only: [:destroy]
def index
@collection_accessories = current_user.collection_job_accessories
.includes(job_accessory: :job)
if params[:job_id]
@collection_accessories = @collection_accessories.by_job(params[:job_id])
end
@collection_accessories = @collection_accessories.page(params[:page])
.per(params[:limit] || 50)
render json: CollectionJobAccessoryBlueprint.render(
@collection_accessories,
root: :collection_job_accessories,
meta: pagination_meta(@collection_accessories)
)
end
def create
@collection_accessory = current_user.collection_job_accessories
.build(collection_job_accessory_params)
if @collection_accessory.save
render json: CollectionJobAccessoryBlueprint.render(
@collection_accessory
), status: :created
else
render_errors(@collection_accessory.errors)
end
end
def destroy
@collection_job_accessory.destroy
head :no_content
end
private
def set_collection_job_accessory
@collection_job_accessory = current_user.collection_job_accessories.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: "Collection job accessory not found" }, status: :not_found
end
def collection_job_accessory_params
params.require(:collection_job_accessory).permit(:job_accessory_id)
end
end
end
end
```
### Step 5: Update Routes
```ruby
# config/routes.rb - Add these routes within the API scope
# User collection viewing (respects privacy settings)
get 'users/:user_id/collection', to: 'collection#show'
get 'users/:user_id/collection/statistics', to: 'collection#statistics'
# Collection management for 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 :job_accessories, controller: '/api/v1/collection_job_accessories',
only: [:index, :create, :destroy]
end
```
### Step 6: Add Helper Methods to ApiController
```ruby
# app/controllers/api/v1/api_controller.rb - Add these helper methods
protected
def pagination_meta(collection)
{
current_page: collection.current_page,
total_pages: collection.total_pages,
total_count: collection.total_count,
per_page: collection.limit_value
}
end
def render_errors(errors, status = :unprocessable_entity)
render json: { errors: errors.full_messages }, status: status
end
```
## Testing the Implementation
### Manual Testing Steps
1. **Start Rails server**
```bash
rails server
```
2. **View a user's complete collection**
```bash
# Get complete collection
curl -X GET http://localhost:3000/api/v1/users/USER_ID/collection
# Get only weapons
curl -X GET http://localhost:3000/api/v1/users/USER_ID/collection?type=weapons
# Get only characters
curl -X GET http://localhost:3000/api/v1/users/USER_ID/collection?type=characters
# Get only summons
curl -X GET http://localhost:3000/api/v1/users/USER_ID/collection?type=summons
# Get only job accessories
curl -X GET http://localhost:3000/api/v1/users/USER_ID/collection?type=job_accessories
```
3. **Get collection statistics**
```bash
curl -X GET http://localhost:3000/api/v1/users/USER_ID/collection/statistics
```
4. **Create collection items (authenticated)**
```bash
# Create a character
curl -X POST http://localhost:3000/api/v1/collection/characters \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"collection_character": {"character_id": "uuid", "uncap_level": 3}}'
# Create a weapon
curl -X POST http://localhost:3000/api/v1/collection/weapons \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"collection_weapon": {"weapon_id": "uuid", "uncap_level": 4}}'
```
## Deployment Checklist
- [ ] Run all migrations in staging
- [ ] Test all endpoints in staging
- [ ] Verify database indexes are created
- [ ] Test with large datasets
- [ ] Set up error tracking (Sentry/Rollbar)
- [ ] Create backup before deployment
- [ ] Prepare rollback plan
- [ ] Update API documentation
- [ ] Notify frontend team of new endpoints
- [ ] Schedule deployment during low-traffic window
- [ ] Monitor application after deployment
## API Endpoint Summary
### Public Collection Viewing (Respects Privacy Settings)
- `GET /api/v1/users/:user_id/collection` - View complete collection (if not private)
- `GET /api/v1/users/:user_id/collection?type=characters` - View characters only (if not private)
- `GET /api/v1/users/:user_id/collection?type=weapons` - View weapons only (if not private)
- `GET /api/v1/users/:user_id/collection?type=summons` - View summons only (if not private)
- `GET /api/v1/users/:user_id/collection?type=job_accessories` - View job accessories only (if not private)
- `GET /api/v1/users/:user_id/collection/statistics` - View collection statistics (if not private)
### Collection Management (Authentication Required)
- `GET/POST/PUT/DELETE /api/v1/collection/characters` - Manage character collection
- `GET/POST/PUT/DELETE /api/v1/collection/weapons` - Manage weapon collection
- `GET/POST/PUT/DELETE /api/v1/collection/summons` - Manage summon collection
- `GET/POST/DELETE /api/v1/collection/job_accessories` - Manage job accessory collection
### Privacy Settings (Authentication Required)
To update collection privacy settings, use the existing user update endpoint:
- `PUT /api/v1/users/:id` - Update user settings including `collection_privacy` field
Privacy levels:
- `0` or `"public"`: Collection is viewable by everyone
- `1` or `"crew_only"`: Collection is viewable only by crew members (when crew feature is implemented)
- `2` or `"private"`: Collection is viewable only by the owner
Example request:
```json
{
"user": {
"collection_privacy": "crew_only"
}
}
```