Implement roles and visibility (#128)
* Add migrations to add user roles and party visibility. * Update schema.rb * Add admin check in User model * Implement rudimentary visibility of teams * Adds checks to Party model * Hides parties from collection views depending on visibility * Disallows viewing private parties if you're not the owner * Add a party's visibility to blueprint * Add admin mode The API Controller checks if the user is logged in and whether they are an admin, and checks for the X-Admin-Mode header * Implement admin mode overrides * Add admin_mode to authorize * Note to self: Implement user editing by admins * Fix syntax error with equality in SQL * Fix syntax error with method name * Fix bug in who can see restricted parties * Add privacy control to user profiles
This commit is contained in:
parent
c79d2717cc
commit
8381c668bc
9 changed files with 600 additions and 525 deletions
|
|
@ -35,7 +35,7 @@ module Api
|
||||||
view :minimal do
|
view :minimal do
|
||||||
fields :name, :element, :shortcode, :favorited, :remix,
|
fields :name, :element, :shortcode, :favorited, :remix,
|
||||||
:extra, :full_auto, :clear_time, :auto_guard, :auto_summon,
|
:extra, :full_auto, :clear_time, :auto_guard, :auto_summon,
|
||||||
:created_at, :updated_at
|
:visibility, :created_at, :updated_at
|
||||||
|
|
||||||
field :guidebooks do |p|
|
field :guidebooks do |p|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,17 @@ module Api
|
||||||
@current_user
|
@current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def admin_mode
|
||||||
|
if current_user && current_user.admin? && request.headers['X-Admin-Mode']
|
||||||
|
@admin_mode ||= request.headers['X-Admin-Mode'] == 'true'
|
||||||
|
end
|
||||||
|
|
||||||
|
@admin_mode
|
||||||
|
end
|
||||||
|
|
||||||
def edit_key
|
def edit_key
|
||||||
@edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key']
|
@edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key']
|
||||||
|
|
||||||
@edit_key
|
@edit_key
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -96,9 +104,9 @@ module Api
|
||||||
|
|
||||||
def render_not_found_response(object)
|
def render_not_found_response(object)
|
||||||
render json: ErrorBlueprint.render(nil, error: {
|
render json: ErrorBlueprint.render(nil, error: {
|
||||||
message: "#{object.capitalize} could not be found",
|
message: "#{object.capitalize} could not be found",
|
||||||
code: 'not_found'
|
code: 'not_found'
|
||||||
}), status: :not_found
|
}), status: :not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_unauthorized_response
|
def render_unauthorized_response
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
# If a party is private, check that the user is the owner or an admin
|
||||||
|
if (@party.private? && !current_user) || (@party.private? && not_owner && !admin_mode)
|
||||||
|
return render_unauthorized_response
|
||||||
|
end
|
||||||
|
|
||||||
return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party
|
return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party
|
||||||
|
|
||||||
render_not_found_response('project')
|
render_not_found_response('project')
|
||||||
|
|
@ -90,7 +95,7 @@ module Api
|
||||||
conditions = build_filters
|
conditions = build_filters
|
||||||
conditions[:favorites] = { user_id: current_user.id }
|
conditions[:favorites] = { user_id: current_user.id }
|
||||||
|
|
||||||
query = build_query(conditions, true)
|
query = build_query(conditions, favorites: true)
|
||||||
query = apply_includes(query, params[:includes]) if params[:includes].present?
|
query = apply_includes(query, params[:includes]) if params[:includes].present?
|
||||||
query = apply_excludes(query, params[:excludes]) if params[:excludes].present?
|
query = apply_excludes(query, params[:excludes]) if params[:excludes].present?
|
||||||
|
|
||||||
|
|
@ -104,7 +109,11 @@ module Api
|
||||||
private
|
private
|
||||||
|
|
||||||
def authorize
|
def authorize
|
||||||
render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key
|
render_unauthorized_response if (not_owner && !admin_mode) || (@party.edit_key != edit_key && !admin_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
def not_owner
|
||||||
|
current_user && @party.user != current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_filters
|
def build_filters
|
||||||
|
|
@ -152,11 +161,12 @@ module Api
|
||||||
value.to_i unless value.blank? || value.to_i == -1
|
value.to_i unless value.blank? || value.to_i == -1
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_query(conditions, favorites = false)
|
def build_query(conditions, favorites: false)
|
||||||
query = Party.distinct
|
query = Party.distinct
|
||||||
.joins(weapons: [:object], summons: [:object], characters: [:object])
|
.joins(weapons: [:object], summons: [:object], characters: [:object])
|
||||||
.group('parties.id')
|
.group('parties.id')
|
||||||
.where(conditions)
|
.where(conditions)
|
||||||
|
.where(privacy(favorites: favorites))
|
||||||
.where(name_quality)
|
.where(name_quality)
|
||||||
.where(user_quality)
|
.where(user_quality)
|
||||||
.where(original)
|
.where(original)
|
||||||
|
|
@ -242,6 +252,16 @@ module Api
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def privacy(favorites: false)
|
||||||
|
return if admin_mode
|
||||||
|
|
||||||
|
if favorites
|
||||||
|
'visibility < 3'
|
||||||
|
else
|
||||||
|
'visibility = 1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def user_quality
|
def user_quality
|
||||||
'user_id IS NOT NULL' unless request.params[:user_quality].blank? || request.params[:user_quality] == 'false'
|
'user_id IS NOT NULL' unless request.params[:user_quality].blank? || request.params[:user_quality] == 'false'
|
||||||
end
|
end
|
||||||
|
|
@ -329,6 +349,7 @@ module Api
|
||||||
:description,
|
:description,
|
||||||
:raid_id,
|
:raid_id,
|
||||||
:job_id,
|
:job_id,
|
||||||
|
:visibility,
|
||||||
:accessory_id,
|
:accessory_id,
|
||||||
:skill0_id,
|
:skill0_id,
|
||||||
:skill1_id,
|
:skill1_id,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ module Api
|
||||||
render_validation_error_response(@user)
|
render_validation_error_response(@user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Allow admins to update other users
|
||||||
|
|
||||||
def update
|
def update
|
||||||
render json: UserBlueprint.render(@user, view: :minimal) if @user.update(user_params)
|
render json: UserBlueprint.render(@user, view: :minimal) if @user.update(user_params)
|
||||||
end
|
end
|
||||||
|
|
@ -57,13 +59,14 @@ module Api
|
||||||
conditions[:user_id] = @user.id
|
conditions[:user_id] = @user.id
|
||||||
|
|
||||||
parties = Party
|
parties = Party
|
||||||
.where(conditions)
|
.where(conditions)
|
||||||
.where(name_quality)
|
.where(name_quality)
|
||||||
.where(user_quality)
|
.where(user_quality)
|
||||||
.where(original)
|
.where(original)
|
||||||
.order(created_at: :desc)
|
.where(privacy)
|
||||||
.paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE)
|
.order(created_at: :desc)
|
||||||
.each do |party|
|
.paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE)
|
||||||
|
.each do |party|
|
||||||
party.favorited = current_user ? party.is_favorited(current_user) : false
|
party.favorited = current_user ? party.is_favorited(current_user) : false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -98,7 +101,7 @@ module Api
|
||||||
|
|
||||||
unless params['recency'].blank?
|
unless params['recency'].blank?
|
||||||
start_time = (DateTime.current - params['recency'].to_i.seconds)
|
start_time = (DateTime.current - params['recency'].to_i.seconds)
|
||||||
.to_datetime.beginning_of_day
|
.to_datetime.beginning_of_day
|
||||||
end
|
end
|
||||||
|
|
||||||
min_characters_count = params['characters_count'].blank? ? DEFAULT_MIN_CHARACTERS : params['characters_count'].to_i
|
min_characters_count = params['characters_count'].blank? ? DEFAULT_MIN_CHARACTERS : params['characters_count'].to_i
|
||||||
|
|
@ -113,9 +116,18 @@ module Api
|
||||||
hash[:created_at] = start_time..DateTime.current unless params['recency'].blank?
|
hash[:created_at] = start_time..DateTime.current unless params['recency'].blank?
|
||||||
|
|
||||||
# Advanced filters: Team parameters
|
# Advanced filters: Team parameters
|
||||||
hash[:full_auto] = params['full_auto'].to_i unless params['full_auto'].blank? || params['full_auto'].to_i == -1
|
unless params['full_auto'].blank? || params['full_auto'].to_i == -1
|
||||||
hash[:auto_guard] = params['auto_guard'].to_i unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1
|
hash[:full_auto] =
|
||||||
hash[:charge_attack] = params['charge_attack'].to_i unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1
|
params['full_auto'].to_i
|
||||||
|
end
|
||||||
|
unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1
|
||||||
|
hash[:auto_guard] =
|
||||||
|
params['auto_guard'].to_i
|
||||||
|
end
|
||||||
|
unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1
|
||||||
|
hash[:charge_attack] =
|
||||||
|
params['charge_attack'].to_i
|
||||||
|
end
|
||||||
|
|
||||||
# Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1
|
# Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1
|
||||||
# How do we do the same for button count since that can reasonably be 1?
|
# How do we do the same for button count since that can reasonably be 1?
|
||||||
|
|
@ -131,38 +143,44 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def original
|
def original
|
||||||
unless params.key?('original') || params['original'].blank? || params['original'] == '0'
|
return if params.key?('original') || params['original'].blank? || params['original'] == '0'
|
||||||
"source_party_id IS NULL"
|
|
||||||
end
|
'source_party_id IS NULL'
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_quality
|
def user_quality
|
||||||
unless params.key?('user_quality') || params[:user_quality].nil? || params[:user_quality] == "0"
|
return if params.key?('user_quality') || params[:user_quality].nil? || params[:user_quality] == '0'
|
||||||
"user_id IS NOT NULL"
|
|
||||||
end
|
'user_id IS NOT NULL'
|
||||||
end
|
end
|
||||||
|
|
||||||
def name_quality
|
def name_quality
|
||||||
low_quality = [
|
low_quality = [
|
||||||
"Untitled",
|
'Untitled',
|
||||||
"Remix of Untitled",
|
'Remix of Untitled',
|
||||||
"Remix of Remix of Untitled",
|
'Remix of Remix of Untitled',
|
||||||
"Remix of Remix of Remix of Untitled",
|
'Remix of Remix of Remix of Untitled',
|
||||||
"Remix of Remix of Remix of Remix of Untitled",
|
'Remix of Remix of Remix of Remix of Untitled',
|
||||||
"Remix of Remix of Remix of Remix of Remix of Untitled",
|
'Remix of Remix of Remix of Remix of Remix of Untitled',
|
||||||
"無題",
|
'無題',
|
||||||
"無題のリミックス",
|
'無題のリミックス',
|
||||||
"無題のリミックスのリミックス",
|
'無題のリミックスのリミックス',
|
||||||
"無題のリミックスのリミックスのリミックス",
|
'無題のリミックスのリミックスのリミックス',
|
||||||
"無題のリミックスのリミックスのリミックスのリミックス",
|
'無題のリミックスのリミックスのリミックスのリミックス',
|
||||||
"無題のリミックスのリミックスのリミックスのリミックスのリミックス"
|
'無題のリミックスのリミックスのリミックスのリミックスのリミックス'
|
||||||
]
|
]
|
||||||
|
|
||||||
joined_names = low_quality.map { |name| "'#{name}'" }.join(',')
|
joined_names = low_quality.map { |name| "'#{name}'" }.join(',')
|
||||||
|
|
||||||
unless params.key?('name_quality') || params[:name_quality].nil? || params[:name_quality] == "0"
|
return if params.key?('name_quality') || params[:name_quality].nil? || params[:name_quality] == '0'
|
||||||
"name NOT IN (#{joined_names})"
|
|
||||||
end
|
"name NOT IN (#{joined_names})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def privacy
|
||||||
|
return if admin_mode
|
||||||
|
|
||||||
|
'visibility = 1' if current_user != @user
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify whitelisted properties that can be modified.
|
# Specify whitelisted properties that can be modified.
|
||||||
|
|
|
||||||
|
|
@ -105,17 +105,29 @@ class Party < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_remix
|
def is_remix
|
||||||
self.source_party != nil
|
!source_party.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remixes
|
def remixes
|
||||||
Party.where(source_party_id: self.id)
|
Party.where(source_party_id: id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blueprint
|
def blueprint
|
||||||
PartyBlueprint
|
PartyBlueprint
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def public?
|
||||||
|
visibility == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlisted?
|
||||||
|
visibility == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
def private?
|
||||||
|
visibility == 3
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_shortcode
|
def set_shortcode
|
||||||
|
|
@ -123,9 +135,9 @@ class Party < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_edit_key
|
def set_edit_key
|
||||||
if !self.user
|
return if user
|
||||||
self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join)
|
|
||||||
end
|
self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join)
|
||||||
end
|
end
|
||||||
|
|
||||||
def random_string
|
def random_string
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@ class User < ApplicationRecord
|
||||||
favorites.map(&:party)
|
favorites.map(&:party)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def admin?
|
||||||
|
role == 9
|
||||||
|
end
|
||||||
|
|
||||||
def blueprint
|
def blueprint
|
||||||
UserBlueprint
|
UserBlueprint
|
||||||
end
|
end
|
||||||
|
|
|
||||||
5
db/migrate/20230824222028_add_role_to_users.rb
Normal file
5
db/migrate/20230824222028_add_role_to_users.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddRoleToUsers < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :users, :role, :integer, default: 1, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
5
db/migrate/20230824222107_add_visibility_to_parties.rb
Normal file
5
db/migrate/20230824222107_add_visibility_to_parties.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddVisibilityToParties < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :parties, :visibility, :integer, default: 1, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
962
db/schema.rb
962
db/schema.rb
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue