Job accessories backend support (#206)
* add jobs search endpoint with pg_search - add en_search and ja_search scopes to Job model - add jobs action to SearchController with filtering - supports row, proficiency, master_level, ultimate_mastery, accessory filters * add jobs create endpoint * add job accessories CRUD - add accessory_type to blueprint - add index, show, create, update, destroy actions - editors only for mutations * add routes for jobs search, create, and accessories CRUD
This commit is contained in:
parent
34e3bbd03b
commit
c3d9efa349
6 changed files with 180 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in a new issue