Merge pull request #5 from jedmund/saving

Implement the ability to save your favorite grids
This commit is contained in:
Justin Edmund 2022-02-28 01:04:21 -08:00 committed by GitHub
commit d09036348d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 262 additions and 77 deletions

View file

@ -1,68 +1,73 @@
class Api::V1::ApiController < ActionController::API module Api::V1
##### Doorkeeper class ApiController < ActionController::API
include Doorkeeper::Rails::Helpers ##### Doorkeeper
include Doorkeeper::Rails::Helpers
##### Errors ##### Errors
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
rescue_from ActiveRecord::RecordNotDestroyed, with: :render_unprocessable_entity_response rescue_from ActiveRecord::RecordNotDestroyed, with: :render_unprocessable_entity_response
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response
rescue_from ActiveRecord::RecordNotSaved, with: :render_unprocessable_entity_response rescue_from ActiveRecord::RecordNotSaved, with: :render_unprocessable_entity_response
rescue_from ActiveRecord::RecordNotUnique, with: :render_unprocessable_entity_response rescue_from ActiveRecord::RecordNotUnique, with: :render_unprocessable_entity_response
rescue_from ActionController::ParameterMissing, with: :render_unprocessable_entity_response rescue_from Api::V1::SameFavoriteUserError, with: :render_unprocessable_entity_response
rescue_from Api::V1::FavoriteAlreadyExistsError, with: :render_unprocessable_entity_response
rescue_from Api::V1::UnauthorizedError, with: :render_unauthorized_response
rescue_from ActionController::ParameterMissing, with: :render_unprocessable_entity_response
##### Hooks ##### Hooks
before_action :current_user before_action :current_user
before_action :set_default_content_type before_action :set_default_content_type
##### Responders ##### Responders
respond_to :json respond_to :json
##### Methods ##### Methods
# Assign the current user if the Doorkeeper token isn't nil, then # Assign the current user if the Doorkeeper token isn't nil, then
# update the current user's last seen datetime and last IP address # update the current user's last seen datetime and last IP address
# before returning # before returning
def current_user def current_user
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
@current_user.update_last_ip_and_last_seen!(request.remote_ip) if @current_user @current_user.update_last_ip_and_last_seen!(request.remote_ip) if @current_user
return @current_user return @current_user
end end
# Set the response content-type # Set the response content-type
def set_content_type(content_type) def set_content_type(content_type)
response.headers["Content-Type"] = content_type response.headers["Content-Type"] = content_type
end end
# Set the default response content-type to application/javascript # Set the default response content-type to application/javascript
# with a UTF-8 charset # with a UTF-8 charset
def set_default_content_type def set_default_content_type
set_content_type("application/javascript; charset=utf-8") set_content_type("application/javascript; charset=utf-8")
end end
def current_user def current_user
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
return @current_user return @current_user
end end
### Error response methods ### Error response methods
def render_unprocessable_entity_response(exception) def render_unprocessable_entity_response(exception)
@exception = exception @exception = exception
render action: 'errors', status: :unprocessable_entity render action: 'errors', status: :unprocessable_entity
end end
def render_not_found_response def render_not_found_response
response = { errors: [{ message: "Record could not be found.", code: "not_found" }]} response = { errors: [{ message: "Record could not be found.", code: "not_found" }]}
render 'not_found', status: :not_found render 'not_found', status: :not_found
end end
def render_unauthorized_response def render_unauthorized_response
render action: 'errors', status: :unauthorized render action: 'errors', status: :unauthorized
end end
private private
def restrict_access def restrict_access
raise UnauthorizedError unless current_user raise UnauthorizedError unless current_user
end
end end
end end

View file

@ -0,0 +1,41 @@
class Api::V1::FavoritesController < Api::V1::ApiController
before_action :set_party, only: ['create']
def create
party_id = favorite_params[:party_id]
party = Party.find(party_id)
if !current_user
raise Api::V1::UnauthorizedError
elsif party.user && current_user.id == party.user.id
raise Api::V1::SameFavoriteUserError
elsif Favorite.where(user_id: current_user.id, party_id: party_id).length > 0
raise Api::V1::FavoriteAlreadyExistsError
else
object = {
user_id: current_user.id,
party_id: favorite_params[:party_id]
}
@favorite = Favorite.new(object)
render :show, status: :created if @favorite.save!
end
end
def destroy
raise Api::V1::UnauthorizedError unless current_user
@favorite = Favorite.where(user_id: current_user.id, party_id: favorite_params[:party_id]).first
render :destroyed, status: :ok if @favorite && Favorite.destroy(@favorite.id)
end
private
def set_party
@party = Party.where("id = ?", params[:party_id]).first
end
def favorite_params
params.require(:favorite).permit(:party_id)
end
end

View file

@ -1,5 +1,5 @@
class Api::V1::PartiesController < Api::V1::ApiController class Api::V1::PartiesController < Api::V1::ApiController
before_action :set_from_slug, except: ['create', 'update', 'index'] before_action :set_from_slug, except: ['create', 'update', 'index', 'favorites']
before_action :set, only: ['update', 'destroy'] before_action :set, only: ['update', 'destroy']
def create def create
@ -17,6 +17,38 @@ class Api::V1::PartiesController < Api::V1::ApiController
render_not_found_response if @party.nil? render_not_found_response if @party.nil?
end end
def index
now = DateTime.current
start_time = (now - params['recency'].to_i.seconds).to_datetime.beginning_of_day unless request.params['recency'].blank?
conditions = {}
conditions[:element] = request.params['element'] unless request.params['element'].blank?
conditions[:raid] = request.params['raid'] unless request.params['raid'].blank?
conditions[:created_at] = start_time..now unless request.params['recency'].blank?
@parties = Party.where(conditions).each { |party|
party.favorited = (current_user) ? party.is_favorited(current_user) : false
}
render :all, status: :ok
end
def favorites
raise Api::V1::UnauthorizedError unless current_user
conditions = {}
conditions[:element] = request.params['element'] unless request.params['element'].blank?
conditions[:raid] = request.params['raid'] unless request.params['raid'].blank?
conditions[:created_at] = start_time..now unless request.params['recency'].blank?
conditions[:favorites] = { user_id: current_user.id }
@parties = Party.joins(:favorites).where(conditions).each { |party|
party.favorited = party.is_favorited(current_user)
}
render :all, status: :ok
end
def update def update
if @party.user != current_user if @party.user != current_user
render_unauthorized_response render_unauthorized_response
@ -49,20 +81,6 @@ class Api::V1::PartiesController < Api::V1::ApiController
render :characters, status: :ok render :characters, status: :ok
end end
def index
now = DateTime.current
start_time = (now - params['recency'].to_i.seconds).to_datetime.beginning_of_day unless request.params['recency'].blank?
conditions = {}
conditions[:element] = request.params['element'] unless request.params['element'].blank?
conditions[:raid] = request.params['raid'] unless request.params['raid'].blank?
conditions[:created_at] = start_time..now unless request.params['recency'].blank?
@parties = Party.where(conditions)
render :all, status: :ok
end
private private
def random_string def random_string
@ -73,6 +91,7 @@ class Api::V1::PartiesController < Api::V1::ApiController
def set_from_slug def set_from_slug
@party = Party.where("shortcode = ?", params[:id]).first @party = Party.where("shortcode = ?", params[:id]).first
@party.favorited = (current_user) ? @party.is_favorited(current_user) : false
end end
def set def set

View file

@ -26,7 +26,6 @@ class Api::V1::UsersController < Api::V1::ApiController
def show def show
@parties = @user.parties @parties = @user.parties
ap "Hello world"
end end
def check_email def check_email

View file

@ -0,0 +1,22 @@
module Api::V1
class FavoriteAlreadyExistsError < StandardError
def http_status
422
end
def code
"favorite_already_exists"
end
def message
"This user has favorited this party already"
end
def to_hash
{
message: message,
code: code
}
end
end
end

View file

@ -0,0 +1,22 @@
module Api::V1
class SameFavoriteUserError < StandardError
def http_status
422
end
def code
"same_favorite_user"
end
def message
"Users cannot favorite their own parties"
end
def to_hash
{
message: message,
code: code
}
end
end
end

View file

@ -0,0 +1,22 @@
module Api::V1
class UnauthorizedError < StandardError
def http_status
401
end
def code
"unauthorized"
end
def message
"User is not allowed to modify that resource"
end
def to_hash
{
message: message,
code: code
}
end
end
end

8
app/models/favorite.rb Normal file
View file

@ -0,0 +1,8 @@
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :party
def party
Party.find(self.party_id)
end
end

View file

@ -2,7 +2,15 @@ class Party < ApplicationRecord
##### ActiveRecord Associations ##### ActiveRecord Associations
belongs_to :user, optional: true belongs_to :user, optional: true
belongs_to :raid, optional: true belongs_to :raid, optional: true
has_many :characters, foreign_key: "party_id", class_name: "GridCharacter", dependent: :destroy has_many :characters, foreign_key: "party_id", class_name: "GridCharacter", dependent: :destroy
has_many :weapons, foreign_key: "party_id", class_name: "GridWeapon", dependent: :destroy has_many :weapons, foreign_key: "party_id", class_name: "GridWeapon", dependent: :destroy
has_many :summons, foreign_key: "party_id", class_name: "GridSummon", dependent: :destroy has_many :summons, foreign_key: "party_id", class_name: "GridSummon", dependent: :destroy
has_many :favorites
attr_accessor :favorited
def is_favorited(user)
user.favorite_parties.include? self
end
end end

View file

@ -3,6 +3,7 @@ class User < ApplicationRecord
##### ActiveRecord Associations ##### ActiveRecord Associations
has_many :parties, dependent: :destroy has_many :parties, dependent: :destroy
has_many :favorites, dependent: :destroy
##### ActiveRecord Validations ##### ActiveRecord Validations
validates :username, validates :username,
@ -35,4 +36,8 @@ class User < ApplicationRecord
##### ActiveModel Security ##### ActiveModel Security
has_secure_password has_secure_password
def favorite_parties
self.favorites.map { |favorite| favorite.party }
end
end end

View file

@ -1,4 +1,4 @@
object :summon object :character
attributes :id, attributes :id,
:granblue_id, :granblue_id,

View file

@ -0,0 +1,3 @@
object :favorite
attributes :id, :user_id, :party_id, :created_at, :updated_at

View file

@ -0,0 +1,5 @@
object false
node :destroyed do
true
end

View file

@ -0,0 +1,3 @@
object @favorite
extends 'api/v1/favorites/base'

View file

@ -1,8 +1,8 @@
object :party object :party
attributes :id, :name, :description, :element, :shortcode, :created_at, :updated_at attributes :id, :name, :description, :element, :favorited, :shortcode, :created_at, :updated_at
node :is_extra do |p| node :extra do |p|
p.extra p.extra
end end

View file

@ -1,6 +1,6 @@
object @party object @party
attributes :id, :name, :description, :shortcode attributes :id, :name, :description, :shortcode, :favorited, :created_at, :updated_at
node :user do |p| node :user do |p|
partial('users/base', :object => p.user) partial('users/base', :object => p.user)

View file

@ -1,6 +1,6 @@
object @party object @party
attributes :id, :name, :description, :shortcode attributes :id, :name, :description, :shortcode, :favorited, :created_at, :updated_at
node :user do |p| node :user do |p|
partial('users/base', :object => p.user) partial('users/base', :object => p.user)

View file

@ -1,6 +1,6 @@
object @party object @party
attributes :id, :name, :description, :shortcode attributes :id, :name, :description, :shortcode, :favorited, :created_at, :updated_at
node :user do |p| node :user do |p|
partial('users/base', :object => p.user) partial('users/base', :object => p.user)
@ -10,7 +10,7 @@ node :raid do |p|
partial('raids/base', :object => p.raid) partial('raids/base', :object => p.raid)
end end
node :is_extra do |p| node :extra do |p|
p.extra p.extra
end end

View file

@ -6,9 +6,12 @@ Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do namespace :api, defaults: { format: :json } do
namespace :v1 do namespace :v1 do
resources :parties, only: [:index, :create, :show, :update, :destroy] resources :parties, only: [:index, :create, :update, :destroy]
resources :users, only: [:create, :show] resources :users, only: [:create, :show]
resources :favorites, only: [:create]
get 'parties/favorites', to: 'parties#favorites'
get 'parties/:id', to: 'parties#show'
get 'parties/:id/weapons', to: 'parties#weapons' get 'parties/:id/weapons', to: 'parties#weapons'
get 'parties/:id/summons', to: 'parties#summons' get 'parties/:id/summons', to: 'parties#summons'
get 'parties/:id/characters', to: 'parties#characters' get 'parties/:id/characters', to: 'parties#characters'
@ -34,6 +37,8 @@ Rails.application.routes.draw do
post 'summons', to: 'grid_summons#create' post 'summons', to: 'grid_summons#create'
post 'summons/update_uncap', to: 'grid_summons#update_uncap_level' post 'summons/update_uncap', to: 'grid_summons#update_uncap_level'
delete 'summons', to: 'grid_summons#destroy' delete 'summons', to: 'grid_summons#destroy'
delete 'favorites', to: 'favorites#destroy'
end end
end end
end end

View file

@ -0,0 +1,9 @@
class CreateFavorites < ActiveRecord::Migration[6.1]
def change
create_table :favorites, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
t.references :user, type: :uuid
t.references :party, type: :uuid
t.timestamps
end
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_02_27_042147) do ActiveRecord::Schema.define(version: 2022_02_28_014758) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto" enable_extension "pgcrypto"
@ -44,6 +44,15 @@ ActiveRecord::Schema.define(version: 2022_02_27_042147) do
t.integer "max_atk_ulb" t.integer "max_atk_ulb"
end end
create_table "favorites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "user_id"
t.uuid "party_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["party_id"], name: "index_favorites_on_party_id"
t.index ["user_id"], name: "index_favorites_on_user_id"
end
create_table "grid_characters", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| create_table "grid_characters", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "party_id" t.uuid "party_id"
t.uuid "character_id" t.uuid "character_id"