diff --git a/app/blueprints/api/v1/job_accessory_blueprint.rb b/app/blueprints/api/v1/job_accessory_blueprint.rb index 24648bc..d5d0dca 100644 --- a/app/blueprints/api/v1/job_accessory_blueprint.rb +++ b/app/blueprints/api/v1/job_accessory_blueprint.rb @@ -14,7 +14,7 @@ module Api name: :job, blueprint: JobBlueprint - fields :granblue_id, :rarity, :release_date + fields :granblue_id, :rarity, :release_date, :accessory_type end end end diff --git a/app/controllers/api/v1/job_accessories_controller.rb b/app/controllers/api/v1/job_accessories_controller.rb index f38a6c6..278bcf4 100644 --- a/app/controllers/api/v1/job_accessories_controller.rb +++ b/app/controllers/api/v1/job_accessories_controller.rb @@ -3,10 +3,84 @@ module Api module V1 class JobAccessoriesController < Api::V1::ApiController - def job - accessories = JobAccessory.where('job_id = ?', params[:id]) + before_action :doorkeeper_authorize!, only: %i[create update destroy] + before_action :ensure_editor_role, only: %i[create update destroy] + + # GET /job_accessories + # Optional filter: ?accessory_type=1 (1=Shield, 2=Manatura) + def index + accessories = JobAccessory.includes(:job).all + accessories = accessories.where(accessory_type: params[:accessory_type]) if params[:accessory_type].present? + accessories = accessories.order(:accessory_type, :granblue_id) render json: JobAccessoryBlueprint.render(accessories) end + + # GET /job_accessories/:id + # Supports lookup by granblue_id or uuid + def show + accessory = find_accessory + return render_not_found_response('job_accessory') unless accessory + + render json: JobAccessoryBlueprint.render(accessory) + end + + # POST /job_accessories + def create + accessory = JobAccessory.new(job_accessory_params) + if accessory.save + render json: JobAccessoryBlueprint.render(accessory), status: :created + else + render_validation_error_response(accessory) + end + end + + # PUT /job_accessories/:id + def update + accessory = find_accessory + return render_not_found_response('job_accessory') unless accessory + + if accessory.update(job_accessory_params) + render json: JobAccessoryBlueprint.render(accessory) + else + render_validation_error_response(accessory) + end + end + + # DELETE /job_accessories/:id + def destroy + accessory = find_accessory + return render_not_found_response('job_accessory') unless accessory + + accessory.destroy + head :no_content + end + + # GET /jobs/:id/accessories + # Legacy endpoint - get accessories for a specific job + def job + job = Job.find_by(granblue_id: params[:id]) || Job.find_by(id: params[:id]) + return render_not_found_response('job') unless job + + accessories = JobAccessory.where(job_id: job.id) + render json: JobAccessoryBlueprint.render(accessories) + end + + private + + def find_accessory + JobAccessory.find_by(granblue_id: params[:id]) || JobAccessory.find_by(id: params[:id]) + end + + def job_accessory_params + params.permit(:name_en, :name_jp, :granblue_id, :rarity, :release_date, :accessory_type, :job_id) + end + + def ensure_editor_role + return if current_user&.role && current_user.role >= 7 + + Rails.logger.warn "[JOB_ACCESSORIES] Unauthorized access attempt by user #{current_user&.id}" + render json: { error: 'Unauthorized - Editor role required' }, status: :unauthorized + end end end end diff --git a/app/controllers/api/v1/jobs_controller.rb b/app/controllers/api/v1/jobs_controller.rb index 0a9d259..62bc962 100644 --- a/app/controllers/api/v1/jobs_controller.rb +++ b/app/controllers/api/v1/jobs_controller.rb @@ -6,7 +6,7 @@ module Api before_action :set_party, only: %w[update_job update_job_skills destroy_job_skill] before_action :authorize_party, only: %w[update_job update_job_skills destroy_job_skill] before_action :set_job, only: %w[update] - before_action :ensure_editor_role, only: %w[update] + before_action :ensure_editor_role, only: %w[create update] def all render json: JobBlueprint.render(Job.all) @@ -16,6 +16,18 @@ module Api render json: JobBlueprint.render(Job.find_by(granblue_id: params[:id])) end + # POST /jobs + # Creates a new job record + def create + @job = Job.new(job_update_params) + + if @job.save + render json: JobBlueprint.render(@job), status: :created + else + render_validation_error_response(@job) + end + end + # PATCH/PUT /jobs/:id # Updates an existing job record def update diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb index b0802bd..e62163f 100644 --- a/app/controllers/api/v1/search_controller.rb +++ b/app/controllers/api/v1/search_controller.rb @@ -284,6 +284,58 @@ module Api meta: pagination_meta(paginated).merge(count: count)) end + def jobs + filters = search_params[:filters] + locale = search_params[:locale] || 'en' + conditions = {} + + if filters + conditions[:row] = filters['row'] unless filters['row'].blank? || filters['row'].empty? + unless filters['proficiency'].blank? || filters['proficiency'].empty? + # Filter by either proficiency1 or proficiency2 matching + proficiency_values = Array(filters['proficiency']).map(&:to_i) + conditions[:proficiency1] = proficiency_values + end + end + + jobs = if search_params[:query].present? && search_params[:query].length >= 2 + if locale == 'ja' + Job.ja_search(search_params[:query]).where(conditions) + else + Job.en_search(search_params[:query]).where(conditions) + end + else + Job.where(conditions) + end + + # Filter by proficiency2 as well (OR condition) + if filters && filters['proficiency'].present? && !filters['proficiency'].empty? + proficiency_values = Array(filters['proficiency']).map(&:to_i) + jobs = jobs.or(Job.where(proficiency2: proficiency_values)) + end + + # Apply feature filters + if filters + jobs = jobs.where(master_level: true) if filters['masterLevel'] == true || filters['masterLevel'] == 'true' + jobs = jobs.where(ultimate_mastery: true) if filters['ultimateMastery'] == true || filters['ultimateMastery'] == 'true' + jobs = jobs.where(accessory: true) if filters['accessory'] == true || filters['accessory'] == 'true' + end + + # Apply sorting if specified, otherwise use default (row, then order) + if search_params[:sort].present? + jobs = apply_job_sort(jobs, search_params[:sort], search_params[:order], locale) + else + jobs = jobs.order(:row, :order) + end + + count = jobs.length + paginated = jobs.paginate(page: search_params[:page], per_page: search_page_size) + + render json: JobBlueprint.render(paginated, + root: :results, + meta: pagination_meta(paginated).merge(count: count)) + end + def guidebooks # Perform the query books = if search_params[:query].present? && search_params[:query].length >= 2 @@ -326,6 +378,23 @@ module Api scope end end + + # Apply sorting for jobs + def apply_job_sort(scope, column, order, locale) + sort_dir = order == 'desc' ? :desc : :asc + + case column + when 'name' + name_col = locale == 'ja' ? :name_ja : :name_en + scope.order(name_col => sort_dir) + when 'row' + scope.order(row: sort_dir, order: :asc) + when 'proficiency' + scope.order(proficiency1: sort_dir) + else + scope.order(:row, :order) + end + end end end end diff --git a/app/models/job.rb b/app/models/job.rb index b1af408..04b9ecd 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -16,6 +16,22 @@ class Job < ApplicationRecord } } + pg_search_scope :en_search, + against: :name_en, + using: { + tsearch: { + prefix: true + } + } + + pg_search_scope :ja_search, + against: :name_jp, + using: { + tsearch: { + prefix: true + } + } + belongs_to :base_job, foreign_key: 'base_job_id', class_name: 'Job', diff --git a/config/routes.rb b/config/routes.rb index fc20e62..9cf0772 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -82,9 +82,11 @@ Rails.application.routes.draw do post 'search/weapons', to: 'search#weapons' post 'search/summons', to: 'search#summons' post 'search/job_skills', to: 'search#job_skills' + post 'search/jobs', to: 'search#jobs' post 'search/guidebooks', to: 'search#guidebooks' get 'jobs', to: 'jobs#all' + post 'jobs', to: 'jobs#create' get 'jobs/skills', to: 'job_skills#all' get 'jobs/:id', to: 'jobs#show' @@ -97,6 +99,9 @@ Rails.application.routes.draw do post 'jobs/:job_id/skills/:id/download_image', to: 'job_skills#download_image' get 'jobs/:id/accessories', to: 'job_accessories#job' + # Job Accessories (database management) + resources :job_accessories, only: %i[index show create update destroy] + get 'characters/:id/related', to: 'characters#related' get 'guidebooks', to: 'guidebooks#all'