diff --git a/.gitignore b/.gitignore index 44fa97956124383c1edf71da20dd0a109873f14b..5767d2bf65adf0dc83b3068f876ec7a1f4384a70 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ Gemfile.lock /shared/* dump.rdb /public/system/* +/db/seeds/assets/* # ignore server scripts puma.sh @@ -37,4 +38,4 @@ autocomplete-server.service # ignore configs /config/database.yml -/config/sidekiq.yml \ No newline at end of file +/config/sidekiq.yml diff --git a/Gemfile b/Gemfile index c4f85f58e9e218ae0de4f0eecc77cd21546775bc..bf2549e36db20c991a787caf7df06bf03a6d9356 100644 --- a/Gemfile +++ b/Gemfile @@ -145,7 +145,11 @@ gem 'activerecord-import' # social connect gem 'omniauth-facebook' gem 'omniauth-twitter' -gem 'omniauth-google-oauth2', '~>0.8.2' +gem 'omniauth-google-oauth2', '0.8.2' + +gem 'faraday' +gem 'net-http-persistent' + # get mime type gem 'mimemagic' @@ -172,3 +176,7 @@ gem 'paranoia' gem 'paper_trail' gem 'acts_as_list' + +gem 'faraday-net_http_persistent', '~> 2.0' + +gem 'elasticsearch' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7d8ea4cded332be1ef2e742db89cef19020aeae2..897f5611fb3d85a137bbe3f85cae25aaa4b300a4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -28,8 +28,10 @@ class ApplicationController < ActionController::API rescue_from Exception do |exception| logger.fatal "The request raised an exception:" + http_request_headers = request.headers.select{|header_name, header_value| header_name.match("^HTTP.*")} + logger.fatal "#{request.method.inspect} request to #{request.url.inspect} from #{request.remote_ip.inspect} with headers #{http_request_headers.inspect} and params #{params.inspect} gave the following exception:" logger.fatal exception - logger.fatal exception.backtrace.first(10).join("\n") + logger.fatal exception.backtrace.first(15).join("\n") unless response_body render status: :internal_server_error end diff --git a/app/controllers/concerns/items_filter.rb b/app/controllers/concerns/items_filter.rb new file mode 100644 index 0000000000000000000000000000000000000000..b0cb5d1217c251846f1c84c5c9f09427cfed106f --- /dev/null +++ b/app/controllers/concerns/items_filter.rb @@ -0,0 +1,48 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +module ItemsFilter + extend ActiveSupport::Concern + + def filter_items(items) + # filter by type + if !params[:item_type].blank? + items = items.where(item_type: params[:item_type]) + end + + # filter by price + if params[:op] == "lt" # less than + items = items.where("price < ?", params[:price]) + elsif params[:op] == "gt" # greater than + items = items.where("price > ?", params[:price]) + elsif params[:op] == "eq" # equals + items = items.where(price: params[:price]) + end + + # filter by the way it's unlocked + if params[:unlock_rule] == "achievement" + items = items.where.not(achievement_id: nil) + elsif params[:unlock_rule] == "purchase" + items = items.where(achievement_id: nil) + end + + items + end + +end diff --git a/app/controllers/v1/achievements_controller.rb b/app/controllers/v1/achievements_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..dcb47a03ad7f96c1c6a4f0e94472208a6de80f51 --- /dev/null +++ b/app/controllers/v1/achievements_controller.rb @@ -0,0 +1,91 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::AchievementsController < ApplicationController + include ::Paginator + + before_action :set_achievement, only: [:show, :update, :destroy] + before_action :authenticate_user!, except: [:index, :show] + before_action :authorize!, only: [:update, :destroy] + + # GET /v1/achievements + def index + achievements = paginate Achievement + render json: achievements + end + + # GET /v1/achievements/1 + def show + render json: @achievement + end + + # POST /v1/achievements + def create + @achievement = Achievement.new(achievement_params) + authorize @achievement + + if @achievement.save + @achievement.add_requirements(extra_params[:requirements]) + render json: @achievement, status: :created + else + render json: @achievement.errors, status: :unprocessable_entity + end + end + + # PUT/PATCH /v1/achievements/1 + def update + if @achievement.update(achievement_params) + @achievement.update_requirements(extra_params[:requirements]) + render json: @achievement, status: :ok + else + render json: @achievement.errors, status: :unprocessable_entity + end + end + + # DELETE /v1/achievements/1 + def destroy + if @achievement.update(state: 2) + render status: :ok, json: @achievement + else + render json: @achievement.errors, status: :unprocessable_entity + end + end + + private + + def set_achievement + @achievement ||= Achievement.find_by_id(params[:id]) + if @achievement.blank? + render status: :not_found + end + end + + def achievement_params + params.require(:achievement).permit(:name, :description, :reward_experience, :reward_points, :state, :repeatable, :resettable) + end + + def extra_params + return {} if params[:achievement].nil? + params[:achievement].permit(requirements: []) + end + + def authorize! + authorize @achievement + end +end diff --git a/app/controllers/v1/action_counters_controller.rb b/app/controllers/v1/action_counters_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..e66a4d38d32b6617b5fb502f942c6f2f481e936a --- /dev/null +++ b/app/controllers/v1/action_counters_controller.rb @@ -0,0 +1,43 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::ActionCountersController < ApplicationController + include ::Paginator + + before_action :set_action_counter, only: :show + + def index + action_counters = paginate ActionCounter + render json: action_counters + end + + def show + render json: @action_counter + end + + private + + def set_action_counter + @action_counter ||= ActionCounter.find_by_id(params[:id]) + if @action_counter.blank? + render status: :not_found + end + end + +end diff --git a/app/controllers/v1/actions_controller.rb b/app/controllers/v1/actions_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..daa70d2c1fd7db5bd0ebb093dd337229d85982fb --- /dev/null +++ b/app/controllers/v1/actions_controller.rb @@ -0,0 +1,83 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::ActionsController < ApplicationController + include ::Paginator + + before_action :set_action, only: [:show, :update, :destroy] + before_action :authenticate_user!, except: [:index, :show] + before_action :authorize!, only: [:update, :destroy] + + # GET /v1/actions + def index + actions = paginate Action + render json: actions + end + + # GET /v1/actions/1 + def show + render json: @action + end + + # POST /v1/actions + def create + @action = Action.new(action_params) + authorize @action + + if @action.save + render json: @action, status: :created + else + render json: @action.errors, status: :unprocessable_entity + end + end + + # PUT/PATCH /v1/actions/1 + def update + if @action.update(action_params) + render json: @action, status: :ok + else + render json: @action.errors, status: :unprocessable_entity + end + end + + # DELETE /v1/actions/1 + def destroy + @action.destroy + response = { 'status': 'deleted' } + render status: :ok, json: response + end + + private + + def set_action + @action ||= Action.find_by_id(params[:id]) + if @action.blank? + render status: :not_found + end + end + + def action_params + params.require(:action_params).permit(:name, :description, :reward_experience) + end + + def authorize! + authorize @action + end + +end diff --git a/app/controllers/v1/items_controller.rb b/app/controllers/v1/items_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..913f9a2fb25be0ddbf7396108dffa5fccdca9229 --- /dev/null +++ b/app/controllers/v1/items_controller.rb @@ -0,0 +1,82 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::ItemsController < ApplicationController + include ::Paginator + include ::ItemsFilter + + before_action :set_item, only: [:show, :update, :destroy] + before_action :authenticate_user!, except: [:index, :show] + before_action :authorize!, only: [:update, :destroy] + + def index + items = paginate filter_items(policy_scope(Item)) + render json: items + end + + def show + render json: @item + end + + def create + @item = Item.new(item_params) + authorize @item + + if @item.save + render json: @item, status: :created + else + render json: @item.errors, status: :unprocessable_entity + end + end + + # PUT/PATCH /v1/items/1 + def update + if @item.update(item_params) + render json: @item, status: :ok + else + render json: @item.errors, status: :unprocessable_entity + end + end + + # DELETE /v1/items/1 + def destroy + if @item.update(state: 2) + render status: :ok, json: @item + else + render json: @item.errors, status: :unprocessable_entity + end + end + + private + + def set_item + @item ||= Item.find_by_id(params[:id]) + if @item.blank? + render status: :not_found + end + end + + def item_params + params.require(:item).permit(:name, :price, :discount, :description, :state, :item_type, :image, :achievement_id) + end + + def authorize! + authorize @item + end +end diff --git a/app/controllers/v1/progresses_controller.rb b/app/controllers/v1/progresses_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..3b672257f54fbebf1919c298f57c44ceadf726b0 --- /dev/null +++ b/app/controllers/v1/progresses_controller.rb @@ -0,0 +1,44 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::ProgressesController < ApplicationController + include ::Paginator + + before_action :set_progress, only: :show + before_action :authenticate_user!, only: :index + + def index + progresses = paginate current_user.progresses + render json: progresses, each_serializer: ProgressSerializer + end + + def show + render json: @progress, serializer: ProgressSerializer + end + + private + + def set_progress + @progress ||= Progress.find_by_id(params[:id]) + if @progress.blank? + render status: :not_found + end + end + +end diff --git a/app/controllers/v1/requirements_controller.rb b/app/controllers/v1/requirements_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..d028b5e955661e4dfc652c561cfea92fb19d6286 --- /dev/null +++ b/app/controllers/v1/requirements_controller.rb @@ -0,0 +1,86 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::RequirementsController < ApplicationController + include ::Paginator + + before_action :set_requirement, only: [:show, :update, :destroy] + before_action :authenticate_user!, except: [:index, :show] + before_action :authorize!, only: [:update, :destroy] + + def index + requirements = paginate Requirement + render json: requirements + end + + def show + render json: @requirement + end + + def create + @requirement = Requirement.new(requirement_params) + authorize @requirement + + if @requirement.save + @requirement.add_achievements(extra_params[:achievements]) + render json: @requirement, status: :created + else + render json: @requirement.errors, status: :unprocessable_entity + end + end + + # PUT/PATCH /v1/requirements/1 + def update + if @requirement.update(requirement_params) + @requirement.update_achievements(extra_params[:achievements]) + render json: @requirement, status: :ok + else + render json: @requirement.errors, status: :unprocessable_entity + end + end + + # DELETE /v1/requirements/1 + def destroy + @requirement.destroy + response = { 'status': 'deleted' } + render status: :ok, json: response + end + + private + + def set_requirement + @requirement ||= Requirement.find_by_id(params[:id]) + if @requirement.blank? + render status: :not_found + end + end + + def requirement_params + params.require(:requirement).permit(:description, :goal, :repeatable, :action_id) + end + + def extra_params + return {} if params[:requirement].nil? + params[:requirement].permit(achievements: []) + end + + def authorize! + authorize @requirement + end +end diff --git a/app/controllers/v1/unlocked_achievements_controller.rb b/app/controllers/v1/unlocked_achievements_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..fd09b410adfe70547fe93f77552a8ed5e1d8621e --- /dev/null +++ b/app/controllers/v1/unlocked_achievements_controller.rb @@ -0,0 +1,44 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::UnlockedAchievementsController < ApplicationController + include ::Paginator + + before_action :set_achievement, only: :show + before_action :authenticate_user!, only: :index + + def index + achievements = paginate current_user.unlocked_achievements + render json: achievements + end + + def show + render json: @achievement + end + + private + + def set_achievement + @achievement ||= UnlockedAchievement.find_by_id(params[:id]) + if @achievement.blank? + render status: :not_found + end + end + +end diff --git a/app/controllers/v1/user_items_controller.rb b/app/controllers/v1/user_items_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..32034788219227e79835d42b518a2ab0c48035b1 --- /dev/null +++ b/app/controllers/v1/user_items_controller.rb @@ -0,0 +1,45 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class V1::UserItemsController < ApplicationController + include ::Paginator + include ::ItemsFilter + + before_action :set_user_item, only: :show + before_action :authenticate_user!, only: :index + + def index + items = paginate filter_items(current_user.items.active) + render json: items + end + + def show + render json: @user_item + end + + private + + def set_user_item + @user_item ||= UserItem.find_by_id(params[:id]) + if @user_item.blank? + render status: :not_found + end + end + +end diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index cfef65445dd3460b651f8663238764fdecc68e96..138c45b5de824b2235d7544ca47c0c839174694c 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -22,15 +22,103 @@ class V1::UsersController < ApplicationController include ::DeletedObjectsController include ::Paginator include ::PublisherController - include ::SubjectableController + include ::SubjectableController + include ::ItemsFilter - before_action :set_user, only: [:show, :update, :destroy, :following, :own_reviews, :received_reviews, :followers, :add_teacher, :remove_teacher, :reactivate_user] + before_action :set_user, only: [:show, :update, :destroy, :following, :own_reviews, :received_reviews, :followers, :add_teacher, :remove_teacher, :reactivate_user, :items] before_action :set_new_user, only: [:teacher_requests, :teacher_request] before_action :authenticate_user!, only: [:create, :update, :destroy, :following, :own_reviews, :received_reviews, :followers, :create_teacher_request, :update_teacher_request, :teacher_requests, :add_teacher, :remove_teacher, :reactivate_user] before_action :authorize_user, only: [:own_reviews, :received_reviews, :update, :destroy, :teacher_requests, :add_teacher, :remove_teacher] + # gamification + # POST /v1/users/complete_action + def complete_action + # current_user completes an action + # action counters and user progresses for achievements are updated, + # and if an achievement was conquered its rewards are given + quantity = params[:quantity].to_i > 1 ? params[:quantity].to_i : 1 + + success, response = current_user.complete_action(Action.where(id: params[:action_id]).first, quantity) + status = success ? :ok : :unprocessable_entity + + render json: response, status: status + end + + # POST /v1/users/purchase_item + def purchase_item + # purchases given item for current user + item = Item.find_by(id: params[:item_id]) + user_item = UserItem.where(user: current_user, item: item).first + if !user_item.blank? + render json: { "error": "You already own this item." }, status: :unprocessable_entity + else + if current_user.points >= item.price - item.discount + current_user.points -= item.price - item.discount + if !current_user.save + render json: current_user.errors, status: :unprocessable_entity + else + current_user.add_user_item(item) + render json: current_user.user_items, status: :ok + end + else + render json: { "error": "You don't have the required points for this item." }, status: :unprocessable_entity + end + end + end + + # POST /v1/users/equip_item + def equip_item + if current_user.equip_item(Item.find_by(id: params[:item_id])) + render json: current_user.user_items, status: :ok + else + render json: current_user.errors, status: :unprocessable_entity + end + end + + # POST /v1/users/unequip_item + def unequip_item + if current_user.unequip_item(Item.find_by(id: params[:item_id])) + render json: current_user.user_items, status: :ok + else + render json: current_user.errors, status: :unprocessable_entity + end + end + + # POST /v1/users/remove_item + def remove_item + if current_user.remove_item(Item.find_by(id: params[:item_id])) + render json: current_user.user_items, status: :ok + else + render json: current_user.errors, status: :unprocessable_entity + end + end + + # GET /v1/users/action_counters + def action_counters + # retorna action_counters e contagens correspondentes + render json: paginate(current_user.action_counters), status: :ok + end + + # GET /v1/users/completed_achievements + def completed_achievements + render json: paginate(current_user.unlocked_achievements), status: :ok + end + + # GET /v1/users/:id/items + def items + query = {} + query[:items] = { item_type: params[:item_type] } if Item.item_types.keys.include? params[:item_type] + query[:being_used] = params[:being_used] if ["true", "false"].include? params[:being_used] + + items = @user.user_items.includes(:item).where(query) + + render json: paginate(items), status: :ok + end +# +# end gamification + # GET /v1/users # GET /v1/users.json def index @@ -139,6 +227,7 @@ class V1::UsersController < ApplicationController @user.submitter_request = :accepted @user.roles << Role.where(name: "publisher") if @user.save + @user.complete_action(Action.find_by_name("Autenticação de Professor")) TeacherMailer.new_teacher_approved(@user).deliver_now render status: :ok else @@ -200,6 +289,10 @@ class V1::UsersController < ApplicationController private + def xp_to_level(x) + (100 + Math.log([x, 900].min, 1.07)).floor + end + def deleted_resource User end diff --git a/app/models/achievement.rb b/app/models/achievement.rb new file mode 100644 index 0000000000000000000000000000000000000000..f6175da002c0750d3947f5e770fea91df5cb8e9f --- /dev/null +++ b/app/models/achievement.rb @@ -0,0 +1,76 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: achievement + +# t.string :name +# t.string :description +# t.integer :reward_experience +# t.integer :reward_points +# t.integer :state +# t.integer :repeatable +# t.boolean :resettable +# t.datetime :created_at +# t.datetime :updated_at + +class Achievement < ApplicationRecord + has_many :items + has_many :unlocked_achievements + has_many :users, through: :unlocked_achievements + has_and_belongs_to_many :requirements + + enum state: [:inactive, :active, :deleted] + enum repeatable: [:never, :daily, :weekly, :monthly, :yearly] + + validates_presence_of :name, :description, :reward_experience, :reward_points, :state, :repeatable + + def add_requirements(ids=[]) + errors = [] + ids.each do |requirement_id| + if !requirement_ids.include?(requirement_id) + requirement = Requirement.where(id: requirement_id).first + if !requirement.blank? + requirements << requirement + else + errors << {error_type:"inexistent", id: requirement_id} + end + else + errors << {error_type:"repeated", id: requirement_id} + end + end + + return errors + end + + def remove_requirements(ids=[]) + ids.each do |requirement_id| + requirement = requirements.where(id: requirement_id).first + if !requirement.blank? + self.requirements.delete(requirement) + end + end + end + + def update_requirements(ids) + ids ||= [] + add_requirements(ids - requirement_ids) + remove_requirements(requirement_ids - ids) + end +end diff --git a/app/models/action.rb b/app/models/action.rb new file mode 100644 index 0000000000000000000000000000000000000000..33cbf2dfc0fc4325ef215348f19c0cec94fd13e5 --- /dev/null +++ b/app/models/action.rb @@ -0,0 +1,35 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: action + +# t.integer "name" +# t.integer "description" +# t.datetime "created_at" +# t.datetime "updated_at" +# t.integer "reward_experience" + +class Action < ApplicationRecord + has_many :requirements + has_many :action_counters + has_many :users, through: :action_counters + + validates_presence_of :name, :description +end diff --git a/app/models/action_counter.rb b/app/models/action_counter.rb new file mode 100644 index 0000000000000000000000000000000000000000..bf221cae4a32d1e6f51db47e0593d8986ba2bf4a --- /dev/null +++ b/app/models/action_counter.rb @@ -0,0 +1,32 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: action_counter + +# t.belongs_to :action, index: true +# t.belongs_to :user, index:true +# t.integer :counter, default: 0 + +class ActionCounter < ApplicationRecord + belongs_to :action + belongs_to :user + + validates_presence_of :user, :action +end diff --git a/app/models/collection.rb b/app/models/collection.rb index 1a4d039e32677b0a993810c643d33302ac71e67d..a9a145d26deea2805bfa2992ed663ca823c1ada5 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -67,6 +67,7 @@ class Collection < ApplicationRecord validates :name, :owner, presence: true validates_inclusion_of :privacy, in: %w(public private), message: 'Privacy must be public or private' + after_create :create_action before_destroy :delete_index scope :from_user, ->(user) { where(owner: user) } @@ -108,7 +109,7 @@ class Collection < ApplicationRecord end def should_index? - deleted_at.nil? + deleted_at.nil? end def delete_index @@ -173,4 +174,8 @@ class Collection < ApplicationRecord def ignore_changes super + %w(score views_count downloads_count likes_count shares_count follows_count) end + + def create_action + owner.complete_action(Action.find_by_name("Criar Coleção")) + end end diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb index 9b421358dbd0a3d9c447f835c6ef2dc5876b4676..9088f56896955cf4f2ce8d8fcd6ebb0917a2395e 100644 --- a/app/models/collection_item.rb +++ b/app/models/collection_item.rb @@ -34,6 +34,8 @@ class CollectionItem < ApplicationRecord # *current_user* add item *collectionable* to *collection* include Trackable + after_create :create_action + belongs_to :collection belongs_to :collectionable, polymorphic: true @@ -58,4 +60,9 @@ class CollectionItem < ApplicationRecord collectionable_type == 'LearningObject' ? LearningObject.find(collectionable_id).default_thumbnail : Collection.find(collectionable_id).thumbnail end + private + + def create_action + collection.owner.complete_action(Action.find_by_name("Adicionar Recurso a Coleção")) if collectionable_type == "LearningObject" + end end diff --git a/app/models/concerns/trackable.rb b/app/models/concerns/trackable.rb index debab1e936ba955ebf07231c6128ab9d8ae58465..97a01079e19e7eb973b82cd52a3f0957ac6ef9f5 100644 --- a/app/models/concerns/trackable.rb +++ b/app/models/concerns/trackable.rb @@ -29,9 +29,9 @@ module Trackable end def new_update_activity - return nil if previous_changes.blank? + return nil if saved_changes.blank? return new_activity(:update) if ignore_changes == %w(updated_at) - filtered = previous_changes.reject { |x| ignore_changes.include?(x) } + filtered = saved_changes.reject { |x| ignore_changes.include?(x.to_s) } new_activity(:update) unless filtered.empty? end diff --git a/app/models/download.rb b/app/models/download.rb index 7f7d8d8bc94e2da66b5066b0961ea90606f1d060..6f19bdd7ac04837e22bc93d023044c3eef4de4e6 100644 --- a/app/models/download.rb +++ b/app/models/download.rb @@ -33,6 +33,8 @@ class Download < ApplicationRecord # *current_user* download *downloadable* include Trackable + after_create :create_action + belongs_to :downloadable, polymorphic: true, counter_cache: true belongs_to :user, optional: true @@ -47,4 +49,8 @@ class Download < ApplicationRecord def new_create_activity new_activity(:create) unless user.nil? end + + def create_action + user.complete_action(Action.find_by_name("Fazer Download de um Recurso")) if !user.nil? && downloadable_type == "LearningObject" + end end diff --git a/app/models/follow.rb b/app/models/follow.rb index f6236a43e469288f4f44cf63c3f0169855aad91a..cc0d6a13d27cd370eeb2ed662321f778b7f30644 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -33,6 +33,8 @@ class Follow < ApplicationRecord # *current_user* follows *followable* include Trackable + after_create :create_action + belongs_to :followable, polymorphic: true, counter_cache: true belongs_to :user @@ -42,4 +44,13 @@ class Follow < ApplicationRecord def recipient followable end + + private + + def create_action + if followable_type == "User" + user.complete_action(Action.find_by_name("Seguir Usuário")) + followable.complete_action(Action.find_by_name("Ser Seguido")) + end + end end diff --git a/app/models/item.rb b/app/models/item.rb new file mode 100644 index 0000000000000000000000000000000000000000..51e40f5508cd88b07fe9bbb5df2376ac43334b96 --- /dev/null +++ b/app/models/item.rb @@ -0,0 +1,56 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: items + +# id: integer +# name: string +# price: integer +# discount: integer +# description: string +# state: integer +# item_type: integer +# image_file_name: string +# image_content_type: string +# image_file_size: integer +# image_updated_at: datetime +# created_at: datetime +# updated_at: datetime + +class Item < ApplicationRecord + belongs_to :achievement, optional: true + has_many :user_items + has_many :users, through: :user_items + + enum state: [:inactive, :active, :removed] + enum item_type: [:avatar_frame, :badge, :card_frame, :cover_frame] + + validates_presence_of :name, :price, :description, :item_type + + # Change to reasonable sizes after the decision by the designers. + # Use conditional size for each item_type accordingly to the design decision (https://github.com/thoughtbot/paperclip/wiki/Conditionally-resizing-images). + has_attached_file :image, styles: {:thumb => "100x100#", :small => "150x150>", :medium => "200x200"} + validates_attachment_content_type :image, content_type: ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'] + validates_attachment_presence :image + + def achievable? + achievement_id? + end +end diff --git a/app/models/like.rb b/app/models/like.rb index 8cd755c0cb480c5cc1a02c1679f34a9dbe3094af..7b1a273890cbde17d0c06740845161ede19c6dff 100644 --- a/app/models/like.rb +++ b/app/models/like.rb @@ -34,6 +34,8 @@ class Like < ApplicationRecord # *current_user* unlikes *likeable* include Trackable + after_create :create_action + belongs_to :likeable, polymorphic: true, counter_cache: true belongs_to :user, counter_cache: true @@ -43,4 +45,10 @@ class Like < ApplicationRecord def recipient likeable end + + private + + def create_action + user.complete_action(Action.find_by_name("Favoritar")) + end end diff --git a/app/models/progress.rb b/app/models/progress.rb new file mode 100644 index 0000000000000000000000000000000000000000..8370eb4f98d4cdb88afd8fcee00249e4418ca005 --- /dev/null +++ b/app/models/progress.rb @@ -0,0 +1,34 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: progress + +# t.belongs_to :user +# t.belongs_to :achievement +# t.belongs_to :progress_type +# t.integer :counter, default: 0 + +class Progress < ApplicationRecord + belongs_to :requirement + belongs_to :user + + validates_presence_of :user, :requirement + scope :user_progresses, -> (id) { where(user_id: id) } +end diff --git a/app/models/requirement.rb b/app/models/requirement.rb new file mode 100644 index 0000000000000000000000000000000000000000..58d1a41b5c95df284bf16efcbda620e86d38681f --- /dev/null +++ b/app/models/requirement.rb @@ -0,0 +1,75 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: progress_type + +# t.belongs_to "action" +# t.belnogs_to "achievement" +# t.string "description" +# t.integer "goal" +# t.boolean "repeatable" + +# t.datetime "created_at" +# t.datetime "updated_at" + +class Requirement < ApplicationRecord + belongs_to :action + has_many :progresses + has_many :users, through: :progresses + has_and_belongs_to_many :achievements + + accepts_nested_attributes_for :progresses, allow_destroy: true + + validates_presence_of :goal, :description, :action + + def add_achievements(ids=[]) + errors = [] + ids.each do |achievement_id| + if !achievement_ids.include?(achievement_id) + achievement = Achievement.where(id: achievement_id).first + if !achievement.blank? + achievements << achievement + else + errors << {error_type:"inexistent", id: achievement_id} + end + else + errors << {error_type:"repeated", id: achievement_id} + end + end + + return errors + end + + def remove_achievements(ids=[]) + ids.each do |achievement_id| + achievement = achievements.where(id: achievement_id).first + if !achievement.blank? + self.achievements.delete(achievement) + end + end + end + + def update_achievements(ids) + ids ||= [] + add_achievements(ids - achievement_ids) + remove_achievements(achievement_ids - ids) + end +end diff --git a/app/models/review.rb b/app/models/review.rb index 9a4d44ba5e838d8e5bc37cce46b9dcc51a50ea92..b1e47ebdabf016803d75eed313317e7cc17314d9 100644 --- a/app/models/review.rb +++ b/app/models/review.rb @@ -41,6 +41,7 @@ class Review < ApplicationRecord after_save :calculate_review_rate after_destroy :calculate_review_rate + after_create :create_action belongs_to :reviewable, polymorphic: true belongs_to :user @@ -96,4 +97,9 @@ class Review < ApplicationRecord # ReviewAverageCalculatorWorker.perform_in(1.minutes, reviewable.id, reviewable.class.name) end + def create_action + user.complete_action(Action.find_by_name("Avaliar")) + user.complete_action(Action.find_by_name("Comentar")) if !description.blank? + end + end diff --git a/app/models/role.rb b/app/models/role.rb index b44c81af046e38d9e22723d3016445fc427a51ab..3a25f8812e181a65d37e3691c3ea6943423ed60b 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -31,6 +31,7 @@ class Role < ApplicationRecord has_and_belongs_to_many :users has_and_belongs_to_many :emails + has_and_belongs_to_many :achievements validates :name, presence: true, uniqueness: true diff --git a/app/models/unlocked_achievement.rb b/app/models/unlocked_achievement.rb new file mode 100644 index 0000000000000000000000000000000000000000..84a33349750dea44d2ae5ae6d60c8ea10323a424 --- /dev/null +++ b/app/models/unlocked_achievement.rb @@ -0,0 +1,31 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: unlocked_achievement + +# t.belongs_to :achievement, index: true +# t.belongs_to :user, index:true + +class UnlockedAchievement < ApplicationRecord + belongs_to :achievement + belongs_to :user + + validates_presence_of :achievement, :user +end diff --git a/app/models/user.rb b/app/models/user.rb index fd7756204827b4690366214de65beea23577ba20..05c0df2fcb1892ab2db2cdcf1d5c4b3f3cb29cdf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -95,10 +95,23 @@ class User < ApplicationRecord has_many :curator_assignments has_many :assignments, through: :curator_assignments, source: :submission + has_many :user_items + has_many :items, through: :user_items + + has_many :progresses + has_many :requirements, through: :progresses + + has_many :unlocked_achievements + has_many :achievements, through: :unlocked_achievements + + has_many :action_counters + has_many :actions, through: :action_counters + after_create :default_role before_save :verify_teacher_id after_save :verify_dspace_info before_destroy :delete_index + after_update :create_actions has_attached_file :avatar, styles: { medium: '300x300>', thumb: '60x60>' }, default_url: '' validates_attachment_content_type :avatar, content_type: ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'] @@ -235,6 +248,132 @@ class User < ApplicationRecord 'reviews.reviewable_id = learning_objects.id') end + # ~~~~ gamification actions ~~~~ # + + def add_user_item(item) + UserItem.where(user_id: self.id, item_id: item.id).first_or_create + end + + def equip_item(item) + user_item = add_user_item(item) + equipped_items = user_items.includes(:item).where(items: {item_type: item.item_type}, being_used: :true) + + if((item.item_type == "badge" && equipped_items.count == 3) || (item.item_type != "badge" && equipped_items.count > 0)) + unequip_item(equipped_items.first) + end + user_item.being_used = true + user_item.save + end + + def unequip_item(item) + user_item = user_items.where(item_id: item.id).first + user_item.being_used = false + user_item.save + end + + def remove_item(item) + UserItem.where(user_id: self.id, item_id: item.id).destroy_all + end + + def xp_to_level(lvl) + # necessary xp to reach level x from level x-1 + (100 + Math.log([lvl, 900].min, 1.07)).floor + end + + def xp_to_next_level + # necessary xp for the user to reach next level + xp_to_level(level + 1) - experience + end + + def calculate_level(xp) + necessary_xp = xp_to_level(level + 1) + new_level = self.level + while xp >= necessary_xp + new_level += 1 + xp -= necessary_xp + necessary_xp = xp_to_level(level + 1) + end + return new_level, xp + end + + def experience=(xp) + new_level, remaining_xp = calculate_level(xp) + self.level = new_level + super(remaining_xp) + end + + def get_achievement(achievement) + return false if !achievement.active? + + unlocked_achievements << UnlockedAchievement.create(user_id: self.id, achievement_id: achievement.id) + + achievement.items.each do |item| + add_user_item(item) + end + + self.points += achievement.reward_points + self.experience += achievement.reward_experience + self.save + + progresses.includes(requirement: :achievements).where(achievements:{ id: achievement.id }).each do |progress| + if achievement.repeatable != "never" + progress.update(counter: 0) + end + end + end + + def complete_action(action, quantity=1) + return false, { "error": "action not found"} if action.blank? + + action_counter = action_counters.where(action: action).first_or_create + action_counter.counter += quantity + + if !action_counter.save + return false, action_counter.errors + else + self.update(experience: experience + (action.reward_experience*quantity)) + action.requirements.each do |requirement| + progress = progresses.where(requirement: requirement).first_or_create + progress.update(counter: progress.counter + quantity) + requirement.achievements.each do |achievement| + if progress.counter >= requirement.goal && unlocked_achievements.where(achievement: achievement).blank? + incomplete = false + achievement_progresses = progresses.includes(requirement: :achievements).where(achievements:{ id: achievement.id }) + achievement_progresses.each do |progress| + incomplete = true if progress.counter < progress.requirement.goal + end + progress.update(counter: progress.counter - requirement.goal) if requirement.repeatable != "never" + get_achievement(achievement) if !incomplete + end + end + end + return true, action_counter + end + end + + def create_actions + if saved_changes.include?("current_sign_in_at") + months_since_creation = ((Time.now - created_at)/1.month).to_i + month_action = Action.find_by_name("Meses de Conta") + month_counter = action_counters.where(action: month_action).first_or_create.counter + + complete_action(month_action, months_since_creation - month_counter) if months_since_creation >= 1 && months_since_creation > month_counter + + complete_action(Action.find_by_name("Fazer Login")) if current_sign_in_at.to_date != last_sign_in_day.to_date + + update(last_sign_in_day: current_sign_in_at) if current_sign_in_at.to_date > last_sign_in_day.to_date + + elsif saved_changes.include?("avatar") + complete_action(Action.find_by_name("Adicionar Foto de Perfil")) + elsif saved_changes.include?("cover") + complete_action(Action.find_by_name("Adicionar Capa de Perfil")) + elsif saved_changes.include?("description") + complete_action(Action.find_by_name("Adicionar Descrição do Usuário")) + end + end + + # ~~~~ end of gamification actions ~~~~ # + # ~~~~ followable actions ~~~~ # # An user can follow anothers users and collections # Examples: diff --git a/app/models/user_item.rb b/app/models/user_item.rb new file mode 100644 index 0000000000000000000000000000000000000000..c45cc51626457a215f9cb3fdf430ee0b56474af2 --- /dev/null +++ b/app/models/user_item.rb @@ -0,0 +1,42 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +# == Schema Information +# +# Table name: inventory + +# t.belongs_to :user, index: true +# t.belongs_to item, index: true +# t.boolean :being_used, default: false +# t.datetime "created_at" +# t.datetime "updated_at" + +class UserItem < ApplicationRecord + belongs_to :user + belongs_to :item + + validates_presence_of :item, :user + + def unlocked_by_achievement? + self.item.achievable? + end + + def unlocked_by_purchase? + !self.item.achievable? + end +end diff --git a/app/models/view.rb b/app/models/view.rb index c6e4f9f1fc93726f5d0c2723080ace7bed326853..c18f1b5cb9927e57e2def31abb15b779dc454d0b 100644 --- a/app/models/view.rb +++ b/app/models/view.rb @@ -40,6 +40,7 @@ class View < ApplicationRecord validates_presence_of :viewable, :ip before_create :current_time_greater_than_last + after_create :create_action def recipient viewable @@ -56,4 +57,16 @@ class View < ApplicationRecord true end + + def create_action + if !user.nil? + if viewable_type == "LearningObject" + user.complete_action(Action.find_by_name("Visualizar um Recurso")) + viewable.subjects.each do |subject| + user.complete_action(Action.find_by_name("Visualizar um Recurso de #{subject.name}")) + end + end + user.complete_action(Action.find_by_name("Visualizar uma Coleção")) if viewable_type == "Collection" + end + end end diff --git a/app/policies/achievement_policy.rb b/app/policies/achievement_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..f00634b01739b78cb54031e5739b3008f907a0e6 --- /dev/null +++ b/app/policies/achievement_policy.rb @@ -0,0 +1,42 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class AchievementPolicy < ApplicationPolicy + + def index? + record + end + + def show? + record + end + + def create? + record if user_exists? && user_can_edit? + end + + def update? + record if user_exists? && user_can_edit? + end + + def destroy? + record if user_exists? && user_can_edit? + end + +end diff --git a/app/policies/action_policy.rb b/app/policies/action_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..fe5440762a6826ff729e63e0cb1801f2949b7199 --- /dev/null +++ b/app/policies/action_policy.rb @@ -0,0 +1,41 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class ActionPolicy < ApplicationPolicy + + def index? + record + end + + def show? + record + end + + def create? + record if user_exists? && user_can_edit? + end + + def update? + record if user_exists? && user_can_edit? + end + + def destroy? + record if user_exists? && user_can_edit? + end +end diff --git a/app/policies/item_policy.rb b/app/policies/item_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..a70e971b339166468d164abd2c5719d67d565d14 --- /dev/null +++ b/app/policies/item_policy.rb @@ -0,0 +1,53 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class ItemPolicy < ApplicationPolicy + + class Scope < Scope + def resolve + if user.nil? + scope.where(state: 'active') + elsif user_can_edit? + scope.all + else + scope.where(state: 'active') + end + end + end + + def index? + record + end + + def show? + record + end + + def create? + record if user_can_edit? + end + + def update? + record if user_can_edit? + end + + def destroy? + record if user_can_edit? + end +end diff --git a/app/policies/requirement_policy.rb b/app/policies/requirement_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..bd3823fc7053ac0e11ccfd88bb1eaf9878116b88 --- /dev/null +++ b/app/policies/requirement_policy.rb @@ -0,0 +1,42 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class RequirementPolicy < ApplicationPolicy + + def index? + record + end + + def show? + record + end + + def create? + record if user_exists? && user_can_edit? + end + + def update? + record if user_exists? && user_can_edit? + end + + def destroy? + record if user_exists? && user_can_edit? + end + +end diff --git a/app/serializers/achievement_serializer.rb b/app/serializers/achievement_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..a742ba6ac8b59367a995d138bb7f4964d0549ee5 --- /dev/null +++ b/app/serializers/achievement_serializer.rb @@ -0,0 +1,35 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class AchievementSerializer < ActiveModel::Serializer + cache key: 'achievement', expires_in: 24.hours + + attributes \ + :id, + :name, + :description, + :reward_experience, + :reward_points, + :state, + :created_at, + :updated_at, + :repeatable, + :resettable + + has_many :requirements +end diff --git a/app/serializers/action_counter_serializer.rb b/app/serializers/action_counter_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..ce998ed95ae72213d620eec58b7bff64a8397120 --- /dev/null +++ b/app/serializers/action_counter_serializer.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class ActionCounterSerializer < ActiveModel::Serializer + cache key: 'action_counter', expires_in: 24.hours + + attributes \ + :id, + :counter, + :created_at, + :updated_at + + belongs_to :action + belongs_to :user +end diff --git a/app/serializers/action_serializer.rb b/app/serializers/action_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..3755dc36fd7782122a7661e699bbdf10dfcd66f8 --- /dev/null +++ b/app/serializers/action_serializer.rb @@ -0,0 +1,31 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class ActionSerializer < ActiveModel::Serializer + cache key: 'action', expires_in: 24.hours + + attributes \ + :id, + :name, + :description, + :created_at, + :updated_at + + has_many :requirements + has_many :action_counters +end diff --git a/app/serializers/item_serializer.rb b/app/serializers/item_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..b282ca8b78706161a98579c9322cbae1f6f8b239 --- /dev/null +++ b/app/serializers/item_serializer.rb @@ -0,0 +1,34 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class ItemSerializer < ActiveModel::Serializer + cache key: 'item', expires_in: 4.hours, except: [:image] + + attributes \ + :id, + :name, + :price, + :discount, + :description, + :state, + :item_type, + :achievement, + :image, + :created_at, + :updated_at +end diff --git a/app/serializers/progress_serializer.rb b/app/serializers/progress_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..dc908110c3ba128681a40c6eea2a49c83625aafe --- /dev/null +++ b/app/serializers/progress_serializer.rb @@ -0,0 +1,31 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class ProgressSerializer < ActiveModel::Serializer + cache key: 'progress', expires_in: 24.hours + + attributes \ + :id, + :counter, + :created_at, + :updated_at + + belongs_to :requirement + belongs_to :user + end + \ No newline at end of file diff --git a/app/serializers/requirement_serializer.rb b/app/serializers/requirement_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..9fd59a4a62b8f467d7e7dfd98c78a4ca0dc2de7e --- /dev/null +++ b/app/serializers/requirement_serializer.rb @@ -0,0 +1,33 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class RequirementSerializer < ActiveModel::Serializer + cache key: 'requirement', expires_in: 24.hours + + attributes \ + :id, + :description, + :goal, + :repeatable, + :created_at, + :updated_at + + belongs_to :action + has_many :achievements + has_many :progresses +end diff --git a/app/serializers/unlocked_achievement_serializer.rb b/app/serializers/unlocked_achievement_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..962877106bee9ea73253a498506c35a180df1264 --- /dev/null +++ b/app/serializers/unlocked_achievement_serializer.rb @@ -0,0 +1,26 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class UnlockedAchievementSerializer < ActiveModel::Serializer + cache key: 'unlocked_achievement', expires_in: 4.hours + + attributes :id, :created_at, :updated_at + belongs_to :achievement + belongs_to :user +end diff --git a/app/serializers/user_item_serializer.rb b/app/serializers/user_item_serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..7e5c85ade40274cc684130f42f85444e5818a35d --- /dev/null +++ b/app/serializers/user_item_serializer.rb @@ -0,0 +1,26 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +class UserItemSerializer < ActiveModel::Serializer + cache key: 'user_item', expires_in: 4.hours, except: [:being_used] + + attributes :id, :being_used, :created_at, :updated_at + belongs_to :item + belongs_to :user +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 630c6aa7307ca6508fb3f4d1f1147efe0c91ef44..7c3ecdca8ec7b244d853dfa293dc30c3c9d04247 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -17,7 +17,7 @@ # along with portalmec. If not, see <http://www.gnu.org/licenses/>. class UserSerializer < ActiveModel::Serializer - cache key: 'user', expires_in: 4.hours, except: [:email, :complained, :followed ] + cache key: 'user', expires_in: 4.hours, except: [:email, :complained, :followed, :level, :level_xp, :experience, :points ] def complained object.complained? current_user @@ -69,6 +69,16 @@ class UserSerializer < ActiveModel::Serializer !current_user.nil? && current_user.is_supervisor? end + def level_xp + object.xp_to_level(object.level + 1) + end + + def user_items + items = object.user_items.where(being_used: true) + ActiveModel::SerializableResource.new(items, {scope: current_user, scope_name: :current_user, each_serializer: ::UserItemSerializer}).serializable_hash + end + + attributes \ :id, :email, @@ -95,7 +105,11 @@ class UserSerializer < ActiveModel::Serializer :created_at, :updated_at, :terms_accepted_at, - :state + :state, + :level, + :level_xp, + :experience, + :points attribute \ :times_blocked, if: :is_current_user? @@ -113,4 +127,5 @@ class UserSerializer < ActiveModel::Serializer has_many :subjects has_many :roles has_many :institutions + has_many :user_items end diff --git a/app/services/learning_object_publisher.rb b/app/services/learning_object_publisher.rb index 830bd7cfd34fa67da3bfd73bb02acca5bf996491..5515fc9541ba0169a08080d77637c9df3ca5d095 100644 --- a/app/services/learning_object_publisher.rb +++ b/app/services/learning_object_publisher.rb @@ -52,6 +52,7 @@ class LearningObjectPublisher learning_object.published_at = Time.current learning_object.save learning_object.new_activity(:publish) + learning_object.publisher.complete_action(Action.find_by_name("Publicar")) if learning_object.link? && learning_object.thumbnail.blank? att = learning_object.attachments.create(retrieve_link: learning_object.link, bundle_name: "LINK") ThumbnailGenerateWorker.perform_async(att.id) diff --git a/app/services/search_service/collection.rb b/app/services/search_service/collection.rb index 0106b3a0c358b0ddef30e02b631231a93e3c1ada..3ce50430c0fd10402a6e487942abd582335497ea 100644 --- a/app/services/search_service/collection.rb +++ b/app/services/search_service/collection.rb @@ -24,7 +24,7 @@ module SearchService end def autocomplete - params = { where: { privacy: 'public' }, + params = { where: { privacy: 'public', empty: false }, fields: ['name^10', 'description', 'owner'] } result = ::Collection.search(@search.query, autocomplete_params.merge(params)) diff --git a/config/initializers/rspec_api_docs.rb b/config/initializers/rspec_api_docs.rb new file mode 100644 index 0000000000000000000000000000000000000000..c938e0781617aa19630ae2691bd8cc6e99f4f69a --- /dev/null +++ b/config/initializers/rspec_api_docs.rb @@ -0,0 +1,28 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +if !Rails.env.production? + module RspecApiDocumentation + class RackTestClient < ClientBase + def response_body + last_response.body.encode("utf-8") + end + end + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index eff4781f2380b2603090ab0e88ff0d1988c7910a..d8d97c45cd6ecedd6a68d62fd1a1e209837a7f69 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -129,11 +129,18 @@ Rails.application.routes.draw do end namespace :v1 do + resources :unlocked_achievements, :user_items, :progresses, \ + :action_counters, only: [:index, :show] + resources :achievements + resources :items + resources :actions + resources :requirements + resources :activities, only: [:index, :show] do - collection do - get 'me' - post 'view' - end + collection do + get 'me' + post 'view' + end end resources :feed, only: [:index] @@ -148,11 +155,21 @@ Rails.application.routes.draw do put 'reactivate_user', to: 'users#reactivate_user' post 'add_teacher', to: 'users#add_teacher' delete 'remove_teacher', to: 'users#remove_teacher' + get 'items' end collection do get 'teacher_requests' post 'teacher_request', to: 'users#create_teacher_request' put 'teacher_request', to: 'users#update_teacher_request' + # Gamefication actions + post 'complete_action' + post 'purchase_item' + post 'equip_item' + post 'unequip_item' + post 'remove_item' + get 'update_level' + get 'action_counters' + get 'completed_achievements' end end diff --git a/db/migrate/20210129141847_create_items.rb b/db/migrate/20210129141847_create_items.rb new file mode 100644 index 0000000000000000000000000000000000000000..ac745c7ebf60abb5c302855859a9d0ebdcc1234b --- /dev/null +++ b/db/migrate/20210129141847_create_items.rb @@ -0,0 +1,15 @@ +class CreateItems < ActiveRecord::Migration[6.0] + def change + create_table :items do |t| + t.string :name + t.integer :price + t.integer :discount, default: 0 + t.text :description + t.integer :state, default: 0 + t.integer :item_type + t.attachment :image + + t.timestamps + end + end +end diff --git a/db/migrate/20210129143448_create_achievements.rb b/db/migrate/20210129143448_create_achievements.rb new file mode 100644 index 0000000000000000000000000000000000000000..1e47ff1a418f14cef8e9fa67e2d36f8050216ba2 --- /dev/null +++ b/db/migrate/20210129143448_create_achievements.rb @@ -0,0 +1,15 @@ +class CreateAchievements < ActiveRecord::Migration[6.0] + def change + create_table :achievements do |t| + t.string :name + t.text :description + t.integer :reward_experience + t.integer :reward_points + t.integer :state + t.integer :repeatable + t.boolean :resettable + + t.timestamps + end + end +end diff --git a/db/migrate/20210129143709_create_actions.rb b/db/migrate/20210129143709_create_actions.rb new file mode 100644 index 0000000000000000000000000000000000000000..5f452e504587bde0ccd8cfa638bd0aa3aacf5d7d --- /dev/null +++ b/db/migrate/20210129143709_create_actions.rb @@ -0,0 +1,11 @@ +class CreateActions < ActiveRecord::Migration[6.0] + def change + create_table :actions do |t| + t.string :name + t.text :description + t.integer :reward_experience + + t.timestamps + end + end +end diff --git a/db/migrate/20210129144002_create_requirements.rb b/db/migrate/20210129144002_create_requirements.rb new file mode 100644 index 0000000000000000000000000000000000000000..0bf30757b4ec6c7bc4197343193d1e72059574c2 --- /dev/null +++ b/db/migrate/20210129144002_create_requirements.rb @@ -0,0 +1,11 @@ +class CreateRequirements < ActiveRecord::Migration[6.0] + def change + create_table :requirements do |t| + t.text :description + t.integer :goal + t.integer :repeatable + + t.timestamps + end + end +end diff --git a/db/migrate/20210129144219_add_level_to_users.rb b/db/migrate/20210129144219_add_level_to_users.rb new file mode 100644 index 0000000000000000000000000000000000000000..b79234193bfaa344b64ff057ffcf0de7bb108346 --- /dev/null +++ b/db/migrate/20210129144219_add_level_to_users.rb @@ -0,0 +1,7 @@ +class AddLevelToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :experience, :integer, default: 0 + add_column :users, :level, :integer, default: 1 + add_column :users, :points, :integer, default: 0 + end +end diff --git a/db/migrate/20210129144650_create_user_items.rb b/db/migrate/20210129144650_create_user_items.rb new file mode 100644 index 0000000000000000000000000000000000000000..cbed350e409e2e2ee74d247d29d65b06fd137577 --- /dev/null +++ b/db/migrate/20210129144650_create_user_items.rb @@ -0,0 +1,12 @@ +class CreateUserItems < ActiveRecord::Migration[6.0] + def change + create_table :user_items do |t| + t.references :item, foreign_key: true + t.references :user, foreign_key: true + t.boolean :being_used, default: false + + t.timestamps + end + add_index :user_items, [:user_id, :item_id], unique: true, name: 'user_item_index' + end +end diff --git a/db/migrate/20210129144819_create_progresses.rb b/db/migrate/20210129144819_create_progresses.rb new file mode 100644 index 0000000000000000000000000000000000000000..c0b19bda1c199796385d72588020f50ea68cf71a --- /dev/null +++ b/db/migrate/20210129144819_create_progresses.rb @@ -0,0 +1,12 @@ +class CreateProgresses < ActiveRecord::Migration[6.0] + def change + create_table :progresses do |t| + t.references :user, foreign_key: true + t.references :requirement, foreign_key: true + t.integer :counter, default: 0 + + t.timestamps + end + add_index :progresses, [:user_id, :requirement_id], unique: true, name: 'user_requirement_index' + end +end diff --git a/db/migrate/20210129145903_create_action_counters.rb b/db/migrate/20210129145903_create_action_counters.rb new file mode 100644 index 0000000000000000000000000000000000000000..fc82411f9a1994969032a010c29eb43eea516fe6 --- /dev/null +++ b/db/migrate/20210129145903_create_action_counters.rb @@ -0,0 +1,12 @@ +class CreateActionCounters < ActiveRecord::Migration[6.0] + def change + create_table :action_counters do |t| + t.references :user, foreign_key: true + t.references :action, foreign_key: true + t.integer :counter, default: 0 + + t.timestamps + end + add_index :action_counters, [:user_id, :action_id], unique: true, name: 'user_action_index' + end +end diff --git a/db/migrate/20210129150315_create_unlocked_achievements.rb b/db/migrate/20210129150315_create_unlocked_achievements.rb new file mode 100644 index 0000000000000000000000000000000000000000..2ea402ab57a9d843f96af98a3b78c5b0579440ab --- /dev/null +++ b/db/migrate/20210129150315_create_unlocked_achievements.rb @@ -0,0 +1,10 @@ +class CreateUnlockedAchievements < ActiveRecord::Migration[6.0] + def change + create_table :unlocked_achievements do |t| + t.references :user, foreign_key: true + t.references :achievement, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20210129150529_create_join_table_achievement_requirement.rb b/db/migrate/20210129150529_create_join_table_achievement_requirement.rb new file mode 100644 index 0000000000000000000000000000000000000000..459f73d4374f189a41a70a1989f7415b8e275d19 --- /dev/null +++ b/db/migrate/20210129150529_create_join_table_achievement_requirement.rb @@ -0,0 +1,8 @@ +class CreateJoinTableAchievementRequirement < ActiveRecord::Migration[6.0] + def change + create_join_table :achievements, :requirements do |t| + t.timestamps + end + add_index :achievements_requirements, [:achievement_id, :requirement_id], unique: true, name: 'achievement_requirement_index' + end +end diff --git a/db/migrate/20210129151023_add_achievement_to_items.rb b/db/migrate/20210129151023_add_achievement_to_items.rb new file mode 100644 index 0000000000000000000000000000000000000000..92280802e0c43ebeb83fdd03616a6c6cea2fba7c --- /dev/null +++ b/db/migrate/20210129151023_add_achievement_to_items.rb @@ -0,0 +1,5 @@ +class AddAchievementToItems < ActiveRecord::Migration[6.0] + def change + add_reference :items, :achievement, foreign_key: true + end +end diff --git a/db/migrate/20210129151109_add_action_to_requirements.rb b/db/migrate/20210129151109_add_action_to_requirements.rb new file mode 100644 index 0000000000000000000000000000000000000000..3f6c0275bca96df37df10bc5532f10d5bd3bb592 --- /dev/null +++ b/db/migrate/20210129151109_add_action_to_requirements.rb @@ -0,0 +1,5 @@ +class AddActionToRequirements < ActiveRecord::Migration[6.0] + def change + add_reference :requirements, :action, foreign_key: true + end +end diff --git a/db/migrate/20210423135443_add_last_sign_in_day_to_user.rb b/db/migrate/20210423135443_add_last_sign_in_day_to_user.rb new file mode 100644 index 0000000000000000000000000000000000000000..a037c8d7cb16bee24c6db77adc5279e875699b11 --- /dev/null +++ b/db/migrate/20210423135443_add_last_sign_in_day_to_user.rb @@ -0,0 +1,5 @@ +class AddLastSignInDayToUser < ActiveRecord::Migration[6.0] + def change + add_column :users, :last_sign_in_day, :datetime, default: -> { 'CURRENT_TIMESTAMP' } + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 7b991bd611c001ac58647ac40922323703191f51..ccad4b9d6d902e55ac3f3dc79ad430a5a86881a7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -24,16 +24,16 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) -Role.create(name: 'teacher', description: 'This role represents a Teacher in Portal MEC.') -Role.create(name: 'student', description: 'This role represents a Student in Portal MEC.') -Role.create(name: 'admin', description: 'This role represents an MEC Admin, that can perform any action.') -Role.create(name: 'curator', description: 'This role represents a content Curator in Portal MEC.') -Role.create(name: 'moderator', description: 'This role represents a content Moderator in Portal MEC, with less privileges than admin.') -Role.create(name: 'supervisor', description: 'This role represents an user Supervisor in Portal MEC.') -Role.create(name: 'editor', description: 'This role represents a content Supervisor in Portal MEC, with less privileges than admin.') -Role.create(name: 'submitter', description: 'This role represents a content submitter in Portal MEC.') -Role.create(name: 'partner', description: 'This role represents a partner Portal MEC.') -Role.create(name: 'publisher', description: 'This role represents a content publisher without supervision in Portal MEC.') +# Role.where(name: 'teacher', description: 'This role represents a Teacher in Portal MEC.').first_or_create +# Role.where(name: 'student', description: 'This role represents a Student in Portal MEC.').first_or_create +Role.where(name: 'admin', description: 'This role represents an MEC Admin, that can perform any action.').first_or_create +Role.where(name: 'curator', description: 'This role represents a content Curator in Portal MEC.').first_or_create +Role.where(name: 'moderator', description: 'This role represents a content Moderator in Portal MEC, with less privileges than admin.').first_or_create +Role.where(name: 'supervisor', description: 'This role represents an user Supervisor in Portal MEC.').first_or_create +Role.where(name: 'editor', description: 'This role represents a content Supervisor in Portal MEC, with less privileges than admin.').first_or_create +Role.where(name: 'submitter', description: 'This role represents a content submitter in Portal MEC.').first_or_create +Role.where(name: 'partner', description: 'This role represents a partner Portal MEC.').first_or_create +Role.where(name: 'publisher', description: 'This role represents a content publisher without supervision in Portal MEC.').first_or_create # create the default admin User.create( @@ -53,3 +53,8 @@ require_relative 'seeds/ratings' require_relative 'seeds/scores' require_relative 'seeds/subjects' require_relative 'seeds/educational_stages' + +require_relative 'seeds/actions' +require_relative 'seeds/achievements' +require_relative 'seeds/requirements' +require_relative 'seeds/items' diff --git a/db/seeds/achievements.rb b/db/seeds/achievements.rb new file mode 100644 index 0000000000000000000000000000000000000000..3bd95b8a8e1e535ccacb62ec1724f62dc97c7ccc --- /dev/null +++ b/db/seeds/achievements.rb @@ -0,0 +1,101 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +def achievements + main_achievements = [ + { name: "Introduzido Nível I", description: "Assistir ao vídeo de apresentação na plataforma", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Introduzido Nível II", description: "Completar atividades básicas na plataforma", reward_experience: 75, reward_points: 50, state: 1, repeatable: 0, resettable: false }, + + { name: "Personalizado", description: "Personalizar seu perfil", reward_experience: 75, reward_points: 50, state: 1, repeatable: 0, resettable: false }, + + { name: "Professor", description: "Ser autenticado como professor", reward_experience: 75, reward_points: 100, state: 1, repeatable: 0, resettable: false }, + + { name: "Frequente Nível I", description: "Acessar a plataforma por 5 dias consecutivos", reward_experience: 75, reward_points: 50, state: 1, repeatable: 0, resettable: true }, + { name: "Frequente Nível II", description: "Acessar a plataforma por 15 dias consecutivos", reward_experience: 75, reward_points: 50, state: 1, repeatable: 0, resettable: true }, + { name: "Frequente Nível III", description: "Acessar a plataforma por 60 dias consecutivos", reward_experience: 75, reward_points: 50, state: 1, repeatable: 0, resettable: true }, + { name: "Frequente Nível IV", description: "Acessar a plataforma por 120 dias consecutivos", reward_experience: 75, reward_points: 50, state: 1, repeatable: 0, resettable: true }, + { name: "Frequente Nível V", description: "Acessar a plataforma por 365 dias consecutivos", reward_experience: 75, reward_points: 100, state: 1, repeatable: 0, resettable: true }, + + { name: "Nostálgico Nível I", description: "Usuário há 6 meses", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Nostálgico Nível II", description: "Usuário há 1 ano", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Nostálgico Nível III", description: "Usuário há 2 anos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Nostálgico Nível IV", description: "Usuário há 3 anos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Nostálgico Nível V", description: "Usuário há 4 anos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Contribuidor Nível I", description: "Publicar 1 recurso", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Contribuidor Nível II", description: "Publicar 10 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Contribuidor Nível III", description: "Publicar 50 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Contribuidor Nível IV", description: "Publicar 200 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Contribuidor Nível V", description: "Publicar 1000 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Dias Produtivos Nível I", description: "Publicar 2 recursos em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + { name: "Dias Produtivos Nível II", description: "Publicar 5 recursos em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + { name: "Dias Produtivos Nível III", description: "Publicar 10 recursos em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + { name: "Dias Produtivos Nível IV", description: "Publicar 20 recursos em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + { name: "Dias Produtivos Nível V", description: "Publicar 50 recursos em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + + { name: "Utilizador Assíduo Nível I", description: "Utilizar 10 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Utilizador Assíduo Nível II", description: "Utilizar 50 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Utilizador Assíduo Nível III", description: "Utilizar 200 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Utilizador Assíduo Nível IV", description: "Utilizar 1000 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Utilizador Assíduo Nível V", description: "Utilizar 5000 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Colecionador Nível I", description: "Utilizar 5 coleções", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Colecionador Nível II", description: "Utilizar 30 coleções", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Colecionador Nível III", description: "Utilizar 100 coleções", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Colecionador Nível IV", description: "Utilizar 1000 coleções", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Curador Nível I", description: "Avaliar 20 recursos com estrelas e comentários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Curador Nível II", description: "Avaliar 200 recursos com estrelas e comentários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Curador Nível III", description: "Avaliar 500 recursos com estrelas e comentários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Curador Nível IV", description: "Avaliar 1000 recursos com estrelas e comentários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + # { name: "Curador Nível V", description: "Avaliar 5000 recursos com estrelas e comentários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Curador Assíduo Nível I", description: "Avaliar 10 recursos com estrelas e comentários em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + { name: "Curador Assíduo Nível II", description: "Avaliar 20 recursos com estrelas e comentários em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + { name: "Curador Assíduo Nível III", description: "Avaliar 50 recursos com estrelas e comentários em um dia", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: true }, + + { name: "Conectado Nível I", description: "Seguir 10 usuários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Conectado Nível II", description: "Seguir 50 usuários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Conectado Nível III", description: "Seguir 200 usuários", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Conhecido Nível I", description: "Ter 10 seguidores", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Conhecido Nível II", description: "Ter 50 seguidores", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Conhecido Nível III", description: "Ter 200 seguidores", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + + { name: "Divulgador Nível I", description: "Compartilhar 1 recurso", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Divulgador Nível II", description: "Compartilhar 20 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false }, + { name: "Divulgador Nível III", description: "Compartilhar 100 recursos", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false } + ] + subject_achievements = [] + Subject.all.each do |subject| + subject_achievements << { name: "Proficiência em #{subject.name} Nível I", description: "Utilizar 20 recursos de #{subject.name}", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false } + subject_achievements << { name: "Proficiência em #{subject.name} Nível II", description: "Utilizar 50 recursos de #{subject.name}", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false } + subject_achievements << { name: "Proficiência em #{subject.name} Nível III", description: "Utilizar 150 recursos de #{subject.name}", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false } + subject_achievements << { name: "Proficiência em #{subject.name} Nível IV", description: "Utilizar 500 recursos de #{subject.name}", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false } + subject_achievements << { name: "Proficiência em #{subject.name} Nível V", description: "Utilizar 1000 recursos de #{subject.name}", reward_experience: 75, reward_points: 10, state: 1, repeatable: 0, resettable: false } + end + main_achievements + subject_achievements +end + +# xp 0 - 75 + +achievements.each do |achievement| + Achievement.where(achievement).first_or_create +end diff --git a/db/seeds/actions.rb b/db/seeds/actions.rb new file mode 100644 index 0000000000000000000000000000000000000000..46c040a34973c597b429a6fa3a0cc16959142bb8 --- /dev/null +++ b/db/seeds/actions.rb @@ -0,0 +1,57 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +def actions + main_actions = [ + {name: "Publicar", description: "Publicar um recurso na plataforma", reward_experience: 50}, + {name: "Favoritar", description: "Favoritar um recurso na plataforma", reward_experience: 1}, + {name: "Avaliar", description: "Avaliar um recurso na plataforma", reward_experience: 5}, + {name: "Comentar", description: "Comentar um recurso na plataforma", reward_experience: 5}, + {name: "Seguir Usuário", description: "Seguir um usuário na plataforma", reward_experience: 2}, + {name: "Ser Seguido", description: "Ser seguido por um usuário na plataforma", reward_experience: 10}, + {name: "Fazer Download de um Recurso", description: "Fazer download de um recurso na plataforma", reward_experience: 2}, + + # {name: "Curadoria de Recurso", description: "Realizar a curadoria de um recurso na plataforma", reward_experience: 5}, + + {name: "Fazer Login", description: "Fazer login na plataforma em um dia", reward_experience: 0}, + {name: "Meses de Conta", description: "Ter a conta por um mês na plataforma", reward_experience: 0}, + {name: "Visualizar um Recurso", description: "Visualizar um recurso na plataforma", reward_experience: 0}, + {name: "Visualizar uma Coleção", description: "Visualizar uma coleção na plataforma", reward_experience: 0}, + {name: "Criar Coleção", description: "Criar uma coleção na plataforma", reward_experience: 0}, + {name: "Adicionar Recurso a Coleção", description: "Adicionar um recurso a uma coleção na plataforma", reward_experience: 0}, + {name: "Adicionar Foto de Perfil", description: "Adicionar uma foto de perfil na sua conta na plataforma", reward_experience: 0}, + {name: "Adicionar Capa de Perfil", description: "Adicionar uma capa de perfil na sua conta na plataforma", reward_experience: 0}, + {name: "Adicionar Descrição do Usuário", description: "Adicionar uma descrição na sua conta na plataforma", reward_experience: 0}, + {name: "Autenticação de Professor", description: "Ser autenticado como professor na plataforma", reward_experience: 0}, + + {name: "Assistir Apresentação", description: "Assistir ao vídeo de apresentação na plataforma", reward_experience: 0}, + {name: "Visualizar um Material", description: "Visualizar um material de formação na plataforma", reward_experience: 0}, + {name: "Compartilhar", description: "Compartilhar um recurso na plataforma", reward_experience: 0} + ] + + subject_actions = [] + Subject.all.each do |subject| + subject_actions << {name: "Visualizar um Recurso de #{subject.name}", description: "Visualizar um recurso da área de #{subject.name} na plataforma", reward_experience: 0} + end + main_actions + subject_actions +end + +actions.each do |action| + Action.where(action).first_or_create +end diff --git a/db/seeds/items.rb b/db/seeds/items.rb new file mode 100644 index 0000000000000000000000000000000000000000..4053c035f09a3c7c642fca9b9fb393d9b831beb4 --- /dev/null +++ b/db/seeds/items.rb @@ -0,0 +1,33 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +def item_data(achievement) + { name: achievement.name, price: 0, discount: 0, description: "Prêmio por desbloquear a conquista #{achievement.name}", state: 1, item_type: 1, achievement_id: achievement.id} +end + +Achievement.all.each do |achievement| + file_path = Rails.root.join('db','seeds','assets','images','items', achievement.name.parameterize.underscore + '.png') + if File.file?(file_path) + item = Item.where(item_data(achievement)).first_or_create + if item.image.blank? + item.image = File.open(file_path) + item.save + end + end +end diff --git a/db/seeds/requirements.rb b/db/seeds/requirements.rb new file mode 100644 index 0000000000000000000000000000000000000000..bef6dc1b965b01c3757cbfb6c51438063e38c8d6 --- /dev/null +++ b/db/seeds/requirements.rb @@ -0,0 +1,131 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +def get_action(name) + Action.where(name: name).first +end + +def get_achievement(name) + Achievement.where(name: name).first +end + +def requirements + main_requirements = { + "Introduzido Nível I": [{ goal: 1, description: "Assistir ao vídeo de apresentação na plataforma", repeatable: false, action: get_action("Assistir Apresentação") }], + + "Introduzido Nível II": [ + { goal: 1, description: "Utilizar um recurso", repeatable: false, action: get_action("Visualizar um Recurso") }, + { goal: 1, description: "Criar uma coleção privada", repeatable: false, action: get_action("Criar Coleção") }, + { goal: 1, description: "Utilizar um material", repeatable: false, action: get_action("Visualizar um Material") }, + { goal: 1, description: "Marcar um item como favorito", repeatable: false, action: get_action("Favoritar") }, + { goal: 1, description: "Avaliar um item", repeatable: false, action: get_action("Avaliar") }, + { goal: 1, description: "Seguir um usuário", repeatable: false, action: get_action("Seguir Usuário") }, + { goal: 1, description: "Salvar um item", repeatable: false, action: get_action("Adicionar Recurso a Coleção") } + ], + + "Personalizado": [ + { goal: 1, description: "Adicionar foto de perfil", repeatable: false, action: get_action("Adicionar Foto de Perfil") }, + { goal: 1, description: "Adicionar capa de perfil", repeatable: false, action: get_action("Adicionar Capa de Perfil") }, + { goal: 1, description: "Adicionar 'Sobre Mim'", repeatable: false, action: get_action("Adicionar Descrição do Usuário") }, + ], + + "Professor": [{ goal: 1, description: "Ser autenticado como professor", repeatable: false, action: get_action("Autenticação de Professor") }], + + "Frequente Nível I": [{ goal: 5, description: "Acessar a plataforma por 5 dias consecutivos", repeatable: false, action: get_action("Fazer Login") }], + "Frequente Nível II": [{ goal: 15, description: "Acessar a plataforma por 15 dias consecutivos", repeatable: false, action: get_action("Fazer Login") }], + "Frequente Nível III": [{ goal: 60, description: "Acessar a plataforma por 60 dias consecutivos", repeatable: false, action: get_action("Fazer Login") }], + "Frequente Nível IV": [{ goal: 120, description: "Acessar a plataforma por 120 dias consecutivos", repeatable: false, action: get_action("Fazer Login") }], + "Frequente Nível V": [{ goal: 365, description: "Acessar a plataforma por 365 dias consecutivos", repeatable: false, action: get_action("Fazer Login") }], + + "Nostálgico Nível I": [{ goal: 6, description: "Usuário há 6 meses", repeatable: false, action: get_action("Meses de Conta") }], + "Nostálgico Nível II": [{ goal: 12, description: "Usuário há 1 ano", repeatable: false, action: get_action("Meses de Conta") }], + "Nostálgico Nível III": [{ goal: 24, description: "Usuário há 2 anos", repeatable: false, action: get_action("Meses de Conta") }], + "Nostálgico Nível IV": [{ goal: 36, description: "Usuário há 3 anos", repeatable: false, action: get_action("Meses de Conta") }], + "Nostálgico Nível V": [{ goal: 48, description: "Usuário há 4 anos", repeatable: false, action: get_action("Meses de Conta") }], + + "Contribuidor Nível I": [{ goal: 1, description: "Publicar 1 recurso", repeatable: false, action: get_action("Publicar") }], + "Contribuidor Nível II": [{ goal: 10, description: "Publicar 10 recursos", repeatable: false, action: get_action("Publicar") }], + "Contribuidor Nível III": [{ goal: 50, description: "Publicar 50 recursos", repeatable: false, action: get_action("Publicar") }], + "Contribuidor Nível IV": [{ goal: 200, description: "Publicar 200 recursos", repeatable: false, action: get_action("Publicar") }], + "Contribuidor Nível V": [{ goal: 1000, description: "Publicar 1000 recursos", repeatable: false, action: get_action("Publicar") }], + + "Dias Produtivos Nível I": [{ goal: 2, description: "Publicar 2 recursos em um dia", repeatable: false, action: get_action("Publicar") }], + "Dias Produtivos Nível II": [{ goal: 5, description: "Publicar 5 recursos em um dia", repeatable: false, action: get_action("Publicar") }], + "Dias Produtivos Nível III": [{ goal: 10, description: "Publicar 10 recursos em um dia", repeatable: false, action: get_action("Publicar") }], + "Dias Produtivos Nível IV": [{ goal: 20, description: "Publicar 20 recursos em um dia", repeatable: false, action: get_action("Publicar") }], + "Dias Produtivos Nível V": [{ goal: 50, description: "Publicar 50 recursos em um dia", repeatable: false, action: get_action("Publicar") }], + + "Utilizador Assíduo Nível I": [{ goal: 10, description: "Utilizar 10 recursos", repeatable: false, action: get_action("Visualizar um Recurso") }], + "Utilizador Assíduo Nível II": [{ goal: 50, description: "Utilizar 50 recursos", repeatable: false, action: get_action("Visualizar um Recurso") }], + "Utilizador Assíduo Nível III": [{ goal: 200, description: "Utilizar 200 recursos", repeatable: false, action: get_action("Visualizar um Recurso") }], + "Utilizador Assíduo Nível IV": [{ goal: 1000, description: "Utilizar 1000 recursos", repeatable: false, action: get_action("Visualizar um Recurso") }], + "Utilizador Assíduo Nível V": [{ goal: 5000, description: "Utilizar 5000 recursos", repeatable: false, action: get_action("Visualizar um Recurso") }], + + "Colecionador Nível I": [{ goal: 5, description: "Utilizar 5 coleções", repeatable: false, action: get_action("Visualizar uma Coleção") }], + "Colecionador Nível II": [{ goal: 30, description: "Utilizar 30 coleções", repeatable: false, action: get_action("Visualizar uma Coleção") }], + "Colecionador Nível III": [{ goal: 100, description: "Utilizar 100 coleções", repeatable: false, action: get_action("Visualizar uma Coleção") }], + "Colecionador Nível IV": [{ goal: 1000, description: "Utilizar 1000 coleções", repeatable: false, action: get_action("Visualizar uma Coleção") }], + + "Curador Nível I": [{ goal: 20, description: "Avaliar 20 recursos com estrelas", repeatable: false, action: get_action("Avaliar") }, + { goal: 20, description: "Avaliar 20 recursos com comentários", repeatable: false, action: get_action("Comentar") }], + "Curador Nível II": [{ goal: 200, description: "Avaliar 200 recursos com estrelas", repeatable: false, action: get_action("Avaliar") }, + { goal: 200, description: "Avaliar 200 recursos com comentários", repeatable: false, action: get_action("Comentar") }], + "Curador Nível III": [{ goal: 500, description: "Avaliar 500 recursos com estrelas", repeatable: false, action: get_action("Avaliar") }, + { goal: 500, description: "Avaliar 500 recursos com comentários", repeatable: false, action: get_action("Comentar") }], + "Curador Nível IV": [{ goal: 1000, description: "Avaliar 1000 recursos com estrelas", repeatable: false, action: get_action("Avaliar") }, + { goal: 1000, description: "Avaliar 1000 recursos com comentários", repeatable: false, action: get_action("Comentar") }], + # "Curador Nível V": [{ goal: 5000, description: "Avaliar 5000 recursos com estrelas", repeatable: false, action: get_action("Avaliar") }, + # { goal: 5000, description: "Avaliar 5000 recursos com comentários", repeatable: false, action: get_action("Comentar") }], + + "Curador Assíduo Nível I": [{ goal: 10, description: "Avaliar 10 recursos com estrelas em um dia", repeatable: false, action: get_action("Avaliar") }, + { goal: 10, description: "Avaliar 10 recursos com comentários em um dia", repeatable: false, action: get_action("Comentar") }], + "Curador Assíduo Nível II": [{ goal: 20, description: "Avaliar 20 recursos com estrelas em um dia", repeatable: false, action: get_action("Avaliar") }, + { goal: 20, description: "Avaliar 20 recursos com comentários em um dia", repeatable: false, action: get_action("Comentar") }], + "Curador Assíduo Nível III": [{ goal: 50, description: "Avaliar 50 recursos com estrelas em um dia", repeatable: false, action: get_action("Avaliar") }, + { goal: 50, description: "Avaliar 50 recursos com comentários em um dia", repeatable: false, action: get_action("Comentar") }], + + "Conectado Nível I": [{ goal: 10, description: "Seguir 10 usuários", repeatable: false, action: get_action("Seguir Usuário") }], + "Conectado Nível II": [{ goal: 50, description: "Seguir 50 usuários", repeatable: false, action: get_action("Seguir Usuário") }], + "Conectado Nível III": [{ goal: 200, description: "Seguir 200 usuários", repeatable: false, action: get_action("Seguir Usuário") }], + + "Conhecido Nível I": [{ goal: 10, description: "Ser seguido por 10 usuários", repeatable: false, action: get_action("Ser Seguido") }], + "Conhecido Nível II": [{ goal: 50, description: "Ser seguido por 50 usuários", repeatable: false, action: get_action("Ser Seguido") }], + "Conhecido Nível III": [{ goal: 200, description: "Ser seguido por 200 usuários", repeatable: false, action: get_action("Ser Seguido") }], + + "Divulgador Nível I": [{ goal: 1, description: "Compartilhar 1 recurso", repeatable: false, action: get_action("Compartilhar") }], + "Divulgador Nível II": [{ goal: 20, description: "Compartilhar 20 recursos", repeatable: false, action: get_action("Compartilhar") }], + "Divulgador Nível III": [{ goal: 100, description: "Compartilhar 100 recursos", repeatable: false, action: get_action("Compartilhar") }] + } + + subject_requirements = {} + Subject.all.each do |subject| + subject_requirements["Proficiência em #{subject.name} Nível I"] = [{ goal: 20, description: "Utilizar 20 recursos de #{subject.name}", repeatable: false, action: get_action("Visualizar um Recurso de #{subject.name}") }] + subject_requirements["Proficiência em #{subject.name} Nível II"] = [{ goal: 50, description: "Utilizar 50 recursos de #{subject.name}", repeatable: false, action: get_action("Visualizar um Recurso de #{subject.name}") }] + subject_requirements["Proficiência em #{subject.name} Nível III"] = [{ goal: 150, description: "Utilizar 150 recursos de #{subject.name}", repeatable: false, action: get_action("Visualizar um Recurso de #{subject.name}") }] + subject_requirements["Proficiência em #{subject.name} Nível IV"] = [{ goal: 500, description: "Utilizar 500 recursos de #{subject.name}", repeatable: false, action: get_action("Visualizar um Recurso de #{subject.name}") }] + subject_requirements["Proficiência em #{subject.name} Nível V"] = [{ goal: 1000, description: "Utilizar 1000 recursos de #{subject.name}", repeatable: false, action: get_action("Visualizar um Recurso de #{subject.name}") }] + end + main_requirements.merge(subject_requirements) +end + +requirements.each do |achievement, requirement| + requirement.each do |req| + get_achievement(achievement).requirements.where(req).first_or_create + end +end diff --git a/lib/tasks/gamefication.rake b/lib/tasks/gamefication.rake new file mode 100644 index 0000000000000000000000000000000000000000..21498b36e82ae76fd008407c55c67f812503bd81 --- /dev/null +++ b/lib/tasks/gamefication.rake @@ -0,0 +1,155 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +namespace :gamefication do + + desc 'Reset repeatable requirements/achievements [weekly, monthly or yearly]' + task :reset_repeatables, [:type] => [:environment] do |task, args| + if ["weekly", "monthly", "yearly"].include? args[:type] + repeatable_ids = Achievement.joins(:requirements).where(repeatable: args[:type], requirements: {repeatable: true}).pluck(:requirement_id) + + start = 1 + finish = 1000 + batch_size = 1000 + last_id = User.count > 0 ? User.last.id : 0 + loop do + p "Resetting weekly Requirements for User ids: #{start} to #{finish}" + begin + User.find_each(start: start, finish: finish) do |user| + user.progresses.where(requirement_id: repeatable_ids).each do |progress| + progress.update(counter: 0) + end + end + rescue Exception => e + p 'Database error, going to sleep' + p e + p e.class + p e.message + ActiveRecord::Base.clear_active_connections! + # Sleeps for a while to wait database's recovery + sleep(60.seconds) + else + start += batch_size + finish += batch_size + break if start >= last_id + end + end + else + p "Repeatable type '#{args[:type]}' not supported." + end + end + + desc 'Daily reset for resettable requirements/achievements' + task daily_reset: :environment do + resettable_ids = Achievement.joins(:requirements).where(resettable: true).pluck(:requirement_id) + frequent_req_ids = Achievement.joins(:requirements).where("name LIKE 'Frequente%'").pluck(:requirement_id) + repeatable_ids = Achievement.joins(:requirements).where(repeatable: "daily", requirements: {repeatable: true}).pluck(:requirement_id) + + start = 1 + finish = 1000 + batch_size = 1000 + last_id = User.count > 0 ? User.last.id : 0 + loop do + p "Resetting daily Requirements for User ids: #{start} to #{finish}" + begin + User.find_each(start: start, finish: finish) do |user| + user.progresses.where(requirement_id: resettable_ids).each do |progress| + days = (Time.now - progress.updated_at)/1.day + frequent = frequent_req_ids.include? progress.requirement_id + if (days > 1 && !frequent) || (days >= 2 && frequent) + progress.update(counter: 0) + end + end + user.progresses.where(requirement_id: repeatable_ids).each do |progress| + progress.update(counter: 0) + end + end + rescue Exception => e + p 'Database error, going to sleep' + p e + p e.class + p e.message + ActiveRecord::Base.clear_active_connections! + # Sleeps for a while to wait database's recovery + sleep(60.seconds) + else + start += batch_size + finish += batch_size + break if start >= last_id + end + end + end + + desc 'Update all users actions and give experience and achievements accordingly' + task complete_actions: :environment do + start = 1 + finish = 1000 + batch_size = 1000 + last_id = User.count > 0 ? User.last.id : 0 + loop do + p "Updating actions for User ids: #{start} to #{finish}" + begin + User.find_each(start: start, finish: finish) do |user| + complete_action("Publicar", user, user.learning_objects.published.count) + complete_action("Favoritar", user, user.likes.count) + complete_action("Avaliar", user, user.reviews.count) + complete_action("Comentar", user, user.reviews.where.not(description: nil).count) + complete_action("Seguir Usuário", user, user.following('User').count) + complete_action("Ser Seguido", user, user.followers.count) + complete_action("Fazer Download de um Recurso", user, user.downloads.count) + + complete_action("Meses de Conta", user, (Time.now - user.created_at)/1.month) + complete_action("Visualizar um Recurso", user, user.views.where(viewable_type: 'User').count) + complete_action("Visualizar uma Coleção", user, user.views.where(viewable_type: 'Collection').count) + complete_action("Criar Coleção", user, user.collections.count) + complete_action("Adicionar Recurso a Coleção", user, user.collections.map { |i| i.collection_items.where(collectionable_type: "LearningObject").count }.sum) + complete_action("Adicionar Foto de Perfil", user) if !user.avatar.blank? + complete_action("Adicionar Capa de Perfil", user) if !user.cover.blank? + complete_action("Adicionar Descrição do Usuário", user) if !user.description.blank? + complete_action("Autenticação de Professor", user) if user.submitter_request == "accepted" + end + rescue Exception => e + p 'Database error, going to sleep' + p e + p e.class + p e.message + ActiveRecord::Base.clear_active_connections! + # Sleeps for a while to wait database's recovery + sleep(60.seconds) + else + start += batch_size + finish += batch_size + break if start >= last_id + end + end + end + + private + + def complete_action(action_name, user, quantity=1) + action = Action.find_by_name(action_name) + action_counter = user.action_counters.where(action: action).first + if action_counter.nil? + user.complete_action(action, quantity) + else + amount = quantity - action_counter.counter + user.complete_action(action, amount) if amount > 0 + end + end +end diff --git a/lib/tasks/user.rake b/lib/tasks/user.rake index 5cf6adbe9f51f3c7187add1310cbf2dbc5710d03..3c2999e6bbcb151f1375914ee0f6410300b0dd33 100644 --- a/lib/tasks/user.rake +++ b/lib/tasks/user.rake @@ -7,4 +7,11 @@ namespace :user do user.save! end end + desc "Add gamification relations to users created before gamification was implemented" + task gamify_existing_users: :environment do + for user in User.all + user.generate_gamification_relations + end + end + end diff --git a/spec/acceptance/achievements_spec.rb b/spec/acceptance/achievements_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..35558e6e3d72911cc28e28c452091a6aeab70236 --- /dev/null +++ b/spec/acceptance/achievements_spec.rb @@ -0,0 +1,132 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'Achievement' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "Users can perform some pre-defined actions in the system to unlock achievements and win rewards." + + before { 12.times { create(:achievement) } } + + let(:achievements) { Achievement.all } + + get '/v1/achievements' do + parameter :limit, 'Limit of achievements' + parameter :offset, 'Offset of achievements' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of achievements' do + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(Achievement.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/achievements/:id' do + let(:id) { achievements.first.id } + + example 'Get an achievement' do + do_request + expect(path).to eq("/v1/achievements/#{id}") # `:id` is replaced with the value of `id` + expect(response_body).to eq(Helper.serialize(Achievement.find(id))) + expect(status).to eq(200) + end + end + + post '/v1/achievements' do + include_context "authenticate_user_editor" + + parameter :name, 'The name of the achievement', scope: :achievement + parameter :description, 'The description of the achievement', scope: :achievement + parameter :reward_experience, 'Amount of experience rewarded when the achievement is completed', scope: :achievement + parameter :reward_points, 'Amount of points rewarded when the achievement is completed', scope: :achievement + parameter :state, 'State of the achievement [inactive, active, deleted]', scope: :achievement + parameter :repeatable, 'Repeatability of the achievement [never, daily, weekly, monthly, yearly]', scope: :achievement + parameter :resettable, 'Whether the achievement can be reseted or not', scope: :achievement + parameter :requirements, 'Array with requirements ids', scope: :achievement + + let(:name) { Faker::Name.name } + let(:description) { Faker::Lorem.paragraph} + let(:reward_experience) { rand(0..100) } + let(:reward_points) { rand(0..100) } + let(:state) { "active" } + let(:repeatable) { "daily" } + let(:resettable) { true } + let(:requirements) { [@requirement.id] } + let(:raw_post) { params.to_json } + + before do + @requirement = create(:requirement) + end + + example 'Creating an achievement' do + do_request + expect(status).to eq(201) + end + end + + put '/v1/achievements/:id' do + include_context "authenticate_user_editor" + + parameter :name, 'The name of the achievement', scope: :achievement + parameter :description, 'The description of the achievement', scope: :achievement + parameter :reward_experience, 'Amount of experience rewarded when the achievement is completed', scope: :achievement + parameter :reward_points, 'Amount of points rewarded when the achievement is completed', scope: :achievement + parameter :state, 'State of the achievement [inactive, active, deleted]', scope: :achievement + parameter :repeatable, 'Repeatability of the achievement [never, daily, weekly, monthly, yearly]', scope: :achievement + parameter :resettable, 'Whether the achievement can be reseted or not', scope: :achievement + parameter :requirements, 'Array with requirements ids', scope: :achievement + + let(:id) { @achievement.id } + let(:description) { Faker::Lorem.paragraph } + let(:raw_post) { params.to_json } + + before do + @achievement = create(:achievement) + end + + example 'Updating an achievement' do + do_request + expect(status).to eq(200) + end + end + + delete '/v1/achievements/:id' do + include_context "authenticate_user_editor" + + let(:id) { @achievement.id } + + before do + @achievement = create(:achievement) + end + + example 'Destroying an achievement' do + do_request + expect(JSON.parse(response_body)['state']).to eq("deleted") + expect(status).to eq(200) + end + + end + +end diff --git a/spec/acceptance/action_counters_spec.rb b/spec/acceptance/action_counters_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4ec2f06da156435bac65a39114819fe49e79f2d6 --- /dev/null +++ b/spec/acceptance/action_counters_spec.rb @@ -0,0 +1,57 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +require 'acceptance_helpers' + +resource 'Action_Counters' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "A counter for pre-actions in the system done from Users" + + before { 12.times { create(:action_counter) } } + + let(:action_counter) { ActionCounter.all } + + get '/v1/action_counters' do + + parameter :limit, 'Limit of actions' + parameter :offset, 'Offset of actions' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of action counters' do + expect( JSON.parse(response_body).map { |o| o['id'] }.sort ).to eq( ActionCounter.limit(limit).offset(offset).pluck(:id).sort ) + + expect(status).to eq(200) + end + end + + get '/v1/action_counters/:id' do + let(:id) { action_counter.first.id } + + example 'Get an action counter' do + do_request + expect(path).to eq("/v1/action_counters/#{id}") + expect(response_body).to eq(Helper.serialize(ActionCounter.find(id))) + expect(status).to eq(200) + end + end +end \ No newline at end of file diff --git a/spec/acceptance/actions_spec.rb b/spec/acceptance/actions_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cc132310b7fbccd71427d579e0f8d55078c6ba7d --- /dev/null +++ b/spec/acceptance/actions_spec.rb @@ -0,0 +1,112 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'Action' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "Users can perform some pre-defined Actions in the system to collect experience." + + before { 12.times { create(:action) } } + + let(:actions) { Action.all } + + get '/v1/actions' do + parameter :limit, 'Limit of actions' + parameter :offset, 'Offset of actions' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of actions' do + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(Action.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/actions/:id' do + let(:id) { actions.first.id } + + example 'Get an action' do + do_request + expect(path).to eq("/v1/actions/#{id}") # `:id` is replaced with the value of `id` + expect(response_body).to eq(Helper.serialize(Action.find(id))) + expect(status).to eq(200) + end + end + + post '/v1/actions' do + include_context "authenticate_user_editor" + + parameter :name, 'The name of the action', scope: :action_params + parameter :description, 'The description of the action', scope: :action_params + parameter :reward_experience, 'Amount of experience rewarded when the action is completed', scope: :action_params + + let(:name) { Faker::Name.name } + let(:description) { Faker::Lorem.paragraph} + let(:reward_experience) { rand(0..100) } + let(:raw_post) { params.to_json } + + example 'Creating an action' do + do_request + expect(status).to eq(201) + end + end + + put '/v1/actions/:id' do + include_context "authenticate_user_editor" + + parameter :name, 'The name of the action', scope: :action_params + parameter :description, 'The description of the action', scope: :action_params + parameter :reward_experience, 'Amount of experience rewarded when the action is completed', scope: :action_params + + let(:id) { @action.id } + let(:description) { Faker::Lorem.paragraph } + let(:raw_post) { params.to_json } + + before do + @action = create(:action) + end + + example 'Updating an action' do + do_request + expect(status).to eq(200) + end + end + + delete '/v1/actions/:id' do + include_context "authenticate_user_editor" + + let(:id) { @action.id } + + before do + @action = create(:action) + end + + example 'Destroying an action' do + do_request + expect(status).to eq(200) + end + + end + +end diff --git a/spec/acceptance/items_spec.rb b/spec/acceptance/items_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9721889b8b706ccde89c54b397d627ae8be29384 --- /dev/null +++ b/spec/acceptance/items_spec.rb @@ -0,0 +1,140 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'Item' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "Users can unlock items through achievements and points won in the platform." + + before { 12.times { create(:item) } + 2.times { create(:achievement) } + } + + let(:items) { Item.all } + let(:achievements) { Achievement.all } + + get '/v1/items' do + parameter :limit, 'Limit of items' + parameter :offset, 'Offset of items' + parameter :item_type, 'Type of the items' + parameter :op, 'Operation to filter the items by price [lt, gt, eq]' + parameter :price, 'Price of the items' + parameter :unlock_rule, 'Rule to unlock the items [achievement, purchase]' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of items' do + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(items.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/items/:id' do + let(:id) { items.first.id } + + example 'Get an item' do + do_request + expect(path).to eq("/v1/items/#{id}") # `:id` is replaced with the value of `id` + expect(response_body).to eq(Helper.serialize(items.find(id))) + expect(status).to eq(200) + end + end + + post '/v1/items' do + include_context "authenticate_user_editor" + + parameter :name, 'The name of the item', scope: :item + parameter :description, 'The description of the item', scope: :item + parameter :price, 'Price of the item in points', scope: :item + parameter :discount, 'Amount of discount given to the item price', scope: :item + parameter :state, 'State of the item [inactive, active, removed]', scope: :item + parameter :item_type, 'Type of the item [avatar_frame, badge, card_frame, cover_frame]', scope: :item + parameter :achievement_id, 'The id of the achievement needed to unlock the item', scope: :item + parameter :image, 'The image of the item', scope: :item + + let(:name) { Faker::Name.name } + let(:description) { Faker::Lorem.paragraph} + let(:price) { rand(0..100) } + let(:discount) { rand(0..100) } + let(:state) { "active" } + let(:item_type) { "badge" } + let(:achievement_id) { achievements.first.id } + let(:image) { @image } + let(:raw_post) { params.to_json } + + before do + @image = "data:image/png;base64," + @image += Base64.encode64(fixture_file_upload(file_fixture('img_test.png'), binary: true).tempfile.open.read.force_encoding(Encoding::UTF_8)).strip + end + + example 'Creating an item' do + do_request + expect(status).to eq(201) + end + end + + put '/v1/items/:id' do + include_context "authenticate_user_editor" + + parameter :name, 'The name of the item', scope: :item + parameter :description, 'The description of the item', scope: :item + parameter :price, 'Price of the item in points', scope: :item + parameter :discount, 'Amount of discount given to the item price', scope: :item + parameter :state, 'State of the item [inactive, active, removed]', scope: :item + parameter :item_type, 'Type of the item [avatar_frame, badge, card_frame, cover_frame]', scope: :item + parameter :achievement_id, 'The id of the achievement needed to unlock the item', scope: :item + parameter :image, 'The image of the item', scope: :item + + let(:id) { @item.id } + let(:description) { Faker::Lorem.paragraph } + let(:raw_post) { params.to_json } + + before do + @item = create(:item) + end + + example 'Updating an item' do + do_request + expect(status).to eq(200) + end + end + + delete '/v1/items/:id' do + include_context "authenticate_user_editor" + + let(:id) { @item.id } + + before do + @item = create(:item) + end + + example 'Destroying an item' do + do_request + expect(JSON.parse(response_body)['state']).to eq("removed") + expect(status).to eq(200) + end + + end + +end diff --git a/spec/acceptance/progresses_spec.rb b/spec/acceptance/progresses_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..5d6af853e8bf1ce10242c193b56a93126df01892 --- /dev/null +++ b/spec/acceptance/progresses_spec.rb @@ -0,0 +1,62 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'Progress' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation " The progress for achievements in the system for each user" + + before { 12.times { create(:progress) } } + + let(:progresses) { Progress.all } + + + get '/v1/progresses' do #index + include_context "authenticate_user" + + before { 12.times { create(:progress, user: @user) }} + + parameter :limit, 'Limit of progresses' + parameter :offset, 'Offset of progresses' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of progresses from a user' do + expect( JSON.parse(response_body).map { |o| o['id'] }.sort ).to eq( @user.progresses.limit(limit).offset(offset).pluck(:id).sort ) + expect(status).to eq(200) + end + end + + get '/v1/progresses/:id' do + + let(:id) { progresses.first.id } + + example 'Get a specific progress' do + do_request + expect(path).to eq("/v1/progresses/#{id}") + expect(response_body).to eq(Helper.serialize(Progress.find(id))) + expect(status).to eq(200) + end + end +end \ No newline at end of file diff --git a/spec/acceptance/requirements_spec.rb b/spec/acceptance/requirements_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..903e8092bf39bb7e0ce1351c93b98d807c83b329 --- /dev/null +++ b/spec/acceptance/requirements_spec.rb @@ -0,0 +1,123 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'Requirement' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "Achievements have requirements that users must complete to unlock them." + + before { 12.times { create(:requirement) } + 2.times { create(:achievement) } + 1.times { create(:action) } + } + + let(:requirements) { Requirement.all } + let(:all_achievements) { Achievement.all } + let(:action) { Action.first } + + get '/v1/requirements' do + parameter :limit, 'Limit of requirements' + parameter :offset, 'Offset of requirements' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of requirements' do + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(requirements.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/requirements/:id' do + let(:id) { requirements.first.id } + + example 'Get a requirement' do + do_request + expect(path).to eq("/v1/requirements/#{id}") # `:id` is replaced with the value of `id` + expect(response_body).to eq(Helper.serialize(requirements.find(id))) + expect(status).to eq(200) + end + end + + post '/v1/requirements' do + include_context "authenticate_user_editor" + + parameter :description, 'The description of the requirement', scope: :requirement + parameter :goal, 'Amount of actions neccessary to fulfill the requirement', scope: :requirement + parameter :repeatable, 'Whether the achievement can be repeated or not', scope: :requirement + parameter :action_id, 'The id of the action needed for the requirement', scope: :requirement + parameter :achievements, 'Array with achievements ids', scope: :requirement + + let(:description) { Faker::Lorem.paragraph} + let(:goal) { rand(0..100) } + let(:repeatable) { true } + let(:action_id) { action.id } + let(:achievements) { [all_achievements.first.id] } + let(:raw_post) { params.to_json } + + example 'Creating an requirement' do + do_request + expect(status).to eq(201) + end + end + + put '/v1/requirements/:id' do + include_context "authenticate_user_editor" + + parameter :description, 'The description of the requirement', scope: :requirement + parameter :goal, 'Amount of actions neccessary to fulfill the requirement', scope: :requirement + parameter :repeatable, 'Whether the achievement can be repeated or not', scope: :requirement + parameter :action_id, 'The id of the action needed for the requirement', scope: :requirement + parameter :achievements, 'Array with achievements ids', scope: :requirement + + let(:id) { @requirement.id } + let(:description) { Faker::Lorem.paragraph } + let(:raw_post) { params.to_json } + + before do + @requirement = create(:requirement) + end + + example 'Updating a requirement' do + do_request + expect(status).to eq(200) + end + end + + delete '/v1/requirements/:id' do + include_context "authenticate_user_editor" + + let(:id) { @requirement.id } + + before do + @requirement = create(:requirement) + end + + example 'Destroying a requirement' do + do_request + expect(status).to eq(200) + end + + end + +end diff --git a/spec/acceptance/unlocked_achievements_spec.rb b/spec/acceptance/unlocked_achievements_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d9c1067726c194d1aed197659c3ceb639d422f0b --- /dev/null +++ b/spec/acceptance/unlocked_achievements_spec.rb @@ -0,0 +1,61 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>.require 'acceptance_helpers' + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'Unlocked Achievements' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "Unlocked Achievements from a User" + + before { 12.times { create(:unlocked_achievement) } } + + let(:unlocked_achievements) { UnlockedAchievement.all } + + get '/v1/unlocked_achievements' do #index + include_context "authenticate_user" + + before { 12.times { create(:unlocked_achievement, user: @user) }} + + parameter :limit, 'Limit of unlocked achievements' + parameter :offset, 'Offset of unlocked achievements' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of unlocked achievements from a user' do + expect( JSON.parse(response_body).map { |o| o['id'] }.sort ).to eq( @user.unlocked_achievements.limit(limit).offset(offset).pluck(:id).sort ) + expect(status).to eq(200) + end + end + + get '/v1/unlocked_achievements/:id' do + + let(:id) { unlocked_achievements.first.id } + + example 'Get a specific unlocked achievement' do + do_request + expect(path).to eq("/v1/unlocked_achievements/#{id}") + expect(response_body).to eq(Helper.serialize(UnlockedAchievement.find(id))) + expect(status).to eq(200) + end + end +end \ No newline at end of file diff --git a/spec/acceptance/user_items_spec.rb b/spec/acceptance/user_items_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..81d0f2e43c7d16400b5d4d6a84644c41bab548e5 --- /dev/null +++ b/spec/acceptance/user_items_spec.rb @@ -0,0 +1,65 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>.require 'acceptance_helpers' + +require 'acceptance_helpers' +require 'shared/contexts' + +resource 'User Items' do + header 'Accept', 'application/json' + header 'Content-Type', 'application/json' + + explanation "Items owned by an User" + + before { 1.times { create(:user_item) } } + + let(:user_items) { UserItem.all } + + get '/v1/user_items' do #index + include_context "authenticate_user" + + before { 12.times { create(:user_item, user: @user) }} + + parameter :limit, 'Limit of user items' + parameter :offset, 'Offset of user items' + parameter :item_type, 'Item_type of the user items' + parameter :op, 'Operation to filter the user items by price [lt, gt, eq]' + parameter :price, 'Price of the user items' + parameter :unlock_rule, 'Rule to unlock the user items [achievement, purchase]' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of user items from a user' do + expect( JSON.parse(response_body).map { |o| o['id'] }.sort ).to eq(@user.items.active.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/user_items/:id' do + + let(:id) { user_items.first.id } + + example 'Get a specific user item' do + do_request + expect(path).to eq("/v1/user_items/#{id}") + expect(response_body).to eq(Helper.serialize(UserItem.find(id))) + expect(status).to eq(200) + end + end +end \ No newline at end of file diff --git a/spec/acceptance/users_spec.rb b/spec/acceptance/users_spec.rb index 1073fa2bd7f34889e0d0d57d9b9d464ba84b20d6..09f73b8de9ac181d7143d4c72b06d1e0200c4214 100644 --- a/spec/acceptance/users_spec.rb +++ b/spec/acceptance/users_spec.rb @@ -420,4 +420,167 @@ resource 'Users' do expect(status).to eq(200) end end + + post '/v1/users/complete_action' do + include_context "authenticate_user" + + parameter :action_id, 'The id of the action the current user is completing' + parameter :quantity, 'The amount of actions the current user is completing (default = 1)' + + let(:action_id) { @action.id } + let(:quantity) { @quantity } + let(:raw_post) { params.to_json } + + before do + @action = create(:action) + @quantity = 1 + end + + example 'Complete an action' do + do_request + expect(JSON.parse(response_body)['action']['id']).to eq(@action.id) + expect(JSON.parse(response_body)['counter']).to eq(@quantity) + expect(status).to eq(200) + end + end + + post '/v1/users/purchase_item' do + include_context "authenticate_user" + + parameter :item_id, 'The id of the item the current user is purchasing' + + let(:item_id) { @item.id } + let(:raw_post) { params.to_json } + + before do + @item = create(:item) + end + + example 'Purchase an item' do + do_request + expect(JSON.parse(response_body).last['item']['id']).to eq(@item.id) + expect(status).to eq(200) + end + end + + post '/v1/users/equip_item' do + include_context "authenticate_user" + + parameter :item_id, 'The id of the item the current user is equiping' + + let(:item_id) { @item.id } + let(:raw_post) { params.to_json } + + before do + @item = create(:item) + end + + example 'Equip an item' do + do_request + expect(JSON.parse(response_body).select {|i| i['being_used'] }.last['item']['id']).to eq(@item.id) + expect(status).to eq(200) + end + end + + post '/v1/users/unequip_item' do + include_context "authenticate_user" + + parameter :item_id, 'The id of the item the current user is unequiping' + + let(:item_id) { @item.id } + let(:raw_post) { params.to_json } + + before do + @item = create(:item) + @user.equip_item(@item) + end + + example 'Unequip an item' do + do_request + expect(JSON.parse(response_body).select {|i| !i['being_used'] }.last['item']['id']).to eq(@item.id) + expect(status).to eq(200) + end + end + + post '/v1/users/remove_item' do + include_context "authenticate_user" + + parameter :item_id, 'The id of the item the current user is removing' + + let(:item_id) { @item.id } + let(:raw_post) { params.to_json } + + before do + @item = create(:item) + @user_item = create(:user_item, user: @user, item: @item) + end + + example 'Remove an item from the user inventory' do + do_request + expect(JSON.parse(response_body).select {|i| i['item']['id'] == @item.id }).to eq([]) + expect(status).to eq(200) + end + end + + get '/v1/users/action_counters' do + include_context "authenticate_user" + + before { 12.times { create(:action_counter, user: @user) }} + + parameter :limit, 'Limit of action counters' + parameter :offset, 'Offset of action counters' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of the current user\'s action counters' do + # active model serializer may render model associations in different order for collections (array of items), so we're verifing only returned ids + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(@user.action_counters.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/users/completed_achievements' do + include_context "authenticate_user" + + before { 12.times { create(:unlocked_achievement, user: @user) }} + + parameter :limit, 'Limit of completed achievements' + parameter :offset, 'Offset of completed achievements' + + let(:limit) { 12 } + let(:offset) { 0 } + + example_request 'Get a list of the current user\'s completed achievements' do + # active model serializer may render model associations in different order for collections (array of items), so we're verifing only returned ids + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(@user.unlocked_achievements.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end + + get '/v1/users/:id/items' do + + parameter :id, 'The id of the user who owns the items' + parameter :being_used, "Filter the user's items by whether they're being used or not. [true, false, all]" + parameter :item_type, "Filter the user's items by their type. [avatar_frame, badge, card_frame, cover_frame, all]" + parameter :limit, 'Limit of user items' + parameter :offset, 'Offset of user items' + + let(:id) {@user.id} + let(:limit) { 12 } + let(:offset) { 0 } + let(:being_used) { "all" } + let(:item_type) { "all" } + + before do + @user = create(:user) + 6.times { create(:user_item, user: @user, being_used: true) } + 6.times { create(:user_item, user: @user, being_used: false) } + end + + example_request 'Get a list of the user\'s items' do + expect(JSON.parse(response_body).map { |o| o['id'] }.sort).to eq(@user.user_items.limit(limit).offset(offset).pluck(:id).sort) + expect(status).to eq(200) + end + end end diff --git a/spec/factories/achievements.rb b/spec/factories/achievements.rb new file mode 100644 index 0000000000000000000000000000000000000000..340771936edc99c35691791e80f2c396d29b151b --- /dev/null +++ b/spec/factories/achievements.rb @@ -0,0 +1,31 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + + +FactoryBot.define do + factory :achievement do + sequence(:name) { |i| "Achievement #{i}" } + description { Faker::Lorem.paragraph} + reward_experience { rand(1..100) } + reward_points { rand(1..100) } + state { 1 } + repeatable { 1 } + resettable { true } + end +end diff --git a/spec/factories/action_counters.rb b/spec/factories/action_counters.rb new file mode 100644 index 0000000000000000000000000000000000000000..b43b5426afd3b7788cc9c051e6e3b723b91829d3 --- /dev/null +++ b/spec/factories/action_counters.rb @@ -0,0 +1,25 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +FactoryBot.define do + factory :action_counter do + user + action + end +end \ No newline at end of file diff --git a/spec/factories/actions.rb b/spec/factories/actions.rb new file mode 100644 index 0000000000000000000000000000000000000000..340e4d01c5d35779a7d53db771a58cebb9d9f945 --- /dev/null +++ b/spec/factories/actions.rb @@ -0,0 +1,27 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + + +FactoryBot.define do + factory :action do + sequence(:name) { |i| "Action #{i}" } + description { Faker::Lorem.paragraph } + reward_experience { rand(100..300) } + end +end diff --git a/spec/factories/experience_level_maps.rb b/spec/factories/experience_level_maps.rb new file mode 100644 index 0000000000000000000000000000000000000000..9b6ac68966572b61ce3e7dd3b694a2744d357cbf --- /dev/null +++ b/spec/factories/experience_level_maps.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :experience_level_map do + + end +end diff --git a/spec/factories/items.rb b/spec/factories/items.rb new file mode 100644 index 0000000000000000000000000000000000000000..2943818ac24906ec57be07672360f541a123d7fa --- /dev/null +++ b/spec/factories/items.rb @@ -0,0 +1,30 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +FactoryBot.define do + factory :item do + sequence(:name) { |i| "Item #{i}" } + description { Faker::Lorem.paragraph} + price { rand(0..100) } + discount { rand(0..100) } + state { 1 } + item_type { 1 } + image { Rack::Test::UploadedFile.new('spec/fixtures/files/img_test.png', 'image/png') } + end +end diff --git a/spec/factories/progresses.rb b/spec/factories/progresses.rb new file mode 100644 index 0000000000000000000000000000000000000000..8f219c473da06ee3ffcf29addcb96828aab5e454 --- /dev/null +++ b/spec/factories/progresses.rb @@ -0,0 +1,25 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +FactoryBot.define do + factory :progress do |p| + user + requirement + counter { 0 } + end +end \ No newline at end of file diff --git a/spec/factories/requirements.rb b/spec/factories/requirements.rb new file mode 100644 index 0000000000000000000000000000000000000000..daded7273f412a950f8ec61454866cb00e362424 --- /dev/null +++ b/spec/factories/requirements.rb @@ -0,0 +1,27 @@ + +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +FactoryBot.define do + factory :requirement do + action + goal { rand(0..10) } + description { Faker::Lorem.paragraph } + repeatable { false } + end +end \ No newline at end of file diff --git a/spec/factories/unlocked_achievements.rb b/spec/factories/unlocked_achievements.rb new file mode 100644 index 0000000000000000000000000000000000000000..229dee257aa383287daf516f7cb202575b2aebb4 --- /dev/null +++ b/spec/factories/unlocked_achievements.rb @@ -0,0 +1,24 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +FactoryBot.define do + factory :unlocked_achievement do |p| + achievement + user + end +end \ No newline at end of file diff --git a/spec/factories/user_items.rb b/spec/factories/user_items.rb new file mode 100644 index 0000000000000000000000000000000000000000..e5027dcb329046500429b79bf87341e4bf31d866 --- /dev/null +++ b/spec/factories/user_items.rb @@ -0,0 +1,24 @@ +# Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre +# Departamento de Informatica - Universidade Federal do Parana +# +# This file is part of portalmec. +# +# portalmec is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# portalmec is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with portalmec. If not, see <http://www.gnu.org/licenses/>. + +FactoryBot.define do + factory :user_item do |p| + item + user + end +end \ No newline at end of file diff --git a/spec/fixtures/files/img_test.png b/spec/fixtures/files/img_test.png new file mode 100644 index 0000000000000000000000000000000000000000..0881b10fef4760663252c7e4e8d8980912818350 Binary files /dev/null and b/spec/fixtures/files/img_test.png differ diff --git a/spec/models/experience_level_map_spec.rb b/spec/models/experience_level_map_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..85722f8c1998be9fe23d1e2d0017b0262a42eb74 --- /dev/null +++ b/spec/models/experience_level_map_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ExperienceLevelMap, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/shared/contexts.rb b/spec/shared/contexts.rb index 8478e0e30d971f3008eb0d8c246a7217cb153fb3..3304617f06ba501b87b8fa83d3dde824fe4e9d86 100644 --- a/spec/shared/contexts.rb +++ b/spec/shared/contexts.rb @@ -29,7 +29,7 @@ RSpec.shared_context "authenticate_user", shared_context: :metadata do let(:role) { Role.all } before do - @user = create(:user, roles: [role.find_by(name: 'submitter')]) + @user = create(:user, roles: [role.find_by(name: 'submitter')], points: 100) @auth_headers = @user.create_new_auth_token end