diff --git a/app/controllers/api/v1/api_controller.rb b/app/controllers/api/v1/api_controller.rb index 871acbf..97cdd8a 100644 --- a/app/controllers/api/v1/api_controller.rb +++ b/app/controllers/api/v1/api_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/api/v1/favorites_controller.rb b/app/controllers/api/v1/favorites_controller.rb new file mode 100644 index 0000000..26da814 --- /dev/null +++ b/app/controllers/api/v1/favorites_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/api/v1/parties_controller.rb b/app/controllers/api/v1/parties_controller.rb index 419a7b4..9ebaee8 100644 --- a/app/controllers/api/v1/parties_controller.rb +++ b/app/controllers/api/v1/parties_controller.rb @@ -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 diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index 38aab2b..46a5990 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -26,7 +26,6 @@ class Api::V1::UsersController < Api::V1::ApiController def show @parties = @user.parties - ap "Hello world" end def check_email diff --git a/app/errors/api/v1/FavoriteAlreadyExistsError.rb b/app/errors/api/v1/FavoriteAlreadyExistsError.rb new file mode 100644 index 0000000..8c7ca73 --- /dev/null +++ b/app/errors/api/v1/FavoriteAlreadyExistsError.rb @@ -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 diff --git a/app/errors/api/v1/SameFavoriteUserError.rb b/app/errors/api/v1/SameFavoriteUserError.rb new file mode 100644 index 0000000..b948c1a --- /dev/null +++ b/app/errors/api/v1/SameFavoriteUserError.rb @@ -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 diff --git a/app/errors/api/v1/UnauthorizedError.rb b/app/errors/api/v1/UnauthorizedError.rb new file mode 100644 index 0000000..842c178 --- /dev/null +++ b/app/errors/api/v1/UnauthorizedError.rb @@ -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 diff --git a/app/models/favorite.rb b/app/models/favorite.rb new file mode 100644 index 0000000..a074c9a --- /dev/null +++ b/app/models/favorite.rb @@ -0,0 +1,8 @@ +class Favorite < ApplicationRecord + belongs_to :user + belongs_to :party + + def party + Party.find(self.party_id) + end +end diff --git a/app/models/party.rb b/app/models/party.rb index f11eebc..0f0c080 100644 --- a/app/models/party.rb +++ b/app/models/party.rb @@ -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 \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 12d64eb..b54636a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/views/api/v1/characters/base.json.rabl b/app/views/api/v1/characters/base.json.rabl index 6d87ab9..8c6c3c7 100644 --- a/app/views/api/v1/characters/base.json.rabl +++ b/app/views/api/v1/characters/base.json.rabl @@ -1,4 +1,4 @@ -object :summon +object :character attributes :id, :granblue_id, diff --git a/app/views/api/v1/favorites/base.json.rabl b/app/views/api/v1/favorites/base.json.rabl new file mode 100644 index 0000000..e7525b8 --- /dev/null +++ b/app/views/api/v1/favorites/base.json.rabl @@ -0,0 +1,3 @@ +object :favorite + +attributes :id, :user_id, :party_id, :created_at, :updated_at diff --git a/app/views/api/v1/favorites/destroyed.json.rabl b/app/views/api/v1/favorites/destroyed.json.rabl new file mode 100644 index 0000000..f791b2f --- /dev/null +++ b/app/views/api/v1/favorites/destroyed.json.rabl @@ -0,0 +1,5 @@ +object false + +node :destroyed do + true +end \ No newline at end of file diff --git a/app/views/api/v1/favorites/show.json.rabl b/app/views/api/v1/favorites/show.json.rabl new file mode 100644 index 0000000..2b85585 --- /dev/null +++ b/app/views/api/v1/favorites/show.json.rabl @@ -0,0 +1,3 @@ +object @favorite + +extends 'api/v1/favorites/base' \ No newline at end of file diff --git a/app/views/api/v1/parties/base.json.rabl b/app/views/api/v1/parties/base.json.rabl index 760a164..fabf183 100644 --- a/app/views/api/v1/parties/base.json.rabl +++ b/app/views/api/v1/parties/base.json.rabl @@ -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 diff --git a/app/views/api/v1/parties/characters.json.rabl b/app/views/api/v1/parties/characters.json.rabl index 17d3d9c..e19cb56 100644 --- a/app/views/api/v1/parties/characters.json.rabl +++ b/app/views/api/v1/parties/characters.json.rabl @@ -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) diff --git a/app/views/api/v1/parties/summons.json.rabl b/app/views/api/v1/parties/summons.json.rabl index f6faea5..d823f06 100644 --- a/app/views/api/v1/parties/summons.json.rabl +++ b/app/views/api/v1/parties/summons.json.rabl @@ -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) diff --git a/app/views/api/v1/parties/weapons.json.rabl b/app/views/api/v1/parties/weapons.json.rabl index 436451b..3722385 100644 --- a/app/views/api/v1/parties/weapons.json.rabl +++ b/app/views/api/v1/parties/weapons.json.rabl @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 43d9dc8..4d03f7e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20220228014758_create_favorites.rb b/db/migrate/20220228014758_create_favorites.rb new file mode 100644 index 0000000..c0a2d17 --- /dev/null +++ b/db/migrate/20220228014758_create_favorites.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index e2f168e..69ed289 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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"