February 2023 Update (#48)

This commit is contained in:
Justin Edmund 2023-02-04 23:46:12 -08:00 committed by GitHub
parent 2c9b15857c
commit 06f8d28874
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 1550 additions and 349 deletions

View file

@ -5,6 +5,7 @@ gem 'bootsnap'
gem 'pg'
gem 'rack-cors'
gem 'rails'
gem 'sprockets-rails'
# A Ruby Web Server Built For Concurrency
gem 'puma'
@ -46,6 +47,9 @@ gem 'will_paginate', '~> 3.3'
# Migrate and update data alongside your database structure.
gem 'data_migrate'
# A ruby gem to allow the copying of ActiveRecord objects and their associated children, configurable with a DSL on the model
gem 'amoeba'
group :doc do
gem 'apipie-rails'
gem 'sdoc'

View file

@ -1,77 +1,79 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
actioncable (7.0.4.1)
actionpack (= 7.0.4.1)
activesupport (= 7.0.4.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
actionmailbox (7.0.4.1)
actionpack (= 7.0.4.1)
activejob (= 7.0.4.1)
activerecord (= 7.0.4.1)
activestorage (= 7.0.4.1)
activesupport (= 7.0.4.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.4)
actionpack (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activesupport (= 7.0.4)
actionmailer (7.0.4.1)
actionpack (= 7.0.4.1)
actionview (= 7.0.4.1)
activejob (= 7.0.4.1)
activesupport (= 7.0.4.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.4)
actionview (= 7.0.4)
activesupport (= 7.0.4)
actionpack (7.0.4.1)
actionview (= 7.0.4.1)
activesupport (= 7.0.4.1)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.4)
actionpack (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
actiontext (7.0.4.1)
actionpack (= 7.0.4.1)
activerecord (= 7.0.4.1)
activestorage (= 7.0.4.1)
activesupport (= 7.0.4.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.4)
activesupport (= 7.0.4)
actionview (7.0.4.1)
activesupport (= 7.0.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.4)
activesupport (= 7.0.4)
activejob (7.0.4.1)
activesupport (= 7.0.4.1)
globalid (>= 0.3.6)
activemodel (7.0.4)
activesupport (= 7.0.4)
activerecord (7.0.4)
activemodel (= 7.0.4)
activesupport (= 7.0.4)
activestorage (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activesupport (= 7.0.4)
activemodel (7.0.4.1)
activesupport (= 7.0.4.1)
activerecord (7.0.4.1)
activemodel (= 7.0.4.1)
activesupport (= 7.0.4.1)
activestorage (7.0.4.1)
actionpack (= 7.0.4.1)
activejob (= 7.0.4.1)
activerecord (= 7.0.4.1)
activesupport (= 7.0.4.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.4)
activesupport (7.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
amazing_print (1.4.0)
amoeba (3.2.0)
activerecord (>= 4.2.0)
api_matchers (0.6.2)
activesupport (>= 3.2.5)
nokogiri (>= 1.5.2)
rspec (>= 3.1)
apipie-rails (0.9.0)
apipie-rails (0.9.1)
actionpack (>= 5.0)
activesupport (>= 5.0)
ast (2.4.2)
@ -122,7 +124,7 @@ GEM
gemoji (4.0.1)
gemoji-parser (1.3.1)
gemoji (>= 2.1.0)
globalid (1.0.0)
globalid (1.0.1)
activesupport (>= 5.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
@ -132,13 +134,13 @@ GEM
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
listen (3.7.1)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.8.0)
mail (2.8.0.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
@ -159,7 +161,7 @@ GEM
net-smtp (0.3.3)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.10)
nokogiri (1.14.0)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
oj (3.13.23)
@ -170,38 +172,38 @@ GEM
pg_search (2.3.6)
activerecord (>= 5.2)
activesupport (>= 5.2)
psych (5.0.1)
psych (5.0.2)
stringio
puma (6.0.2)
nio4r (~> 2.0)
racc (1.6.2)
rack (2.2.5)
rack (2.2.6.2)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-test (2.0.2)
rack (>= 1.3)
rails (7.0.4)
actioncable (= 7.0.4)
actionmailbox (= 7.0.4)
actionmailer (= 7.0.4)
actionpack (= 7.0.4)
actiontext (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activemodel (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
rails (7.0.4.1)
actioncable (= 7.0.4.1)
actionmailbox (= 7.0.4.1)
actionmailer (= 7.0.4.1)
actionpack (= 7.0.4.1)
actiontext (= 7.0.4.1)
actionview (= 7.0.4.1)
activejob (= 7.0.4.1)
activemodel (= 7.0.4.1)
activerecord (= 7.0.4.1)
activestorage (= 7.0.4.1)
activesupport (= 7.0.4.1)
bundler (>= 1.15.0)
railties (= 7.0.4)
railties (= 7.0.4.1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4)
rails-html-sanitizer (1.5.0)
loofah (~> 2.19, >= 2.19.1)
railties (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
railties (7.0.4.1)
actionpack (= 7.0.4.1)
activesupport (= 7.0.4.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -213,7 +215,7 @@ GEM
ffi (~> 1.0)
rdoc (6.5.0)
psych (>= 4.0.0)
regexp_parser (2.6.1)
regexp_parser (2.6.2)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
@ -226,10 +228,10 @@ GEM
rspec-mocks (~> 3.12.0)
rspec-core (3.12.0)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.1)
rspec-expectations (3.12.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.1)
rspec-mocks (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
@ -243,20 +245,20 @@ GEM
rspec-support (3.12.0)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.42.0)
rubocop (1.43.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
ruby-progressbar (1.11.0)
sdoc (2.5.0)
sdoc (2.6.0)
rdoc (>= 5.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
@ -281,16 +283,23 @@ GEM
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
spring (4.1.0)
spring (4.1.1)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (4.2.0)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
stringio (3.0.4)
thor (1.2.1)
tilt (2.0.11)
timeout (0.3.1)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.0)
unicode-display_width (2.4.2)
webrick (1.7.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
@ -305,6 +314,7 @@ PLATFORMS
DEPENDENCIES
amazing_print
amoeba
api_matchers
apipie-rails
awesome_nested_set
@ -338,6 +348,7 @@ DEPENDENCIES
solargraph
spring
spring-commands-rspec
sprockets-rails
will_paginate (~> 3.3)
RUBY VERSION

View file

View file

@ -14,6 +14,10 @@ module Api
field :errors, if: ->(_field_name, _error, options) { options.key?(:exception) } do |_, options|
options[:exception]
end
field :errors, if: ->(_field_name, object, options) { options.key?(:errors) } do |_, options|
options[:errors]
end
end
end
end

View file

@ -11,11 +11,33 @@ module Api
view :nested do
fields :position, :uncap_level, :perpetuity
field :transcendence_step, if: lambda { |_fn, obj, _opt|
obj.character.ulb
} do |c|
c.transcendence_step
end
field :awakening do |c|
{
type: c.awakening_type,
level: c.awakening_level
}
c.awakening
end
field :over_mastery, if: lambda { |_fn, obj, _opt|
!obj.ring1['modifier'].nil? && !obj.ring2['modifier'].nil?
} do |c|
rings = []
rings.push(c.ring1) unless c.ring1['modifier'].nil?
rings.push(c.ring2) unless c.ring2['modifier'].nil?
rings.push(c.ring3) unless c.ring3['modifier'].nil?
rings.push(c.ring4) unless c.ring4['modifier'].nil?
rings
end
field :aetherial_mastery, if: lambda { |_fn, obj, _opt|
!obj.earring['modifier'].nil?
} do |c|
c.earring
end
association :character, name: :object, blueprint: CharacterBlueprint
@ -25,6 +47,10 @@ module Api
include_view :nested
association :party, blueprint: PartyBlueprint, view: :minimal
end
view :destroyed do
fields :position, :created_at, :updated_at
end
end
end
end

View file

@ -5,11 +5,11 @@ module Api
class GridSummonBlueprint < ApiBlueprint
view :uncap do
association :party, blueprint: PartyBlueprint, view: :minimal
fields :position, :uncap_level
fields :position, :uncap_level, :transcendence_step
end
view :nested do
fields :main, :friend, :position, :uncap_level
fields :main, :friend, :position, :uncap_level, :transcendence_step
association :summon, name: :object, blueprint: SummonBlueprint
end
@ -17,6 +17,10 @@ module Api
include_view :nested
association :party, blueprint: PartyBlueprint, view: :minimal
end
view :destroyed do
fields :main, :friend, :position, :created_at, :updated_at
end
end
end
end

View file

@ -43,6 +43,10 @@ module Api
include_view :nested
association :party, blueprint: PartyBlueprint, view: :minimal
end
view :destroyed do
fields :mainhand, :position, :created_at, :updated_at
end
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Api
module V1
class JobAccessoryBlueprint < ApiBlueprint
field :name do |skill|
{
en: skill.name_en,
ja: skill.name_jp
}
end
association :job,
name: :job,
blueprint: JobBlueprint
fields :granblue_id, :rarity, :release_date
end
end
end

View file

@ -17,7 +17,7 @@ module Api
]
end
fields :row, :ml, :order
fields :granblue_id, :row, :ml, :order, :accessory, :accessory_type
end
end
end

View file

@ -36,6 +36,10 @@ module Api
fields :name, :element, :shortcode, :favorited, :extra,
:full_auto, :clear_time, :auto_guard, :created_at, :updated_at
field :remix do |p|
p.is_remix
end
association :raid,
blueprint: RaidBlueprint
@ -64,13 +68,30 @@ module Api
include_view :characters
include_view :job_skills
fields :description, :charge_attack, :button_count, :turn_count, :chain_count
fields :local_id, :description, :charge_attack, :button_count, :turn_count, :chain_count
association :accessory,
blueprint: JobAccessoryBlueprint
association :source_party,
blueprint: PartyBlueprint,
view: :minimal
# TODO: This should probably be paginated
association :remixes,
blueprint: PartyBlueprint,
view: :collection
end
view :collection do
include_view :preview
end
view :created do
include_view :full
fields :edit_key
end
view :destroyed do
fields :name, :description, :created_at, :updated_at
end

View file

@ -15,7 +15,8 @@ module Api
field :uncap do |w|
{
flb: w.flb,
ulb: w.ulb
ulb: w.ulb,
xlb: w.xlb
}
end
@ -24,7 +25,8 @@ module Api
min_hp: w.min_hp,
max_hp: w.max_hp,
max_hp_flb: w.max_hp_flb,
max_hp_ulb: w.max_hp_ulb
max_hp_ulb: w.max_hp_ulb,
max_hp_xlb: w.max_hp_xlb
}
end
@ -33,7 +35,8 @@ module Api
min_atk: w.min_atk,
max_atk: w.max_atk,
max_atk_flb: w.max_atk_flb,
max_atk_ulb: w.max_atk_ulb
max_atk_ulb: w.max_atk_ulb,
max_atk_xlb: w.max_atk_xlb
}
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V1
class UpdateBlueprint < Blueprinter::Base
fields :version, :update_type, :updated_at
end
end
end

View file

@ -36,6 +36,11 @@ module Api
respond_to :json
##### Methods
# Returns the latest update
def version
render json: UpdateBlueprint.render_as_json(AppUpdate.last)
end
# Assign the current user if the Doorkeeper token isn't nil, then
# update the current user's last seen datetime and last IP address
# before returning
@ -45,6 +50,12 @@ module Api
@current_user
end
def edit_key
@edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key']
@edit_key
end
# Set the response content-type
def content_type(content_type)
response.headers['Content-Type'] = content_type

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Api
module V1
class CharactersController < Api::V1::ApiController
before_action :set
def show
render json: CharacterBlueprint.render(@character)
end
private
def set
@character = Character.where(granblue_id: params[:id]).first
end
end
end
end

View file

@ -6,6 +6,8 @@ module Api
attr_reader :party, :incoming_character, :current_characters
before_action :find_party, only: :create
before_action :set, only: %i[update destroy]
before_action :authorize, only: %i[create update destroy]
before_action :find_incoming_character, only: :create
before_action :find_current_characters, only: :create
@ -19,16 +21,15 @@ module Api
conflict_view = render_conflict_view(conflict_characters, incoming_character, character_params[:position])
render json: conflict_view
else
# Replace the grid character in the position if it is already filled
# Destroy the grid character in the position if it is already filled
if GridCharacter.where(party_id: party.id, position: character_params[:position]).exists?
character = GridCharacter.where(party_id: party.id, position: character_params[:position]).limit(1)[0]
character.character_id = incoming_character.id
character.destroy
end
# Otherwise, create a new grid character
else
# Then, create a new grid character
character = GridCharacter.create!(character_params.merge(party_id: party.id,
character_id: incoming_character.id))
end
if character.save!
grid_character_view = render_grid_character_view(character)
@ -37,6 +38,20 @@ module Api
end
end
def update
mastery = {}
%i[ring1 ring2 ring3 ring4 earring awakening].each do |key|
value = character_params.to_h[key]
mastery[key] = value unless value.nil?
end
@character.attributes = character_params.merge(mastery)
return render json: GridCharacterBlueprint.render(@character, view: :full) if @character.save
render_validation_error_response(@character)
end
def resolve
incoming = Character.find(resolve_params[:incoming])
conflicting = resolve_params[:conflicting].map { |id| GridCharacter.find(id) }
@ -70,13 +85,17 @@ module Api
render_unauthorized_response if current_user && (character.party.user != current_user)
character.uncap_level = character_params[:uncap_level]
character.transcendence_step = character_params[:transcendence_step]
return unless character.save!
render json: GridCharacterBlueprint.render(character, view: :nested, root: :grid_character)
end
# TODO: Implement removing characters
def destroy; end
def destroy
render_unauthorized_response if @character.party.user != current_user
return render json: GridCharacterBlueprint.render(@character, view: :destroyed) if @character.destroy
end
private
@ -103,6 +122,10 @@ module Api
end.flatten
end
def set
@character = GridCharacter.find(params[:id])
end
def find_incoming_character
@incoming_character = Character.find(character_params[:character_id])
end
@ -112,10 +135,25 @@ module Api
render_unauthorized_response if current_user && (party.user != current_user)
end
def authorize
# Create
unauthorized_create = @party && (@party.user != current_user || @party.edit_key != edit_key)
unauthorized_update = @character && @character.party && (@character.party.user != current_user || @character.party.edit_key != edit_key)
render_unauthorized_response if unauthorized_create || unauthorized_update
end
# Specify whitelisted properties that can be modified.
def character_params
params.require(:character).permit(:id, :party_id, :character_id, :position, :uncap_level, :conflicting,
:incoming)
params.require(:character).permit(:id, :party_id, :character_id, :position,
:uncap_level, :transcendence_step, :perpetuity,
ring1: %i[modifier strength], ring2: %i[modifier strength],
ring3: %i[modifier strength], ring4: %i[modifier strength],
earring: %i[modifier strength], awakening: %i[type level])
end
def resolve_params
params.require(:resolve).permit(:position, :incoming, conflicting: [])
end
def render_conflict_view(conflict_characters, incoming_character, incoming_position)
@ -129,10 +167,6 @@ module Api
def render_grid_character_view(grid_character)
GridCharacterBlueprint.render(grid_character, view: :nested)
end
def resolve_params
params.require(:resolve).permit(:position, :incoming, conflicting: [])
end
end
end
end

View file

@ -3,12 +3,34 @@
module Api
module V1
class GridSummonsController < Api::V1::ApiController
attr_reader :party, :incoming_summon
before_action :set, only: %w[update destroy]
before_action :find_party, only: :create
before_action :find_incoming_summon, only: :create
before_action :authorize, only: %i[create update destroy]
def create
party = Party.find(summon_params[:party_id])
canonical_summon = Summon.find(summon_params[:summon_id])
# Create the GridSummon with the desired parameters
summon = GridSummon.new
summon.attributes = summon_params.merge(party_id: party.id, summon_id: incoming_summon.id)
render_unauthorized_response if current_user && (party.user != current_user)
if summon.validate
save_summon(summon)
else
handle_conflict(summon)
end
end
def update
@summon.attributes = summon_params
return render json: GridSummonBlueprint.render(@summon, view: :nested, root: :grid_summon) if @summon.save
render_validation_error_response(@character)
end
def save_summon(summon)
if (grid_summon = GridSummon.where(
party_id: party.id,
position: summon_params[:position]
@ -16,8 +38,24 @@ module Api
GridSummon.destroy(grid_summon.id)
end
summon = GridSummon.create!(summon_params.merge(party_id: party.id, summon_id: canonical_summon.id))
render json: GridSummonBlueprint.render(summon, view: :nested), status: :created if summon.save!
return unless summon.save
output = render_grid_summon_view(summon)
render json: output, status: :created
end
def handle_conflict(summon)
conflict_summon = summon.conflicts(party)
ap conflict_summon
return unless conflict_summon.summon.id == incoming_summon.id
old_position = conflict_summon.position
conflict_summon.position = summon_params[:position]
return unless conflict_summon.save
output = render_grid_summon_view(conflict_summon, old_position)
render json: output
end
def update_uncap_level
@ -26,19 +64,52 @@ module Api
render_unauthorized_response if current_user && (summon.party.user != current_user)
summon.uncap_level = summon_params[:uncap_level]
summon.transcendence_step = 0
return unless summon.save!
render json: GridSummonBlueprint.render(summon, view: :nested, root: :grid_summon)
end
# TODO: Implement removing summons
def destroy; end
def destroy
render_unauthorized_response if @summon.party.user != current_user
return render json: GridSummonBlueprint.render(@summon, view: :destroyed) if @summon.destroy
end
private
def find_incoming_summon
@incoming_summon = Summon.find_by(id: summon_params[:summon_id])
end
def find_party
# BUG: I can create grid weapons even when I'm not logged in on an authenticated party
@party = Party.find(summon_params[:party_id])
render_unauthorized_response if current_user && (party.user != current_user)
end
def render_grid_summon_view(grid_summon, conflict_position = nil)
GridSummonBlueprint.render(grid_summon, view: :nested,
root: :grid_summon,
meta: { replaced: conflict_position })
end
def authorize
# Create
unauthorized_create = @party && (@party.user != current_user || @party.edit_key != edit_key)
unauthorized_update = @summon && @summon.party && (@summon.party.user != current_user || @summon.party.edit_key != edit_key)
render_unauthorized_response if unauthorized_create || unauthorized_update
end
def set
@summon = GridSummon.where('id = ?', params[:id]).first
end
# Specify whitelisted properties that can be modified.
def summon_params
params.require(:summon).permit(:id, :party_id, :summon_id, :position, :main, :friend, :uncap_level)
params.require(:summon).permit(:id, :party_id, :summon_id, :position, :main, :friend, :uncap_level,
:transcendence_step)
end
end
end

View file

@ -3,12 +3,12 @@
module Api
module V1
class GridWeaponsController < Api::V1::ApiController
before_action :set, except: %w[create update_uncap_level destroy]
attr_reader :party, :incoming_weapon
before_action :set, except: %w[create update_uncap_level]
before_action :find_party, only: :create
before_action :find_incoming_weapon, only: :create
before_action :authorize, only: %i[create update destroy]
def create
# Create the GridWeapon with the desired parameters
@ -40,7 +40,11 @@ module Api
weapon = GridWeapon.create!(party_id: party.id, weapon_id: incoming.id,
position: resolve_params[:position], uncap_level: uncap_level)
render json: GridWeaponBlueprint.render(weapon, view: :nested), status: :created if weapon.save!
if weapon.save
view = render_grid_weapon_view(weapon, resolve_params[:position])
render json: view, status: :created
end
end
def update
@ -55,8 +59,10 @@ module Api
render json: GridWeaponBlueprint.render(@weapon, view: :nested) if @weapon.update(weapon_params)
end
# TODO: Implement removing characters
def destroy; end
def destroy
render_unauthorized_response if @weapon.party.user != current_user
return render json: GridCharacterBlueprint.render(@weapon, view: :destroyed) if @weapon.destroy
end
def update_uncap_level
weapon = GridWeapon.find(weapon_params[:id])
@ -177,6 +183,15 @@ module Api
@weapon = GridWeapon.where('id = ?', params[:id]).first
end
def authorize
# Create
ap @party
unauthorized_create = @party && (@party.user != current_user || @party.edit_key != edit_key)
unauthorized_update = @weapon && @weapon.party && (@weapon.party.user != current_user || @weapon.party.edit_key != edit_key)
render_unauthorized_response if unauthorized_create || unauthorized_update
end
# Specify whitelisted properties that can be modified.
def weapon_params
params.require(:weapon).permit(

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Api
module V1
class JobAccessoriesController < Api::V1::ApiController
def job
accessories = JobAccessory.where('job_id = ?', params[:id])
render json: JobAccessoryBlueprint.render(accessories)
end
end
end
end

View file

@ -4,6 +4,7 @@ module Api
module V1
class JobsController < Api::V1::ApiController
before_action :set, only: %w[update_job update_job_skills]
before_action :authorize, only: %w[update_job update_job_skills]
def all
render json: JobBlueprint.render(Job.all)
@ -63,8 +64,7 @@ module Api
new_skill_ids = new_skill_keys.map { |key| job_params[key] }
new_skill_ids.map do |id|
skill = JobSkill.find(id)
raise Api::V1::IncompatibleSkillError.new(job: @party.job, skill: skill) if mismatched_skill(@party.job,
skill)
raise Api::V1::IncompatibleSkillError.new(job: @party.job, skill: skill) if mismatched_skill(@party.job, skill)
end
positions = extract_positions_from_keys(new_skill_keys)
@ -154,7 +154,11 @@ module Api
mismatched_base = skill.job.base_job && (job.row != 'ex2' || skill.job.base_job.id != job.base_job.id) && skill.base
if %w[4 5 ex2].include?(job.row)
true if mismatched_emp || mismatched_base || mismatched_main
if skill.base && !mismatched_base
false
else
true if mismatched_emp || mismatched_main
end
elsif mismatched_emp || mismatched_main
true
else
@ -162,6 +166,10 @@ module Api
end
end
def authorize
render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key
end
def set
@party = Party.where('id = ?', params[:id]).first
end

View file

@ -6,14 +6,12 @@ module Api
before_action :set_from_slug,
except: %w[create destroy update index favorites]
before_action :set, only: %w[update destroy]
before_action :authorize, only: %w[update destroy]
def create
party = Party.new(shortcode: random_string)
party = Party.new
party.user = current_user if current_user
if party_params
party.attributes = party_params
end
party.attributes = party_params if party_params
# unless party_params.empty?
# party.attributes = party_params
@ -29,7 +27,7 @@ module Api
# end
if party.save!
return render json: PartyBlueprint.render(party, view: :full, root: :party),
return render json: PartyBlueprint.render(party, view: :created, root: :party),
status: :created
end
@ -43,20 +41,37 @@ module Api
end
def update
render_unauthorized_response if @party.user != current_user
@party.attributes = party_params.except(:skill1_id, :skill2_id, :skill3_id)
# TODO: Validate accessory with job
return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party.save!
render_validation_error_response(@party)
end
def destroy
render_unauthorized_response if @party.user != current_user
return render json: PartyBlueprint.render(@party, view: :destroyed, root: :checkin) if @party.destroy
end
def remix
new_party = @party.amoeba_dup
new_party.attributes = {
user: current_user,
name: remixed_name(@party.name),
source_party: @party
}
new_party.local_id = party_params[:local_id] if !party_params.nil?
if new_party.save
render json: PartyBlueprint.render(new_party, view: :full, root: :party,
meta: { remix: true })
else
render_validation_error_response(new_party)
end
end
def index
conditions = build_conditions(request.params)
@ -108,6 +123,10 @@ module Api
private
def authorize
render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key
end
def build_conditions(params)
unless params['recency'].blank?
start_time = (DateTime.current - params['recency'].to_i.seconds)
@ -122,15 +141,31 @@ module Api
end
end
def random_string
num_chars = 6
o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
(0...num_chars).map { o[rand(o.length)] }.join
def remixed_name(name)
blanked_name = {
en: name.blank? ? 'Untitled team' : name,
ja: name.blank? ? '無名の編成' : name
}
if current_user
case current_user.language
when 'en'
"Remix of #{blanked_name[:en]}"
when 'ja'
"#{blanked_name[:ja]}のリミックス"
end
else
"Remix of #{blanked_name[:en]}"
end
end
def set_from_slug
@party = Party.where('shortcode = ?', params[:id]).first
if @party
@party.favorited = current_user && @party ? @party.is_favorited(current_user) : false
else
render_not_found_response('party')
end
end
def set
@ -138,13 +173,18 @@ module Api
end
def party_params
return unless params[:party].present?
params.require(:party).permit(
:user_id,
:local_id,
:edit_key,
:extra,
:name,
:description,
:raid_id,
:job_id,
:accessory_id,
:skill0_id,
:skill1_id,
:skill2_id,
@ -156,7 +196,7 @@ module Api
:button_count,
:turn_count,
:chain_count
) if params[:party].present?
)
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Api
module V1
class SummonsController < Api::V1::ApiController
before_action :set
def show
render json: SummonBlueprint.render(@summon)
end
private
def set
@summon = Summon.where(granblue_id: params[:id]).first
end
end
end
end

View file

@ -40,7 +40,9 @@ module Api
end
def show
render_not_found_response('user') unless @user
if @user.nil?
render_not_found_response('user')
else
conditions = build_conditions(request.params)
conditions[:user_id] = @user.id
@ -65,6 +67,7 @@ module Api
per_page: COLLECTION_PER_PAGE
})
end
end
def check_email
render json: EmptyBlueprint.render_as_json(nil, email: params[:email], availability: true)

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Api
module V1
class WeaponsController < Api::V1::ApiController
before_action :set
def show
render json: WeaponBlueprint.render(@weapon)
end
private
def set
@weapon = Weapon.where(granblue_id: params[:id]).first
end
end
end
end

5
app/models/app_update.rb Normal file
View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class AppUpdate < ApplicationRecord
end

View file

@ -1,7 +1,78 @@
# frozen_string_literal: true
class GridCharacter < ApplicationRecord
belongs_to :party
belongs_to :party,
counter_cache: :characters_count,
inverse_of: :characters
validates_presence_of :party
validate :awakening_level, on: :update
validate :transcendence, on: :update
validate :validate_over_mastery_values, on: :update
validate :validate_aetherial_mastery_value, on: :update
validate :over_mastery_attack_matches_hp, on: :update
##### Amoeba configuration
amoeba do
set ring1: { modifier: nil, strength: nil }
set ring2: { modifier: nil, strength: nil }
set ring3: { modifier: nil, strength: nil }
set ring4: { modifier: nil, strength: nil }
set earring: { modifier: nil, strength: nil }
set perpetuity: false
end
def awakening_level
return if awakening.nil?
errors.add(:awakening, 'awakening level too low') if awakening['level'] < 1
errors.add(:awakening, 'awakening level too high') if awakening['level'] > 9
end
def transcendence
errors.add(:transcendence_step, 'character has no transcendence') if transcendence_step.positive? && !character.ulb
errors.add(:transcendence_step, 'transcendence step too high') if transcendence_step > 5 && character.ulb
errors.add(:transcendence_step, 'transcendence step too low') if transcendence_step.negative? && character.ulb
end
def over_mastery_attack
errors.add(:ring1, 'invalid value') unless ring1['modifier'].nil? || atk_values.include?(ring1['strength'])
end
def over_mastery_hp
return if ring2['modifier'].nil?
errors.add(:ring2, 'invalid value') unless hp_values.include?(ring2['strength'])
end
def over_mastery_attack_matches_hp
return if ring1[:modifier].nil? && ring2[:modifier].nil?
return if ring2[:strength] == (ring1[:strength] / 2)
errors.add(:over_mastery,
'over mastery attack and hp values do not match')
end
def validate_over_mastery_values
[ring1, ring2, ring3, ring4].each_with_index do |ring, index|
next if ring['modifier'].nil?
modifier = over_mastery_modifiers[ring['modifier']]
check_value({ "ring#{index}": { ring[modifier] => ring['strength'] } },
'over_mastery')
end
end
def validate_aetherial_mastery_value
return if earring['modifier'].nil?
return unless earring['modifier'].positive?
modifier = aetherial_mastery_modifiers[earring['modifier']].to_sym
check_value({ "earring": { modifier => earring['strength'] } },
'aetherial_mastery')
end
def character
Character.find(character_id)
@ -10,4 +81,132 @@ class GridCharacter < ApplicationRecord
def blueprint
GridCharacterBlueprint
end
private
def check_value(property, type)
# Input format
# { ring1: { atk: 300 } }
key = property.keys.first
modifier = property[key].keys.first
return if modifier.nil?
case type
when 'over_mastery'
errors.add(key, 'invalid value') unless over_mastery_values.include?(key['strength'])
when 'aetherial_mastery'
errors.add(key, 'value too low') if aetherial_mastery_values[modifier][:min] > self[key]['strength']
errors.add(key, 'value too high') if aetherial_mastery_values[modifier][:max] < self[key]['strength']
end
end
def over_mastery_modifiers
{
1 => 'atk',
2 => 'hp',
3 => 'debuff_success',
4 => 'skill_cap',
5 => 'ca_dmg',
6 => 'ca_cap',
7 => 'stamina',
8 => 'enmity',
9 => 'crit',
10 => 'da',
11 => 'ta',
12 => 'def',
13 => 'heal',
14 => 'debuff_resist',
15 => 'dodge'
}
end
def over_mastery_values
{
atk: [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000],
hp: [150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500],
debuff_success: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
skill_cap: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
ca_dmg: [10, 12, 14, 16, 18, 20, 22, 24, 27, 30],
ca_cap: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
crit: [10, 12, 14, 16, 18, 20, 22, 24, 27, 30],
enmity: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
stamina: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
def: [6, 7, 8, 9, 10, 12, 14, 16, 18, 20],
heal: [3, 6, 9, 12, 15, 18, 21, 24, 27, 30],
debuff_resist: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
dodge: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
da: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
ta: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
end
def aetherial_mastery_modifiers
{
1 => 'da',
2 => 'ta',
3 => 'ele_atk',
4 => 'ele_resist',
5 => 'stamina',
6 => 'enmity',
7 => 'supplemental',
8 => 'crit',
9 => 'counter_dodge',
10 => 'counter_dmg'
}
end
def aetherial_mastery_values
{
da: {
min: 10,
max: 17
},
ta: {
min: 5,
max: 12
},
ele_atk: {
min: 15,
max: 22
},
ele_resist: {
min: 5,
max: 12
},
stamina: {
min: 5,
max: 12
},
enmity: {
min: 5,
max: 12
},
supplemental: {
min: 5,
max: 12
},
crit: {
min: 18,
max: 35
},
counter_dodge: {
min: 5,
max: 12
},
counter_dmg: {
min: 10,
max: 17
}
}
end
def atk_values
[300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000]
end
def hp_values
[150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500]
end
end

View file

@ -1,7 +1,13 @@
# frozen_string_literal: true
class GridSummon < ApplicationRecord
belongs_to :party
belongs_to :party,
counter_cache: :summons_count,
inverse_of: :summons
validates_presence_of :party
validate :compatible_with_position, on: :create
validate :no_conflicts, on: :create
def summon
Summon.find(summon_id)
@ -10,4 +16,29 @@ class GridSummon < ApplicationRecord
def blueprint
GridSummonBlueprint
end
# Returns conflicting summons if they exist
def conflicts(party)
return unless summon.limit
party.summons.find do |grid_summon|
return unless grid_summon.id
grid_summon if summon.id == grid_summon.summon.id
end
end
private
# Validates whether there is a conflict with the party
def no_conflicts
# Check if the grid summon conflicts with any of the other grid summons in the party
errors.add(:series, 'must not conflict with existing summons') unless conflicts(party).nil?
end
# Validates whether the summon can be added to the desired position
def compatible_with_position
return unless [4, 5].include?(position.to_i) && !summon.subaura
errors.add(:position, 'must have subaura for position')
end
end

View file

@ -2,7 +2,9 @@
class GridWeapon < ApplicationRecord
belongs_to :party,
counter_cache: :weapons_count
counter_cache: :weapons_count,
inverse_of: :weapons
validates_presence_of :party
belongs_to :weapon_key1, class_name: 'WeaponKey', foreign_key: :weapon_key1_id, optional: true
belongs_to :weapon_key2, class_name: 'WeaponKey', foreign_key: :weapon_key2_id, optional: true
@ -11,6 +13,16 @@ class GridWeapon < ApplicationRecord
validate :compatible_with_position, on: :create
validate :no_conflicts, on: :create
before_save :is_mainhand
##### Amoeba configuration
amoeba do
nullify :ax_modifier1
nullify :ax_modifier2
nullify :ax_strength1
nullify :ax_strength2
end
# Helper methods
def blueprint
GridWeaponBlueprint
@ -29,6 +41,8 @@ class GridWeapon < ApplicationRecord
return unless weapon.limit
party.weapons.find do |party_weapon|
return unless party_weapon.id
id_match = weapon.id == party_weapon.id
series_match = weapon.series == party_weapon.weapon.series
both_opus_or_draconic = weapon.opus_or_draconic? && party_weapon.weapon.opus_or_draconic?
@ -59,4 +73,13 @@ class GridWeapon < ApplicationRecord
# Check if the grid weapon conflicts with any of the other grid weapons in the party
errors.add(:series, 'must not conflict with existing weapons') unless conflicts(party).nil?
end
# Checks if the weapon should be a mainhand before saving the model
def is_mainhand
if self.position == -1
self.mainhand = true
else
self.mainhand = false
end
end
end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
class JobAccessory < ApplicationRecord
include PgSearch::Model
belongs_to :job
pg_search_scope :en_search,
against: :name_en,
using: {
tsearch: {
prefix: true,
dictionary: 'simple'
}
}
pg_search_scope :jp_search,
against: :name_jp,
using: {
tsearch: {
prefix: true,
dictionary: 'simple'
}
}
def blueprint
JobAccessoryBlueprint
end
def display_resource(skill)
skill.name_en
end
def ==(other)
self.class == other.class && id == other.id
end
end

View file

@ -2,10 +2,25 @@
class Party < ApplicationRecord
##### ActiveRecord Associations
belongs_to :source_party,
class_name: 'Party',
foreign_key: :source_party_id,
optional: true
has_many :derivative_parties,
class_name: 'Party',
foreign_key: :source_party_id,
inverse_of: :source_party
belongs_to :user, optional: true
belongs_to :raid, optional: true
belongs_to :job, optional: true
belongs_to :accessory,
foreign_key: 'accessory_id',
class_name: 'JobAccessory',
optional: true
belongs_to :skill0,
foreign_key: 'skill0_id',
class_name: 'JobSkill',
@ -29,20 +44,36 @@ class Party < ApplicationRecord
has_many :characters,
foreign_key: 'party_id',
class_name: 'GridCharacter',
dependent: :destroy
dependent: :destroy,
inverse_of: :party
has_many :weapons,
foreign_key: 'party_id',
class_name: 'GridWeapon',
dependent: :destroy
dependent: :destroy,
inverse_of: :party
has_many :summons,
foreign_key: 'party_id',
class_name: 'GridSummon',
dependent: :destroy
dependent: :destroy,
inverse_of: :party
has_many :favorites
before_create :set_shortcode
before_create :set_edit_key
##### Amoeba configuration
amoeba do
nullify :description
nullify :shortcode
include_association :characters
include_association :weapons
include_association :summons
end
##### ActiveRecord Validations
validate :skills_are_unique
@ -52,12 +83,36 @@ class Party < ApplicationRecord
user.favorite_parties.include? self
end
def is_remix
self.source_party != nil
end
def remixes
Party.where(source_party_id: self.id)
end
def blueprint
PartyBlueprint
end
private
def set_shortcode
self.shortcode = random_string
end
def set_edit_key
if !self.user
self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join)
end
end
def random_string
num_chars = 6
o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
(0...num_chars).map { o[rand(o.length)] }.join
end
def skills_are_unique
skills = [skill0, skill1, skill2, skill3].compact

View file

@ -1,9 +1,4 @@
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"

View file

@ -1,9 +1,4 @@
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require_relative '../config/boot'
require 'rake'
require_relative "../config/boot"
require "rake"
Rake.application.run

View file

@ -1,33 +1,33 @@
#!/usr/bin/env ruby
require 'fileutils'
require "fileutils"
# path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
FileUtils.chdir APP_ROOT do
# This script is a way to setup or update your development environment automatically.
# This script is idempotent, so that you can run it at anytime and get an expectable outcome.
# This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
system! 'bin/rails db:prepare'
system! "bin/rails db:prepare"
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server =="
system! 'bin/rails restart'
system! "bin/rails restart"
end

View file

@ -1,4 +1,4 @@
require_relative 'boot'
require_relative "boot"
require "rails"
# Pick the frameworks you want:
@ -7,10 +7,11 @@ require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_mailbox/engine"
require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
@ -22,10 +23,13 @@ module HenseiApi
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.

View file

@ -1,4 +1,4 @@
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
require "bundler/setup" # Set up gems listed in the Gemfile.
require "bootsnap/setup" # Speed up boot time by caching expensive operations.

View file

@ -1,5 +1,5 @@
# Load the Rails application.
require_relative 'application'
require_relative "application"
# Initialize the Rails application.
Rails.application.initialize!

View file

@ -1,14 +1,11 @@
require "active_support/core_ext/integer/time"
Rails.application.configure do
config.after_initialize do
ActiveRecord::Base.logger = Rails.logger.clone
ActiveRecord::Base.logger.level = Logger::INFO
end
# Settings specified here will take precedence over those in config/application.rb.
config.hosts << "grid-api.ngrok.io"
config.hosts << "staging-api.granblue.team"
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# In the development environment your application's code is reloaded any time
# it changes. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
@ -18,12 +15,15 @@ Rails.application.configure do
# Show full error reports.
config.consider_all_requests_local = true
# Enable server timing
config.server_timing = true
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
if Rails.root.join("tmp/caching-dev.txt").exist?
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.to_i}"
"Cache-Control" => "public, max-age=#{2.days.to_i}"
}
else
config.action_controller.perform_caching = false
@ -31,30 +31,30 @@ Rails.application.configure do
config.cache_store = :null_store
end
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Don't care if the mailer can't send.
# config.action_mailer.raise_delivery_errors = false
# config.action_mailer.perform_caching = false
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Raises error for missing translations.
# config.action_view.raise_on_missing_translations = true
# config.i18n.raise_on_missing_translations = true
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
end

View file

@ -1,3 +1,5 @@
require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
@ -13,40 +15,38 @@ Rails.application.configure do
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.allow_forgery_protection = false
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# config.asset_host = "http://assets.example.com"
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# config.action_cable.url = "wss://example.com/cable"
# config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :debug
# Include generic and useful information about system operation, but avoid logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII).
config.log_level = :info
# Prepend all log lines with the following tags.
config.log_tags = [ :request_id ]
config.log_tags = [:request_id]
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
@ -55,25 +55,19 @@ Rails.application.configure do
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "hensei_api_production"
# config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
# require "syslog/logger"
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
@ -83,25 +77,4 @@ Rails.application.configure do
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Inserts middleware to perform automatic connection switching.
# The `database_selector` hash is used to pass options to the DatabaseSelector
# middleware. The `delay` is used to determine how long to wait after a write
# to send a subsequent read to the primary.
#
# The `database_resolver` class is used by the middleware to determine which
# database is appropriate to use based on the time delay.
#
# The `database_resolver_context` class is used by the middleware to set
# timestamps for the last write to the primary. The resolver uses the context
# class timestamps to determine how long to wait before reading from the
# replica.
#
# By default Rails will store a last write timestamp in the session. The
# DatabaseSelector middleware is designed as such you can define your own
# strategy for connection switching and pass that into the middleware through
# these configuration options.
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end

View file

@ -1,3 +1,5 @@
require "active_support/core_ext/integer/time"
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
@ -6,18 +8,18 @@
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
config.cache_classes = false
config.action_view.cache_template_loading = true
# Turn false under Spring and add config.action_view.cache_template_loading = true.
config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Eager loading loads your whole application. When running a single test locally,
# this probably isn't necessary. It's a good idea to do in a continuous integration
# system, or in some way before deploying your code.
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
"Cache-Control" => "public, max-age=#{1.hour.to_i}"
}
# Show full error reports and disable caching.
@ -34,16 +36,18 @@ Rails.application.configure do
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raises error for missing translations.
# config.action_view.raise_on_missing_translations = true
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
end

View file

@ -10,11 +10,11 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do
if Rails.env.production?
origins %w[app.granblue.team hensei-web-production.up.railway.app]
else
origins %w[127.0.0.1:1234 grid.ngrok.io]
origins %w[staging.granblue.team 127.0.0.1:1234]
end
resource '*',
resource "*",
headers: :any,
methods: %i[get post put patch delete options head]
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end

View file

@ -1,4 +1,8 @@
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password]
# Configure parameters to be filtered from the log file. Use this to limit dissemination of
# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
# notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
]

View file

@ -4,13 +4,13 @@
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.plural /^(ox)$/i, "\\1en"
# inflect.singular /^(ox)en/i, "\\1"
# inflect.irregular "person", "people"
# inflect.uncountable %w( fish sheep )
# end
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# inflect.acronym "RESTful"
# end

View file

@ -0,0 +1,135 @@
# Be sure to restart your server when you modify this file.
#
# This file eases your Rails 7.0 framework defaults upgrade.
#
# Uncomment each configuration one by one to switch to the new default.
# Once your application is ready to run with all new defaults, you can remove
# this file and set the `config.load_defaults` to `7.0`.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
# `button_to` view helper will render `<button>` element, regardless of whether
# or not the content is passed as the first argument or as a block.
# Rails.application.config.action_view.button_to_generates_button_tag = true
# `stylesheet_link_tag` view helper will not render the media attribute by default.
# Rails.application.config.action_view.apply_stylesheet_media_default = false
# Change the digest class for the key generators to `OpenSSL::Digest::SHA256`.
# Changing this default means invalidate all encrypted messages generated by
# your application and, all the encrypted cookies. Only change this after you
# rotated all the messages using the key rotator.
#
# See upgrading guide for more information on how to build a rotator.
# https://guides.rubyonrails.org/v7.0/upgrading_ruby_on_rails.html
# Rails.application.config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA256
# Change the digest class for ActiveSupport::Digest.
# Changing this default means that for example Etags change and
# various cache keys leading to cache invalidation.
# Rails.application.config.active_support.hash_digest_class = OpenSSL::Digest::SHA256
# Don't override ActiveSupport::TimeWithZone.name and use the default Ruby
# implementation.
# Rails.application.config.active_support.remove_deprecated_time_with_zone_name = true
# Calls `Rails.application.executor.wrap` around test cases.
# This makes test cases behave closer to an actual request or job.
# Several features that are normally disabled in test, such as Active Record query cache
# and asynchronous queries will then be enabled.
# Rails.application.config.active_support.executor_around_test_case = true
# Define the isolation level of most of Rails internal state.
# If you use a fiber based server or job processor, you should set it to `:fiber`.
# Otherwise the default of `:thread` if preferable.
# Rails.application.config.active_support.isolation_level = :thread
# Set both the `:open_timeout` and `:read_timeout` values for `:smtp` delivery method.
# Rails.application.config.action_mailer.smtp_timeout = 5
# The ActiveStorage video previewer will now use scene change detection to generate
# better preview images (rather than the previous default of using the first frame
# of the video).
# Rails.application.config.active_storage.video_preview_arguments =
# "-vf 'select=eq(n\\,0)+eq(key\\,1)+gt(scene\\,0.015),loop=loop=-1:size=2,trim=start_frame=1' -frames:v 1 -f image2"
# Automatically infer `inverse_of` for associations with a scope.
# Rails.application.config.active_record.automatic_scope_inversing = true
# Raise when running tests if fixtures contained foreign key violations
# Rails.application.config.active_record.verify_foreign_keys_for_fixtures = true
# Disable partial inserts.
# This default means that all columns will be referenced in INSERT queries
# regardless of whether they have a default or not.
# Rails.application.config.active_record.partial_inserts = false
# Protect from open redirect attacks in `redirect_back_or_to` and `redirect_to`.
# Rails.application.config.action_controller.raise_on_open_redirects = true
# Change the variant processor for Active Storage.
# Changing this default means updating all places in your code that
# generate variants to use image processing macros and ruby-vips
# operations. See the upgrading guide for detail on the changes required.
# The `:mini_magick` option is not deprecated; it's fine to keep using it.
# Rails.application.config.active_storage.variant_processor = :vips
# Enable parameter wrapping for JSON.
# Previously this was set in an initializer. It's fine to keep using that initializer if you've customized it.
# To disable parameter wrapping entirely, set this config to `false`.
# Rails.application.config.action_controller.wrap_parameters_by_default = true
# Specifies whether generated namespaced UUIDs follow the RFC 4122 standard for namespace IDs provided as a
# `String` to `Digest::UUID.uuid_v3` or `Digest::UUID.uuid_v5` method calls.
#
# See https://guides.rubyonrails.org/configuring.html#config-active-support-use-rfc4122-namespaced-uuids for
# more information.
# Rails.application.config.active_support.use_rfc4122_namespaced_uuids = true
# Change the default headers to disable browsers' flawed legacy XSS protection.
# Rails.application.config.action_dispatch.default_headers = {
# "X-Frame-Options" => "SAMEORIGIN",
# "X-XSS-Protection" => "0",
# "X-Content-Type-Options" => "nosniff",
# "X-Download-Options" => "noopen",
# "X-Permitted-Cross-Domain-Policies" => "none",
# "Referrer-Policy" => "strict-origin-when-cross-origin"
# }
# ** Please read carefully, this must be configured in config/application.rb **
# Change the format of the cache entry.
# Changing this default means that all new cache entries added to the cache
# will have a different format that is not supported by Rails 6.1 applications.
# Only change this value after your application is fully deployed to Rails 7.0
# and you have no plans to rollback.
# When you're ready to change format, add this to `config/application.rb` (NOT this file):
# config.active_support.cache_format_version = 7.0
# Cookie serializer: 2 options
#
# If you're upgrading and haven't set `cookies_serializer` previously, your cookie serializer
# is `:marshal`. The default for new apps is `:json`.
#
# Rails.application.config.action_dispatch.cookies_serializer = :json
#
#
# To migrate an existing application to the `:json` serializer, use the `:hybrid` option.
#
# Rails transparently deserializes existing (Marshal-serialized) cookies on read and
# re-writes them in the JSON format.
#
# It is fine to use `:hybrid` long term; you should do that until you're confident *all* your cookies
# have been converted to JSON. To keep using `:hybrid` long term, move this config to its own
# initializer or to `config/application.rb`.
#
# Rails.application.config.action_dispatch.cookies_serializer = :hybrid
#
#
# If your cookies can't yet be serialized to JSON, keep using `:marshal` for backward-compatibility.
#
# If you have configured the serializer elsewhere, you can remove this section of the file.
#
# See https://guides.rubyonrails.org/action_controller_overview.html#cookies for more information.

View file

@ -8,13 +8,21 @@ Rails.application.routes.draw do
namespace :v1 do
resources :parties, only: %i[index create update destroy]
resources :users, only: %i[create update show]
resources :grid_weapons, only: [:update]
resources :grid_weapons, only: %i[update destroy]
resources :grid_characters, only: %i[update destroy]
resources :grid_summons, only: %i[update destroy]
resources :weapons, only: :show
resources :characters, only: :show
resources :summons, only: :show
resources :favorites, only: [:create]
get 'version', to: 'api#version'
get 'users/info/:id', to: 'users#info'
get 'parties/favorites', to: 'parties#favorites'
get 'parties/:id', to: 'parties#show'
post 'parties/:id/remix', to: 'parties#remix'
put 'parties/:id/jobs', to: 'jobs#update_job'
put 'parties/:id/job_skills', to: 'jobs#update_job_skills'
@ -31,6 +39,7 @@ Rails.application.routes.draw do
get 'jobs/skills', to: 'job_skills#all'
get 'jobs/:id/skills', to: 'job_skills#job'
get 'jobs/:id/accessories', to: 'job_accessories#job'
get 'raids', to: 'raids#all'
get 'weapon_keys', to: 'weapon_keys#all'

View file

@ -0,0 +1,5 @@
class ChangeAwakeningTypeDefaultValue < ActiveRecord::Migration[7.0]
def change
change_column :grid_characters, :awakening_type, :integer, null: false, default: 1
end
end

View file

@ -0,0 +1,22 @@
class ChangeMasteryColumnsToJsonb < ActiveRecord::Migration[7.0]
def change
# Remove old columns
remove_column :grid_characters, :ring_modifier1, :integer
remove_column :grid_characters, :ring_modifier2, :integer
remove_column :grid_characters, :ring_modifier3, :integer
remove_column :grid_characters, :ring_modifier4, :integer
remove_column :grid_characters, :ring_strength1, :integer
remove_column :grid_characters, :ring_strength2, :integer
remove_column :grid_characters, :ring_strength3, :integer
remove_column :grid_characters, :ring_strength4, :integer
remove_column :grid_characters, :earring_modifier, :integer
remove_column :grid_characters, :earring_strength, :integer
# Add new columns
add_column :grid_characters, :ring1, :jsonb, default: { modifier: nil, strength: nil }
add_column :grid_characters, :ring2, :jsonb, default: { modifier: nil, strength: nil }
add_column :grid_characters, :ring3, :jsonb, default: { modifier: nil, strength: nil }
add_column :grid_characters, :ring4, :jsonb, default: { modifier: nil, strength: nil }
add_column :grid_characters, :earring, :jsonb, default: { modifier: nil, strength: nil }
end
end

View file

@ -0,0 +1,10 @@
class ChangeAwakeningColumnsToJsonb < ActiveRecord::Migration[7.0]
def change
# Remove old columns
remove_column :grid_characters, :awakening_type, :integer
remove_column :grid_characters, :awakening_level, :integer
# Add new column
add_column :grid_characters, :awakening, :jsonb, default: { type: 1, level: 1 }
end
end

View file

@ -0,0 +1,10 @@
class MakeMasteryColumnsNotNullable < ActiveRecord::Migration[7.0]
def change
change_column :grid_characters, :ring1, :jsonb, null: false
change_column :grid_characters, :ring2, :jsonb, null: false
change_column :grid_characters, :ring3, :jsonb, null: false
change_column :grid_characters, :ring4, :jsonb, null: false
change_column :grid_characters, :earring, :jsonb, null: false
change_column :grid_characters, :awakening, :jsonb, null: false
end
end

View file

@ -0,0 +1,7 @@
class AddSourcePartyToParties < ActiveRecord::Migration[7.0]
def change
change_table(:parties) do |t|
t.references :source_party, type: :uuid, foreign_key: { to_table: 'parties' }
end
end
end

View file

@ -0,0 +1,6 @@
class AddMaxHpatkxlbToSummon < ActiveRecord::Migration[7.0]
def change
add_column :summons, :max_atk_xlb, :integer
add_column :summons, :max_hp_xlb, :integer
end
end

View file

@ -0,0 +1,5 @@
class AddGranblueIdToJobs < ActiveRecord::Migration[7.0]
def change
add_column :jobs, :granblue_id, :string
end
end

View file

@ -0,0 +1,14 @@
class CreateJobAccessories < ActiveRecord::Migration[7.0]
def change
create_table :job_accessories, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
t.references :job, type: :uuid
t.string :name_en, null: false, unique: true
t.string :name_jp, null: false, unique: true
t.string :granblue_id, null: false, unique: true
t.integer :rarity
t.date :release_date
end
end
end

View file

@ -0,0 +1,5 @@
class AddAccessoryTypeToJobAccessories < ActiveRecord::Migration[7.0]
def change
add_column :job_accessories, :accessory_type, :integer
end
end

View file

@ -0,0 +1,7 @@
class AddAccessoryIdToParty < ActiveRecord::Migration[7.0]
def change
change_table(:parties) do |t|
t.references :accessory, type: :uuid, foreign_key: { to_table: 'job_accessories' }
end
end
end

View file

@ -0,0 +1,8 @@
class AddUpdatesTable < ActiveRecord::Migration[7.0]
def change
create_table :app_updates, id: false do |t|
t.string :update_type, null: false
t.datetime :updated_at, null: false, unique: true, primary_key: true
end
end
end

View file

@ -0,0 +1,5 @@
class AddVersionToAppUpdates < ActiveRecord::Migration[7.0]
def change
add_column :app_updates, :version, :string
end
end

View file

@ -0,0 +1,6 @@
class AddCharacterAndSummonCountsToParty < ActiveRecord::Migration[7.0]
def change
add_column :parties, :characters_count, :integer
add_column :parties, :summons_count, :integer
end
end

View file

@ -0,0 +1,6 @@
class AddAccessoryAndTypeToJobs < ActiveRecord::Migration[7.0]
def change
add_column :jobs, :accessory, :boolean, default: false unless column_exists?(:jobs, :accessory)
add_column :jobs, :accessory_type, :integer, default: 0 unless column_exists?(:jobs, :accessory_type)
end
end

View file

@ -0,0 +1,5 @@
class AddEditKeyToParties < ActiveRecord::Migration[7.0]
def change
add_column :parties, :edit_key, :string, unique: true, null: true
end
end

View file

@ -0,0 +1,5 @@
class AddLocalIdToParties < ActiveRecord::Migration[7.0]
def change
add_column :parties, :local_id, :uuid, null: true, unique: true
end
end

View file

@ -10,12 +10,18 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
ActiveRecord::Schema[7.0].define(version: 2023_01_31_084343) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "pg_trgm"
enable_extension "pgcrypto"
enable_extension "plpgsql"
enable_extension "timescaledb"
create_table "app_updates", primary_key: "updated_at", id: :datetime, force: :cascade do |t|
t.string "update_type", null: false
t.string "version"
end
create_table "characters", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name_en"
@ -47,9 +53,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
t.index ["name_en"], name: "index_characters_on_name_en", opclass: :gin_trgm_ops, using: :gin
end
create_table "data_migrations", primary_key: "version", id: :string, force: :cascade do |t|
end
create_table "favorites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "user_id"
t.uuid "party_id"
@ -67,19 +70,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "perpetuity", default: false, null: false
t.integer "awakening_type", default: 0, null: false
t.integer "awakening_level", default: 1, null: false
t.integer "transcendence_step", default: 0, null: false
t.integer "ring_modifier1"
t.float "ring_strength1"
t.integer "ring_modifier2"
t.float "ring_strength2"
t.integer "ring_modifier3"
t.float "ring_strength3"
t.integer "ring_modifier4"
t.float "ring_strength4"
t.integer "earring_modifier"
t.float "earring_strength"
t.jsonb "ring1", default: {"modifier"=>nil, "strength"=>nil}, null: false
t.jsonb "ring2", default: {"modifier"=>nil, "strength"=>nil}, null: false
t.jsonb "ring3", default: {"modifier"=>nil, "strength"=>nil}, null: false
t.jsonb "ring4", default: {"modifier"=>nil, "strength"=>nil}, null: false
t.jsonb "earring", default: {"modifier"=>nil, "strength"=>nil}, null: false
t.jsonb "awakening", default: {"type"=>1, "level"=>1}, null: false
t.index ["character_id"], name: "index_grid_characters_on_character_id"
t.index ["party_id"], name: "index_grid_characters_on_party_id"
end
@ -118,6 +115,20 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
t.integer "awakening_level", default: 1, null: false
t.index ["party_id"], name: "index_grid_weapons_on_party_id"
t.index ["weapon_id"], name: "index_grid_weapons_on_weapon_id"
t.index ["weapon_key1_id"], name: "index_grid_weapons_on_weapon_key1_id"
t.index ["weapon_key2_id"], name: "index_grid_weapons_on_weapon_key2_id"
t.index ["weapon_key3_id"], name: "index_grid_weapons_on_weapon_key3_id"
end
create_table "job_accessories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "job_id"
t.string "name_en", null: false
t.string "name_jp", null: false
t.string "granblue_id", null: false
t.integer "rarity"
t.date "release_date"
t.integer "accessory_type"
t.index ["job_id"], name: "index_job_accessories_on_job_id"
end
create_table "job_skills", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
@ -143,6 +154,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
t.boolean "ml", default: false
t.integer "order"
t.uuid "base_job_id"
t.string "granblue_id"
t.boolean "accessory", default: false
t.integer "accessory_type", default: 0
t.index ["base_job_id"], name: "index_jobs_on_base_job_id"
end
@ -208,11 +222,19 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
t.integer "button_count"
t.integer "chain_count"
t.integer "turn_count"
t.uuid "source_party_id"
t.uuid "accessory_id"
t.integer "characters_count"
t.integer "summons_count"
t.string "edit_key"
t.uuid "local_id"
t.index ["accessory_id"], name: "index_parties_on_accessory_id"
t.index ["job_id"], name: "index_parties_on_job_id"
t.index ["skill0_id"], name: "index_parties_on_skill0_id"
t.index ["skill1_id"], name: "index_parties_on_skill1_id"
t.index ["skill2_id"], name: "index_parties_on_skill2_id"
t.index ["skill3_id"], name: "index_parties_on_skill3_id"
t.index ["source_party_id"], name: "index_parties_on_source_party_id"
t.index ["user_id"], name: "index_parties_on_user_id"
end
@ -246,6 +268,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
t.boolean "subaura", default: false, null: false
t.boolean "limit", default: false, null: false
t.boolean "xlb", default: false, null: false
t.integer "max_atk_xlb"
t.integer "max_hp_xlb"
t.index ["name_en"], name: "index_summons_on_name_en", opclass: :gin_trgm_ops, using: :gin
end
@ -314,11 +338,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_03_180458) do
add_foreign_key "jobs", "jobs", column: "base_job_id"
add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
add_foreign_key "parties", "job_accessories", column: "accessory_id"
add_foreign_key "parties", "job_skills", column: "skill0_id"
add_foreign_key "parties", "job_skills", column: "skill1_id"
add_foreign_key "parties", "job_skills", column: "skill2_id"
add_foreign_key "parties", "job_skills", column: "skill3_id"
add_foreign_key "parties", "jobs"
add_foreign_key "parties", "parties", column: "source_party_id"
add_foreign_key "parties", "raids"
add_foreign_key "parties", "users"
end

View file

@ -0,0 +1,41 @@
namespace :granblue do
def _progress_reporter(count:, total:, result:, bar_len: 40, multi: true)
filled_len = (bar_len * count / total).round
status = File.basename(result)
percents = (100.0 * count / total).round(1)
bar = '=' * filled_len + '-' * (bar_len - filled_len)
if !multi
print("[#{bar}] #{percents}% ...#{' ' * 14}#{status}\n")
else
print "\n"
end
end
desc 'Downloads images for the given object type at the given size'
task :download_all_images, %i[object size] => :environment do |_t, args|
require 'open-uri'
filename = "export/#{args[:object]}-#{args[:size]}.txt"
count = `wc -l #{filename}`.split.first.to_i
path = "#{Rails.root}/download/#{args[:object]}-#{args[:size]}"
FileUtils.mkdir_p(path) unless Dir.exist?(path)
puts "Downloading #{count} images from #{args[:object]}-#{args[:size]}.txt..."
if File.exist?(filename)
File.readlines(filename).each_with_index do |line, i|
download = URI.parse(line.strip).open
download_URI = "#{path}/#{download.base_uri.to_s.split('/')[-1]}"
if File.exist?(download_URI)
puts "Skipping #{line}"
else
IO.copy_stream(download, "#{path}/#{download.base_uri.to_s.split('/')[-1]}")
_progress_reporter(count: i, total: count, result: download_URI, bar_len: 40, multi: false)
end
rescue StandardError => e
puts "#{e}: #{line}"
end
end
end
end

View file

@ -12,29 +12,120 @@ namespace :granblue do
end
end
desc 'Exports a list of character URLs for a given size'
task :download_images, %i[object size] => :environment do |_t, args|
require 'open-uri'
def build_weapon_url(id, size)
# Set up URL
base_url = 'http://gbf.game-a.mbga.jp/assets/img/sp/assets/weapon'
extension = '.jpg'
filename = "export/#{args[:object]}-#{args[:size]}.txt"
count = `wc -l #{filename}`.split.first.to_i
directory = 'ls' if size.to_s == 'main'
directory = 'm' if size.to_s == 'grid'
directory = 's' if size.to_s == 'square'
path = "#{Rails.root}/download/#{args[:object]}-#{args[:size]}"
FileUtils.mkdir_p(path) unless Dir.exist?(path)
"#{base_url}/#{directory}/#{id}#{extension}"
end
puts "Downloading #{count} images from #{args[:object]}-#{args[:size]}.txt..."
if File.exist?(filename)
File.readlines(filename).each_with_index do |line, i|
download = URI.parse(line.strip).open
def build_summon_url(id, size)
# Set up URL
base_url = 'http://gbf.game-a.mbga.jp/assets/img/sp/assets/summon'
extension = '.jpg'
directory = 'party_main' if size.to_s == 'main'
directory = 'party_sub' if size.to_s == 'grid'
directory = 's' if size.to_s == 'square'
"#{base_url}/#{directory}/#{id}#{extension}"
end
def build_chara_url(id, size)
# Set up URL
base_url = 'http://gbf.game-a.mbga.jp/assets/img/sp/assets/npc'
extension = '.jpg'
directory = 'f' if size.to_s == 'main'
directory = 'm' if size.to_s == 'grid'
directory = 's' if size.to_s == 'square'
"#{base_url}/#{directory}/#{id}#{extension}"
end
def download_images(url, size, path)
begin
download = URI.parse(url).open
download_URI = "#{path}/#{download.base_uri.to_s.split('/')[-1]}"
if File.exist?(download_URI)
puts "Skipping #{line}"
puts "\tSkipping #{size}\t#{url}"
else
puts "\tDownloading #{size}\t#{url}..."
IO.copy_stream(download, "#{path}/#{download.base_uri.to_s.split('/')[-1]}")
_progress_reporter(count: i, total: count, result: download_URI, bar_len: 40, multi: false)
end
rescue StandardError => e
puts "#{e}: #{line}"
rescue OpenURI::HTTPError
puts "\t404 returned\t#{url}"
end
end
def download_chara_images(id)
sizes = %w[main grid square]
url = {
'main': build_chara_url(id, 'main'),
'grid': build_chara_url(id, 'grid'),
'square': build_chara_url(id, 'square')
}
puts "Character #{id}"
sizes.each do |size|
path = "#{Rails.root}/download/character-#{size}"
download_images(url[size.to_sym], size, path)
end
end
def download_weapon_images(id)
sizes = %w[main grid square]
url = {
'main': build_weapon_url(id, 'main'),
'grid': build_weapon_url(id, 'grid'),
'square': build_weapon_url(id, 'square')
}
puts "Weapon #{id}"
sizes.each do |size|
path = "#{Rails.root}/download/weapon-#{size}"
download_images(url[size.to_sym], size, path)
end
end
def download_summon_images(id)
sizes = %w[main grid square]
url = {
'main': build_summon_url(id, 'main'),
'grid': build_summon_url(id, 'grid'),
'square': build_summon_url(id, 'square')
}
puts "Summon #{id}"
sizes.each do |size|
path = "#{Rails.root}/download/character-#{size}"
download_images(url[size.to_sym], size, path)
end
end
desc 'Downloads images for the given Granblue_IDs'
task :download_images, %i[object] => :environment do |_t, args|
object = args[:object]
list = args.extras
list.each do |id|
if object == 'character'
download_chara_images("#{id}_01")
download_chara_images("#{id}_02")
download_chara_images("#{id}_03")
download_chara_images("#{id}_04")
elsif object == 'weapon'
download_weapon_images(id)
elsif object == 'summon'
download_summon_images(id)
end
end
end

View file

@ -0,0 +1,42 @@
namespace :granblue do
namespace :export do
def build_url(id, type, size)
# Set up URL
base_url = 'https://prd-game-a-granbluefantasy.akamaized.net/assets_en/img/sp/assets'
extension = '.jpg'
directory = 'm' if size.to_s == 'grid'
directory = 's' if size.to_s == 'square'
"#{base_url}/#{type}/#{directory}/#{id}#{extension}"
end
desc 'Exports a list of accessories for a given size'
task :accessory, [:size] => :environment do |_t, args|
# Set up options
size = args[:size]
Dir.glob("#{Rails.root}/app/models/job_accessory.rb").each { |file| require file }
# Set up filepath
dir = "#{Rails.root}/export/"
filename = "#{dir}/accessory-#{size}.txt"
FileUtils.mkdir(dir) unless Dir.exist?(dir)
# Write to file
File.open(filename, 'w') do |f|
JobAccessory.all.each do |w|
if w.accessory_type === 1
f.write("#{build_url(w.granblue_id.to_s, "shield", size)} \n")
elsif w.accessory_type === 2
f.write("#{build_url(w.granblue_id.to_s, "familiar", size)} \n")
end
end
end
# CLI output
count = `wc -l #{filename}`.split.first.to_i
puts "Wrote #{count} job accessory URLs for \"#{size}\" size"
end
end
end

View file

@ -32,7 +32,18 @@ namespace :granblue do
Rake::Task['granblue:export:summon'].invoke('square')
Rake::Task['granblue:export:summon'].reenable
puts 'Exported 9 files'
# Run job tasks
Rake::Task['granblue:export:job'].invoke
Rake::Task['granblue:export:job'].reenable
# Run job accessory tasks
Rake::Task['granblue:export:accessory'].invoke('grid')
Rake::Task['granblue:export:accessory'].reenable
Rake::Task['granblue:export:accessory'].invoke('square')
Rake::Task['granblue:export:accessory'].reenable
puts 'Exported 12 files'
end
end
end

33
lib/tasks/export_job.rake Normal file
View file

@ -0,0 +1,33 @@
namespace :granblue do
namespace :export do
def build_job_icon_url(id)
# Set up URL
base_url = 'https://prd-game-a-granbluefantasy.akamaized.net/assets_en/img/sp/ui/icon/job'
extension = '.png'
"#{base_url}/#{id}#{extension}"
end
desc 'Exports a list of weapon URLs for a given size'
task :job do |_t, _args|
# Include weapon model
Dir.glob("#{Rails.root}/app/models/job.rb").each { |file| require file }
# Set up filepath
dir = "#{Rails.root}/export/"
filename = "#{dir}/job-icon.txt"
FileUtils.mkdir(dir) unless Dir.exist?(dir)
# Write to file
File.open(filename, 'w') do |f|
Job.all.each do |w|
f.write("#{build_job_icon_url(w.granblue_id.to_s)} \n")
end
end
# CLI output
count = `wc -l #{filename}`.split.first.to_i
puts "Wrote #{count} job URLs for icon size"
end
end
end

View file

@ -36,6 +36,11 @@ namespace :granblue do
f.write("#{build_summon_url("#{s.granblue_id}_02",
size)} \n")
end
if s.xlb
f.write("#{build_summon_url("#{s.granblue_id}_03",
size)} \n")
end
end
end