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,
|
name: :job,
|
||||||
blueprint: JobBlueprint
|
blueprint: JobBlueprint
|
||||||
|
|
||||||
fields :granblue_id, :rarity, :release_date
|
fields :granblue_id, :rarity, :release_date, :accessory_type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,84 @@
|
||||||
module Api
|
module Api
|
||||||
module V1
|
module V1
|
||||||
class JobAccessoriesController < Api::V1::ApiController
|
class JobAccessoriesController < Api::V1::ApiController
|
||||||
def job
|
before_action :doorkeeper_authorize!, only: %i[create update destroy]
|
||||||
accessories = JobAccessory.where('job_id = ?', params[:id])
|
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)
|
render json: JobAccessoryBlueprint.render(accessories)
|
||||||
end
|
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
|
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 :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 :authorize_party, only: %w[update_job update_job_skills destroy_job_skill]
|
||||||
before_action :set_job, only: %w[update]
|
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
|
def all
|
||||||
render json: JobBlueprint.render(Job.all)
|
render json: JobBlueprint.render(Job.all)
|
||||||
|
|
@ -16,6 +16,18 @@ module Api
|
||||||
render json: JobBlueprint.render(Job.find_by(granblue_id: params[:id]))
|
render json: JobBlueprint.render(Job.find_by(granblue_id: params[:id]))
|
||||||
end
|
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
|
# PATCH/PUT /jobs/:id
|
||||||
# Updates an existing job record
|
# Updates an existing job record
|
||||||
def update
|
def update
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,58 @@ module Api
|
||||||
meta: pagination_meta(paginated).merge(count: count))
|
meta: pagination_meta(paginated).merge(count: count))
|
||||||
end
|
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
|
def guidebooks
|
||||||
# Perform the query
|
# Perform the query
|
||||||
books = if search_params[:query].present? && search_params[:query].length >= 2
|
books = if search_params[:query].present? && search_params[:query].length >= 2
|
||||||
|
|
@ -326,6 +378,23 @@ module Api
|
||||||
scope
|
scope
|
||||||
end
|
end
|
||||||
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
|
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,
|
belongs_to :base_job,
|
||||||
foreign_key: 'base_job_id',
|
foreign_key: 'base_job_id',
|
||||||
class_name: 'Job',
|
class_name: 'Job',
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,11 @@ Rails.application.routes.draw do
|
||||||
post 'search/weapons', to: 'search#weapons'
|
post 'search/weapons', to: 'search#weapons'
|
||||||
post 'search/summons', to: 'search#summons'
|
post 'search/summons', to: 'search#summons'
|
||||||
post 'search/job_skills', to: 'search#job_skills'
|
post 'search/job_skills', to: 'search#job_skills'
|
||||||
|
post 'search/jobs', to: 'search#jobs'
|
||||||
post 'search/guidebooks', to: 'search#guidebooks'
|
post 'search/guidebooks', to: 'search#guidebooks'
|
||||||
|
|
||||||
get 'jobs', to: 'jobs#all'
|
get 'jobs', to: 'jobs#all'
|
||||||
|
post 'jobs', to: 'jobs#create'
|
||||||
|
|
||||||
get 'jobs/skills', to: 'job_skills#all'
|
get 'jobs/skills', to: 'job_skills#all'
|
||||||
get 'jobs/:id', to: 'jobs#show'
|
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'
|
post 'jobs/:job_id/skills/:id/download_image', to: 'job_skills#download_image'
|
||||||
get 'jobs/:id/accessories', to: 'job_accessories#job'
|
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 'characters/:id/related', to: 'characters#related'
|
||||||
|
|
||||||
get 'guidebooks', to: 'guidebooks#all'
|
get 'guidebooks', to: 'guidebooks#all'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue