add crew controllers, blueprints, routes, and errors
- CrewsController: create, show, update, members, leave, transfer_captain - CrewMembershipsController: update, destroy, promote, demote - CrewAuthorizationConcern for member/officer/captain checks - blueprints for serialization - custom error classes for crew operations
This commit is contained in:
parent
9b01aa0ff3
commit
e98e59491d
7 changed files with 359 additions and 1 deletions
41
app/blueprints/api/v1/crew_blueprint.rb
Normal file
41
app/blueprints/api/v1/crew_blueprint.rb
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class CrewBlueprint < ApiBlueprint
|
||||||
|
fields :name, :gamertag, :granblue_crew_id, :description, :created_at
|
||||||
|
|
||||||
|
view :minimal do
|
||||||
|
fields :name, :gamertag
|
||||||
|
end
|
||||||
|
|
||||||
|
view :full do
|
||||||
|
fields :name, :gamertag, :granblue_crew_id, :description, :created_at
|
||||||
|
|
||||||
|
field :member_count do |crew|
|
||||||
|
crew.active_memberships.count
|
||||||
|
end
|
||||||
|
|
||||||
|
field :captain do |crew|
|
||||||
|
captain = crew.captain
|
||||||
|
UserBlueprint.render_as_hash(captain, view: :minimal) if captain
|
||||||
|
end
|
||||||
|
|
||||||
|
field :vice_captains do |crew|
|
||||||
|
UserBlueprint.render_as_hash(crew.vice_captains, view: :minimal)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
view :with_members do
|
||||||
|
include_view :full
|
||||||
|
|
||||||
|
field :members do |crew|
|
||||||
|
CrewMembershipBlueprint.render_as_hash(
|
||||||
|
crew.active_memberships.includes(:user).order(role: :desc, created_at: :asc),
|
||||||
|
view: :with_user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
25
app/blueprints/api/v1/crew_membership_blueprint.rb
Normal file
25
app/blueprints/api/v1/crew_membership_blueprint.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class CrewMembershipBlueprint < ApiBlueprint
|
||||||
|
fields :role, :retired, :retired_at, :created_at
|
||||||
|
|
||||||
|
view :with_user do
|
||||||
|
fields :role, :retired, :retired_at, :created_at
|
||||||
|
|
||||||
|
field :user do |membership|
|
||||||
|
UserBlueprint.render_as_hash(membership.user, view: :minimal)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
view :with_crew do
|
||||||
|
fields :role, :retired, :retired_at, :created_at
|
||||||
|
|
||||||
|
field :crew do |membership|
|
||||||
|
CrewBlueprint.render_as_hash(membership.crew, view: :minimal)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
69
app/controllers/api/v1/crew_memberships_controller.rb
Normal file
69
app/controllers/api/v1/crew_memberships_controller.rb
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class CrewMembershipsController < Api::V1::ApiController
|
||||||
|
include CrewAuthorizationConcern
|
||||||
|
|
||||||
|
before_action :restrict_access
|
||||||
|
before_action :set_crew
|
||||||
|
before_action :set_membership, only: %i[update destroy promote demote]
|
||||||
|
before_action :authorize_crew_officer!, only: %i[destroy]
|
||||||
|
before_action :authorize_crew_captain!, only: %i[promote demote]
|
||||||
|
|
||||||
|
# PUT /crews/:crew_id/memberships/:id
|
||||||
|
def update
|
||||||
|
# Only captain can update roles
|
||||||
|
authorize_crew_captain!
|
||||||
|
|
||||||
|
if @membership.update(membership_params)
|
||||||
|
render json: CrewMembershipBlueprint.render(@membership, view: :with_user, root: :membership)
|
||||||
|
else
|
||||||
|
render_validation_error_response(@membership)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /crews/:crew_id/memberships/:id
|
||||||
|
def destroy
|
||||||
|
raise CannotRemoveCaptainError if @membership.captain?
|
||||||
|
|
||||||
|
@membership.retire!
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /crews/:crew_id/memberships/:id/promote
|
||||||
|
def promote
|
||||||
|
raise CannotRemoveCaptainError if @membership.captain?
|
||||||
|
|
||||||
|
# Check vice captain limit
|
||||||
|
current_vc_count = @crew.crew_memberships.where(role: :vice_captain, retired: false).count
|
||||||
|
raise ViceCaptainLimitError if current_vc_count >= 3 && !@membership.vice_captain?
|
||||||
|
|
||||||
|
@membership.update!(role: :vice_captain)
|
||||||
|
render json: CrewMembershipBlueprint.render(@membership, view: :with_user, root: :membership)
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /crews/:crew_id/memberships/:id/demote
|
||||||
|
def demote
|
||||||
|
raise CannotRemoveCaptainError if @membership.captain?
|
||||||
|
|
||||||
|
@membership.update!(role: :member)
|
||||||
|
render json: CrewMembershipBlueprint.render(@membership, view: :with_user, root: :membership)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_crew
|
||||||
|
@crew = Crew.find(params[:crew_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_membership
|
||||||
|
@membership = @crew.crew_memberships.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def membership_params
|
||||||
|
params.require(:membership).permit(:role)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
89
app/controllers/api/v1/crews_controller.rb
Normal file
89
app/controllers/api/v1/crews_controller.rb
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class CrewsController < Api::V1::ApiController
|
||||||
|
include CrewAuthorizationConcern
|
||||||
|
|
||||||
|
before_action :restrict_access
|
||||||
|
before_action :set_crew, only: %i[show update members leave transfer_captain]
|
||||||
|
before_action :authorize_crew_member!, only: %i[show members]
|
||||||
|
before_action :authorize_crew_officer!, only: %i[update]
|
||||||
|
before_action :authorize_crew_captain!, only: %i[transfer_captain]
|
||||||
|
|
||||||
|
# GET /crew or GET /crews/:id
|
||||||
|
def show
|
||||||
|
render json: CrewBlueprint.render(@crew, view: :full, root: :crew)
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /crews
|
||||||
|
def create
|
||||||
|
raise AlreadyInCrewError if current_user.crew.present?
|
||||||
|
|
||||||
|
@crew = Crew.new(crew_params)
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
@crew.save!
|
||||||
|
CrewMembership.create!(crew: @crew, user: current_user, role: :captain)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: CrewBlueprint.render(@crew, view: :full, root: :crew), status: :created
|
||||||
|
end
|
||||||
|
|
||||||
|
# PUT /crew
|
||||||
|
def update
|
||||||
|
if @crew.update(crew_params)
|
||||||
|
render json: CrewBlueprint.render(@crew, view: :full, root: :crew)
|
||||||
|
else
|
||||||
|
render_validation_error_response(@crew)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /crew/members
|
||||||
|
def members
|
||||||
|
members = @crew.active_memberships.includes(:user).order(role: :desc, created_at: :asc)
|
||||||
|
render json: CrewMembershipBlueprint.render(members, view: :with_user, root: :members)
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /crew/leave
|
||||||
|
def leave
|
||||||
|
raise NotInCrewError unless @crew
|
||||||
|
|
||||||
|
membership = current_user.active_crew_membership
|
||||||
|
raise CaptainCannotLeaveError if membership.captain?
|
||||||
|
|
||||||
|
membership.retire!
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /crews/:id/transfer_captain
|
||||||
|
def transfer_captain
|
||||||
|
new_captain_id = params[:user_id]
|
||||||
|
new_captain_membership = @crew.active_memberships.find_by(user_id: new_captain_id)
|
||||||
|
|
||||||
|
raise MemberNotFoundError unless new_captain_membership
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
current_user.active_crew_membership.update!(role: :vice_captain)
|
||||||
|
new_captain_membership.update!(role: :captain)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: CrewBlueprint.render(@crew.reload, view: :full, root: :crew)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_crew
|
||||||
|
@crew = if params[:id]
|
||||||
|
Crew.find(params[:id])
|
||||||
|
else
|
||||||
|
current_user&.crew
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def crew_params
|
||||||
|
params.require(:crew).permit(:name, :gamertag, :granblue_crew_id, :description)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
20
app/controllers/concerns/crew_authorization_concern.rb
Normal file
20
app/controllers/concerns/crew_authorization_concern.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module CrewAuthorizationConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# Checks whether the current user is a member of the crew
|
||||||
|
def authorize_crew_member!
|
||||||
|
render_unauthorized_response unless current_user&.crew == @crew
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks whether the current user is an officer (captain or vice captain) of the crew
|
||||||
|
def authorize_crew_officer!
|
||||||
|
render_unauthorized_response unless current_user&.crew == @crew && current_user.crew_officer?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks whether the current user is the captain of the crew
|
||||||
|
def authorize_crew_captain!
|
||||||
|
render_unauthorized_response unless current_user&.crew == @crew && current_user.crew_captain?
|
||||||
|
end
|
||||||
|
end
|
||||||
89
app/errors/api/v1/crew_errors.rb
Normal file
89
app/errors/api/v1/crew_errors.rb
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class AlreadyInCrewError < GranblueError
|
||||||
|
def http_status
|
||||||
|
422
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
'already_in_crew'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
'You are already in a crew'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CaptainCannotLeaveError < GranblueError
|
||||||
|
def http_status
|
||||||
|
422
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
'captain_cannot_leave'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
'Captain must transfer ownership before leaving'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CannotRemoveCaptainError < GranblueError
|
||||||
|
def http_status
|
||||||
|
422
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
'cannot_remove_captain'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
'Cannot remove the captain from the crew'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ViceCaptainLimitError < GranblueError
|
||||||
|
def http_status
|
||||||
|
422
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
'vice_captain_limit'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
'Crew can only have up to 3 vice captains'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NotInCrewError < GranblueError
|
||||||
|
def http_status
|
||||||
|
422
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
'not_in_crew'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
'You are not in a crew'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MemberNotFoundError < GranblueError
|
||||||
|
def http_status
|
||||||
|
404
|
||||||
|
end
|
||||||
|
|
||||||
|
def code
|
||||||
|
'member_not_found'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
'Member not found in this crew'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -147,7 +147,29 @@ Rails.application.routes.draw do
|
||||||
post 'parties/:id/grid_update', to: 'parties#grid_update'
|
post 'parties/:id/grid_update', to: 'parties#grid_update'
|
||||||
|
|
||||||
delete 'favorites', to: 'favorites#destroy'
|
delete 'favorites', to: 'favorites#destroy'
|
||||||
|
|
||||||
|
# Crews - current user's crew (no ID needed)
|
||||||
|
resource :crew, only: %i[show update], controller: 'crews' do
|
||||||
|
member do
|
||||||
|
get :members
|
||||||
|
post :leave
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Crews - create and manage by ID
|
||||||
|
resources :crews, only: %i[create] do
|
||||||
|
member do
|
||||||
|
post :transfer_captain
|
||||||
|
end
|
||||||
|
|
||||||
|
resources :memberships, controller: 'crew_memberships', only: %i[update destroy] do
|
||||||
|
member do
|
||||||
|
post :promote
|
||||||
|
post :demote
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Reading collections - works for any user with privacy check
|
# Reading collections - works for any user with privacy check
|
||||||
scope 'users/:user_id' do
|
scope 'users/:user_id' do
|
||||||
namespace :collection do
|
namespace :collection do
|
||||||
|
|
@ -163,16 +185,19 @@ Rails.application.routes.draw do
|
||||||
resources :characters, only: [:create, :update, :destroy], controller: '/api/v1/collection_characters' do
|
resources :characters, only: [:create, :update, :destroy], controller: '/api/v1/collection_characters' do
|
||||||
collection do
|
collection do
|
||||||
post :batch
|
post :batch
|
||||||
|
post :import
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :weapons, only: [:create, :update, :destroy], controller: '/api/v1/collection_weapons' do
|
resources :weapons, only: [:create, :update, :destroy], controller: '/api/v1/collection_weapons' do
|
||||||
collection do
|
collection do
|
||||||
post :batch
|
post :batch
|
||||||
|
post :import
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :summons, only: [:create, :update, :destroy], controller: '/api/v1/collection_summons' do
|
resources :summons, only: [:create, :update, :destroy], controller: '/api/v1/collection_summons' do
|
||||||
collection do
|
collection do
|
||||||
post :batch
|
post :batch
|
||||||
|
post :import
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :job_accessories, controller: '/api/v1/collection_job_accessories',
|
resources :job_accessories, controller: '/api/v1/collection_job_accessories',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue