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'
|
||||
|
||||
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
|
||||
scope 'users/:user_id' 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
|
||||
collection do
|
||||
post :batch
|
||||
post :import
|
||||
end
|
||||
end
|
||||
resources :weapons, only: [:create, :update, :destroy], controller: '/api/v1/collection_weapons' do
|
||||
collection do
|
||||
post :batch
|
||||
post :import
|
||||
end
|
||||
end
|
||||
resources :summons, only: [:create, :update, :destroy], controller: '/api/v1/collection_summons' do
|
||||
collection do
|
||||
post :batch
|
||||
post :import
|
||||
end
|
||||
end
|
||||
resources :job_accessories, controller: '/api/v1/collection_job_accessories',
|
||||
|
|
|
|||
Loading…
Reference in a new issue