diff --git a/app/blueprints/api/v1/raid_blueprint.rb b/app/blueprints/api/v1/raid_blueprint.rb index af688d1..afd190b 100644 --- a/app/blueprints/api/v1/raid_blueprint.rb +++ b/app/blueprints/api/v1/raid_blueprint.rb @@ -4,6 +4,8 @@ module Api module V1 class RaidBlueprint < ApiBlueprint view :nested do + identifier :id + field :name do |raid| { en: raid.name_en, @@ -18,7 +20,6 @@ module Api view :full do include_view :nested - association :group, blueprint: RaidGroupBlueprint, view: :flat end end end diff --git a/app/controllers/api/v1/raids_controller.rb b/app/controllers/api/v1/raids_controller.rb index c08326d..9619d7c 100644 --- a/app/controllers/api/v1/raids_controller.rb +++ b/app/controllers/api/v1/raids_controller.rb @@ -3,17 +3,99 @@ module Api module V1 class RaidsController < Api::V1::ApiController - def all - render json: RaidBlueprint.render(Raid.includes(:group).all, view: :nested) + before_action :set_raid, only: %i[show update destroy] + before_action :ensure_editor_role, only: %i[create update destroy] + + # GET /raids + def index + raids = Raid.includes(:group) + raids = apply_filters(raids) + raids = raids.ordered + + render json: RaidBlueprint.render(raids, view: :nested) end + # GET /raids/:id def show - raid = Raid.find_by(slug: params[:id]) - render json: RaidBlueprint.render(Raid.find_by(slug: params[:id]), view: :full) if raid + if @raid + render json: RaidBlueprint.render(@raid, view: :full) + else + render json: { error: 'Raid not found' }, status: :not_found + end end + # POST /raids + def create + raid = Raid.new(raid_params) + + if raid.save + render json: RaidBlueprint.render(raid, view: :full), status: :created + else + render_validation_error_response(raid) + end + end + + # PATCH/PUT /raids/:id + def update + if @raid.update(raid_params) + render json: RaidBlueprint.render(@raid, view: :full) + else + render_validation_error_response(@raid) + end + end + + # DELETE /raids/:id + def destroy + if Party.where(raid_id: @raid.id).exists? + render json: ErrorBlueprint.render(nil, error: { + message: 'Cannot delete raid with associated parties', + code: 'has_dependencies' + }), status: :unprocessable_entity + else + @raid.destroy! + head :no_content + end + end + + # GET /raids/groups (legacy endpoint) def groups - render json: RaidGroupBlueprint.render(RaidGroup.includes(raids: :group).all, view: :full) + render json: RaidGroupBlueprint.render(RaidGroup.includes(raids: :group).ordered, view: :full) + end + + # Legacy alias for index + def all + index + end + + private + + def set_raid + @raid = Raid.find_by(slug: params[:id]) || Raid.find_by(id: params[:id]) + end + + def raid_params + params.require(:raid).permit(:name_en, :name_jp, :level, :element, :slug, :group_id) + end + + def apply_filters(scope) + scope = scope.by_element(filter_params[:element]) if filter_params[:element].present? + scope = scope.by_group(filter_params[:group_id]) if filter_params[:group_id].present? + scope = scope.by_difficulty(filter_params[:difficulty]) if filter_params[:difficulty].present? + scope = scope.by_hl(filter_params[:hl]) if filter_params[:hl].present? + scope = scope.by_extra(filter_params[:extra]) if filter_params[:extra].present? + scope = scope.with_guidebooks if filter_params[:guidebooks] == 'true' + scope + end + + def filter_params + params.permit(:element, :group_id, :difficulty, :hl, :extra, :guidebooks) + end + + def ensure_editor_role + return if current_user&.role && current_user.role >= 7 + + Rails.logger.warn "[RAIDS] Unauthorized access attempt by user #{current_user&.id}" + render json: { error: 'Unauthorized - Editor role required' }, status: :unauthorized end end end diff --git a/app/models/raid.rb b/app/models/raid.rb index 05a6934..b90f370 100644 --- a/app/models/raid.rb +++ b/app/models/raid.rb @@ -3,6 +3,22 @@ class Raid < ApplicationRecord belongs_to :group, class_name: 'RaidGroup', foreign_key: :group_id + # Validations + validates :name_en, presence: true + validates :slug, presence: true, uniqueness: true + validates :group_id, presence: true + validates :element, inclusion: { in: 0..6 }, allow_nil: true + validates :level, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true + + # Filter scopes + scope :by_element, ->(element) { where(element: element) if element.present? } + scope :by_group, ->(group_id) { where(group_id: group_id) if group_id.present? } + scope :by_difficulty, ->(difficulty) { joins(:group).where(raid_groups: { difficulty: difficulty }) if difficulty.present? } + scope :by_hl, ->(hl) { joins(:group).where(raid_groups: { hl: hl }) if hl.present? } + scope :by_extra, ->(extra) { joins(:group).where(raid_groups: { extra: extra }) if extra.present? } + scope :with_guidebooks, -> { joins(:group).where(raid_groups: { guidebooks: true }) } + scope :ordered, -> { joins(:group).order('raid_groups.order ASC, raids.level DESC') } + def blueprint RaidBlueprint end diff --git a/db/schema.rb b/db/schema.rb index 27ba140..137d7df 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_19_064037) do +ActiveRecord::Schema[8.0].define(version: 2025_12_20_014422) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "pg_catalog.plpgsql" @@ -712,6 +712,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_19_064037) do t.boolean "extra", default: false, null: false t.boolean "hl", default: true, null: false t.boolean "guidebooks", default: false, null: false + t.boolean "unlimited", default: false, null: false end create_table "raids", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|