diff --git a/Gemfile b/Gemfile index a7dff37..6ec0481 100644 --- a/Gemfile +++ b/Gemfile @@ -17,21 +17,10 @@ gem 'sass-rails', '>= 6' gem 'webpacker', '~> 5.0' # Turbolinks makes navigating your web appliletter_openercation faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 2.7' # Use Redis adapter to run Action Cable in production -gem 'redis', '~> 4.0' -# Use Active Model has_secure_password -# gem 'bcrypt', '~> 3.1.7' - -# Use Active Storage variant -# gem 'image_processing', '~> 1.2' -gem 'letter_opener', group: :development - -# Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.4', require: false +gem 'redis', '~> 4.0' -gem 'bootstrap' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: %i[mri mingw x64_mingw] @@ -39,15 +28,16 @@ end group :development do gem 'bullet', '~> 7.0' - + gem 'delayed_job_web', '~> 1.4.4', require: 'delayed_job_active_record' + gem 'erb_lint', '~> 0.4.0' + gem 'htmlbeautifier', '~> 1.4' + gem 'letter_opener' # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 4.1.0' # Display performance information such as SQL time and flame graphs for each request in your browser. # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md gem 'listen', '~> 3.3' gem 'rack-mini-profiler', '~> 2.0' - # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - # gem 'spring' end group :test do @@ -62,6 +52,7 @@ end gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] gem 'activestorage', '~> 6.1' + gem 'devise', '~> 4.9' gem 'tailwindcss', '~> 0.1.1' @@ -70,28 +61,16 @@ gem 'tailwindcss-rails', '~> 2.0' gem 'devise-two-factor', '~> 4.0' -gem 'dotenv-rails' -gem 'rqrcode', '~> 2.1' - gem 'stimulus-rails' gem 'hotwire-rails', '~> 0.1.3' -# gem "autoprefixer" - gem 'kaminari' -gem 'whenever', '~> 1.0' gem 'cancancan', '~> 3.5' -gem 'attr_encrypted', '~> 3.1' - gem 'daemons' + gem 'delayed_job_active_record' -gem 'delayed_job_web', '~> 1.4.4', require: 'delayed_job_active_record' gem 'twilio-ruby', '~> 5.77' - -gem 'erb_lint', '~> 0.4.0' - -gem 'htmlbeautifier', '~> 1.4' diff --git a/Gemfile.lock b/Gemfile.lock index 0af0d81..2b08291 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,10 +63,8 @@ GEM addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) - attr_encrypted (3.1.0) + attr_encrypted (4.0.0) encryptor (~> 3.0.0) - autoprefixer-rails (10.4.13.0) - execjs (~> 2) bcrypt (3.1.18) better_html (2.0.1) actionview (>= 6.0) @@ -78,17 +76,13 @@ GEM bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) - bootstrap (5.2.3) - autoprefixer-rails (>= 9.1.0) - popper_js (>= 2.11.6, < 3) - sassc-rails (>= 2.0.0) builder (3.2.4) bullet (7.0.7) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) cancancan (3.5.0) - capybara (3.39.1) + capybara (3.39.2) addressable matrix mini_mime (>= 0.1.3) @@ -97,8 +91,6 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - chronic (0.10.2) - chunky_png (1.4.0) concurrent-ruby (1.2.2) crass (1.0.6) daemons (1.4.1) @@ -125,10 +117,6 @@ GEM devise (~> 4.0) railties (< 7.1) rotp (~> 6.0) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) encryptor (3.0.0) erb_lint (0.4.0) activesupport @@ -138,8 +126,7 @@ GEM rubocop smart_properties erubi (1.12.0) - execjs (2.8.1) - faraday (2.7.5) + faraday (2.7.6) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) @@ -151,13 +138,10 @@ GEM stimulus-rails turbo-rails htmlbeautifier (1.4.2) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) - jbuilder (2.11.5) - actionview (>= 5.0.0) - activesupport (>= 5.0.0) json (2.6.3) - jwt (2.7.0) + jwt (2.7.1) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -193,7 +177,7 @@ GEM msgpack (1.7.1) mustermann (3.0.0) ruby2_keywords (~> 0.0.1) - net-imap (0.3.4) + net-imap (0.3.6) date net-protocol net-pop (0.1.2) @@ -209,14 +193,14 @@ GEM racc (~> 1.4) orm_adapter (0.5.0) parallel (1.23.0) - parser (3.2.2.1) + parser (3.2.2.3) ast (~> 2.4.1) + racc pg (1.5.3) - popper_js (2.11.7) public_suffix (5.0.1) puma (5.6.5) nio4r (~> 2.0) - racc (1.6.2) + racc (1.7.0) rack (2.2.7) rack-mini-profiler (2.3.4) rack (>= 1.2.0) @@ -244,8 +228,9 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) railties (6.1.7.3) actionpack (= 6.1.7.3) activesupport (= 6.1.7.3) @@ -258,27 +243,23 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redis (4.8.1) - regexp_parser (2.8.0) + regexp_parser (2.8.1) responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.5) rotp (6.2.2) - rqrcode (2.2.0) - chunky_png (~> 1.0) - rqrcode_core (~> 1.0) - rqrcode_core (1.2.0) - rubocop (1.51.0) + rubocop (1.52.1) json (~> 2.3) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.28.1) + rubocop-ast (1.29.0) parser (>= 3.2.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) @@ -293,7 +274,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.9.1) + selenium-webdriver (4.10.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -319,7 +300,7 @@ GEM tailwindcss-rails (2.0.29-x86_64-linux) railties (>= 6.0.0) thor (1.2.2) - tilt (2.1.0) + tilt (2.2.0) timeout (0.3.2) turbo-rails (1.4.0) actionpack (>= 6.0.0) @@ -356,8 +337,6 @@ GEM websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - whenever (1.0.0) - chronic (>= 0.6.3) xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.6.8) @@ -368,9 +347,7 @@ PLATFORMS DEPENDENCIES activestorage (~> 6.1) - attr_encrypted (~> 3.1) bootsnap (>= 1.4.4) - bootstrap bullet (~> 7.0) byebug cancancan (~> 3.5) @@ -380,11 +357,9 @@ DEPENDENCIES delayed_job_web (~> 1.4.4) devise (~> 4.9) devise-two-factor (~> 4.0) - dotenv-rails erb_lint (~> 0.4.0) hotwire-rails (~> 0.1.3) htmlbeautifier (~> 1.4) - jbuilder (~> 2.7) kaminari letter_opener listen (~> 3.3) @@ -393,7 +368,6 @@ DEPENDENCIES rack-mini-profiler (~> 2.0) rails (~> 6.1.7, >= 6.1.7.2) redis (~> 4.0) - rqrcode (~> 2.1) sass-rails (>= 6) selenium-webdriver (>= 4.0.0.rc1) stimulus-rails @@ -405,7 +379,6 @@ DEPENDENCIES web-console (>= 4.1.0) webdrivers webpacker (~> 5.0) - whenever (~> 1.0) RUBY VERSION ruby 3.1.3p185 diff --git a/app/channels/match_channel.rb b/app/channels/match_channel.rb new file mode 100644 index 0000000..7f9af39 --- /dev/null +++ b/app/channels/match_channel.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# channel for timer and selection +class MatchChannel < ApplicationCable::Channel + def subscribed + stream_from "match_channel_#{params[:match_id]}" + end +end diff --git a/app/channels/timer_channel.rb b/app/channels/timer_channel.rb deleted file mode 100644 index 84df354..0000000 --- a/app/channels/timer_channel.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -# channel for timer and selection -class TimerChannel < ApplicationCable::Channel - def subscribed - stream_from "timer_channel_#{params[:match_id]}" - end -end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e55c247..1060670 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,14 +3,15 @@ class ApplicationController < ActionController::Base protect_from_forgery prepend: true before_action :configure_permitted_parameters, if: :devise_controller? - rescue_from ActiveRecord::RecordNotFound, with: :record_not_found + rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from CanCan::AccessDenied, with: :access_denied + rescue_from ActionController::RoutingError, with: :not_found def access_denied render file: "#{Rails.root}/public/403.html", formats: [:html], status: 403, layout: false end - def record_not_found + def not_found render file: "#{Rails.root}/public/404.html", formats: [:html], status: 404, layout: false end @@ -26,7 +27,7 @@ def after_resetting_password_path_for(user) def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: %i[name email password phone_number]) - devise_parameter_sanitizer.permit(:account_update, keys: %i[name email password phone_number avatar]) + devise_parameter_sanitizer.permit(:account_update, keys: %i[name email password phone_number image]) devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt]) end end diff --git a/app/controllers/leaderboard_controller.rb b/app/controllers/leaderboard_controller.rb index 0223eaa..0dac98e 100644 --- a/app/controllers/leaderboard_controller.rb +++ b/app/controllers/leaderboard_controller.rb @@ -1,6 +1,6 @@ # LeaderBoard Controller class LeaderboardController < ApplicationController def index - @players = LeaderBoard.new.result + @players = LeaderBoardCreator.call end end diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index 88aa8f4..6670e1a 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# Matches_Controller class MatchesController < ApplicationController load_and_authorize_resource :tournament load_and_authorize_resource through: :tournament @@ -16,31 +15,19 @@ def all def show; end - def new - @match = Match.new - end - def playmatch - @match = Match.includes([:users]).find(params[:match_id]) - redirect_to result_tournament_match_path(match_id: @match) unless @match.match_winner_id.nil? - @players = @match.users - @remaining_tries = @match.remaining_tries(@players.first.id) - @done_tries = @match.selections.by_user(@players.first.id).size - @is_player = @match.users.include?(current_user) @messages = @match.messages.includes([:user]).all.reverse - end + return if @match.done? - def result - @is_player = @match.users.include?(current_user) - @players = @match.users - @players_selections = @match.selections.order(:try_num).group_by(&:user_id) - @players_scores = @match.selections.group(:user_id).winner.count - @result_message = @match.result_message(current_user.id) + @players_data = @match.players_data_by_current_user(current_user) end - private + def result + return if @match.undone? - def match_params - params.require(:match).permit(:match_winner_id, :winner_score, :match_time, :tournament_id) + @players_data = @match.players_data_by_current_user(current_user) + @players_selections = @match.selections.includes(:user).order(:try_num).group_by(&:user_id) + @players_scores = @match.selections.winner.group(:user_id).count + @result_message = @match.result_message(current_user.id) end end diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 641887d..d413533 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -2,26 +2,20 @@ class MessagesController < ApplicationController load_and_authorize_resource - def index; end - - def new - @message = Message.new - end def create @message = Message.new(message_params) - respond_to do |format| - if @message.save - format.json { render json: { data: 'Saved' }, status: :ok } - else - format.json { render json: { errors: @message.errors.full_messages }, status: :unprocessable_entity } - end + @message.user_id = current_user.id + if @message.save + flash[:notice] = 'Message Sent' + else + flash[:error] = @message.errors.full_messages.join(', ') end end private def message_params - params.permit(:user_id, :match_id, :message) + params.permit(:match_id, :content) end end diff --git a/app/controllers/selection_controller.rb b/app/controllers/selection_controller.rb index a558ca6..5d890e7 100644 --- a/app/controllers/selection_controller.rb +++ b/app/controllers/selection_controller.rb @@ -1,26 +1,21 @@ # frozen_string_literal: true class SelectionController < ApplicationController + load_and_authorize_resource before_action :authenticate_user! + def create @selection = Selection.new(selection_params) - @selection.add_try_num - respond_to do |format| - if @selection.save - format.json { render json: { data: 'Saved' }, status: :ok } - else - format.json { render json: { errors: @selection.errors.full_messages }, status: :unprocessable_entity } - end + if @selection.save + flash[:notice] = 'Choice Saved' + else + flash[:error] = @selection.errors.full_messages.join(', ') end end - def new - @selection = Selection.new - end - private def selection_params - params.require(:selection).permit(:match_id, :user_id, :selection) + params.require(:selection).permit(:match_id, :user_id, :choice) end end diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index d50a962..49058d4 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,73 +1,56 @@ -# Tournamnets Controller +# frozen_string_literal: true + class TournamentsController < ApplicationController load_and_authorize_resource before_action :authenticate_user!, except: [:index] def index - @tournaments = Tournament.includes(:users).all - @tournaments = @tournaments.order(:registration_deadline).page(params[:page]) + @tournaments = @tournaments.includes(%i[users winner]).desc.page(params[:page]) end def show; end - def new - @tournament = Tournament.new - end + def new; end def edit; end - def register - @tournaments_user = TournamentsUser.new(user: current_user, tournament: @tournament) - if @tournaments_user.save - redirect_to tournament_url(@tournament), notice: 'You have registered for the tournament!' - else - redirect_to tournament_url(@tournament), notice: 'Already Registered' - end - end - def create_matches - registered_users = @tournament.users - length = registered_users.length - return unless (length - 8) != 0 - begin - MatchCreator.new(@tournament, registered_users).create_match - redirect_to tournament_path(@tournament), notice: 'Matches Generated' + TournamentMatchesCreator.new(@tournament, @tournament.users).call + flash[:notice] = 'Matches Generated.' rescue StandardError => e - redirect_to tournament_path(@tournament), alert: "Error generating matches: #{e.message}" + flash[:alert] = "Error generating matches: #{e.message}" end + redirect_to tournament_path(@tournament) end def create - @tournament = Tournament.new(tournament_params) - - respond_to do |format| - if @tournament.save - format.html { redirect_to tournament_url(@tournament), notice: 'Tournament was successfully created.' } - else - format.html { render :new, status: :unprocessable_entity } - end + if @tournament.save + flash[:notice] = 'Tournament was successfully created.' + redirect_to tournament_path(@tournament) + else + flash[:errors] = @tournament.errors.full_messages.join(', ') + render :new end end def update - respond_to do |format| - if @tournament.update(tournament_params) - format.html { redirect_to tournament_url(@tournament), notice: 'Tournament was successfully updated.' } - else - format.html { render :edit, status: :unprocessable_entity } - end + if @tournament.update(tournament_params) + flash[:notice] = 'Tournament was successfully updated.' + redirect_to tournament_path(@tournament) + else + flash[:errors] = @tournament.errors.full_messages.join(', ') + render :edit end end def destroy - respond_to do |format| - if @tournament.destroy - format.html { redirect_to tournaments_url, notice: 'Tournament was successfully deleted.' } - else - format.html { render :edit, status: :unprocessable_entity } - end + if @tournament.destroy + flash[:notice] = 'Tournament was successfully deleted.' + else + flash[:errors] = @tournament.errors.full_messages.join(', ') end + redirect_to tournaments_path end private @@ -77,7 +60,7 @@ def tournament_params :description, :start_date, :end_date, - :tournament_winner_id, + :winner_id, :image, :registration_deadline) end diff --git a/app/controllers/tournaments_users_controller.rb b/app/controllers/tournaments_users_controller.rb new file mode 100644 index 0000000..06c1158 --- /dev/null +++ b/app/controllers/tournaments_users_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class TournamentsUsersController < ApplicationController + before_action :authenticate_user! + + def create + @tournament_user = TournamentsUser.new(tournament_user_params) + if @tournament_user.save + flash[:notice] = 'You have registered for the tournament!' + else + flash[:alert] = @tournament_user.errors.full_messages.join(', ') + end + redirect_to tournament_path(params[:id]) + end + + private + + def tournament_user_params + { tournament_id: params[:id].to_i, user_id: current_user.id } + end +end diff --git a/app/controllers/users/confirmations_controller.rb b/app/controllers/users/confirmations_controller.rb index 90e7ff0..c1c81d6 100644 --- a/app/controllers/users/confirmations_controller.rb +++ b/app/controllers/users/confirmations_controller.rb @@ -15,7 +15,8 @@ def show end end - # protected + protected + def after_confirmation_path_for(_resource_name, resource) sign_in(resource) root_path diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 71271bc..66df3b6 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -27,9 +27,4 @@ def create def after_sign_up_path_for(_resource) root_path end - - def configure_permitted_parameters - devise_parameter_sanitizer.permit(:sign_up, keys: %i[avatar name phone_number]) - devise_parameter_sanitizer.permit(:account_update, keys: %i[avatar name phone_number]) - end end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index cf3a3da..a0d13d4 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -2,57 +2,31 @@ module Users class SessionsController < Devise::SessionsController - before_action :authenticate_2fa!, only: [:create] + before_action :generate_and_send_otp, only: [:create] before_action :authenticate_user!, except: %i[new create destroy] - before_action :load_and_authorize_resource, except: %i[new create destroy] - - def create - super do |resource| - if resource.valid? && resource.persisted? - resource.update( - otp_required_for_login: true - ) - end + def verify_otp + user = User.find_by(id: session[:user_id]) + if User.auth_with_2fa(user_params[:otp_attempt], user) + sign_in(:user, user) + redirect_to root_path, notice: 'OTP consumed Successfully.' + else + redirect_to new_user_session_path, alert: 'Invalid OTP code entered.' end end private - def authenticate_2fa! - user = self.resource = find_user + def generate_and_send_otp + user = self.resource = User.find_by(email: user_params[:email]) + return unless user - if otp_attempt_present? - authenticate_with_2fa(user) - sign_in(:user, user) - elsif valid_password_and_otp_required?(user) + if user.valid_password?(user_params[:password]) session[:user_id] = user.id - send_otp_code(user) + TwoFactorAuthenticator.new(user).call render 'user_otp/two_fa' - end - end - - def otp_attempt_present? - user_params[:otp_attempt].present? - end - - def authenticate_with_2fa(user) - User.auth_with_2fa(user_params[:otp_attempt], user) - end - - def valid_password_and_otp_required?(user) - user.valid_password?(user_params[:password]) && user.otp_required_for_login - end - - def send_otp_code(user) - TwoFactorAuth.new(user).send_otp_code - end - - def find_user - if session[:user_id] - User.find_by(id: session[:user_id]) - elsif user_params[:email] - User.find_by(email: user_params[:email]) + else + redirect_to new_user_session_path, alert: 'Invalid Email or Password.' end end diff --git a/app/javascript/channels/match_channel.js b/app/javascript/channels/match_channel.js new file mode 100644 index 0000000..4b22322 --- /dev/null +++ b/app/javascript/channels/match_channel.js @@ -0,0 +1,67 @@ +import consumer from "./consumer"; +import { + eventListenerToButtons, + disableButtons, +} from "../packs/buttons_activity"; +import { selectionPost } from "../packs/selection_post"; + +document.addEventListener("turbolinks:load", () => { + const matchId = document + .getElementById("match_id") + .getAttribute("data-match-id"); + consumer.subscriptions.create( + { channel: "MatchChannel", match_id: matchId }, + { + received(data) { + const player1Id = document + .getElementById("player1_id") + .getAttribute("data-player1-id"); + const player2Id = document + .getElementById("player2_id") + .getAttribute("data-player2-id"); + const userId = document + .getElementById("user_id") + .getAttribute("data-user-id"); + if (data.user1_id != undefined) { + var choice1 = data.choice1; + var choice2 = data.choice2; + var status = data.status; + const user1ChoiceElement = document.getElementById(data.user1_id); + const user2ChoiceElement = document.getElementById(data.user2_id); + const displayDiv = document.getElementById("second_timer"); + user1ChoiceElement.src = "/assets/" + choice1 + ".png"; + user2ChoiceElement.src = "/assets/" + choice2 + ".png"; + displayDiv.textContent = status; + if (data.done != undefined) { + setTimeout(function () { + location.reload(true); + }, 2000); + return; + } + } else { + var seconds = data.seconds; + var tryNum = data.try_num; + var tries = data.tries; + const displayDiv = document.getElementById("second_timer"); + const triesDiv = document.getElementById(`tries_${matchId}`); + displayDiv.textContent = seconds; + if (seconds != 0) { + triesDiv.textContent = `Try ${tryNum} of ${tries}`; + if (seconds === 5) { + eventListenerToButtons(); + const user1ChoiceElement = document.getElementById(player1Id); + const user2ChoiceElement = document.getElementById(player2Id); + user1ChoiceElement.src = "/assets/question.png"; + user2ChoiceElement.src = "/assets/question.png"; + } + } else { + disableButtons(); + if ([player1Id, player2Id].includes(userId)) { + selectionPost(); + } + } + } + }, + } + ); +}); diff --git a/app/javascript/channels/room_channel.js b/app/javascript/channels/room_channel.js index 9a4c390..97db491 100644 --- a/app/javascript/channels/room_channel.js +++ b/app/javascript/channels/room_channel.js @@ -12,48 +12,44 @@ document.addEventListener("turbolinks:load", () => { received(data) { const msgs = document.getElementById("message-list"); - const current_user = document.getElementById("current_user"); - const current_user_name = current_user.getAttribute( - "data-current-user-name" - ); - const msg_element = document.getElementById( - `msg_field_${data.user_name}` - ); + const user_id = document + .getElementById("user_id") + .getAttribute("data-user-id"); + const msg_element = document.getElementById(`msg_field_${user_id}`); + const current_user_name = document + .getElementById("current_user") + .getAttribute("data-current-user-name"); let messageHTML; - if (data.user_name === current_user_name) { - messageHTML = `
-
- ${data.user_name.substring(0, 3)} -
-
-
- ${data.message} -
-
-
${data.created_at}
-
-
-
`; - } else { - messageHTML = `
-
- ${data.user_name.substring(0, 3)} -
-
-
- ${data.message} -
-
-
${data.created_at}
-
-
-
`; - } - + messageHTML = `
+
+ ${data.sender_name.substring(0, 3)} +
+
+
+ ${data.message} +
+
+
${data.created_at} +
+
+
+
`; msgs.insertAdjacentHTML("afterbegin", messageHTML); const last_elem = msgs.firstChild; last_elem.scrollIntoView({ behavior: "smooth", block: "end" }); - if (msg_element) { + if (user_id == data.user_id) { msg_element.value = ""; } }, diff --git a/app/javascript/channels/timer_channel.js b/app/javascript/channels/timer_channel.js deleted file mode 100644 index e74b80d..0000000 --- a/app/javascript/channels/timer_channel.js +++ /dev/null @@ -1,61 +0,0 @@ -import consumer from "./consumer"; -import { - event_listener_to_buttons, - disable_buttons, -} from "../packs/buttons_activity"; -import { try_post } from "../packs/try_post"; - -document.addEventListener("turbolinks:load", () => { - const element = document.getElementById("match_id"); - const user1Element = document.getElementById("player1_id"); - const user2Element = document.getElementById("player2_id"); - const playerElement = document.getElementById("player_check"); - const match_id = element.getAttribute("data-match-id"); - const player1_id = user1Element.getAttribute("data-player1-id"); - const player2_id = user2Element.getAttribute("data-player2-id"); - const is_player = playerElement.getAttribute("data-player-check"); - consumer.subscriptions.create( - { channel: "TimerChannel", match_id: match_id }, - { - connected() {}, - - disconnected() {}, - - received(data) { - if (data.user1_id != undefined) { - var selection1 = data.selection1; - var selection2 = data.selection2; - var status = data.status; - const user1_selection = document.getElementById(player1_id); - const user2_selection = document.getElementById(player2_id); - const displayDiv = document.getElementById("second_timer"); - user1_selection.src = "/assets/" + selection1 + ".png"; - user2_selection.src = "/assets/" + selection2 + ".png"; - displayDiv.textContent = status; - } else { - var seconds = data.seconds; - var try_num = data.try_num; - var tries = data.tries; - const displayDiv = document.getElementById("second_timer"); - const triesDiv = document.getElementById(`tries_${match_id}`); - displayDiv.textContent = seconds; - if (seconds != 0) { - triesDiv.textContent = `Try ${try_num} of ${tries}`; - if (seconds === 5) { - event_listener_to_buttons(); - const user_selection = document.getElementById(player1_id); - const opponent_selection = document.getElementById(player2_id); - user_selection.src = "/assets/question.png"; - opponent_selection.src = "/assets/question.png"; - } - } else { - disable_buttons(); - if (is_player) { - try_post(); - } - } - } - }, - } - ); -}); diff --git a/app/javascript/controllers/countdown_controller.js b/app/javascript/controllers/countdown_controller.js index 9f69b68..24a282f 100644 --- a/app/javascript/controllers/countdown_controller.js +++ b/app/javascript/controllers/countdown_controller.js @@ -1,49 +1,25 @@ import { Controller } from "@hotwired/stimulus"; export default class extends Controller { - connect = (format = "seconds") => { - const reg_date = this.data.get("time"); - const tournament_id = this.data.get("id"); - const number = reg_date; + connect() { + const timeInSeconds = this.data.get("time"); const daysElement = document.getElementById("days"); const hoursElement = document.getElementById("hours"); const minutesElement = document.getElementById("minutes"); const secondsElement = document.getElementById("seconds"); let countdown; - convertFormat(format); - function convertFormat(format) { - switch (format) { - case "seconds": - return timer(number); - case "minutes": - return timer(number * 60); - case "hours": - return timer(number * 60 * 60); - case "days": - return timer(number * 60 * 60 * 24); - } - } - - function timer(seconds) { - const now = Date.now(); - const then = now + seconds * 1000; - + return countdownRefreshTimer(timeInSeconds); + function countdownRefreshTimer(seconds) { + const then = Date.now() + seconds * 1000; countdown = setInterval(() => { const secondsLeft = Math.round((then - Date.now()) / 1000); - if (secondsLeft <= 0) { - document.getElementById("days").style.display = "none"; - document.getElementById("hours").style.display = "none"; - document.getElementById("minutes").style.display = "none"; - document.getElementById("seconds").style.display = "none"; - clearInterval(countdown); + location.reload(true); return; } - displayTimeLeft(secondsLeft); }, 1000); } - function displayTimeLeft(seconds) { daysElement.textContent = Math.floor(seconds / 86400); hoursElement.textContent = Math.floor((seconds % 86400) / 3600); @@ -51,5 +27,5 @@ export default class extends Controller { secondsElement.textContent = seconds % 60 < 10 ? `0${seconds % 60}` : seconds % 60; } - }; + } } diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 8b8c589..ca6bc48 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -8,5 +8,5 @@ application.load(definitionsFromContext(context)); import CountdownController from "./countdown_controller"; application.register("countdown", CountdownController); -import LoadController from "./load_controller" -application.register("load", LoadController) \ No newline at end of file +import LoadController from "./load_controller"; +application.register("load", LoadController); diff --git a/app/javascript/controllers/load_controller.js b/app/javascript/controllers/load_controller.js index 999438c..2516e28 100644 --- a/app/javascript/controllers/load_controller.js +++ b/app/javascript/controllers/load_controller.js @@ -1,6 +1,5 @@ -import { Controller } from "@hotwired/stimulus" +import { Controller } from "@hotwired/stimulus"; -// Connects to data-controller="laod-file" export default class extends Controller { connect() { var loadFile = function (event) { diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 7582811..4fdcb7b 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -2,11 +2,11 @@ import Rails from "@rails/ujs"; import Turbolinks from "turbolinks"; import * as ActiveStorage from "@rails/activestorage"; import "./buttons_activity"; -import "./try_post"; +import "./selection_post"; import "channels"; import "../css/application.scss"; import "../controllers/countdown_controller"; -import "../packs/navigation.js"; +import "./navigation.js"; import "./flash.js"; Rails.start(); diff --git a/app/javascript/packs/buttons_activity.js b/app/javascript/packs/buttons_activity.js index 01f28d3..4887017 100644 --- a/app/javascript/packs/buttons_activity.js +++ b/app/javascript/packs/buttons_activity.js @@ -1,61 +1,51 @@ -function event_listener_to_buttons() { +function eventListenerToButtons() { const user1Element = document.getElementById("player1_id"); const user2Element = document.getElementById("player2_id"); - const user = document.getElementById("user_id"); + const currentUserElement = document.getElementById("user_id"); - const player1_id = user1Element.getAttribute("data-player1-id"); - const player2_id = user2Element.getAttribute("data-player2-id"); - const user_id = user.getAttribute("data-user-id"); - var user_image; - var flag = false; + const player1Id = user1Element.getAttribute("data-player1-id"); + const player2Id = user2Element.getAttribute("data-player2-id"); + const currentUserId = currentUserElement.getAttribute("data-user-id"); - if (player1_id === user_id) { - user_image = document.getElementById(player1_id); - flag = true; - } else if (player2_id === user_id) { - user_image = document.getElementById(player2_id); - flag = true; - } - if (flag) { + if ([player1Id, player2Id].includes(currentUserId)) { + var userImageElement = document.getElementById(currentUserId); document .getElementById("rock_button") .addEventListener("click", function () { - user_image.src = "/assets/rock.png"; + userImageElement.src = "/assets/rock.png"; }); document .getElementById("paper_button") .addEventListener("click", function () { - user_image.src = "/assets/paper.png"; + userImageElement.src = "/assets/paper.png"; }); document .getElementById("scissor_button") .addEventListener("click", function () { - user_image.src = "/assets/scissor.png"; + userImageElement.src = "/assets/scissor.png"; }); - enable_button("rock_button"); - enable_button("paper_button"); - enable_button("scissor_button"); + enableButtons(); } } -function disable_buttons() { - disable_button("rock_button"); - disable_button("paper_button"); - disable_button("scissor_button"); +function disableButtons() { + disableButton("rock_button"); + disableButton("paper_button"); + disableButton("scissor_button"); } -function enable_buttons() { - enable_button("rock_button"); - enable_button("paper_button"); - enable_button("scissor_button"); +function enableButtons() { + enableButton("rock_button"); + enableButton("paper_button"); + enableButton("scissor_button"); } -function disable_button(id) { +function disableButton(id) { document.getElementById(id).disabled = true; document.getElementById(id).classList.remove("choice-button"); document.getElementById(id).classList.add("choice-button2"); } -function enable_button(id) { +function enableButton(id) { document.getElementById(id).disabled = false; document.getElementById(id).classList.remove("choice-button"); document.getElementById(id).classList.add("choice-button2"); } -export { event_listener_to_buttons, disable_buttons }; +export { eventListenerToButtons, disableButtons }; diff --git a/app/javascript/packs/selection_post.js b/app/javascript/packs/selection_post.js new file mode 100644 index 0000000..40be531 --- /dev/null +++ b/app/javascript/packs/selection_post.js @@ -0,0 +1,39 @@ +import Rails from "@rails/ujs"; +function selectionPost() { + const user1Element = document.getElementById("player1_id"); + const user2Element = document.getElementById("player2_id"); + const currentUserElement = document.getElementById("user_id"); + + const player1Id = user1Element.getAttribute("data-player1-id"); + const player2Id = user2Element.getAttribute("data-player2-id"); + const currentUserId = currentUserElement.getAttribute("data-user-id"); + const matchElement = document.getElementById("match_id"); + const tournamentElement = document.getElementById("tournament_id"); + const matchId = matchElement.getAttribute("data-match-id"); + const tournamentId = tournamentElement.getAttribute("data-tournament-id"); + if ([player1Id, player2Id].includes(currentUserId)) { + const imageElement = document.getElementById(currentUserId); + var src = imageElement.src; + var fileName = src.substring(src.lastIndexOf("/") + 1); + const userSelectedChoice = fileName.substring(0, fileName.lastIndexOf(".")); + if (fileName[0] == "q") { + return; + } + var data = { + match_id: matchId, + user_id: currentUserId, + choice: userSelectedChoice, + }; + Rails.ajax({ + url: `/tournaments/${tournamentId}/matches/${matchId}/selection`, + type: "post", + beforeSend(xhr, options) { + xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); + options.data = JSON.stringify(data); + return true; + }, + }); + } +} + +export { selectionPost }; diff --git a/app/javascript/packs/try_post.js b/app/javascript/packs/try_post.js deleted file mode 100644 index ffed2d1..0000000 --- a/app/javascript/packs/try_post.js +++ /dev/null @@ -1,54 +0,0 @@ -function try_post() { - const user1Element = document.getElementById("player1_id"); - const user2Element = document.getElementById("player2_id"); - const user = document.getElementById("user_id"); - - const player1_id = user1Element.getAttribute("data-player1-id"); - const player2_id = user2Element.getAttribute("data-player2-id"); - const user_id = user.getAttribute("data-user-id"); - const match_element = document.getElementById("match_id"); - const match_id = match_element.getAttribute("data-match-id"); - let imageElement; - if (player1_id == user_id) { - imageElement = document.getElementById(player1_id); - } else if (player2_id == user_id) { - imageElement = document.getElementById(player2_id); - } - var src = imageElement.src; - var fileName = src.substring(src.lastIndexOf("/") + 1); - const user_selection_image = fileName.substring(0, fileName.lastIndexOf(".")); - if (fileName[0] == "q") { - return; - } - var data = { - selection: { - match_id: match_id, - user_id: user_id, - selection: user_selection_image, - }, - }; - - console.log(data); - var xhr = new XMLHttpRequest(); - xhr.open("POST", "/selection", true); - xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - var csrf_token = document.getElementsByName("csrf-token")[0].content; - xhr.setRequestHeader("X-CSRF-Token", csrf_token); - xhr.onreadystatechange = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { - if (xhr.status === 200 || xhr.status === 201) { - var response = JSON.parse(xhr.responseText); - console.log(response.data); - } else if (xhr.status === 422) { - console.error("Error:", xhr.responseText); - } else { - console.error("Error:", xhr.responseText); - } - } - }; - if (data != undefined && data != "string") { - xhr.send(JSON.stringify(data)); - } -} - -export { try_post }; diff --git a/app/jobs/match_broadcast_job.rb b/app/jobs/match_broadcast_job.rb deleted file mode 100644 index 1a6d2ed..0000000 --- a/app/jobs/match_broadcast_job.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -# Match_Broadcast_Job -class MatchBroadcastJob < ApplicationJob - queue_as :default - - def perform(match_id, try_num, tries) - 5.downto(0) do |seconds| - ActionCable.server.broadcast("timer_channel_#{match_id}", { seconds:, try_num:, tries: }) - sleep 1 - end - end - - after_perform do |job| - try_num = job.arguments[1] - 1 - match = Match.find(job.arguments[0]) - sleep 3 - match.handle_missing_selections(try_num) - UpdateWinner.new(match, try_num).update_winner - user1_id, user2_id = match.users.ids - selection1, selection2 = match.selections.by_try_num(try_num).group_by(&:user_id).values_at(user1_id, user2_id) - selection1 = selection1.first - selection2 = selection2.first - status = selection1.status || selection2.status || 'Draw' - broadcast(match.id, user1_id, user2_id, status, [selection1, selection2]) - sleep(0.5) - user1_winning_selections, user2_winning_selections = match.selections.winner.group_by(&:user_id).values_at( - user1_id, user2_id - ) - score1 = score(user1_winning_selections) - score2 = score(user2_winning_selections) - try_num += 1 - monitor_tries(match, try_num, score1, score2, [selection1, selection2]) - end - - def score(selections) - if selections.nil? - 0 - else - selections.size - end - end - - def monitor_tries(match, try_num, score1, score2, selections) - user1_id, user2_id = match.users.ids - if try_num < 3 || try_num == 4 - match.delayed_job(Time.zone.now + 5.seconds, try_num + 1) - elsif score1 == score2 - handle_equal_scores(match, try_num, selections) - elsif [3, 5].include?(try_num) - winner_id = score1 > score2 ? user1_id : user2_id - match.update(match_winner_id: winner_id) - broadcast(match.id, user1_id, user2_id, "#{match.winner.name} won", selections) - sleep 2 - generate_matches(match, selections) - end - end - - def handle_equal_scores(match, try_num, selections) - user1_id, user2_id = match.users.ids - if try_num == 3 - add_tries_and_broadcast(match, try_num, selections) - elsif try_num == 5 - broadcast(match.id, user1_id, user2_id, 'Random Picking', selections) - sleep 2 - match.update(match_winner_id: [user1_id, user2_id].sample) - broadcast(match.id, user1_id, user2_id, "#{match.winner.name} won", selections) - generate_matches(match, selections) - end - end - - def add_tries_and_broadcast(match, try_num, selections) - user1_id, user2_id = match.users.ids - match.update(tries: 5) - broadcast(match.id, user1_id, user2_id, '2 tries added', selections) - sleep 2 - MatchBroadcastJob.perform_later(match.id, try_num + 1, 5) - end - - def broadcast(match_id, user1_id, user2_id, status, selections) - ActionCable.server.broadcast("timer_channel_#{match_id}", - { user1_id:, user2_id:, status:, - selection1: selections.first.selection, - selection2: selections.last.selection }) - end - - def generate_matches(match, selections) - user1_id, user2_id = match.users.ids - tournament = match.tournament - current_round_remaining_matches = tournament.remaining_matches_by_round_size(match.round) - done_matches_size = tournament.done_matches_size(match.round) - matches_by_round_size = tournament.matches_by_round_size(match.round) - if done_matches_size == 1 && matches_by_round_size == 1 - tournament.update_column(:tournament_winner_id, match.match_winner_id) - broadcast(match.id, user1_id, user2_id, "Tournament Winner is #{tournament.winner.name}", selections) - elsif current_round_remaining_matches.zero? - tournament.create_matches(match) - end - end -end diff --git a/app/jobs/match_execution_job.rb b/app/jobs/match_execution_job.rb new file mode 100644 index 0000000..c3971eb --- /dev/null +++ b/app/jobs/match_execution_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class MatchExecutionJob < ApplicationJob + queue_as :default + + def perform(match_id, try_num, tries) + 5.downto(0) do |seconds| + SelectionCountdownBroadcaster.new(match_id, seconds, try_num, tries).call + sleep 1 + end + end + + after_perform do |job| + try_num = job.arguments[1] + match = Match.find(job.arguments[0]) + match.handle_missing_selections(try_num) + SelectionUpdateWinner.new(match, try_num).call + selection1, selection2 = match.selections.by_try_num(try_num) + status = selection1.status || selection2.status || 'Draw Try' + SelectionResultBroadcaster.new(status, [selection1, selection2]).call + sleep 1 + score1, score2 = match.scores + MatchProgressionHandler.new(match, try_num, score1, score2, [selection1, selection2]).call + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index d84cb6e..7c37ea6 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true class ApplicationMailer < ActionMailer::Base - default from: 'from@example.com' + default from: 'muhammad.ramzan@techieminions.co' layout 'mailer' end diff --git a/app/models/ability.rb b/app/models/ability.rb index e49957a..9f34218 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -9,13 +9,13 @@ def initialize(user) can %i[create new], [User] if user.member? can %i[create new index], Message - can :register, Tournament can %i[playmatch index show result all], Match - can :create, Selection, match: { user_id: user.id } + can :create, TournamentsUser + can :create, Selection, match: { users: { id: user.id } } can :authenticate_2fa, User elsif user.admin? can %i[create new index], Message - can %i[new create create_matches index edit], Tournament + can %i[new create create_matches index update], Tournament can %i[playmatch result index show new create all], Match can :authenticate_2fa, User end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/concerns/image_validatable.rb b/app/models/concerns/image_validatable.rb new file mode 100644 index 0000000..c928d20 --- /dev/null +++ b/app/models/concerns/image_validatable.rb @@ -0,0 +1,31 @@ +# frozen_literal_string: true + +# app/models/concerns/image_validatable.rb +module ImageValidatable + extend ActiveSupport::Concern + + included do + validate on: %i[create update], unless: :skip_validation? do + validate :validate_image_type + validate :validate_image_size + end + end + + private + + def skip_validation? + !image.attached? + end + + def validate_image_type + return unless image.attached? && !image.content_typecontent_type.in?(%w[image/png image/jpeg image/jpg]) + + errors.add(:image, 'must be a JPG, JPEG or PNG') + end + + def validate_image_size + return unless image.attached? && image.byte_size > 5.megabytes + + errors.add(:image, 'size should be less than 5MB') + end +end diff --git a/app/models/match.rb b/app/models/match.rb index 5a9b01d..a9538c9 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -4,47 +4,71 @@ class Match < ApplicationRecord paginates_per 5 belongs_to :tournament - belongs_to :winner, class_name: 'User', foreign_key: 'match_winner_id', optional: true + belongs_to :winner, class_name: 'User', foreign_key: 'winner_id', optional: true has_many :messages has_many :users_matches has_many :users, through: :users_matches has_many :selections scope :desc, -> { order(round: :desc) } scope :by_round, ->(round) { where(round:) } - scope :done, -> { where.not(match_winner_id: nil) } - CHOICES = %w[rock rock rock].freeze - - def remaining_tries(user) - done_tries = selections.by_user(user).size - tries - done_tries - end + scope :done, -> { where.not(winner_id: nil) } + scope :undone, -> { where(winner_id: nil) } + scope :won, ->(user_id) { where(winner_id: user_id) } + CHOICES = %w[rock paper scissor].freeze + after_create :schedule + accepts_nested_attributes_for :users_matches def result_message(current_user_id) - if match_winner_id == current_user_id + if winner_id == current_user_id 'You Won' else - "#{winner.name} won" + "#{winner&.name} won" end end - def delayed_job(run_at = tournament.start_date + 10.seconds, try_num = 1) - MatchBroadcastJob.delay(run_at:).perform_later(id, try_num, tries) + def schedule(run_at = time + 10.seconds, try_num = 1) + MatchExecutionJob.delay(run_at:).perform_later(id, try_num, tries) end - def set_random_choices(user_id, try_num) - selections.create(user_id:, selection: CHOICES.sample, try_num:) + def add_random_choices(user_id) + selections.create(user_id:, choice: CHOICES.sample) end def handle_missing_selections(try_num) players_selections = selections.by_try_num(try_num) first_user_id, second_user_id = users.ids - set_random_choices(first_user_id, try_num) unless players_selections.exists?(user_id: first_user_id) + add_random_choices(first_user_id) unless players_selections.exists?(user_id: first_user_id) return if players_selections.exists?(user_id: second_user_id) - set_random_choices(second_user_id, try_num) + add_random_choices(second_user_id) + end + + def done? + winner_id.present? + end + + def undone? + winner_id.nil? + end + + def scores + users_score = selections.winner.group(:user_id).count + users.map { |user| users_score[user.id] || 0 } + end + + def update_winner_on_scores + score1, score2 = scores + winner_id = score1 > score2 ? users.first.id : users.second.id + update(winner_id:) + end + + def update_random_winner + update(winner_id: users.sample.id) end - def other_user(user1) - users.where.not(id: user1.id).first + def players_data_by_current_user(current_user) + current_user_data = { current_user.id => current_user.name } + players_data = users.where.not(id: current_user.id).pluck(:id, :name).to_h + players_data.length == 1 ? current_user_data.merge(players_data) : players_data # current_user is player then place its data at first place and other player_data at last. end end diff --git a/app/models/message.rb b/app/models/message.rb index 9e6b9a3..d402a6b 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -7,6 +7,6 @@ class Message < ApplicationRecord after_save :broadcast def broadcast - BroadcastMessage.send(message, name, created_at.strftime('%H:%M:%S'), match_id) + MessageBroadcaster.new(self).call end end diff --git a/app/models/selection.rb b/app/models/selection.rb index f8987a4..448e0ff 100644 --- a/app/models/selection.rb +++ b/app/models/selection.rb @@ -6,10 +6,11 @@ class Selection < ApplicationRecord scope :winner, -> { where(winner: true) } scope :by_user, ->(user_id) { where(user_id:) } scope :by_try_num, ->(try_num) { where(try_num:) } + after_initialize :add_try_num + validates :try_num, uniqueness: { scope: %i[match_id user_id] } def add_try_num - done_tries_size = match.selections.by_user(user_id).size - self.try_num = match.tries - (match.tries - done_tries_size) + self.try_num ||= match.selections.by_user(user_id).size + 1 end def status diff --git a/app/models/tournament.rb b/app/models/tournament.rb index b917c84..77d0387 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -1,30 +1,33 @@ # frozen_string_literal: true -# Tournaments:: controller class Tournament < ApplicationRecord paginates_per 3 + include ImageValidatable has_many :tournaments_users + has_many :users has_many :users, through: :tournaments_users - belongs_to :winner, class_name: 'User', foreign_key: 'tournament_winner_id', optional: true + belongs_to :winner, class_name: 'User', foreign_key: 'winner_id', optional: true has_many :matches, dependent: :destroy has_one_attached :image validates :name, :description, :start_date, :end_date, :registration_deadline, presence: true validate :end_date_is_after_start_date validate :start_date_validation validate :deadline_before_start_date + scope :won, ->(user_id) { where(winner_id: user_id) } + scope :desc, -> { order(registration_deadline: :desc) } def deadline_before_start_date return if registration_deadline.nil? || start_date.nil? - return unless registration_deadline > start_date || registration_deadline < Time.zone.now + return if registration_deadline < start_date && registration_deadline > Time.zone.now - errors.add(:registration_deadline, 'Registration Deadline is after Start Date') + errors.add(:registration_deadline, ':Please Check Registration Deadline') end def end_date_is_after_start_date return if end_date.nil? || start_date.nil? return unless end_date < start_date - errors.add(:end_date, 'End Date is before Start Date') + errors.add(:end_date, ':End Date is before Start Date') end def start_date_validation @@ -34,32 +37,50 @@ def start_date_validation errors.add(:start_date, 'Start Date is in Past') end - def done_matches_size(round) - matches.by_round(round).where.not(match_winner_id: nil).size - end - - def remaining_matches_by_round_size(round) - matches.by_round(round).where(match_winner_id: nil).size - end - - def matches_by_round_size(round) - matches.by_round(round).size - end - def current_round_winners(round) - users.where(id: matches.select(:match_winner_id).by_round(round)) + users.where(id: matches.select(:winner_id).by_round(round)) end def current_match_time if matches.empty? - start_date + 30.seconds + start_date + 60.seconds else Time.zone.now + 90.seconds end end - def create_matches(match) - MatchCreator.new(self, current_round_winners(match.round), - match.round + 1).create_match + def create_matches(round) + TournamentMatchesCreator.new(self, current_round_winners(round), + round + 1).call + end + + def done_matches_count_by_round(round) + matches.by_round(round).done.count + end + + def matches_count_by_round(round) + matches.by_round(round).count + end + + def create_next_round_matches? + pending_matches_count.zero? + end + + def pending_matches_count + matches.undone.count + end + + def final_match_done?(round) + current_round_done_matches_count = done_matches_count_by_round(round) + current_round_matches_count = matches_count_by_round(round) + current_round_done_matches_count == 1 && current_round_matches_count == 1 + end + + def generate_matches_or_update_winner(match) + if final_match_done?(match.round) + update_column(:winner_id, match.winner_id) + elsif create_next_round_matches? + create_matches(match.round) + end end end diff --git a/app/models/tournaments_user.rb b/app/models/tournaments_user.rb index 2b51150..50f7918 100644 --- a/app/models/tournaments_user.rb +++ b/app/models/tournaments_user.rb @@ -3,6 +3,5 @@ class TournamentsUser < ApplicationRecord belongs_to :user belongs_to :tournament - validates :user_id, uniqueness: { scope: :tournament_id } end diff --git a/app/models/user.rb b/app/models/user.rb index e377e80..0ae82a4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,15 +1,17 @@ # frozen_string_literal: true class User < ApplicationRecord + include ImageValidatable has_many :tournaments_users has_many :messages has_many :tournaments, through: :tournaments_users has_many :users_matches has_many :matches, through: :users_matches - has_one_attached :avatar - scope :members, -> { where(role: 'member') } - # enum role: %w[member admin] - + has_one_attached :image + enum role: %w[member admin] + validates :phone_number, presence: true, + numericality: true, + length: { minimum: 10, maximum: 15 } devise :registerable, :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable, :confirmable, @@ -35,22 +37,14 @@ def total_matches_played end def total_matches_won - matches.where(match_winner_id: id).size + matches.won(id).count end def total_tournaments_played - matches.distinct.count(:tournament_id) + tournaments.size end def total_tournaments_won - tournaments.distinct.where(tournament_winner_id: id).size - end - - def member? - role == 'member' - end - - def admin? - role == 'admin' + tournaments.won(id).count end end diff --git a/app/models/users_match.rb b/app/models/users_match.rb index f5b4676..e0c636e 100644 --- a/app/models/users_match.rb +++ b/app/models/users_match.rb @@ -3,8 +3,4 @@ class UsersMatch < ApplicationRecord belongs_to :user belongs_to :match - - def self.opponent_user_id(match_id, current_user_id) - UsersMatch.where(match_id:).where.not(user_id: current_user_id).pluck(:user_id).first - end end diff --git a/app/services/action_cable_broadcaster.rb b/app/services/action_cable_broadcaster.rb new file mode 100644 index 0000000..8a04d53 --- /dev/null +++ b/app/services/action_cable_broadcaster.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# broad_cast_to_a_channel_service +class ActionCableBroadcaster + def initialize(channel, data) + @channel = channel + @data = data + end + + def call + ActionCable.server.broadcast(@channel, @data) + end +end diff --git a/app/services/broadcast_message.rb b/app/services/broadcast_message.rb deleted file mode 100644 index 70964a1..0000000 --- a/app/services/broadcast_message.rb +++ /dev/null @@ -1,11 +0,0 @@ -# broad_cast_message_service -class BroadcastMessage - def self.send(message, user_name, created_at, match_id) - data = { - message:, - user_name:, - created_at: - } - ActionCable.server.broadcast("room_channel_#{match_id}", data) - end -end diff --git a/app/services/leader_board.rb b/app/services/leader_board.rb deleted file mode 100644 index ed5438e..0000000 --- a/app/services/leader_board.rb +++ /dev/null @@ -1,24 +0,0 @@ -# match_creator service -class LeaderBoard - def result - data = [] - User.all.each do |user| - tournaments_won = user.total_tournaments_won - data << { user:, tournaments_won: } - end - top_players(data) - end - - private - - def top_players(data) - top_tournaments_winners = data.sort_by! { |record| record[:tournaments_won] }.reverse!.take(10) - top_tournaments_winners.each do |record| - matches_played = record[:user].total_matches_played - tournaments_played = record[:user].total_tournaments_played - matches_won = record[:user].total_matches_won - record.merge!({ matches_played:, tournaments_played:, matches_won: }) - end - top_tournaments_winners - end -end diff --git a/app/services/leader_board_creator.rb b/app/services/leader_board_creator.rb new file mode 100644 index 0000000..dfad0f9 --- /dev/null +++ b/app/services/leader_board_creator.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# leader_board_service +class LeaderBoardCreator + def self.call + players_ids = Tournament.having('winner_id IS NOT NULL') + .group(:winner_id) + .order(count: :desc) + .pluck(:winner_id) + .take(10) + User.where(id: players_ids) + end +end diff --git a/app/services/match_creator.rb b/app/services/match_creator.rb deleted file mode 100644 index 41d4c44..0000000 --- a/app/services/match_creator.rb +++ /dev/null @@ -1,35 +0,0 @@ -# match_creator service -class MatchCreator - def initialize(tournament, registered_users, round = 0) - @tournament = tournament - @registered_users = registered_users.shuffle - @round = round - end - - def create_match - data = group_by_two - matches = data[1] - user_matches = data[0] - Match.transaction do - user_matches.each(&:save!) - end - matches.each(&:delayed_job) - end - - def group_by_two - user_matches = [] - matches = [] - match_time = @tournament.current_match_time - @registered_users.each_slice(2) do |user1, user2| - match = Match.create(tournament_id: @tournament.id, match_time:, round: @round) - matches << match - user_matches << user_match_obj(match, user1) - user_matches << user_match_obj(match, user2) - end - [user_matches, matches] - end - - def user_match_obj(match, user) - UsersMatch.new(match:, user:) - end -end diff --git a/app/services/match_progression_handler.rb b/app/services/match_progression_handler.rb new file mode 100644 index 0000000..70d105f --- /dev/null +++ b/app/services/match_progression_handler.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class MatchProgressionHandler + def initialize(match, try_num, score1, score2, selections) + @match = match + @try_num = try_num + @score1 = score1 + @score2 = score2 + @selections = selections + end + + def call + case @try_num + when 1, 2, 4 + schedule_next_try + when 3, 5 + if @score1 == @score2 + handle_equal_scores + else + update_winner_and_broadcast_status + generate_matches + end + end + end + + private + + def schedule_next_try + @match.schedule(Time.zone.now, @try_num + 1) + end + + def handle_equal_scores + if @try_num == 3 + handle_equal_scores_three + elsif @try_num == 5 + handle_equal_scores_five + end + end + + def handle_equal_scores_three + add_tries_and_broadcast + schedule_next_try + end + + def handle_equal_scores_five + broadcast_selections_result('Random Picking') + @match.update_random_winner + broadcast_selections_result("Match Winner is #{@match.winner.name}") + generate_matches + end + + def add_tries_and_broadcast + @match.update(tries: 5) + broadcast_selections_result('2 Tries Added') + end + + def broadcast_selections_result(status) + SelectionResultBroadcaster.new(status, @selections).call + sleep 1 + end + + def generate_matches + tournament = @match.tournament + tournament.generate_matches_or_update_winner(@match) + end + + def update_winner_and_broadcast_status + @match.update_winner_on_scores + broadcast_selections_result("Match Winner is #{@match.winner.name}") + end +end diff --git a/app/services/message_broadcaster.rb b/app/services/message_broadcaster.rb new file mode 100644 index 0000000..e8ac8d0 --- /dev/null +++ b/app/services/message_broadcaster.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# message_broadcast_service +class MessageBroadcaster < ActionCableBroadcaster + def initialize(message) + super("room_channel_#{message.match_id}", { + message: message.content, + sender_name: message.name, + user_id: message.user_id, + created_at: message.created_at.strftime('%H:%M:%S') + }) + end +end diff --git a/app/services/selection_countdown_broadcaster.rb b/app/services/selection_countdown_broadcaster.rb new file mode 100644 index 0000000..73aeb06 --- /dev/null +++ b/app/services/selection_countdown_broadcaster.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# selection_time_broadcast_service +class SelectionCountdownBroadcaster < ActionCableBroadcaster + def initialize(match_id, seconds, try_num, tries) + super("match_channel_#{match_id}", { + seconds:, + try_num:, + tries: + }) + end +end diff --git a/app/services/selection_result_broadcaster.rb b/app/services/selection_result_broadcaster.rb new file mode 100644 index 0000000..a76d76c --- /dev/null +++ b/app/services/selection_result_broadcaster.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# selection_result_broadcast_service +class SelectionResultBroadcaster < ActionCableBroadcaster + def initialize(status, selections) + @status = status + @match = selections.first.match + @user1_id, @user2_id = selections.pluck(:user_id).first(2) + @choice1, @choice2 = selections.pluck(:choice).first(2) + @data = data_hash + super("match_channel_#{@match.id}", @match.done? ? @data.merge!(done: true) : @data) + end + + private + + def data_hash + { + status: @status, + user1_id: @user1_id, + user2_id: @user2_id, + choice1: @choice1, + choice2: @choice2 + } + end +end diff --git a/app/jobs/update_winner.rb b/app/services/selection_update_winner.rb similarity index 70% rename from app/jobs/update_winner.rb rename to app/services/selection_update_winner.rb index 1ce0b68..97f7346 100644 --- a/app/jobs/update_winner.rb +++ b/app/services/selection_update_winner.rb @@ -1,12 +1,14 @@ -class UpdateWinner +# frozen_string_literal: true + +class SelectionUpdateWinner def initialize(match, try_num) @match = match @try_num = try_num end - def update_winner - selections = @match.selections.where(try_num: @try_num) - choice1, choice2 = selections.pluck(:selection).first(2) + def call + selections = @match.selections.by_try_num(@try_num) + choice1, choice2 = selections.map(&:choice) return if choice1 == choice2 if winning_combination?(choice1, choice2) @@ -16,6 +18,8 @@ def update_winner end end + private + def winning_combination?(choice1, choice2) combinations = [%w[rock scissor], %w[scissor paper], %w[paper rock]] combinations.include?([choice1, choice2]) diff --git a/app/services/tournament_matches_creator.rb b/app/services/tournament_matches_creator.rb new file mode 100644 index 0000000..3e023e5 --- /dev/null +++ b/app/services/tournament_matches_creator.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# match_creator service +class TournamentMatchesCreator + def initialize(tournament, registered_users, round = 0) + @tournament = tournament + @registered_users = registered_users.shuffle + @round = round + @length = registered_users.length + end + + def call + raise StandardError, 'Players should be in range 2 to 32' if @length <= 1 || @length > 32 + + raise StandardError, 'Players must be multiple of 8' unless (@length & (@length - 1)).zero? + + time = @tournament.current_match_time + @registered_users.each_slice(2) do |user1, user2| + Match.create!(tournament_id: @tournament.id, time:, round: @round, + users_matches_attributes: [{ user_id: user1.id }, { user_id: user2.id }]) + end + end +end diff --git a/app/services/two_factor_auth.rb b/app/services/two_factor_authenticator.rb similarity index 92% rename from app/services/two_factor_auth.rb rename to app/services/two_factor_authenticator.rb index 7f0c66f..65b535c 100644 --- a/app/services/two_factor_auth.rb +++ b/app/services/two_factor_authenticator.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true # two factor authentication -class TwoFactorAuth +class TwoFactorAuthenticator def initialize(user) @user = user end - def send_otp_code + def call @code = User.generate_otp(@user.otp_secret) CodeMailer.send_code(@code, @user.email).deliver_now # message = Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']).messages.create( diff --git a/app/services/update_winner.rb b/app/services/update_winner.rb deleted file mode 100644 index 1ce0b68..0000000 --- a/app/services/update_winner.rb +++ /dev/null @@ -1,23 +0,0 @@ -class UpdateWinner - def initialize(match, try_num) - @match = match - @try_num = try_num - end - - def update_winner - selections = @match.selections.where(try_num: @try_num) - choice1, choice2 = selections.pluck(:selection).first(2) - return if choice1 == choice2 - - if winning_combination?(choice1, choice2) - selections.first.update(winner: true) - else - selections.last.update(winner: true) - end - end - - def winning_combination?(choice1, choice2) - combinations = [%w[rock scissor], %w[scissor paper], %w[paper rock]] - combinations.include?([choice1, choice2]) - end -end diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index c04b81d..953a9b0 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -21,7 +21,7 @@

Welcome <%= @email %>!

You can confirm your account email through the link below:

-

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

+

<%= link_to "Confirm my account", confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index 1e58142..b1b5e23 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -1,5 +1,5 @@

Hello <%= @resource.email %>!

Someone has requested a link to change your password. You can do this through the link below.

-

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+

<%= link_to "Change my password", edit_password_url(@resource, reset_password_token: @token) %>

If you didn't request this, please ignore this email.

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb index 2f51880..e36bf6c 100644 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -1,4 +1,4 @@

Hello <%= @resource.email %>!

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

Click the link below to unlock your account:

-

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

+

<%= link_to "Unlock my account", unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/registrations/_text_field.html.erb b/app/views/devise/registrations/_text_field.html.erb deleted file mode 100644 index d982997..0000000 --- a/app/views/devise/registrations/_text_field.html.erb +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 6f78004..e850ea2 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,6 +1,6 @@
-
+

Edit <%= resource_name.to_s.humanize %>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> @@ -8,10 +8,14 @@
@@ -33,7 +37,7 @@ <%= f.password_field :password, autocomplete: "new-password", class: "border border-gold w-full rounded" %> <% if @minimum_password_length %>
- <%= @minimum_password_length %> characters minimum + <%= @minimum_password_length %>characters minimum <% end %>
@@ -48,7 +52,7 @@ <%= f.submit "Update", class: "px-7 py-2 btn-brown" %>
<% end %> - <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 5d634c4..6a58ad3 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -24,7 +24,7 @@ <%= f.label :password_confirmation, class: "text-white" %> <%= f.password_field :password_confirmation, class: "border border-gold w-full rounded" %>
-
+
By signing up, you agree to the Terms of Service @@ -40,8 +40,8 @@
- Already have an account? - Login + Already have an Account? + <%= link_to "Log in", new_user_session_path, class: "text-gold font-black hover:text-gold_shade2" %>
<% end %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index c7e3ebd..4696aa5 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,23 +1,25 @@
-
-

Sign in

+
+

Log in

<%= render "devise/shared/error_messages", resource: resource %> <%= form_for(resource, as: resource_name, url:session_path(resource_name)) do |f| %>
<%= f.label :email, class: "text-white" %> <%= f.email_field :email, - autofocus: true, autocomplete: "email", class: "border border-gold w-full rounded pt-3 mb-3" %> + autofocus: true, autocomplete: "email", class: "border border-gold w-full rounded pt-3 mb-3" %>
<%= f.label :password, class: "text-white" %> <%= f.password_field :password, autocomplete: "current-password", class: "border border-gold w-full rounded mb-3 py-3" %>
- <%= f.submit "Sign in", class: "actions mb-4 px-10 py-3 btn-brown" %> + <%= f.submit "Log in", class: "actions mb-4 px-10 py-3 btn-brown" %>
+ <% end %> +
+ Don't have an Account? + <%= link_to "Sign up", new_user_registration_path, class: "text-gold font-black hover:text-gold_shade2" %>
- <% end %> +
-
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index f8bc70f..9e0c1c5 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -1,20 +1,12 @@ -<%- if controller_name != 'sessions' %> +<%- if controller_name != "sessions" %> <%= link_to "Log in", new_session_path(resource_name) %>
<% end %> -<%- if devise_mapping.registerable? && controller_name != 'registrations' %> +<%- if devise_mapping.registerable? && controller_name != "registrations" %> <%= link_to "Sign up", new_registration_path(resource_name) %>
<% end %> -<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> +<%- if devise_mapping.recoverable? && controller_name != "passwords" && controller_name != "registrations" %> <%= link_to "Forgot your password?", new_password_path(resource_name) %>
<% end %> -<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> +<%- if devise_mapping.confirmable? && controller_name != "confirmations" %> <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
<% end %> -<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> - <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
-<% end %> -<%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
- <% end %> -<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb deleted file mode 100644 index 33abe51..0000000 --- a/app/views/devise/unlocks/new.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -

Resend unlock instructions

-<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
-
- <%= f.submit "Resend unlock instructions" %> -
-<% end %> -<%= render "devise/shared/links" %> diff --git a/app/views/layouts/_flash.js.erb b/app/views/layouts/_flash.js.erb new file mode 100644 index 0000000..b10c3be --- /dev/null +++ b/app/views/layouts/_flash.js.erb @@ -0,0 +1,15 @@ +var noticeElement = document.getElementById('flash-response-container'); +var newContent = "<%= flash[:notice] || flash[:error] %>"; +<% if flash[:error].present? %> + noticeElement.classList.add('bg-red-600'); +<% end %> +if (noticeElement.innerHTML.trim() !== '') { + newContent = "
" + newContent; +} +noticeElement.insertAdjacentHTML('beforeend',newContent); +noticeElement.style.display = 'block'; + +setTimeout(function() { + noticeElement.style.display = 'none'; + noticeElement.innerHTML = ''; +}, 2000); diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 1e403f3..2c6529f 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -5,9 +5,9 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> + <%= javascript_pack_tag "application", "data-turbolinks-track": "reload" %> <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" %> <% if notice %> -
- +
+ - <% end %> - <% if alert %> -
- +
+ <% end %> + <% if alert %> +
+ - <% end %> +
+ <% end %> <%= yield %> diff --git a/app/views/leaderboard/index.html.erb b/app/views/leaderboard/index.html.erb index 2d7d258..e0732ee 100644 --- a/app/views/leaderboard/index.html.erb +++ b/app/views/leaderboard/index.html.erb @@ -1,5 +1,5 @@
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
@@ -19,14 +19,14 @@ - <% @players.each.with_index(1) do |user_data, index| %> + <% @players.each.with_index(1) do |player, index| %> - - - - - + + + + + <% end %> diff --git a/app/views/matches/_choice_button.html.erb b/app/views/matches/_choice_button.html.erb new file mode 100644 index 0000000..df04854 --- /dev/null +++ b/app/views/matches/_choice_button.html.erb @@ -0,0 +1,4 @@ + diff --git a/app/views/matches/_choice_image.html.erb b/app/views/matches/_choice_image.html.erb new file mode 100644 index 0000000..2a5ae42 --- /dev/null +++ b/app/views/matches/_choice_image.html.erb @@ -0,0 +1,6 @@ +
+
+ <%= player_name %> +
+ <%= image_tag("question.png", id: player_id, width: "100", height: "100", class: "px-2 bg-no-repeat rounded-lg bg-white") %> +
diff --git a/app/views/matches/_choice_timer.html.erb b/app/views/matches/_choice_timer.html.erb index 8c1eb77..c8e7df8 100644 --- a/app/views/matches/_choice_timer.html.erb +++ b/app/views/matches/_choice_timer.html.erb @@ -1,5 +1,5 @@
-
- 0 +
+ 0
diff --git a/app/views/matches/_form.html.erb b/app/views/matches/_form.html.erb index 016db39..986e026 100644 --- a/app/views/matches/_form.html.erb +++ b/app/views/matches/_form.html.erb @@ -10,16 +10,16 @@
<% end %>
- <%= form.label :match_winner_id %> - <%= form.number_field :match_winner_id, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> + <%= form.label :winner_id %> + <%= form.number_field :winner_id, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
<%= form.label :winner_score %> <%= form.number_field :winner_score, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
- <%= form.label :match_time %> - <%= form.datetime_select :match_time, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> + <%= form.label :time %> + <%= form.datetime_select :time, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
<%= form.label :tournament_id %> diff --git a/app/views/matches/_match_message.html.erb b/app/views/matches/_match_message.html.erb index 4232282..127ff02 100644 --- a/app/views/matches/_match_message.html.erb +++ b/app/views/matches/_match_message.html.erb @@ -1,20 +1,20 @@ -
-
+
+
<% @messages.each do |message| %>
-
- <%= message.user.name[0,3] %> +
"> + <%= message.name[0,3] %>
-
- <%= message.message %> +
"> + <%= message.content %>
- <%= (message.created_at.in_time_zone).strftime('%H:%M:%S') %> + <%= (message.created_at.in_time_zone).strftime("%H:%M:%S") %>
diff --git a/app/views/matches/_match_timer.html.erb b/app/views/matches/_match_timer.html.erb index 14b4b62..dc269a3 100644 --- a/app/views/matches/_match_timer.html.erb +++ b/app/views/matches/_match_timer.html.erb @@ -1,20 +1,19 @@ -<% if match.match_time.before?(Time.zone.now) %> -<% else %> -
+<% if match.time.after?(Time.zone.now) %> +
-
+
d
-
-
+
+
h
-
-
+
+
m
-
-
+
+
s
diff --git a/app/views/matches/_random_images.html.erb b/app/views/matches/_random_images.html.erb deleted file mode 100644 index cbf8122..0000000 --- a/app/views/matches/_random_images.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -
-
- <%= match.users.find(user_id).name %> -
- <%= image_tag('question.png', id: user_id, width: "100", height: "100", class: "px-2 bg-no-repeat rounded-lg bg-white") %> -
diff --git a/app/views/matches/all.html.erb b/app/views/matches/all.html.erb index dbb53ae..8b7d5ef 100644 --- a/app/views/matches/all.html.erb +++ b/app/views/matches/all.html.erb @@ -1,5 +1,5 @@
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
<%= index %><%= user_data[:user].name %><%= user_data[:tournaments_played] %><%= user_data[:matches_won] %><%= user_data[:tournaments_won] %><%= player.name %><%= player.total_tournaments_played %><%= player.total_matches_won %><%= player.total_tournaments_won %>
@@ -15,20 +15,19 @@ - <% adjusted_index = @matches.current_page * 5 - 4 %> - <% @matches.each_with_index do |match, index| %> + <% index = @matches.offset_value + 1 %> + <% @matches.each.with_index(index) do |match, index| %> - - + + - + - <% adjusted_index = adjusted_index + 1 %> <% end %>
<%= match.users.first.name %> <%= match.users.last.name %> <%= match.winner.nil? ? '--' : match.winner.name %><%= match.winner&.name || "--" %> <%= link_to "show", tournament_match_url(tournament_id: match.tournament_id, id: match.id), method: :get, class: "mb-4 px-3 py-2 btn-brown" %>
@@ -36,9 +35,7 @@
-
- <%= paginate @matches, window: 1, outer_window: 2 %> -
+ <%= paginate @matches, window: 1, outer_window: 2 %>
diff --git a/app/views/matches/edit.html.erb b/app/views/matches/edit.html.erb deleted file mode 100644 index a220124..0000000 --- a/app/views/matches/edit.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -
-

Editing match

- <%= render "form", match: @match %> - <%= link_to "Show this match", @match, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> - <%= link_to 'Back', request.referer || root_path, class: "px-7 py-2 btn-brown" %> -
diff --git a/app/views/matches/index.html.erb b/app/views/matches/index.html.erb index 49049e8..c5b279a 100644 --- a/app/views/matches/index.html.erb +++ b/app/views/matches/index.html.erb @@ -1,5 +1,5 @@
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
@@ -14,21 +14,20 @@ + <% index = @matches.offset_value+1 %> - <% adjusted_index = @matches.current_page * 5 - 4 %> - <% @matches.each_with_index do |match, index| %> + <% @matches.each.with_index(index) do |match, index| %> - - + + - + - <% adjusted_index = adjusted_index + 1 %> <% end %>
View
<%= match.users.first.name %> <%= match.users.last.name %> <%= match.winner.nil? ? '--' : match.winner.name %><%= match.winner&.name || "--" %> <%= link_to "show", tournament_match_url(id: match.id), method: :get, class: "mb-4 px-3 py-2 btn-brown" %>
@@ -36,9 +35,7 @@
-
- <%= paginate @matches, window: 1,outer_window: 2 %> -
+ <%= paginate @matches, window: 1, outer_window: 2 %>
diff --git a/app/views/matches/new.html.erb b/app/views/matches/new.html.erb deleted file mode 100644 index 180474a..0000000 --- a/app/views/matches/new.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -
-

New match

- <%= render "form", match: @match %> - <%= link_to 'Back', request.referer || root_path, class: "px-7 py-2 btn-brown" %> -
diff --git a/app/views/matches/playmatch.html.erb b/app/views/matches/playmatch.html.erb index 0479dd8..d77462f 100644 --- a/app/views/matches/playmatch.html.erb +++ b/app/views/matches/playmatch.html.erb @@ -1,63 +1,62 @@ -
-
-
-
-
+
+
+
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
-
" class="my-3 bg-gold text-black text-xl py-3 px-3 rounded-lg mr-3"> - <%= "Remaining Tries : #{@remaining_tries}" %> -
-
- <% if @is_player %> - <% player1 = current_user %> - <% else %> - <% player1 = @match.users.first %> - <% end %> - <%= render 'random_images', user_id: player1.id %> - <%= render 'choice_timer', match: @match, remaining_seconds: @end_time %> - <%= render 'random_images', user_id: @match.other_user(player1).id %> -
+ <% if @match.done? %> +
+
+
Match Ended
+

Please go to Result Screen
+ to check winner and their selections

+ <%= link_to "Result", result_tournament_match_path(id: @match, tournament_id: @tournament), method: :get, class: "text-white hover:text-black" %> +
+
+ <% else %> + <% player1_id, player2_id = @players_data.keys %> +
+
+
+
" class="my-3 bg-gold text-black text-xl py-3 px-3 rounded-lg mr-3"> + Please Wait for the Match to Start +
+
+ <%= render "choice_image", player_name: @players_data[player1_id], player_id: player1_id %> + <%= render "choice_timer" %> + <%= render "choice_image", player_name: @players_data[player2_id], player_id: player2_id %> +
+ <% end %>
- <% if (can? :create, Selection) && @is_player %> -
-
Please select
-
- - - + <% if (can? :create, Selection) && @match.undone? %> + <% if @players_data.keys.include?(current_user.id) %> +
+
Please select
+
+ <%= render "choice_button", id: "rock_button", image: "rock.png" %> + <%= render "choice_button", id: "paper_button", image: "paper.png" %> + <%= render "choice_button", id: "scissor_button", image: "scissor.png" %> +
-
+ <% end %> <% end %>
-

Messages

-
- <%= form_with(url: tournament_match_messages_path(match_id: @match), remote: true, local: false) do |form| %> -
- <%= form.hidden_field :match_id, value: @match.id %> - <%= form.text_field :message, :autocomplete => :off, :required => 'required', placeholder: 'type message', class: "block shadow rounded-md border border-gray-200 outline-gold py-3 mb-4", id:"msg_field_" + current_user.name %> - <%= form.hidden_field :user_id, value: current_user.id %> +

Messages

+ <% if @match.undone? %> +
+ <%= form_with(url: tournament_match_messages_path(match_id: @match), remote: true, local: false) do |form| %>
+ <%= form.hidden_field :match_id, value: @match.id %> + <%= form.text_field :content, autocomplete: :off, required: :"required", placeholder: "type message", class: "block shadow rounded-md border border-gray-200 outline-gold py-3 mb-4", id:"msg_field_#{current_user.id}" %> <%= form.submit %>
-
- <% end %> -
+ <% end %> +
+ <% end %>
<%= render partial: "matches/match_message", locals: { messages: @messages, match: @match } %>
-
diff --git a/app/views/matches/result.html.erb b/app/views/matches/result.html.erb index 2e49c95..489ade5 100644 --- a/app/views/matches/result.html.erb +++ b/app/views/matches/result.html.erb @@ -1,50 +1,59 @@ -
- <% if @is_player %> - <% player1 = current_user %> +
+
+ <% if @match.undone? %> +
+
+
Winner Not Decided Yet
+

Please wait
+ or go to

+ <%= link_to "Live Match", playmatch_tournament_match_path(id: @match, tournament_id: @tournament), method: :get, class: "text-white hover:text-black" %> +
+
<% else %> - <% player1 = @players.first %> - <% end %> - <% player2 = @match.other_user(player1) %> - <% i = 0 %> -
-
- <%= player1.name %>
- scores -
- <%= @player1_scores %> + <% player1_id, player2_id = @players_data.keys %> +
+
+ <%= @players_data[player1_id] %>
+ scores
- <%= @players_scores[player1.id] || 0 %> + <%= @players_scores[player1_id] || 0 %>
-
-
-

- <%= @result_message %> -

-
-
- <%= player2.name %>
- scores -
- <%= @players_scores[player2.id] || 0 %> +
+

+ <%= @result_message %> +

-
-
- <% player1_selections = @players_selections[player1.id] %> - <% player2_selections = @players_selections[player2.id] %> - <% player1_selections.each do |player1_selection| %> - <% player1_choice = player1_selection.selection + ".png" %> - <% opponent_user_choice = player2_selections[i].selection + ".png" %> - <% status = player1_selection.status || player2_selections[i].status %> - <% status = !status ? "Draw" : status %> -
- <%= image_tag(player1_choice, width: "100", class:"m-5 mt-5 p-2 rounded-lg bg-gold") %> -
- <%= status %> +
+ <%= @players_data[player2_id] %>
+ scores +
+ <%= @players_scores[player2_id] || 0 %> +
- <%= image_tag(opponent_user_choice, width: "100", class: "m-5 mt-5 p-2 rounded-lg bg-gold") %>
- <% i += 1 %> + <% player1_selections = @players_selections[player1_id] %> + <% player2_selections = @players_selections[player2_id] %> + <% i = 0 %> + <% player1_selections.each do |player1_selection| %> + <% player1_choice = player1_selection.choice + ".png" %> + <% opponent_user_choice = player2_selections[i].choice + ".png" %> + <% status = player1_selection.status || player2_selections[i].status || "Draw Try" %> +
+
+ <%= image_tag(player1_choice, width: "100", class: "rounded-lg bg-gold p-1") %> +
+
+
+ <%= status %> +
+
+
+ <%= image_tag(opponent_user_choice, width: "100", class: "rounded-lg bg-gold p-1") %> +
+
+ <% i += 1 %> + <% end %> <% end %> - <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
diff --git a/app/views/matches/show.html.erb b/app/views/matches/show.html.erb index 7f594a9..ad19b6f 100644 --- a/app/views/matches/show.html.erb +++ b/app/views/matches/show.html.erb @@ -1,30 +1,30 @@
-
+

Match

-
- <%= @match.users.first.name.capitalize %> -
-
- vs -
-
- <%= @match.users.last.name.capitalize %> -
+
+ <%= @match.users.first.name.capitalize %> +
+
+ vs +
+
+ <%= @match.users.last.name.capitalize %> +
- <%= render 'match_timer', match: @match %> + <%= render "match_timer", match: @match %>

Match Time

- <%= @match.match_time.strftime("%B %d, %Y %I:%M %p") %> + <%= @match.time.strftime("%B %d, %Y %I:%M %p") %>

<% players = @match.users.pluck(:id) %>
<%= link_to "view", playmatch_tournament_match_path(match_id: @match.id), class: "px-7 py-2 btn-brown" %>
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
diff --git a/app/views/messages/create.js.erb b/app/views/messages/create.js.erb new file mode 100644 index 0000000..c89e50a --- /dev/null +++ b/app/views/messages/create.js.erb @@ -0,0 +1 @@ +<%= render partial: 'layouts/flash' %> diff --git a/app/views/selection/create.js.erb b/app/views/selection/create.js.erb new file mode 100644 index 0000000..c89e50a --- /dev/null +++ b/app/views/selection/create.js.erb @@ -0,0 +1 @@ +<%= render partial: 'layouts/flash' %> diff --git a/app/views/tournaments/_form.html.erb b/app/views/tournaments/_form.html.erb index f9e2faf..247b3e5 100644 --- a/app/views/tournaments/_form.html.erb +++ b/app/views/tournaments/_form.html.erb @@ -19,11 +19,11 @@ <%= form.label :image, class: "text-white" %>
@@ -41,7 +41,7 @@
<%= form.label :end_date, class: "text-white" %> - <%= form.datetime_local_field :end_date, class: "block shadow rounded-md border booutline-none px-3 py-2 mt-2 w-full" %> + <%= form.datetime_local_field :end_date, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
@@ -49,7 +49,7 @@
<% end %> - <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
diff --git a/app/views/tournaments/_registration_deadline.html.erb b/app/views/tournaments/_registration_deadline.html.erb index 300836e..933be59 100644 --- a/app/views/tournaments/_registration_deadline.html.erb +++ b/app/views/tournaments/_registration_deadline.html.erb @@ -5,21 +5,21 @@
<% else %> -
+
-
+
d
-
+
h
-
+
m
-
+
s
diff --git a/app/views/tournaments/_tournament.html.erb b/app/views/tournaments/_tournament.html.erb index f78cc2b..5ed03ba 100644 --- a/app/views/tournaments/_tournament.html.erb +++ b/app/views/tournaments/_tournament.html.erb @@ -4,13 +4,13 @@ <%= tournament.name %>

-
+
<% if tournament.image.attached? %> - <%= image_tag(tournament.image, class: "rounded-xl h-72 w-full") %> + <%= image_tag(tournament.image, width: "100%", height:"30", class: "rounded-xl") %> <% end %>
<% if action_name != "index" %> - <%= render 'description', tournament: tournament %> + <%= render "description", tournament: tournament %> <% end %>

Registration deadline @@ -18,18 +18,18 @@ <%= tournament.registration_deadline.strftime("%B %d, %Y %I:%M %p") %>

-<%= render 'registration_deadline', tournament: tournament %> +<%= render "registration_deadline", tournament: tournament %>

Registered Users (should be 8)

<%= @tournament.users.length %>

-<% if @tournament.tournament_winner_id %> +<% if @tournament.winner_id %>

Tournament Winner

- <%= User.find(@tournament.tournament_winner_id).name %> + <%= @tournament.winner.name %>

<% end %> @@ -45,12 +45,10 @@ <%= tournament.end_date.strftime("%B %d, %Y %I:%M %p") %>

-<% if can? :register, Tournament %> +<% if can? :create, TournamentsUser %> <% if Time.zone.now.before?(tournament.registration_deadline) %>
<%= form_with url: register_tournament_path(tournament.id), method: :post do |form| %> - <%= form.hidden_field :user_id, value: current_user.id %> - <%= form.hidden_field :tournament_id, value: tournament.id %> <%= form.submit "Register", class: "actions my-4 px-10 py-3 btn-brown" %> <% end %>
diff --git a/app/views/tournaments/edit.html.erb b/app/views/tournaments/edit.html.erb index 9970261..d653bb4 100644 --- a/app/views/tournaments/edit.html.erb +++ b/app/views/tournaments/edit.html.erb @@ -1,4 +1 @@ -

Editing tournament

-<%= render "form", tournament: @tournament %> -<%= link_to "Show this tournament", @tournament, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> -<%= link_to "Back to tournaments", tournaments_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> +<%= render "form", tournament: @tournament %> \ No newline at end of file diff --git a/app/views/tournaments/index.html.erb b/app/views/tournaments/index.html.erb index 3622783..743f2ce 100644 --- a/app/views/tournaments/index.html.erb +++ b/app/views/tournaments/index.html.erb @@ -6,7 +6,7 @@
<% @tournaments.each do |tournament| %> -
+

Name

@@ -21,9 +21,9 @@

Tournament Winner -

- <% if tournament.tournament_winner_id %> - <%= User.find(tournament.tournament_winner_id).name %> +
+ <% if tournament.winner_id %> + <%= tournament.winner.name %> <% else %> <%= 'Winner not decide yet' %> <% end %> @@ -43,9 +43,7 @@
-
- <%= paginate @tournaments, window: 1,outer_window: 2 %> -
+ <%= paginate @tournaments, window: 1, outer_window: 2 %>
diff --git a/app/views/tournaments/show.html.erb b/app/views/tournaments/show.html.erb index ce529ad..0affbb8 100644 --- a/app/views/tournaments/show.html.erb +++ b/app/views/tournaments/show.html.erb @@ -1,5 +1,5 @@ -
-
+
+

Tournament

@@ -9,30 +9,30 @@
<% if Time.zone.now.before?(@tournament.start_date) %> <% if can? :edit, Tournament %> - <%= link_to 'Edit', edit_tournament_path(@tournament), class: "mb-4 px-10 py-3 btn-brown" %> + <%= link_to "Edit", edit_tournament_path(@tournament), class: "mb-4 px-10 py-3 btn-brown" %> <% end %> <% end %> <% if Time.zone.now.before?(@tournament.start_date) %>
<% if can? :delete, Tournament %> - <%= button_to 'Delete', tournament_path(@tournament), method: :delete, class: "actions mb-4 px-10 py-3 btn-brown", data: {confirm: "Are you sure?" } %> + <%= button_to "Delete", tournament_path(@tournament), method: :delete, class: "actions mb-4 px-10 py-3 btn-brown", data: { confirm: "Are you sure?" } %>
<% end %> <% end %> <% if Time.zone.now.after?(@tournament.registration_deadline) %> <% if @tournament.matches.size==0 %> <% if can? :create_matches, Tournament %> - <%= link_to 'Generate Matches', create_matches_tournament_path(@tournament.id), method: :post, class: "mb-4 px-10 py-3 btn-brown" %> + <%= link_to "Generate Matches", create_matches_tournament_path(@tournament.id), method: :post, class: "mb-4 px-10 py-3 btn-brown" %> <% end %> <% else %> <% if can? :show, Match %> - <%= link_to 'View Matches', tournament_matches_path(@tournament), method: :get, class: "mb-4 px-10 py-3 btn-brown" %> + <%= link_to "View Matches", tournament_matches_path(@tournament), method: :get, class: "mb-4 px-10 py-3 btn-brown" %> <% end %> <% end %> <% end %>
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %> + <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
diff --git a/app/views/user_otp/two_fa.html.erb b/app/views/user_otp/two_fa.html.erb index 74c2042..a36dcae 100644 --- a/app/views/user_otp/two_fa.html.erb +++ b/app/views/user_otp/two_fa.html.erb @@ -3,7 +3,7 @@

OTP Verification

- <%= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| %> + <%= form_for(resource, as: resource_name, url: verify_otp_path(resource_name), method: :get) do |f| %>
<%= f.label "OTP" %> diff --git a/config/routes.rb b/config/routes.rb index ee48211..ae6f6df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,25 +1,21 @@ # frozen_string_literal: true -require 'delayed_job_web' Rails.application.routes.draw do get 'leaderboard/index' - patch 'user_otp/enable' - get 'user_otp/disable' match '/delayed_job' => DelayedJobWeb, :anchor => false, :via => %i[get post] - resources :selection - get '/matches_all', to: 'matches#all' resources :tournaments do member do - post 'register' + post '/register', to: 'tournaments_users#create' post '/create_matches', to: 'tournaments#create_matches' end - resources :matches do + resources :matches, except: %i[new create] do member do get '/playmatch', to: 'matches#playmatch', as: 'playmatch' get '/result', to: 'matches#result', as: 'result' end + resources :selection, only: [:create] resources :messages, only: [:create] end end @@ -31,7 +27,10 @@ } devise_scope :user do get '/users/sign_out' => 'devise/sessions#destroy' + get '/verify_otp', to: 'users/sessions#verify_otp' end root 'tournaments#index' - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + match '*path', to: 'application#not_found', via: :all, constraints: lambda { |req| + req.path.exclude?('/rails/active_storage/') + } end diff --git a/config/tailwind.config.js b/config/tailwind.config.js index 9473560..8f80a55 100644 --- a/config/tailwind.config.js +++ b/config/tailwind.config.js @@ -9,9 +9,12 @@ module.exports = { ], theme: { extend: { - + screens: { + 'mini': '200px', + 'md': '768px', + 'lg': '1024px', + }, backgroundImage: { - 'background-image': "url('background1.jpeg')", 'default_avatar': "url('default_avatar.png')", 'default_image': "url('image_icon.png')", @@ -25,11 +28,6 @@ module.exports = { '404': "url('404.png')", }, - spacing: { - '40': '40%', - '50': '50%', - '20': '20%', - }, colors: { "gold": "#d97706", "gold_shade2": "#b45309", diff --git a/db/migrate/20230324081551_create_matches.rb b/db/migrate/20230324081551_create_matches.rb index f0b2400..fa46077 100644 --- a/db/migrate/20230324081551_create_matches.rb +++ b/db/migrate/20230324081551_create_matches.rb @@ -3,7 +3,7 @@ class CreateMatches < ActiveRecord::Migration[6.1] def change create_table :matches do |t| - t.integer :match_winner_id + t.integer :winner_id t.integer :winner_score t.datetime :match_time t.references :tournament, null: false, foreign_key: true diff --git a/db/migrate/20230605083742_change_column_type_role_in_users.rb b/db/migrate/20230605083742_change_column_type_role_in_users.rb new file mode 100644 index 0000000..e832b6f --- /dev/null +++ b/db/migrate/20230605083742_change_column_type_role_in_users.rb @@ -0,0 +1,10 @@ +class ChangeColumnTypeRoleInUsers < ActiveRecord::Migration[6.1] + def up + execute <<-SQL + ALTER TABLE users + ALTER COLUMN role + SET DATA TYPE integer + USING (CASE WHEN role ~ E'^\\\\d+$' THEN role::integer ELSE 0 END); + SQL + end +end diff --git a/db/migrate/20230605111323_change_message_name_in_message.rb b/db/migrate/20230605111323_change_message_name_in_message.rb new file mode 100644 index 0000000..328e23c --- /dev/null +++ b/db/migrate/20230605111323_change_message_name_in_message.rb @@ -0,0 +1,5 @@ +class ChangeMessageNameInMessage < ActiveRecord::Migration[6.1] + def change + rename_column :messages, :message, :content + end +end diff --git a/db/migrate/20230605111804_change_match_time_name_in_match.rb b/db/migrate/20230605111804_change_match_time_name_in_match.rb new file mode 100644 index 0000000..9bec34f --- /dev/null +++ b/db/migrate/20230605111804_change_match_time_name_in_match.rb @@ -0,0 +1,5 @@ +class ChangeMatchTimeNameInMatch < ActiveRecord::Migration[6.1] + def change + rename_column :matches, :match_time, :time + end +end diff --git a/db/migrate/20230605113201_change_selection_name_in_selection.rb b/db/migrate/20230605113201_change_selection_name_in_selection.rb new file mode 100644 index 0000000..f6a1be7 --- /dev/null +++ b/db/migrate/20230605113201_change_selection_name_in_selection.rb @@ -0,0 +1,5 @@ +class ChangeSelectionNameInSelection < ActiveRecord::Migration[6.1] + def change + rename_column :selections, :selection, :choice + end +end diff --git a/db/migrate/20230605114240_change_match_winner_id_name_in_match.rb b/db/migrate/20230605114240_change_match_winner_id_name_in_match.rb new file mode 100644 index 0000000..125afc8 --- /dev/null +++ b/db/migrate/20230605114240_change_match_winner_id_name_in_match.rb @@ -0,0 +1,5 @@ +class ChangeMatchWinnerIdNameInMatch < ActiveRecord::Migration[6.1] + def change + rename_column :matches, :match_winner_id, :winner_id + end +end diff --git a/db/migrate/20230605115053_change_tournament_winner_id_name_in_tournament.rb b/db/migrate/20230605115053_change_tournament_winner_id_name_in_tournament.rb new file mode 100644 index 0000000..a31e1ac --- /dev/null +++ b/db/migrate/20230605115053_change_tournament_winner_id_name_in_tournament.rb @@ -0,0 +1,5 @@ +class ChangeTournamentWinnerIdNameInTournament < ActiveRecord::Migration[6.1] + def change + rename_column :tournaments, :tournament_winner_id, :winner_id + end +end diff --git a/db/migrate/20230613063351_remove_winner_score_from_matches.rb b/db/migrate/20230613063351_remove_winner_score_from_matches.rb new file mode 100644 index 0000000..844b095 --- /dev/null +++ b/db/migrate/20230613063351_remove_winner_score_from_matches.rb @@ -0,0 +1,5 @@ +class RemoveWinnerScoreFromMatches < ActiveRecord::Migration[6.1] + def change + remove_column :matches, :winner_score + end +end diff --git a/db/schema.rb b/db/schema.rb index 2a7de0f..31fe775 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_05_30_120913) do +ActiveRecord::Schema.define(version: 2023_06_13_063351) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -59,9 +59,8 @@ end create_table "matches", force: :cascade do |t| - t.integer "match_winner_id" - t.integer "winner_score" - t.datetime "match_time" + t.integer "winner_id" + t.datetime "time" t.bigint "tournament_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false @@ -71,7 +70,7 @@ end create_table "messages", force: :cascade do |t| - t.string "message" + t.string "content" t.bigint "user_id", null: false t.bigint "match_id", null: false t.datetime "created_at", precision: 6, null: false @@ -83,7 +82,7 @@ create_table "selections", force: :cascade do |t| t.bigint "match_id" t.integer "user_id" - t.string "selection" + t.string "choice" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.boolean "winner", default: false @@ -96,7 +95,7 @@ t.string "description" t.datetime "start_date" t.datetime "end_date" - t.integer "tournament_winner_id" + t.integer "winner_id" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.datetime "registration_deadline" @@ -120,7 +119,7 @@ t.string "name" t.string "phone_number" t.boolean "banned" - t.string "role" + t.integer "role" t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_salt" diff --git a/db/seeds.rb b/db/seeds.rb index d10e421..5521ef8 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,11 +7,14 @@ # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) -TournamentsUser.create(tournament_id: 107, user_id: 10) -TournamentsUser.create(tournament_id: 107, user_id: 11) -TournamentsUser.create(tournament_id: 107, user_id: 13) -TournamentsUser.create(tournament_id: 107, user_id: 14) -TournamentsUser.create(tournament_id: 107, user_id: 15) -TournamentsUser.create(tournament_id: 107, user_id: 16) -TournamentsUser.create(tournament_id: 107, user_id: 17) -TournamentsUser.create(tournament_id: 107, user_id: 18) + +# TournamentsUser.create(tournament_id: 129, user_id: 25) + +TournamentsUser.create(tournament_id: 129, user_id: 10) +TournamentsUser.create(tournament_id: 129, user_id: 11) +TournamentsUser.create(tournament_id: 129, user_id: 13) +TournamentsUser.create(tournament_id: 129, user_id: 14) +TournamentsUser.create(tournament_id: 129, user_id: 15) +TournamentsUser.create(tournament_id: 129, user_id: 16) +TournamentsUser.create(tournament_id: 129, user_id: 23) +# TournamentsUser.create(tournament_id: 129, user_id: 24) diff --git a/dump.rdb b/dump.rdb index 39c89c7..4e8d606 100644 Binary files a/dump.rdb and b/dump.rdb differ diff --git a/package.json b/package.json index 12eea94..22b728a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "@popperjs/core": "^2.11.7", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", - "@rails/ujs": "^6.0.0", + "@rails/ujs": "^7.0.5", "@rails/webpacker": "5.4.4", "@tailwindcss/line-clamp": "^0.4.4", "stimulus": "^3.2.1", diff --git a/public/404.html b/public/404.html index d70d302..c695a83 100644 --- a/public/404.html +++ b/public/404.html @@ -70,7 +70,6 @@ -

The page you were looking for doesn't exist.

diff --git a/test/controllers/matches_controller_test.rb b/test/controllers/matches_controller_test.rb index 48e140e..361eee7 100644 --- a/test/controllers/matches_controller_test.rb +++ b/test/controllers/matches_controller_test.rb @@ -20,7 +20,7 @@ class MatchesControllerTest < ActionDispatch::IntegrationTest test 'should create match' do assert_difference('Match.count') do post matches_url, - params: { match: { match_time: @match.match_time, match_winner_id: @match.match_winner_id, + params: { match: { match_time: @match.time, winner_id: @match.winner_id, tournament_id: @match.tournament_id, winner_score: @match.winner_score } } end @@ -39,7 +39,7 @@ class MatchesControllerTest < ActionDispatch::IntegrationTest test 'should update match' do patch match_url(@match), - params: { match: { match_time: @match.match_time, match_winner_id: @match.match_winner_id, + params: { match: { match_time: @match.time, winner_id: @match.winner_id, tournament_id: @match.tournament_id, winner_score: @match.winner_score } } assert_redirected_to match_url(@match) end diff --git a/test/controllers/tournaments_controller_test.rb b/test/controllers/tournaments_controller_test.rb index f1321bc..620d207 100644 --- a/test/controllers/tournaments_controller_test.rb +++ b/test/controllers/tournaments_controller_test.rb @@ -21,7 +21,7 @@ class TournamentsControllerTest < ActionDispatch::IntegrationTest assert_difference('Tournament.count') do post tournaments_url, params: { tournament: { description: @tournament.description, end_date: @tournament.end_date, name: @tournament.name, - start_date: @tournament.start_date, tournament_winner_id: @tournament.tournament_winner_id } } + start_date: @tournament.start_date, winner_id: @tournament.winner_id } } end assert_redirected_to tournament_url(Tournament.last) @@ -40,7 +40,7 @@ class TournamentsControllerTest < ActionDispatch::IntegrationTest test 'should update tournament' do patch tournament_url(@tournament), params: { tournament: { description: @tournament.description, end_date: @tournament.end_date, name: @tournament.name, - start_date: @tournament.start_date, tournament_winner_id: @tournament.tournament_winner_id } } + start_date: @tournament.start_date, winner_id: @tournament.winner_id } } assert_redirected_to tournament_url(@tournament) end diff --git a/test/controllers/tournaments_user_controller_test.rb b/test/controllers/tournaments_user_controller_test.rb new file mode 100644 index 0000000..b1b1d95 --- /dev/null +++ b/test/controllers/tournaments_user_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class TournamentsUserControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/matches.yml b/test/fixtures/matches.yml index 79e07dc..cdcb4fa 100644 --- a/test/fixtures/matches.yml +++ b/test/fixtures/matches.yml @@ -1,13 +1,13 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - match_winner_id: 1 + winner_id: 1 winner_score: 1 match_time: 2023-03-24 13:15:51 tournament: one two: - match_winner_id: 1 + winner_id: 1 winner_score: 1 match_time: 2023-03-24 13:15:51 tournament: two diff --git a/test/fixtures/tournaments.yml b/test/fixtures/tournaments.yml index bd34474..6516551 100644 --- a/test/fixtures/tournaments.yml +++ b/test/fixtures/tournaments.yml @@ -5,11 +5,11 @@ one: description: MyString start_date: 2023-03-22 18:21:26 end_date: 2023-03-22 18:21:26 - tournament_winner_id: 1 + winner_id: 1 two: name: MyString description: MyString start_date: 2023-03-22 18:21:26 end_date: 2023-03-22 18:21:26 - tournament_winner_id: 1 + winner_id: 1 diff --git a/test/system/matches_test.rb b/test/system/matches_test.rb index 9140467..a828e45 100644 --- a/test/system/matches_test.rb +++ b/test/system/matches_test.rb @@ -16,8 +16,8 @@ class MatchesTest < ApplicationSystemTestCase visit matches_url click_on 'New Match' - fill_in 'Match time', with: @match.match_time - fill_in 'Match winner', with: @match.match_winner_id + fill_in 'Match time', with: @match.time + fill_in 'Match winner', with: @match.winner_id fill_in 'Tournament', with: @match.tournament_id fill_in 'Winner score', with: @match.winner_score click_on 'Create Match' @@ -30,8 +30,8 @@ class MatchesTest < ApplicationSystemTestCase visit matches_url click_on 'Edit', match: :first - fill_in 'Match time', with: @match.match_time - fill_in 'Match winner', with: @match.match_winner_id + fill_in 'Match time', with: @match.time + fill_in 'Match winner', with: @match.winner_id fill_in 'Tournament', with: @match.tournament_id fill_in 'Winner score', with: @match.winner_score click_on 'Update Match' diff --git a/test/system/tournaments_test.rb b/test/system/tournaments_test.rb index 8ff9f1c..aae8e03 100644 --- a/test/system/tournaments_test.rb +++ b/test/system/tournaments_test.rb @@ -20,7 +20,7 @@ class TournamentsTest < ApplicationSystemTestCase fill_in 'End date', with: @tournament.end_date fill_in 'Name', with: @tournament.name fill_in 'Start date', with: @tournament.start_date - fill_in 'Tournament winner', with: @tournament.tournament_winner_id + fill_in 'Tournament winner', with: @tournament.winner_id click_on 'Create Tournament' assert_text 'Tournament was successfully created' diff --git a/yarn.lock b/yarn.lock index c3fd7c0..7fbf2fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1044,10 +1044,10 @@ dependencies: spark-md5 "^3.0.0" -"@rails/ujs@^6.0.0": - version "6.1.7" - resolved "https://registry.npmjs.org/@rails/ujs/-/ujs-6.1.7.tgz#b09dc5b2105dd267e8374c47e4490240451dc7f6" - integrity sha512-0e7WQ4LE/+LEfW2zfAw9ppsB6A8RmxbdAUPAF++UT80epY+7emuQDkKXmaK0a9lp6An50RvzezI0cIQjp1A58w== +"@rails/ujs@^7.0.5": + version "7.0.5" + resolved "https://registry.npmjs.org/@rails/ujs/-/ujs-7.0.5.tgz#d35a71fe6ecd4ab88bbe4bc6c2f287c992c2de4c" + integrity sha512-/wQ6v4DGxhxflPWoLwGkOQoKbtIXx/LsVNKd8shRM+/HXxlVBEb+F5znN99TlxX8e/R3dN6anTPAqA+mfA5YtQ== "@rails/webpacker@5.4.4": version "5.4.4"