346 lines
11 KiB
Ruby
346 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Api
|
|
module V1
|
|
##
|
|
# ImportController is responsible for importing game data (e.g. deck data)
|
|
# and creating a new Party along with associated records (job, characters, weapons, summons, etc.).
|
|
#
|
|
# The controller expects a JSON payload whose top-level key is "import". If not wrapped,
|
|
# the controller will wrap the raw data automatically.
|
|
#
|
|
# @example Valid payload structure
|
|
# {
|
|
# "import": {
|
|
# "deck": { "name": "My Party", ... },
|
|
# "pc": { "job": { "master": { "name": "Warrior" } }, ... }
|
|
# }
|
|
# }
|
|
class ImportController < Api::V1::ApiController
|
|
ELEMENT_MAPPING = {
|
|
0 => nil,
|
|
1 => 4,
|
|
2 => 2,
|
|
3 => 3,
|
|
4 => 1,
|
|
5 => 6,
|
|
6 => 5
|
|
}.freeze
|
|
|
|
# GBF series_id to CharacterSeries slug mapping
|
|
GBF_SERIES_TO_SLUG = {
|
|
1 => 'summer',
|
|
2 => 'yukata',
|
|
3 => 'valentine',
|
|
4 => 'halloween',
|
|
5 => 'holiday',
|
|
6 => 'zodiac',
|
|
7 => 'grand',
|
|
8 => 'fantasy',
|
|
9 => 'collab',
|
|
10 => 'eternal',
|
|
11 => 'evoker',
|
|
12 => 'saint',
|
|
13 => 'formal'
|
|
}.freeze
|
|
|
|
# GBF series_id to WeaponSeries slug mapping
|
|
GBF_WEAPON_SERIES_TO_SLUG = {
|
|
1 => 'seraphic',
|
|
2 => 'grand',
|
|
3 => 'dark-opus',
|
|
4 => 'revenant',
|
|
5 => 'primal',
|
|
6 => 'beast',
|
|
7 => 'regalia',
|
|
8 => 'omega',
|
|
9 => 'olden-primal',
|
|
10 => 'hollowsky',
|
|
11 => 'xeno',
|
|
12 => 'rose',
|
|
13 => 'ultima',
|
|
14 => 'bahamut',
|
|
15 => 'epic',
|
|
16 => 'cosmos',
|
|
17 => 'superlative',
|
|
18 => 'vintage',
|
|
19 => 'class-champion',
|
|
20 => 'replica',
|
|
21 => 'relic',
|
|
22 => 'rusted',
|
|
23 => 'sephira',
|
|
24 => 'vyrmament',
|
|
25 => 'upgrader',
|
|
26 => 'astral',
|
|
27 => 'draconic',
|
|
28 => 'eternal-splendor',
|
|
29 => 'ancestral',
|
|
30 => 'new-world-foundation',
|
|
31 => 'ennead',
|
|
32 => 'militis',
|
|
33 => 'malice',
|
|
34 => 'menace',
|
|
35 => 'illustrious',
|
|
36 => 'proven',
|
|
37 => 'revans',
|
|
38 => 'world',
|
|
39 => 'exo',
|
|
40 => 'draconic-providence',
|
|
41 => 'celestial',
|
|
42 => 'omega-rebirth',
|
|
43 => 'collab',
|
|
44 => 'destroyer'
|
|
}.freeze
|
|
|
|
# GBF series_id to SummonSeries slug mapping
|
|
GBF_SUMMON_SERIES_TO_SLUG = {
|
|
1 => 'providence',
|
|
2 => 'genesis',
|
|
3 => 'magna',
|
|
4 => 'optimus',
|
|
5 => 'demi-optimus',
|
|
6 => 'archangel',
|
|
7 => 'arcarum',
|
|
8 => 'epic',
|
|
9 => 'carbuncle',
|
|
10 => 'dynamis',
|
|
12 => 'cryptid',
|
|
13 => 'six-dragons',
|
|
14 => 'summer',
|
|
15 => 'yukata',
|
|
16 => 'holiday',
|
|
17 => 'collab',
|
|
18 => 'bellum',
|
|
19 => 'crest',
|
|
20 => 'robur'
|
|
}.freeze
|
|
|
|
before_action :ensure_admin_role, only: %i[weapons summons characters]
|
|
|
|
##
|
|
# Processes an import request.
|
|
#
|
|
# It reads and parses the raw JSON, wraps the data under the "import" key if necessary,
|
|
# transforms the deck data using BaseDeckTransformer, validates that the transformed data
|
|
# contains required fields, and then creates a new Party record (and its associated objects)
|
|
# inside a transaction.
|
|
#
|
|
# @return [void] Renders JSON response with a party shortcode or an error message.
|
|
def create
|
|
Rails.logger.info '[IMPORT] Checking input...'
|
|
|
|
body = parse_request_body
|
|
return unless body
|
|
|
|
raw_params = body['import']
|
|
unless raw_params.is_a?(Hash)
|
|
Rails.logger.error "[IMPORT] 'import' key is missing or not a hash."
|
|
return render json: { error: 'Invalid JSON data' }, status: :unprocessable_content
|
|
end
|
|
|
|
unless raw_params['deck'].is_a?(Hash) &&
|
|
raw_params['deck'].key?('pc') &&
|
|
raw_params['deck'].key?('npc')
|
|
Rails.logger.error '[IMPORT] Deck data incomplete or missing.'
|
|
return render json: { error: 'Invalid deck data' }, status: :unprocessable_content
|
|
end
|
|
|
|
Rails.logger.info '[IMPORT] Starting import...'
|
|
|
|
return if performed? # Rendered an error response already
|
|
|
|
party = Party.create(user: current_user)
|
|
deck_data = raw_params['import']
|
|
process_data(party, deck_data)
|
|
|
|
render json: { shortcode: party.shortcode }, status: :created
|
|
rescue StandardError => e
|
|
render json: { error: e.message }, status: :unprocessable_content
|
|
end
|
|
|
|
def weapons
|
|
Rails.logger.info '[IMPORT] Checking weapon gamedata input...'
|
|
|
|
body = parse_request_body
|
|
return unless body
|
|
|
|
weapon = Weapon.find_by(granblue_id: body['id'])
|
|
unless weapon
|
|
Rails.logger.error "[IMPORT] Weapon not found with ID: #{body['id']}"
|
|
return render json: { error: 'Weapon not found' }, status: :not_found
|
|
end
|
|
|
|
lang = params[:lang]
|
|
unless %w[en jp].include?(lang)
|
|
Rails.logger.error "[IMPORT] Invalid language: #{lang}"
|
|
return render json: { error: 'Invalid language' }, status: :unprocessable_content
|
|
end
|
|
|
|
begin
|
|
weapon.update!(
|
|
"game_raw_#{lang}" => body.to_json
|
|
)
|
|
|
|
# Parse series_id and assign WeaponSeries
|
|
series_id = body['series_id'] || body.dig('master', 'series_id')
|
|
if series_id
|
|
slug = GBF_WEAPON_SERIES_TO_SLUG[series_id.to_i]
|
|
if slug
|
|
series_record = WeaponSeries.find_by(slug: slug)
|
|
if series_record && weapon.weapon_series != series_record
|
|
weapon.update!(weapon_series: series_record)
|
|
Rails.logger.info "[IMPORT] Set series '#{slug}' for weapon #{weapon.granblue_id}"
|
|
end
|
|
end
|
|
end
|
|
|
|
render json: { message: 'Weapon gamedata updated successfully' }, status: :ok
|
|
rescue StandardError => e
|
|
Rails.logger.error "[IMPORT] Failed to update weapon gamedata: #{e.message}"
|
|
render json: { error: e.message }, status: :unprocessable_content
|
|
end
|
|
end
|
|
|
|
def summons
|
|
Rails.logger.info '[IMPORT] Checking summon gamedata input...'
|
|
|
|
body = parse_request_body
|
|
return unless body
|
|
|
|
summon = Summon.find_by(granblue_id: body['id'])
|
|
unless summon
|
|
Rails.logger.error "[IMPORT] Summon not found with ID: #{body['id']}"
|
|
return render json: { error: 'Summon not found' }, status: :not_found
|
|
end
|
|
|
|
lang = params[:lang]
|
|
unless %w[en jp].include?(lang)
|
|
Rails.logger.error "[IMPORT] Invalid language: #{lang}"
|
|
return render json: { error: 'Invalid language' }, status: :unprocessable_content
|
|
end
|
|
|
|
begin
|
|
summon.update!(
|
|
"game_raw_#{lang}" => body.to_json
|
|
)
|
|
|
|
# Parse series_id and assign SummonSeries
|
|
series_id = body['series_id'] || body.dig('master', 'series_id')
|
|
if series_id
|
|
slug = GBF_SUMMON_SERIES_TO_SLUG[series_id.to_i]
|
|
if slug
|
|
series_record = SummonSeries.find_by(slug: slug)
|
|
if series_record && summon.summon_series != series_record
|
|
summon.update!(summon_series: series_record)
|
|
Rails.logger.info "[IMPORT] Set series '#{slug}' for summon #{summon.granblue_id}"
|
|
end
|
|
end
|
|
end
|
|
|
|
render json: { message: 'Summon gamedata updated successfully' }, status: :ok
|
|
rescue StandardError => e
|
|
Rails.logger.error "[IMPORT] Failed to update summon gamedata: #{e.message}"
|
|
render json: { error: e.message }, status: :unprocessable_content
|
|
end
|
|
end
|
|
|
|
##
|
|
# Updates character gamedata from JSON blob.
|
|
#
|
|
# @return [void] Renders JSON response with success or error message.
|
|
def characters
|
|
Rails.logger.info '[IMPORT] Checking character gamedata input...'
|
|
|
|
body = parse_request_body
|
|
return unless body
|
|
|
|
character = Character.find_by(granblue_id: body['id'])
|
|
unless character
|
|
Rails.logger.error "[IMPORT] Character not found with ID: #{body['id']}"
|
|
return render json: { error: 'Character not found' }, status: :not_found
|
|
end
|
|
|
|
lang = params[:lang]
|
|
unless %w[en jp].include?(lang)
|
|
Rails.logger.error "[IMPORT] Invalid language: #{lang}"
|
|
return render json: { error: 'Invalid language' }, status: :unprocessable_content
|
|
end
|
|
|
|
begin
|
|
character.update!(
|
|
"game_raw_#{lang}" => body.to_json
|
|
)
|
|
|
|
# Parse series_id and create CharacterSeriesMembership
|
|
series_id = body['series_id'] || body.dig('master', 'series_id')
|
|
if series_id
|
|
slug = GBF_SERIES_TO_SLUG[series_id.to_i]
|
|
if slug
|
|
series_record = CharacterSeries.find_by(slug: slug)
|
|
if series_record && !character.character_series_records.include?(series_record)
|
|
character.character_series_memberships.create!(character_series: series_record)
|
|
Rails.logger.info "[IMPORT] Added series '#{slug}' to character #{character.granblue_id}"
|
|
end
|
|
end
|
|
end
|
|
|
|
render json: { message: 'Character gamedata updated successfully' }, status: :ok
|
|
rescue StandardError => e
|
|
Rails.logger.error "[IMPORT] Failed to update character gamedata: #{e.message}"
|
|
render json: { error: e.message }, status: :unprocessable_content
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
##
|
|
# Ensures the current user has admin role (role 9).
|
|
# Renders an error if the user is not an admin.
|
|
#
|
|
# @return [void]
|
|
def ensure_admin_role
|
|
return if current_user&.role == 9
|
|
|
|
Rails.logger.error "[IMPORT] Unauthorized access attempt by user #{current_user&.id}"
|
|
render json: { error: 'Unauthorized' }, status: :unauthorized
|
|
end
|
|
|
|
##
|
|
# Reads and parses the raw JSON request body.
|
|
#
|
|
# @return [Hash] Parsed JSON data.
|
|
# @raise [JSON::ParserError] If the JSON is invalid.
|
|
def parse_request_body
|
|
raw_body = request.raw_post
|
|
JSON.parse(raw_body)
|
|
rescue JSON::ParserError => e
|
|
Rails.logger.error "[IMPORT] Invalid JSON: #{e.message}"
|
|
render json: { error: 'Invalid JSON data' }, status: :bad_request and return
|
|
end
|
|
|
|
##
|
|
# Ensures that the provided data is wrapped under an "import" key.
|
|
#
|
|
# @param data [Hash] The parsed JSON data.
|
|
# @return [Hash] Data wrapped under the "import" key.
|
|
def wrap_import_data(data)
|
|
data.key?('import') ? data : { 'import' => data }
|
|
end
|
|
|
|
##
|
|
# Processes the deck data using processors.
|
|
#
|
|
# @param party [Party] The party to insert data into
|
|
# @param data [Hash] The wrapped data.
|
|
# @return [Hash] The transformed deck data.
|
|
def process_data(party, data)
|
|
Rails.logger.info '[IMPORT] Transforming deck data'
|
|
|
|
Processors::JobProcessor.new(party, data).process
|
|
Processors::CharacterProcessor.new(party, data).process
|
|
Processors::SummonProcessor.new(party, data).process
|
|
Processors::WeaponProcessor.new(party, data).process
|
|
end
|
|
end
|
|
end
|
|
end
|