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
##### Doorkeeper
include Doorkeeper::Rails::Helpers
module Api::V1
class ApiController < ActionController::API
##### Doorkeeper
include Doorkeeper::Rails::Helpers
##### Errors
rescue_from ActiveRecord::RecordInvalid, 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::RecordNotSaved, with: :render_unprocessable_entity_response
rescue_from ActiveRecord::RecordNotUnique, with: :render_unprocessable_entity_response
rescue_from ActionController::ParameterMissing, with: :render_unprocessable_entity_response
##### Errors
rescue_from ActiveRecord::RecordInvalid, 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::RecordNotSaved, with: :render_unprocessable_entity_response
rescue_from ActiveRecord::RecordNotUnique, 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
before_action :current_user
before_action :set_default_content_type
##### Hooks
before_action :current_user
before_action :set_default_content_type
##### Responders
respond_to :json
##### Responders
respond_to :json
##### Methods
# 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
def current_user
@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
##### Methods
# 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
def current_user
@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
return @current_user
end
return @current_user
end
# Set the response content-type
def set_content_type(content_type)
response.headers["Content-Type"] = content_type
end
# Set the response content-type
def set_content_type(content_type)
response.headers["Content-Type"] = content_type
end
# Set the default response content-type to application/javascript
# with a UTF-8 charset
def set_default_content_type
set_content_type("application/javascript; charset=utf-8")
end
# Set the default response content-type to application/javascript
# with a UTF-8 charset
def set_default_content_type
set_content_type("application/javascript; charset=utf-8")
end
def current_user
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
def current_user
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
return @current_user
end
return @current_user
end
### Error response methods
def render_unprocessable_entity_response(exception)
@exception = exception
render action: 'errors', status: :unprocessable_entity
end
### Error response methods
def render_unprocessable_entity_response(exception)
@exception = exception
render action: 'errors', status: :unprocessable_entity
end
def render_not_found_response
response = { errors: [{ message: "Record could not be found.", code: "not_found" }]}
render 'not_found', status: :not_found
end
def render_not_found_response
response = { errors: [{ message: "Record could not be found.", code: "not_found" }]}
render 'not_found', status: :not_found
end
def render_unauthorized_response
render action: 'errors', status: :unauthorized
end
def render_unauthorized_response
render action: 'errors', status: :unauthorized
end
private
private
def restrict_access
raise UnauthorizedError unless current_user
def restrict_access
raise UnauthorizedError unless current_user
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
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']
def create
@ -17,6 +17,38 @@ class Api::V1::PartiesController < Api::V1::ApiController
render_not_found_response if @party.nil?
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
if @party.user != current_user
render_unauthorized_response
@ -49,20 +81,6 @@ class Api::V1::PartiesController < Api::V1::ApiController
render :characters, status: :ok
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
def random_string
@ -73,6 +91,7 @@ class Api::V1::PartiesController < Api::V1::ApiController
def set_from_slug
@party = Party.where("shortcode = ?", params[:id]).first
@party.favorited = (current_user) ? @party.is_favorited(current_user) : false
end
def set

View file

@ -26,7 +26,6 @@ class Api::V1::UsersController < Api::V1::ApiController
def show
@parties = @user.parties
ap "Hello world"
end
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
belongs_to :user, optional: true
belongs_to :raid, optional: true
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 :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

View file

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

View file

@ -1,4 +1,4 @@
object :summon
object :character
attributes :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
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
end

View file

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

View file

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

View file

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

View file

@ -6,9 +6,12 @@ Rails.application.routes.draw do
namespace :api, defaults: { format: :json } 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 :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/summons', to: 'parties#summons'
get 'parties/:id/characters', to: 'parties#characters'
@ -34,6 +37,8 @@ Rails.application.routes.draw do
post 'summons', to: 'grid_summons#create'
post 'summons/update_uncap', to: 'grid_summons#update_uncap_level'
delete 'summons', to: 'grid_summons#destroy'
delete 'favorites', to: 'favorites#destroy'
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.
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
enable_extension "pgcrypto"
@ -44,6 +44,15 @@ ActiveRecord::Schema.define(version: 2022_02_27_042147) do
t.integer "max_atk_ulb"
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|
t.uuid "party_id"
t.uuid "character_id"