From f8dd83d1b9f1318c601601bdb7ef3e67032b64c1 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 12:53:48 +0900 Subject: [PATCH 001/286] Updated Readme with basics db create / bundle / server --- README.md | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7db80e4..4e9dd76 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,17 @@ -# README - -This README would normally document whatever steps are necessary to get the -application up and running. - -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... +# predictor-app +## Project setup + +### Create DB / Migrate / Seed +``` +rails db:create +rails db:migrate +rails db:seed +``` +### Installs dependencies +``` +bundle install +``` +### Launch a server +``` +rails s +``` From 0030a664c8473f61fe6e0441af0a6adc8c2359d2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:00:16 +0900 Subject: [PATCH 002/286] added user model --- Gemfile | 5 +- Gemfile.lock | 17 + app/models/user.rb | 6 + config/initializers/devise.rb | 311 ++++++++++++++++++ config/locales/devise.en.yml | 65 ++++ config/routes.rb | 1 + .../20210407055611_devise_create_users.rb | 48 +++ test/fixtures/users.yml | 11 + test/models/user_test.rb | 7 + 9 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 app/models/user.rb create mode 100644 config/initializers/devise.rb create mode 100644 config/locales/devise.en.yml create mode 100644 db/migrate/20210407055611_devise_create_users.rb create mode 100644 test/fixtures/users.yml create mode 100644 test/models/user_test.rb diff --git a/Gemfile b/Gemfile index b7ab363..797f41e 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,10 @@ gem 'puma', '~> 5.0' gem 'bootsnap', '>= 1.4.4', require: false # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible -# gem 'rack-cors' +gem 'rack-cors' + +# Added +gem 'devise' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 6f5cbe8..6b09cf6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,12 +60,19 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) + bcrypt (3.1.16) bootsnap (1.7.3) msgpack (~> 1.0) builder (3.2.4) byebug (11.1.3) concurrent-ruby (1.1.8) crass (1.0.6) + devise (4.7.3) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) erubi (1.10.0) ffi (1.15.0) globalid (0.4.2) @@ -90,11 +97,14 @@ GEM nokogiri (1.11.2) mini_portile2 (~> 2.5.0) racc (~> 1.4) + orm_adapter (0.5.0) pg (1.2.3) puma (5.2.2) nio4r (~> 2.0) racc (1.5.2) rack (2.2.3) + rack-cors (1.1.1) + rack (>= 2.0.0) rack-test (1.1.0) rack (>= 1.0, < 3) rails (6.1.3.1) @@ -127,6 +137,9 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) + responders (3.0.1) + actionpack (>= 5.0) + railties (>= 5.0) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -138,6 +151,8 @@ GEM thor (1.1.0) tzinfo (2.0.4) concurrent-ruby (~> 1.0) + warden (1.2.9) + rack (>= 2.0.9) websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -149,9 +164,11 @@ PLATFORMS DEPENDENCIES bootsnap (>= 1.4.4) byebug + devise listen (~> 3.3) pg (~> 1.1) puma (~> 5.0) + rack-cors rails (~> 6.1.3, >= 6.1.3.1) spring tzinfo-data diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..4756799 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,6 @@ +class User < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable +end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000..1f21bd7 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true + +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '2505273ec1e312a38dbca7936d018c72686a981c6a8215998ae782a88ae61579aaf7c8faea0ba1915b0b56e8acd027c769269c68ac520c7c01be3a3f0494d625' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 12. If + # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 12 + + # Set up a pepper to generate the hashed password. + # config.pepper = '012475b20535e1594840f0a98f63681154bd03b41438c0032c130ee6fa43ff17959e152d2230c0e851b4bb5064662a21b0623cf926c691be491222a2864cb905' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Turbolinks configuration + # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: + # + # ActiveSupport.on_load(:devise_failure_app) do + # include Turbolinks::Controller + # end + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true +end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 0000000..ab1f070 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,65 @@ +# Additional translations at https://github.com/heartcombo/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + email_changed: + subject: "Email Changed" + password_change: + subject: "Password Changed" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." + updated: "Your account has been updated successfully." + updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again" + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/routes.rb b/config/routes.rb index c06383a..54b04d7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ Rails.application.routes.draw do + devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end diff --git a/db/migrate/20210407055611_devise_create_users.rb b/db/migrate/20210407055611_devise_create_users.rb new file mode 100644 index 0000000..b5910db --- /dev/null +++ b/db/migrate/20210407055611_devise_create_users.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class DeviseCreateUsers < ActiveRecord::Migration[6.1] + def change + create_table :users do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + ## Rememberable + t.datetime :remember_created_at + + ## Trackable + # t.integer :sign_in_count, default: 0, null: false + # t.datetime :current_sign_in_at + # t.datetime :last_sign_in_at + # t.string :current_sign_in_ip + # t.string :last_sign_in_ip + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + ## Manually Added + t.string :name + t.boolean :admin, default: false + t.string :timezone + + t.timestamps null: false + end + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end +end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000..5181636 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the '{}' from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 0000000..5c07f49 --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 80b62b4f4c1853cf95a5c1c4d18102428db6a825 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:02:33 +0900 Subject: [PATCH 003/286] added model - competition --- app/models/competition.rb | 2 ++ db/migrate/20210407060207_create_competitions.rb | 11 +++++++++++ test/fixtures/competitions.yml | 11 +++++++++++ test/models/competition_test.rb | 7 +++++++ 4 files changed, 31 insertions(+) create mode 100644 app/models/competition.rb create mode 100644 db/migrate/20210407060207_create_competitions.rb create mode 100644 test/fixtures/competitions.yml create mode 100644 test/models/competition_test.rb diff --git a/app/models/competition.rb b/app/models/competition.rb new file mode 100644 index 0000000..aab8d55 --- /dev/null +++ b/app/models/competition.rb @@ -0,0 +1,2 @@ +class Competition < ApplicationRecord +end diff --git a/db/migrate/20210407060207_create_competitions.rb b/db/migrate/20210407060207_create_competitions.rb new file mode 100644 index 0000000..6e38fb2 --- /dev/null +++ b/db/migrate/20210407060207_create_competitions.rb @@ -0,0 +1,11 @@ +class CreateCompetitions < ActiveRecord::Migration[6.1] + def change + create_table :competitions do |t| + t.string :name + t.date :start_date + t.date :end_date + + t.timestamps + end + end +end diff --git a/test/fixtures/competitions.yml b/test/fixtures/competitions.yml new file mode 100644 index 0000000..53b38a1 --- /dev/null +++ b/test/fixtures/competitions.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + start_date: 2021-04-07 + end_date: 2021-04-07 + +two: + name: MyString + start_date: 2021-04-07 + end_date: 2021-04-07 diff --git a/test/models/competition_test.rb b/test/models/competition_test.rb new file mode 100644 index 0000000..97b649e --- /dev/null +++ b/test/models/competition_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class CompetitionTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From adbcc30c4949f3db02c56470d21a8f079aa4dd2b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:04:04 +0900 Subject: [PATCH 004/286] added model - league --- app/models/league.rb | 4 ++++ db/migrate/20210407060356_create_leagues.rb | 12 ++++++++++++ test/fixtures/leagues.yml | 13 +++++++++++++ test/models/league_test.rb | 7 +++++++ 4 files changed, 36 insertions(+) create mode 100644 app/models/league.rb create mode 100644 db/migrate/20210407060356_create_leagues.rb create mode 100644 test/fixtures/leagues.yml create mode 100644 test/models/league_test.rb diff --git a/app/models/league.rb b/app/models/league.rb new file mode 100644 index 0000000..f616a3f --- /dev/null +++ b/app/models/league.rb @@ -0,0 +1,4 @@ +class League < ApplicationRecord + belongs_to :user + belongs_to :competition +end diff --git a/db/migrate/20210407060356_create_leagues.rb b/db/migrate/20210407060356_create_leagues.rb new file mode 100644 index 0000000..6fc3276 --- /dev/null +++ b/db/migrate/20210407060356_create_leagues.rb @@ -0,0 +1,12 @@ +class CreateLeagues < ActiveRecord::Migration[6.1] + def change + create_table :leagues do |t| + t.string :name + t.string :password + t.references :user, null: false, foreign_key: true + t.references :competition, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/test/fixtures/leagues.yml b/test/fixtures/leagues.yml new file mode 100644 index 0000000..90fc450 --- /dev/null +++ b/test/fixtures/leagues.yml @@ -0,0 +1,13 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + password: MyString + user: one + competition: one + +two: + name: MyString + password: MyString + user: two + competition: two diff --git a/test/models/league_test.rb b/test/models/league_test.rb new file mode 100644 index 0000000..d3ae350 --- /dev/null +++ b/test/models/league_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class LeagueTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 649247c63dc4de095be91625587c99ecdaf95de4 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:05:02 +0900 Subject: [PATCH 005/286] added model - membership --- app/models/membership.rb | 4 ++++ db/migrate/20210407060437_create_memberships.rb | 10 ++++++++++ test/fixtures/memberships.yml | 9 +++++++++ test/models/membership_test.rb | 7 +++++++ 4 files changed, 30 insertions(+) create mode 100644 app/models/membership.rb create mode 100644 db/migrate/20210407060437_create_memberships.rb create mode 100644 test/fixtures/memberships.yml create mode 100644 test/models/membership_test.rb diff --git a/app/models/membership.rb b/app/models/membership.rb new file mode 100644 index 0000000..f6f6e20 --- /dev/null +++ b/app/models/membership.rb @@ -0,0 +1,4 @@ +class Membership < ApplicationRecord + belongs_to :league + belongs_to :user +end diff --git a/db/migrate/20210407060437_create_memberships.rb b/db/migrate/20210407060437_create_memberships.rb new file mode 100644 index 0000000..c0e22c3 --- /dev/null +++ b/db/migrate/20210407060437_create_memberships.rb @@ -0,0 +1,10 @@ +class CreateMemberships < ActiveRecord::Migration[6.1] + def change + create_table :memberships do |t| + t.references :league, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/test/fixtures/memberships.yml b/test/fixtures/memberships.yml new file mode 100644 index 0000000..818ce23 --- /dev/null +++ b/test/fixtures/memberships.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + league: one + user: one + +two: + league: two + user: two diff --git a/test/models/membership_test.rb b/test/models/membership_test.rb new file mode 100644 index 0000000..8506331 --- /dev/null +++ b/test/models/membership_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MembershipTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 7adfaedb7d4a90fbe7d774d3c46707e0d0d1af06 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:06:04 +0900 Subject: [PATCH 006/286] added model - round --- app/models/round.rb | 3 +++ db/migrate/20210407060556_create_rounds.rb | 11 +++++++++++ test/fixtures/rounds.yml | 11 +++++++++++ test/models/round_test.rb | 7 +++++++ 4 files changed, 32 insertions(+) create mode 100644 app/models/round.rb create mode 100644 db/migrate/20210407060556_create_rounds.rb create mode 100644 test/fixtures/rounds.yml create mode 100644 test/models/round_test.rb diff --git a/app/models/round.rb b/app/models/round.rb new file mode 100644 index 0000000..05beb29 --- /dev/null +++ b/app/models/round.rb @@ -0,0 +1,3 @@ +class Round < ApplicationRecord + belongs_to :competition +end diff --git a/db/migrate/20210407060556_create_rounds.rb b/db/migrate/20210407060556_create_rounds.rb new file mode 100644 index 0000000..79baeb4 --- /dev/null +++ b/db/migrate/20210407060556_create_rounds.rb @@ -0,0 +1,11 @@ +class CreateRounds < ActiveRecord::Migration[6.1] + def change + create_table :rounds do |t| + t.integer :number + t.string :name + t.references :competition, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/test/fixtures/rounds.yml b/test/fixtures/rounds.yml new file mode 100644 index 0000000..871da78 --- /dev/null +++ b/test/fixtures/rounds.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + number: 1 + name: MyString + competition: one + +two: + number: 1 + name: MyString + competition: two diff --git a/test/models/round_test.rb b/test/models/round_test.rb new file mode 100644 index 0000000..2f5b4bb --- /dev/null +++ b/test/models/round_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class RoundTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 83a5fcb3fc7306a97260e96960c52ac5efe92c5b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:06:23 +0900 Subject: [PATCH 007/286] added model - group --- app/models/group.rb | 3 +++ db/migrate/20210407060617_create_groups.rb | 10 ++++++++++ test/fixtures/groups.yml | 9 +++++++++ test/models/group_test.rb | 7 +++++++ 4 files changed, 29 insertions(+) create mode 100644 app/models/group.rb create mode 100644 db/migrate/20210407060617_create_groups.rb create mode 100644 test/fixtures/groups.yml create mode 100644 test/models/group_test.rb diff --git a/app/models/group.rb b/app/models/group.rb new file mode 100644 index 0000000..afa5c33 --- /dev/null +++ b/app/models/group.rb @@ -0,0 +1,3 @@ +class Group < ApplicationRecord + belongs_to :round +end diff --git a/db/migrate/20210407060617_create_groups.rb b/db/migrate/20210407060617_create_groups.rb new file mode 100644 index 0000000..71ad95c --- /dev/null +++ b/db/migrate/20210407060617_create_groups.rb @@ -0,0 +1,10 @@ +class CreateGroups < ActiveRecord::Migration[6.1] + def change + create_table :groups do |t| + t.string :name + t.references :round, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/test/fixtures/groups.yml b/test/fixtures/groups.yml new file mode 100644 index 0000000..dd54fdf --- /dev/null +++ b/test/fixtures/groups.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + round: one + +two: + name: MyString + round: two diff --git a/test/models/group_test.rb b/test/models/group_test.rb new file mode 100644 index 0000000..eddbcc8 --- /dev/null +++ b/test/models/group_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class GroupTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 123299906641015adb0a39b8cb9aeaed712c6b28 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:07:03 +0900 Subject: [PATCH 008/286] added model - team --- app/models/team.rb | 2 ++ db/migrate/20210407060657_create_teams.rb | 10 ++++++++++ test/fixtures/teams.yml | 9 +++++++++ test/models/team_test.rb | 7 +++++++ 4 files changed, 28 insertions(+) create mode 100644 app/models/team.rb create mode 100644 db/migrate/20210407060657_create_teams.rb create mode 100644 test/fixtures/teams.yml create mode 100644 test/models/team_test.rb diff --git a/app/models/team.rb b/app/models/team.rb new file mode 100644 index 0000000..48a6c83 --- /dev/null +++ b/app/models/team.rb @@ -0,0 +1,2 @@ +class Team < ApplicationRecord +end diff --git a/db/migrate/20210407060657_create_teams.rb b/db/migrate/20210407060657_create_teams.rb new file mode 100644 index 0000000..c55813a --- /dev/null +++ b/db/migrate/20210407060657_create_teams.rb @@ -0,0 +1,10 @@ +class CreateTeams < ActiveRecord::Migration[6.1] + def change + create_table :teams do |t| + t.string :name + t.string :abbrev + + t.timestamps + end + end +end diff --git a/test/fixtures/teams.yml b/test/fixtures/teams.yml new file mode 100644 index 0000000..e23a176 --- /dev/null +++ b/test/fixtures/teams.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + abbrev: MyString + +two: + name: MyString + abbrev: MyString diff --git a/test/models/team_test.rb b/test/models/team_test.rb new file mode 100644 index 0000000..c6cf23d --- /dev/null +++ b/test/models/team_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class TeamTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From f3510797125c9c5f4309fda840ab621b7c60768a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:07:38 +0900 Subject: [PATCH 009/286] added model - affiliation --- app/models/affiliation.rb | 4 ++++ db/migrate/20210407060723_create_affiliations.rb | 10 ++++++++++ test/fixtures/affiliations.yml | 9 +++++++++ test/models/affiliation_test.rb | 7 +++++++ 4 files changed, 30 insertions(+) create mode 100644 app/models/affiliation.rb create mode 100644 db/migrate/20210407060723_create_affiliations.rb create mode 100644 test/fixtures/affiliations.yml create mode 100644 test/models/affiliation_test.rb diff --git a/app/models/affiliation.rb b/app/models/affiliation.rb new file mode 100644 index 0000000..5f94bfa --- /dev/null +++ b/app/models/affiliation.rb @@ -0,0 +1,4 @@ +class Affiliation < ApplicationRecord + belongs_to :team + belongs_to :group +end diff --git a/db/migrate/20210407060723_create_affiliations.rb b/db/migrate/20210407060723_create_affiliations.rb new file mode 100644 index 0000000..d6e839e --- /dev/null +++ b/db/migrate/20210407060723_create_affiliations.rb @@ -0,0 +1,10 @@ +class CreateAffiliations < ActiveRecord::Migration[6.1] + def change + create_table :affiliations do |t| + t.references :team, null: false, foreign_key: true + t.references :group, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/test/fixtures/affiliations.yml b/test/fixtures/affiliations.yml new file mode 100644 index 0000000..ff59a07 --- /dev/null +++ b/test/fixtures/affiliations.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + team: one + group: one + +two: + team: two + group: two diff --git a/test/models/affiliation_test.rb b/test/models/affiliation_test.rb new file mode 100644 index 0000000..e59f623 --- /dev/null +++ b/test/models/affiliation_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AffiliationTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 4f71193ae22c2464c677554a59ed4fd9d0019466 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:13:08 +0900 Subject: [PATCH 010/286] added model - match --- app/models/match.rb | 6 ++++++ db/migrate/20210407061008_create_matches.rb | 16 ++++++++++++++++ test/fixtures/matches.yml | 21 +++++++++++++++++++++ test/models/match_test.rb | 7 +++++++ 4 files changed, 50 insertions(+) create mode 100644 app/models/match.rb create mode 100644 db/migrate/20210407061008_create_matches.rb create mode 100644 test/fixtures/matches.yml create mode 100644 test/models/match_test.rb diff --git a/app/models/match.rb b/app/models/match.rb new file mode 100644 index 0000000..e4073ee --- /dev/null +++ b/app/models/match.rb @@ -0,0 +1,6 @@ +class Match < ApplicationRecord + belongs_to :group + belongs_to :team_away + belongs_to :team_home + belongs_to :next_match +end diff --git a/db/migrate/20210407061008_create_matches.rb b/db/migrate/20210407061008_create_matches.rb new file mode 100644 index 0000000..3e04686 --- /dev/null +++ b/db/migrate/20210407061008_create_matches.rb @@ -0,0 +1,16 @@ +class CreateMatches < ActiveRecord::Migration[6.1] + def change + create_table :matches do |t| + t.datetime :kickoff_time + t.integer :team_home_score + t.integer :team_away_score + t.integer :status + t.references :group, null: false, foreign_key: true + t.references :team_away, foreign_key: { to_table: :teams } + t.references :team_home, foreign_key: { to_table: :teams } + t.references :next_match, foreign_key: { to_table: :match } + + t.timestamps + end + end +end diff --git a/test/fixtures/matches.yml b/test/fixtures/matches.yml new file mode 100644 index 0000000..0d04a67 --- /dev/null +++ b/test/fixtures/matches.yml @@ -0,0 +1,21 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + kickoff_time: 2021-04-07 15:10:08 + team_home_score: 1 + team_away_score: 1 + status: 1 + group: one + team_away: one + team_home: one + next_match: one + +two: + kickoff_time: 2021-04-07 15:10:08 + team_home_score: 1 + team_away_score: 1 + status: 1 + group: two + team_away: two + team_home: two + next_match: two diff --git a/test/models/match_test.rb b/test/models/match_test.rb new file mode 100644 index 0000000..6d3de7c --- /dev/null +++ b/test/models/match_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MatchTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From f4259937133d486938068e4b9fb2c84a3e552c08 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:14:44 +0900 Subject: [PATCH 011/286] added model - prediction --- app/models/prediction.rb | 4 ++++ db/migrate/20210407061425_create_predictions.rb | 11 +++++++++++ test/fixtures/predictions.yml | 11 +++++++++++ test/models/prediction_test.rb | 7 +++++++ 4 files changed, 33 insertions(+) create mode 100644 app/models/prediction.rb create mode 100644 db/migrate/20210407061425_create_predictions.rb create mode 100644 test/fixtures/predictions.yml create mode 100644 test/models/prediction_test.rb diff --git a/app/models/prediction.rb b/app/models/prediction.rb new file mode 100644 index 0000000..ebed0d6 --- /dev/null +++ b/app/models/prediction.rb @@ -0,0 +1,4 @@ +class Prediction < ApplicationRecord + belongs_to :match + belongs_to :user +end diff --git a/db/migrate/20210407061425_create_predictions.rb b/db/migrate/20210407061425_create_predictions.rb new file mode 100644 index 0000000..73d7690 --- /dev/null +++ b/db/migrate/20210407061425_create_predictions.rb @@ -0,0 +1,11 @@ +class CreatePredictions < ActiveRecord::Migration[6.1] + def change + create_table :predictions do |t| + t.integer :choice + t.references :match, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/test/fixtures/predictions.yml b/test/fixtures/predictions.yml new file mode 100644 index 0000000..2df6041 --- /dev/null +++ b/test/fixtures/predictions.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + choice: 1 + match: one + user: one + +two: + choice: 1 + match: two + user: two diff --git a/test/models/prediction_test.rb b/test/models/prediction_test.rb new file mode 100644 index 0000000..b776078 --- /dev/null +++ b/test/models/prediction_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class PredictionTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 1ae714f4489f0c57623bda60b10cf8ffa4acd226 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:20:06 +0900 Subject: [PATCH 012/286] added current round to competitions && next_match to matches --- db/migrate/20210407061008_create_matches.rb | 1 - ...61624_add_current_round_to_competitions.rb | 6 + db/schema.rb | 138 ++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210407061624_add_current_round_to_competitions.rb create mode 100644 db/schema.rb diff --git a/db/migrate/20210407061008_create_matches.rb b/db/migrate/20210407061008_create_matches.rb index 3e04686..db47f39 100644 --- a/db/migrate/20210407061008_create_matches.rb +++ b/db/migrate/20210407061008_create_matches.rb @@ -8,7 +8,6 @@ def change t.references :group, null: false, foreign_key: true t.references :team_away, foreign_key: { to_table: :teams } t.references :team_home, foreign_key: { to_table: :teams } - t.references :next_match, foreign_key: { to_table: :match } t.timestamps end diff --git a/db/migrate/20210407061624_add_current_round_to_competitions.rb b/db/migrate/20210407061624_add_current_round_to_competitions.rb new file mode 100644 index 0000000..0814001 --- /dev/null +++ b/db/migrate/20210407061624_add_current_round_to_competitions.rb @@ -0,0 +1,6 @@ +class AddCurrentRoundToCompetitions < ActiveRecord::Migration[6.1] + def change + add_reference :competitions, :current_round, foreign_key: { to_table: :rounds } + add_reference :matches, :next_match, foreign_key: { to_table: :matches } + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..65c9dd5 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,138 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2021_04_07_061624) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "affiliations", force: :cascade do |t| + t.bigint "team_id", null: false + t.bigint "group_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["group_id"], name: "index_affiliations_on_group_id" + t.index ["team_id"], name: "index_affiliations_on_team_id" + end + + create_table "competitions", force: :cascade do |t| + t.string "name" + t.date "start_date" + t.date "end_date" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.bigint "current_round_id" + t.index ["current_round_id"], name: "index_competitions_on_current_round_id" + end + + create_table "groups", force: :cascade do |t| + t.string "name" + t.bigint "round_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["round_id"], name: "index_groups_on_round_id" + end + + create_table "leagues", force: :cascade do |t| + t.string "name" + t.string "password" + t.bigint "user_id", null: false + t.bigint "competition_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["competition_id"], name: "index_leagues_on_competition_id" + t.index ["user_id"], name: "index_leagues_on_user_id" + end + + create_table "matches", force: :cascade do |t| + t.datetime "kickoff_time" + t.integer "team_home_score" + t.integer "team_away_score" + t.integer "status" + t.bigint "group_id", null: false + t.bigint "team_away_id" + t.bigint "team_home_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.bigint "next_match_id" + t.index ["group_id"], name: "index_matches_on_group_id" + t.index ["next_match_id"], name: "index_matches_on_next_match_id" + t.index ["team_away_id"], name: "index_matches_on_team_away_id" + t.index ["team_home_id"], name: "index_matches_on_team_home_id" + end + + create_table "memberships", force: :cascade do |t| + t.bigint "league_id", null: false + t.bigint "user_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["league_id"], name: "index_memberships_on_league_id" + t.index ["user_id"], name: "index_memberships_on_user_id" + end + + create_table "predictions", force: :cascade do |t| + t.integer "choice" + t.bigint "match_id", null: false + t.bigint "user_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["match_id"], name: "index_predictions_on_match_id" + t.index ["user_id"], name: "index_predictions_on_user_id" + end + + create_table "rounds", force: :cascade do |t| + t.integer "number" + t.string "name" + t.bigint "competition_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["competition_id"], name: "index_rounds_on_competition_id" + end + + create_table "teams", force: :cascade do |t| + t.string "name" + t.string "abbrev" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.string "name" + t.boolean "admin", default: false + t.string "timezone" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + end + + add_foreign_key "affiliations", "groups" + add_foreign_key "affiliations", "teams" + add_foreign_key "competitions", "rounds", column: "current_round_id" + add_foreign_key "groups", "rounds" + add_foreign_key "leagues", "competitions" + add_foreign_key "leagues", "users" + add_foreign_key "matches", "groups" + add_foreign_key "matches", "matches", column: "next_match_id" + add_foreign_key "matches", "teams", column: "team_away_id" + add_foreign_key "matches", "teams", column: "team_home_id" + add_foreign_key "memberships", "leagues" + add_foreign_key "memberships", "users" + add_foreign_key "predictions", "matches" + add_foreign_key "predictions", "users" + add_foreign_key "rounds", "competitions" +end From fd4a971df5093d3e7bf4eff877a6ad8dd5563554 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 15:41:55 +0900 Subject: [PATCH 013/286] added initial associations --- app/models/competition.rb | 9 +++++++++ app/models/group.rb | 3 +++ app/models/league.rb | 3 +++ app/models/match.rb | 10 ++++++---- app/models/round.rb | 2 ++ app/models/team.rb | 4 ++++ app/models/user.rb | 8 +++++++- 7 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index aab8d55..0104ee1 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1,2 +1,11 @@ class Competition < ApplicationRecord + belongs_to :current_round, class_name: 'Round', optional: true + has_many :rounds + has_many :groups, through: :rounds + has_many :affiliations, through: :groups + has_many :teams, through: :affiliations + has_many :leagues + # TODO: Technically a User doesn't need to join a league to be involved in a competition + # has_many :memberships, through: :leagues + # has_many :users, through: :memberships end diff --git a/app/models/group.rb b/app/models/group.rb index afa5c33..77c8fdd 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,3 +1,6 @@ class Group < ApplicationRecord belongs_to :round + has_many :matches + has_many :affiliations + has_many :teams, through: :affiliations end diff --git a/app/models/league.rb b/app/models/league.rb index f616a3f..3295b07 100644 --- a/app/models/league.rb +++ b/app/models/league.rb @@ -1,4 +1,7 @@ class League < ApplicationRecord belongs_to :user belongs_to :competition + has_many :memberships + # TODO: Do we create a membership for the owner? If not, we need another method for all + has_many :users, through: :memberships end diff --git a/app/models/match.rb b/app/models/match.rb index e4073ee..11a25ab 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -1,6 +1,8 @@ class Match < ApplicationRecord - belongs_to :group - belongs_to :team_away - belongs_to :team_home - belongs_to :next_match + belongs_to :team_away, class_name: 'Team' + belongs_to :team_home, class_name: 'Team' + belongs_to :group, optional: true + belongs_to :next_match, class_name: 'Match', optional: true + has_many :predictions + has_many :users, through: :predictions end diff --git a/app/models/round.rb b/app/models/round.rb index 05beb29..e6658e9 100644 --- a/app/models/round.rb +++ b/app/models/round.rb @@ -1,3 +1,5 @@ class Round < ApplicationRecord belongs_to :competition + has_many :groups + has_many :matches, through: :groups end diff --git a/app/models/team.rb b/app/models/team.rb index 48a6c83..2aa2934 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,2 +1,6 @@ class Team < ApplicationRecord + # TODO: home and away teams are on a match + # has_many :matches + has_many :affiliations + has_many :groups, through: :affiliations end diff --git a/app/models/user.rb b/app/models/user.rb index 4756799..d2326b6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,5 +2,11 @@ class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable + :recoverable, :rememberable, :validatable + has_many :memberships + has_many :leagues, through: :memberships + # has_many :leagues_as_owner, mod + has_many :competitions, through: :leagues + has_many :predictions + has_many :matches, through: :predictions end From 62363bbc366263ff43fca6d1f3f352daaa3a1308 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 16:00:39 +0900 Subject: [PATCH 014/286] Added valdiations --- app/models/competition.rb | 3 +++ app/models/group.rb | 1 + app/models/league.rb | 2 ++ app/models/match.rb | 3 +++ app/models/prediction.rb | 2 ++ app/models/round.rb | 1 + app/models/team.rb | 2 ++ app/models/user.rb | 2 ++ db/migrate/20210407061008_create_matches.rb | 2 +- db/schema.rb | 2 +- 10 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index 0104ee1..396db05 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -8,4 +8,7 @@ class Competition < ApplicationRecord # TODO: Technically a User doesn't need to join a league to be involved in a competition # has_many :memberships, through: :leagues # has_many :users, through: :memberships + validates :name, presence: true + validates :start_date, presence: true + validates :end_date, presence: true end diff --git a/app/models/group.rb b/app/models/group.rb index 77c8fdd..3f33125 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -3,4 +3,5 @@ class Group < ApplicationRecord has_many :matches has_many :affiliations has_many :teams, through: :affiliations + validates :name, presence: true end diff --git a/app/models/league.rb b/app/models/league.rb index 3295b07..fc53635 100644 --- a/app/models/league.rb +++ b/app/models/league.rb @@ -4,4 +4,6 @@ class League < ApplicationRecord has_many :memberships # TODO: Do we create a membership for the owner? If not, we need another method for all has_many :users, through: :memberships + validates :name, presence: true + validates :password, presence: true end diff --git a/app/models/match.rb b/app/models/match.rb index 11a25ab..bbbb4ba 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -5,4 +5,7 @@ class Match < ApplicationRecord belongs_to :next_match, class_name: 'Match', optional: true has_many :predictions has_many :users, through: :predictions + validates :kickoff_time, presence: true + validates :status, presence: true + enum status: %i[awaiting started finished] end diff --git a/app/models/prediction.rb b/app/models/prediction.rb index ebed0d6..0c137f5 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -1,4 +1,6 @@ class Prediction < ApplicationRecord belongs_to :match belongs_to :user + validates :choice, presence: true + enum choice: %i[home away draw] end diff --git a/app/models/round.rb b/app/models/round.rb index e6658e9..826a100 100644 --- a/app/models/round.rb +++ b/app/models/round.rb @@ -2,4 +2,5 @@ class Round < ApplicationRecord belongs_to :competition has_many :groups has_many :matches, through: :groups + validates :name, presence: true end diff --git a/app/models/team.rb b/app/models/team.rb index 2aa2934..d6e2984 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -3,4 +3,6 @@ class Team < ApplicationRecord # has_many :matches has_many :affiliations has_many :groups, through: :affiliations + validates :name, presence: true, uniqueness: true + validates :abbrev, presence: true, uniqueness: true end diff --git a/app/models/user.rb b/app/models/user.rb index d2326b6..6b17008 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,4 +9,6 @@ class User < ApplicationRecord has_many :competitions, through: :leagues has_many :predictions has_many :matches, through: :predictions + validates :name, presence: true + validates :timezone, presence: true end diff --git a/db/migrate/20210407061008_create_matches.rb b/db/migrate/20210407061008_create_matches.rb index db47f39..a55900a 100644 --- a/db/migrate/20210407061008_create_matches.rb +++ b/db/migrate/20210407061008_create_matches.rb @@ -4,7 +4,7 @@ def change t.datetime :kickoff_time t.integer :team_home_score t.integer :team_away_score - t.integer :status + t.integer :status, default: 0 t.references :group, null: false, foreign_key: true t.references :team_away, foreign_key: { to_table: :teams } t.references :team_home, foreign_key: { to_table: :teams } diff --git a/db/schema.rb b/db/schema.rb index 65c9dd5..47f6847 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -57,7 +57,7 @@ t.datetime "kickoff_time" t.integer "team_home_score" t.integer "team_away_score" - t.integer "status" + t.integer "status", default: 0 t.bigint "group_id", null: false t.bigint "team_away_id" t.bigint "team_home_id" From 22baaafb9d280156c6f62f79a39b6a9f6498db2e Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 16:42:24 +0900 Subject: [PATCH 015/286] added more thorough validations --- app/models/affiliation.rb | 1 + app/models/group.rb | 1 + app/models/membership.rb | 1 + app/models/prediction.rb | 1 + app/models/team.rb | 6 ++++-- app/models/user.rb | 6 ++++-- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/models/affiliation.rb b/app/models/affiliation.rb index 5f94bfa..a3186bc 100644 --- a/app/models/affiliation.rb +++ b/app/models/affiliation.rb @@ -1,4 +1,5 @@ class Affiliation < ApplicationRecord belongs_to :team belongs_to :group + validates_uniqueness_of :team, scope: :group end diff --git a/app/models/group.rb b/app/models/group.rb index 3f33125..4541c83 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -4,4 +4,5 @@ class Group < ApplicationRecord has_many :affiliations has_many :teams, through: :affiliations validates :name, presence: true + validates_uniqueness_of :name, scope: :round end diff --git a/app/models/membership.rb b/app/models/membership.rb index f6f6e20..a04f362 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -1,4 +1,5 @@ class Membership < ApplicationRecord belongs_to :league belongs_to :user + validates_uniqueness_of :user, scope: :league end diff --git a/app/models/prediction.rb b/app/models/prediction.rb index 0c137f5..5725364 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -1,6 +1,7 @@ class Prediction < ApplicationRecord belongs_to :match belongs_to :user + validates_uniqueness_of :user, scope: :match validates :choice, presence: true enum choice: %i[home away draw] end diff --git a/app/models/team.rb b/app/models/team.rb index d6e2984..1603cf0 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,8 +1,10 @@ class Team < ApplicationRecord - # TODO: home and away teams are on a match - # has_many :matches has_many :affiliations has_many :groups, through: :affiliations validates :name, presence: true, uniqueness: true validates :abbrev, presence: true, uniqueness: true + + def matches + Match.where('team_home_id = ? OR team_away_id = ?', id, id) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 6b17008..83ebb13 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,11 +4,13 @@ class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :memberships - has_many :leagues, through: :memberships - # has_many :leagues_as_owner, mod has_many :competitions, through: :leagues has_many :predictions has_many :matches, through: :predictions validates :name, presence: true validates :timezone, presence: true + + def leagues + League.includes(:memberships).where(memberships: { user: self }).or(League.where(user: self)) + end end From 3714b7d17fa0e11c9b3035c70d4493b3b2f2553b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 16:43:56 +0900 Subject: [PATCH 016/286] Added comments to clarify SQL --- app/models/team.rb | 1 + app/models/user.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/team.rb b/app/models/team.rb index 1603cf0..63a183e 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -5,6 +5,7 @@ class Team < ApplicationRecord validates :abbrev, presence: true, uniqueness: true def matches + # teams can either be home or away Match.where('team_home_id = ? OR team_away_id = ?', id, id) end end diff --git a/app/models/user.rb b/app/models/user.rb index 83ebb13..dacd02a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,6 +11,7 @@ class User < ApplicationRecord validates :timezone, presence: true def leagues + # this includes creator or league and members League.includes(:memberships).where(memberships: { user: self }).or(League.where(user: self)) end end From ef09833728cb60896152372d97fe5a1cfd3cf699 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 16:46:59 +0900 Subject: [PATCH 017/286] Added token to users --- Gemfile | 1 + Gemfile.lock | 5 +++++ db/migrate/20210407074540_add_token_to_users.rb | 6 ++++++ db/schema.rb | 4 +++- 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210407074540_add_token_to_users.rb diff --git a/Gemfile b/Gemfile index 797f41e..b47fa33 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'rack-cors' # Added gem 'devise' +gem 'simple_token_authentication' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 6b09cf6..e090d6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,6 +140,10 @@ GEM responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) + simple_token_authentication (1.17.0) + actionmailer (>= 3.2.6, < 7) + actionpack (>= 3.2.6, < 7) + devise (>= 3.2, < 6) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -170,6 +174,7 @@ DEPENDENCIES puma (~> 5.0) rack-cors rails (~> 6.1.3, >= 6.1.3.1) + simple_token_authentication spring tzinfo-data diff --git a/db/migrate/20210407074540_add_token_to_users.rb b/db/migrate/20210407074540_add_token_to_users.rb new file mode 100644 index 0000000..ac2c0e1 --- /dev/null +++ b/db/migrate/20210407074540_add_token_to_users.rb @@ -0,0 +1,6 @@ +class AddTokenToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :authentication_token, :string, limit: 30 + add_index :users, :authentication_token, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 47f6847..9ee5d9e 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: 2021_04_07_061624) do +ActiveRecord::Schema.define(version: 2021_04_07_074540) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -116,6 +116,8 @@ t.string "timezone" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "authentication_token", limit: 30 + t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end From 829ca0581742a9458a5e30ae5bb60aa1d325c792 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:00:38 +0900 Subject: [PATCH 018/286] added dependent destory on ncessary models --- app/models/competition.rb | 4 ++-- app/models/group.rb | 4 ++-- app/models/league.rb | 2 +- app/models/match.rb | 2 +- app/models/round.rb | 2 +- app/models/team.rb | 2 +- app/models/user.rb | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index 396db05..53c3b62 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1,10 +1,10 @@ class Competition < ApplicationRecord belongs_to :current_round, class_name: 'Round', optional: true - has_many :rounds + has_many :rounds, dependent: :destroy has_many :groups, through: :rounds has_many :affiliations, through: :groups has_many :teams, through: :affiliations - has_many :leagues + has_many :leagues, dependent: :destroy # TODO: Technically a User doesn't need to join a league to be involved in a competition # has_many :memberships, through: :leagues # has_many :users, through: :memberships diff --git a/app/models/group.rb b/app/models/group.rb index 4541c83..3888ab3 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,7 +1,7 @@ class Group < ApplicationRecord belongs_to :round - has_many :matches - has_many :affiliations + has_many :matches, dependent: :destroy + has_many :affiliations, dependent: :destroy has_many :teams, through: :affiliations validates :name, presence: true validates_uniqueness_of :name, scope: :round diff --git a/app/models/league.rb b/app/models/league.rb index fc53635..cb688e3 100644 --- a/app/models/league.rb +++ b/app/models/league.rb @@ -1,7 +1,7 @@ class League < ApplicationRecord belongs_to :user belongs_to :competition - has_many :memberships + has_many :memberships, dependent: :destroy # TODO: Do we create a membership for the owner? If not, we need another method for all has_many :users, through: :memberships validates :name, presence: true diff --git a/app/models/match.rb b/app/models/match.rb index bbbb4ba..1f0789d 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -3,7 +3,7 @@ class Match < ApplicationRecord belongs_to :team_home, class_name: 'Team' belongs_to :group, optional: true belongs_to :next_match, class_name: 'Match', optional: true - has_many :predictions + has_many :predictions, dependent: :destroy has_many :users, through: :predictions validates :kickoff_time, presence: true validates :status, presence: true diff --git a/app/models/round.rb b/app/models/round.rb index 826a100..1064cb4 100644 --- a/app/models/round.rb +++ b/app/models/round.rb @@ -1,6 +1,6 @@ class Round < ApplicationRecord belongs_to :competition - has_many :groups + has_many :groups, dependent: :destroy has_many :matches, through: :groups validates :name, presence: true end diff --git a/app/models/team.rb b/app/models/team.rb index 63a183e..dc5cc4d 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,5 +1,5 @@ class Team < ApplicationRecord - has_many :affiliations + has_many :affiliations, dependent: :destroy has_many :groups, through: :affiliations validates :name, presence: true, uniqueness: true validates :abbrev, presence: true, uniqueness: true diff --git a/app/models/user.rb b/app/models/user.rb index dacd02a..93a2b03 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,9 +3,9 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable - has_many :memberships + has_many :memberships, dependent: :destroy has_many :competitions, through: :leagues - has_many :predictions + has_many :predictions, dependent: :destroy has_many :matches, through: :predictions validates :name, presence: true validates :timezone, presence: true From 99a96cc6d82f2b2e6960919bddc4fdbef4e23f2f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:05:26 +0900 Subject: [PATCH 019/286] removed simple_token gem --- Gemfile | 1 - Gemfile.lock | 5 ----- db/migrate/20210407074540_add_token_to_users.rb | 6 ------ db/schema.rb | 4 +--- 4 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 db/migrate/20210407074540_add_token_to_users.rb diff --git a/Gemfile b/Gemfile index b47fa33..797f41e 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,6 @@ gem 'rack-cors' # Added gem 'devise' -gem 'simple_token_authentication' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index e090d6f..6b09cf6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -140,10 +140,6 @@ GEM responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) - simple_token_authentication (1.17.0) - actionmailer (>= 3.2.6, < 7) - actionpack (>= 3.2.6, < 7) - devise (>= 3.2, < 6) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -174,7 +170,6 @@ DEPENDENCIES puma (~> 5.0) rack-cors rails (~> 6.1.3, >= 6.1.3.1) - simple_token_authentication spring tzinfo-data diff --git a/db/migrate/20210407074540_add_token_to_users.rb b/db/migrate/20210407074540_add_token_to_users.rb deleted file mode 100644 index ac2c0e1..0000000 --- a/db/migrate/20210407074540_add_token_to_users.rb +++ /dev/null @@ -1,6 +0,0 @@ -class AddTokenToUsers < ActiveRecord::Migration[6.1] - def change - add_column :users, :authentication_token, :string, limit: 30 - add_index :users, :authentication_token, unique: true - end -end diff --git a/db/schema.rb b/db/schema.rb index 9ee5d9e..47f6847 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: 2021_04_07_074540) do +ActiveRecord::Schema.define(version: 2021_04_07_061624) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -116,8 +116,6 @@ t.string "timezone" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false - t.string "authentication_token", limit: 30 - t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end From cdb4016e22c09df1644f7d1a692ca734625f6fe4 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:20:33 +0900 Subject: [PATCH 020/286] added cloudinary and activestorage migrations --- .gitignore | 1 + Gemfile | 2 ++ Gemfile.lock | 27 ++++++++++++++ ...te_active_storage_tables.active_storage.rb | 36 +++++++++++++++++++ db/schema.rb | 32 ++++++++++++++++- 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210407082013_create_active_storage_tables.active_storage.rb diff --git a/.gitignore b/.gitignore index 8a1b113..b10b609 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ # Ignore master key for decrypting credentials and more. /config/master.key +.env* diff --git a/Gemfile b/Gemfile index 797f41e..bf4c5e0 100644 --- a/Gemfile +++ b/Gemfile @@ -26,11 +26,13 @@ gem 'bootsnap', '>= 1.4.4', require: false gem 'rack-cors' # Added +gem 'cloudinary', '~> 1.16.0' gem 'devise' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'dotenv-rails' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 6b09cf6..f1fa69b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -60,11 +60,15 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) + aws_cf_signer (0.1.3) bcrypt (3.1.16) bootsnap (1.7.3) msgpack (~> 1.0) builder (3.2.4) byebug (11.1.3) + cloudinary (1.16.1) + aws_cf_signer + rest-client concurrent-ruby (1.1.8) crass (1.0.6) devise (4.7.3) @@ -73,10 +77,19 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + dotenv-rails (2.7.6) + dotenv (= 2.7.6) + railties (>= 3.2) erubi (1.10.0) ffi (1.15.0) globalid (0.4.2) activesupport (>= 4.2.0) + http-accept (1.7.0) + http-cookie (1.0.3) + domain_name (~> 0.5) i18n (1.8.10) concurrent-ruby (~> 1.0) listen (3.5.1) @@ -89,10 +102,14 @@ GEM mini_mime (>= 0.1.1) marcel (1.0.1) method_source (1.0.0) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2021.0212) mini_mime (1.0.3) mini_portile2 (2.5.0) minitest (5.14.4) msgpack (1.4.2) + netrc (0.11.0) nio4r (2.5.7) nokogiri (1.11.2) mini_portile2 (~> 2.5.0) @@ -140,6 +157,11 @@ GEM responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -151,6 +173,9 @@ GEM thor (1.1.0) tzinfo (2.0.4) concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) warden (1.2.9) rack (>= 2.0.9) websocket-driver (0.7.3) @@ -164,7 +189,9 @@ PLATFORMS DEPENDENCIES bootsnap (>= 1.4.4) byebug + cloudinary (~> 1.16.0) devise + dotenv-rails listen (~> 3.3) pg (~> 1.1) puma (~> 5.0) diff --git a/db/migrate/20210407082013_create_active_storage_tables.active_storage.rb b/db/migrate/20210407082013_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..8779826 --- /dev/null +++ b/db/migrate/20210407082013_create_active_storage_tables.active_storage.rb @@ -0,0 +1,36 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records do |t| + t.belongs_to :blob, null: false, index: false + t.string :variation_digest, null: false + + t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 47f6847..b395d2e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_04_07_061624) do +ActiveRecord::Schema.define(version: 2021_04_07_082013) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "affiliations", force: :cascade do |t| t.bigint "team_id", null: false t.bigint "group_id", null: false @@ -120,6 +148,8 @@ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "affiliations", "groups" add_foreign_key "affiliations", "teams" add_foreign_key "competitions", "rounds", column: "current_round_id" From 36a25bfc8e2ec04e073f949e74cb11f71a5f465e Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:21:41 +0900 Subject: [PATCH 021/286] configured cloudinary --- app/models/team.rb | 1 + config/environments/development.rb | 2 +- config/environments/production.rb | 2 +- config/storage.yml | 36 ++---------------------------- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/app/models/team.rb b/app/models/team.rb index dc5cc4d..c64463a 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -3,6 +3,7 @@ class Team < ApplicationRecord has_many :groups, through: :affiliations validates :name, presence: true, uniqueness: true validates :abbrev, presence: true, uniqueness: true + has_one_attached :badge def matches # teams can either be home or away diff --git a/config/environments/development.rb b/config/environments/development.rb index 231c2a6..79d1002 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -28,7 +28,7 @@ end # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local + config.active_storage.service = :cloudinary # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/production.rb b/config/environments/production.rb index a929a51..15f35f9 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -31,7 +31,7 @@ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local + config.active_storage.service = :cloudinary # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil diff --git a/config/storage.yml b/config/storage.yml index d32f76e..796066a 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -1,34 +1,2 @@ -test: - service: Disk - root: <%= Rails.root.join("tmp/storage") %> - -local: - service: Disk - root: <%= Rails.root.join("storage") %> - -# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 -# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> -# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket - -# Remember not to checkin your GCS keyfile to a repository -# google: -# service: GCS -# project: your_project -# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> -# bucket: your_own_bucket - -# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) -# microsoft: -# service: AzureStorage -# storage_account_name: your_account_name -# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> -# container: your_container_name - -# mirror: -# service: Mirror -# primary: local -# mirrors: [ amazon, google, microsoft ] +cloudinary: + service: Cloudinary From 25cb8c11f170a6a317a3881bda01bcdb7aa34777 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:39:01 +0900 Subject: [PATCH 022/286] added hash of groups and teams --- db/seeds.rb | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index f3a0480..e8f2f86 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,46 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Examples: -# -# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) -# Character.create(name: 'Luke', movie: movies.first) +# url = "https://www.uefa.com/imgml/flags/70x70/CZE.png?imwidth=2048%202048w" +groups = { + 'Group A' => [ + { name: 'Italy', abbrev: 'ITA' }, + { name: 'Switzerland ', abbrev: 'SUI' }, + { name: 'Turkey', abbrev: 'TUR' }, + { name: 'Wales', abbrev: 'WAL' } + ], + 'Group B' => [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Finland', abbrev: 'FIN' }, + { name: 'Russia', abbrev: 'RUS' } + ], + 'Group C' => [ + { name: 'Austria', abbrev: 'AUT' }, + { name: 'Netherlands', abbrev: 'NED' }, + { name: 'North Macedonia', abbrev: 'MKD' }, + { name: 'Ukraine', abbrev: 'UKR' } + ], + 'Group D' => [ + { name: 'Croatia', abbrev: 'CRO' }, + { name: 'Czech Republic', abbrev: 'CZE' }, + { name: 'England', abbrev: 'ENG' }, + { name: 'Scotland', abbrev: 'SCO' } + ], + 'Group E' => [ + { name: 'Poland', abbrev: 'POL' }, + { name: 'Slovakia ', abbrev: 'SVK' }, + { name: 'Spain', abbrev: 'TUR' }, + { name: 'Sweden', abbrev: 'WAL' } + ], + 'Group F' => [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Germany ', abbrev: 'GER' }, + { name: 'Hungary', abbrev: 'HUN' }, + { name: 'Portugal', abbrev: 'POR' } + ] +} +puts 'Creating or finding first round...' +first_round = Round.find_or_create_by(name: 'Group Stage', round: 1) +puts "...#{Round.count} Total Rounds" + +puts 'Creating or finding groups...' +Group.find_or_create_by(name: groups.keys, round: first_round) +puts "...#{Group.count} Total Groups" From faa34a99b6ef6b8ae290cf474909201e8d6514fc Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:48:12 +0900 Subject: [PATCH 023/286] creating competition, round 1, groups --- db/seeds.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index e8f2f86..618c2df 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -37,10 +37,21 @@ { name: 'Portugal', abbrev: 'POR' } ] } + +puts 'Creating the Euros...' +competition = Competition.find_or_create_by(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) +p competition.errors.full_messages if competition.errors.any? +puts '.. created the Euros' + puts 'Creating or finding first round...' -first_round = Round.find_or_create_by(name: 'Group Stage', round: 1) +first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: competition) +p first_round.errors.full_messages if first_round.errors.any? puts "...#{Round.count} Total Rounds" puts 'Creating or finding groups...' -Group.find_or_create_by(name: groups.keys, round: first_round) +groups.each_key do |group_name| + p "...#{group_name}..." + group = Group.find_or_create_by(name: group_name, round: first_round) + p group.errors.full_messages if group.errors.any? +end puts "...#{Group.count} Total Groups" From 2d175739ffcadf0e3b98b8833136d0fb9981987f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 17:54:55 +0900 Subject: [PATCH 024/286] creating teams --- db/seeds.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 618c2df..6f01514 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -26,9 +26,9 @@ ], 'Group E' => [ { name: 'Poland', abbrev: 'POL' }, - { name: 'Slovakia ', abbrev: 'SVK' }, - { name: 'Spain', abbrev: 'TUR' }, - { name: 'Sweden', abbrev: 'WAL' } + { name: 'Slovakia', abbrev: 'SVK' }, + { name: 'Spain', abbrev: 'ESP' }, + { name: 'Sweden', abbrev: 'SWE' } ], 'Group F' => [ { name: 'France', abbrev: 'FRA' }, @@ -50,8 +50,14 @@ puts 'Creating or finding groups...' groups.each_key do |group_name| - p "...#{group_name}..." + puts "...#{group_name}..." group = Group.find_or_create_by(name: group_name, round: first_round) p group.errors.full_messages if group.errors.any? + groups[group_name].each do |team_hash| + puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" + team = Team.find_or_create_by(team_hash) + p team.errors.full_messages if team.errors.any? + end end +puts "...#{Team.count} Total Teams" puts "...#{Group.count} Total Groups" From 3af401667f39e887f5c9b0efc14272de23bc020a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:02:32 +0900 Subject: [PATCH 025/286] remove password validation --- app/models/league.rb | 4 ++-- app/models/team.rb | 2 +- app/models/user.rb | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/models/league.rb b/app/models/league.rb index cb688e3..d22c6cc 100644 --- a/app/models/league.rb +++ b/app/models/league.rb @@ -2,8 +2,8 @@ class League < ApplicationRecord belongs_to :user belongs_to :competition has_many :memberships, dependent: :destroy - # TODO: Do we create a membership for the owner? If not, we need another method for all has_many :users, through: :memberships validates :name, presence: true - validates :password, presence: true + # TODO: Think about how users join groups + # validates :password, presence: true end diff --git a/app/models/team.rb b/app/models/team.rb index dc5cc4d..a07e0a3 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -6,6 +6,6 @@ class Team < ApplicationRecord def matches # teams can either be home or away - Match.where('team_home_id = ? OR team_away_id = ?', id, id) + Match.where('team_home_id = :id OR team_away_id = :id', id: id) end end diff --git a/app/models/user.rb b/app/models/user.rb index 93a2b03..9af98fe 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,8 +7,6 @@ class User < ApplicationRecord has_many :competitions, through: :leagues has_many :predictions, dependent: :destroy has_many :matches, through: :predictions - validates :name, presence: true - validates :timezone, presence: true def leagues # this includes creator or league and members From 75413f8dfaa5caac49dd638a3ec497f8cae2f12c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:26:02 +0900 Subject: [PATCH 026/286] attaching a photo to the teams --- db/seeds.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index 6f01514..a420409 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,4 +1,5 @@ -# url = "https://www.uefa.com/imgml/flags/70x70/CZE.png?imwidth=2048%202048w" +require 'open-uri' + groups = { 'Group A' => [ { name: 'Italy', abbrev: 'ITA' }, @@ -61,3 +62,13 @@ end puts "...#{Team.count} Total Teams" puts "...#{Group.count} Total Groups" + +Team.find_each do |team| + next if unless team.badge.attached? + + url = "https://www.uefa.com/imgml/flags/70x70/#{team.abbrev}.png?imwidth=2048%202048w" + puts "#{team.name}: #{url}" + file = URI.open(url) + team.badge.attach(id: file, filename: 'badge.png', content_type: 'image/png') + puts team.badge.attached? ? 'Success' : 'Failed' +end From 8f995e265fcb7c6a79e29c59ac385add4fa312a2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:33:05 +0900 Subject: [PATCH 027/286] fixed type and increased size of image --- db/seeds.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index a420409..054653f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -3,7 +3,7 @@ groups = { 'Group A' => [ { name: 'Italy', abbrev: 'ITA' }, - { name: 'Switzerland ', abbrev: 'SUI' }, + { name: 'Switzerland', abbrev: 'SUI' }, { name: 'Turkey', abbrev: 'TUR' }, { name: 'Wales', abbrev: 'WAL' } ], @@ -33,7 +33,7 @@ ], 'Group F' => [ { name: 'France', abbrev: 'FRA' }, - { name: 'Germany ', abbrev: 'GER' }, + { name: 'Germany', abbrev: 'GER' }, { name: 'Hungary', abbrev: 'HUN' }, { name: 'Portugal', abbrev: 'POR' } ] @@ -64,11 +64,11 @@ puts "...#{Group.count} Total Groups" Team.find_each do |team| - next if unless team.badge.attached? + next if team.badge.attached? - url = "https://www.uefa.com/imgml/flags/70x70/#{team.abbrev}.png?imwidth=2048%202048w" + url = "https://www.uefa.com/imgml/flags/140x140/#{team.abbrev}.png?imwidth=2048%202048w" puts "#{team.name}: #{url}" file = URI.open(url) - team.badge.attach(id: file, filename: 'badge.png', content_type: 'image/png') + team.badge.attach(io: file, filename: 'badge.png', content_type: 'image/png') puts team.badge.attached? ? 'Success' : 'Failed' end From b2e75306d3cda9f26bd6d61224bcb3c707b19244 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:47:56 +0900 Subject: [PATCH 028/286] creating users and adding teams to groups --- db/seeds.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 054653f..d14d6b8 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,5 +1,9 @@ require 'open-uri' +doug = User.find_by(email: 'douglasmberkley@gmail.com') || User.create(email: 'douglasmberkley@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) +trouni = User.find_by(email: 'trouni@gmail.com') || User.create(email: 'trouni@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) +james = User.find_by(email: 'devereuxjj@gmail.com') || User.create(email: 'devereuxjj@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) + groups = { 'Group A' => [ { name: 'Italy', abbrev: 'ITA' }, @@ -40,24 +44,21 @@ } puts 'Creating the Euros...' -competition = Competition.find_or_create_by(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) -p competition.errors.full_messages if competition.errors.any? +competition = Competition.find_or_create_by!(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) puts '.. created the Euros' puts 'Creating or finding first round...' -first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: competition) -p first_round.errors.full_messages if first_round.errors.any? +first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: competition) puts "...#{Round.count} Total Rounds" puts 'Creating or finding groups...' groups.each_key do |group_name| puts "...#{group_name}..." - group = Group.find_or_create_by(name: group_name, round: first_round) - p group.errors.full_messages if group.errors.any? + group = Group.find_or_create_by!(name: group_name, round: first_round) groups[group_name].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" - team = Team.find_or_create_by(team_hash) - p team.errors.full_messages if team.errors.any? + team = Team.find_or_create_by!(team_hash) + Affiliation.find_or_create_by!(team: team, group: group) end end puts "...#{Team.count} Total Teams" From 7de32577e38e8c87b39fa2eeaf77256a3b2c6f71 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:52:22 +0900 Subject: [PATCH 029/286] creating test league and memberships --- db/seeds.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index d14d6b8..0de9d82 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -44,11 +44,11 @@ } puts 'Creating the Euros...' -competition = Competition.find_or_create_by!(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) +euros = Competition.find_or_create_by!(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) puts '.. created the Euros' puts 'Creating or finding first round...' -first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: competition) +first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros) puts "...#{Round.count} Total Rounds" puts 'Creating or finding groups...' @@ -73,3 +73,15 @@ team.badge.attach(io: file, filename: 'badge.png', content_type: 'image/png') puts team.badge.attached? ? 'Success' : 'Failed' end + +puts 'Creating a test league as James' +league = League.find_or_create_by!( + name: 'Admin League', + competition: euros, + user: james +) + +puts 'Adding Trouni and Doug to the league' +[trouni, doug].each do |user| + Membership.find_or_create_by!(league: league, user: user) +end From c1fe9301a8f9b5ee6424ade47784d24b4f181d20 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:56:45 +0900 Subject: [PATCH 030/286] added some fake users w/faker --- Gemfile | 1 + Gemfile.lock | 3 +++ db/seeds.rb | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/Gemfile b/Gemfile index bf4c5e0..2069dd8 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ gem 'rack-cors' # Added gem 'cloudinary', '~> 1.16.0' gem 'devise' +gem 'faker' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index f1fa69b..374c7f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,6 +84,8 @@ GEM dotenv (= 2.7.6) railties (>= 3.2) erubi (1.10.0) + faker (2.16.0) + i18n (>= 1.6, < 2) ffi (1.15.0) globalid (0.4.2) activesupport (>= 4.2.0) @@ -192,6 +194,7 @@ DEPENDENCIES cloudinary (~> 1.16.0) devise dotenv-rails + faker listen (~> 3.3) pg (~> 1.1) puma (~> 5.0) diff --git a/db/seeds.rb b/db/seeds.rb index 0de9d82..346bae0 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,9 +1,19 @@ require 'open-uri' +puts 'Getting Admin users...' doug = User.find_by(email: 'douglasmberkley@gmail.com') || User.create(email: 'douglasmberkley@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) trouni = User.find_by(email: 'trouni@gmail.com') || User.create(email: 'trouni@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) james = User.find_by(email: 'devereuxjj@gmail.com') || User.create(email: 'devereuxjj@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) +puts 'Creating test users...' +20.times do + User.create( + email: Faker::Internet.safe_email, + password: '123123' + ) +end +puts "... #{User.count} Total Users" + groups = { 'Group A' => [ { name: 'Italy', abbrev: 'ITA' }, From 8549aafcb90c775094b569781471bade2f5c4f08 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 18:58:12 +0900 Subject: [PATCH 031/286] updated feedback --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index 346bae0..b7dc3f8 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -84,7 +84,7 @@ puts team.badge.attached? ? 'Success' : 'Failed' end -puts 'Creating a test league as James' +puts 'Creating a test league w/ James as creator' league = League.find_or_create_by!( name: 'Admin League', competition: euros, From 2605e1857b319f04585133d6124f354d148f59db Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 21:28:05 +0900 Subject: [PATCH 032/286] created service object to scrape for matches --- Gemfile | 3 ++ Gemfile.lock | 23 ++++++++ app/models/match.rb | 3 +- app/services/scrape_matches_service.rb | 72 ++++++++++++++++++++++++++ db/seeds.rb | 1 + 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 app/services/scrape_matches_service.rb diff --git a/Gemfile b/Gemfile index 2069dd8..0fe1973 100644 --- a/Gemfile +++ b/Gemfile @@ -29,11 +29,14 @@ gem 'rack-cors' gem 'cloudinary', '~> 1.16.0' gem 'devise' gem 'faker' +gem 'watir', '6.16.5' +gem 'webdrivers' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'dotenv-rails' + gem 'pry-byebug' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 374c7f2..3f99a50 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,9 +66,11 @@ GEM msgpack (~> 1.0) builder (3.2.4) byebug (11.1.3) + childprocess (3.0.0) cloudinary (1.16.1) aws_cf_signer rest-client + coderay (1.1.3) concurrent-ruby (1.1.8) crass (1.0.6) devise (4.7.3) @@ -118,6 +120,12 @@ GEM racc (~> 1.4) orm_adapter (0.5.0) pg (1.2.3) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.9.0) + byebug (~> 11.0) + pry (~> 0.13.0) puma (5.2.2) nio4r (~> 2.0) racc (1.5.2) @@ -156,6 +164,7 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) + regexp_parser (1.8.2) responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) @@ -164,6 +173,10 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rubyzip (2.3.0) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -180,6 +193,13 @@ GEM unf_ext (0.0.7.7) warden (1.2.9) rack (>= 2.0.9) + watir (6.16.5) + regexp_parser (~> 1.2) + selenium-webdriver (~> 3.6) + webdrivers (4.6.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -197,11 +217,14 @@ DEPENDENCIES faker listen (~> 3.3) pg (~> 1.1) + pry-byebug puma (~> 5.0) rack-cors rails (~> 6.1.3, >= 6.1.3.1) spring tzinfo-data + watir (= 6.16.5) + webdrivers RUBY VERSION ruby 2.6.6p146 diff --git a/app/models/match.rb b/app/models/match.rb index 1f0789d..bda999a 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -7,5 +7,6 @@ class Match < ApplicationRecord has_many :users, through: :predictions validates :kickoff_time, presence: true validates :status, presence: true - enum status: %i[awaiting started finished] + enum status: %i[upcoming started finished] + validates_uniqueness_of :kickoff_time, scope: [:team_home, :team_away] end diff --git a/app/services/scrape_matches_service.rb b/app/services/scrape_matches_service.rb new file mode 100644 index 0000000..a493106 --- /dev/null +++ b/app/services/scrape_matches_service.rb @@ -0,0 +1,72 @@ +# TODO: We'll need to install the drivers to work on Heroku +require 'nokogiri' + +class ScrapeMatchesService + attr_reader :urls, :browser + + def initialize + # ids pulled from the UEFA website to scrape matches + @urls = [] + group_stages_ids = [33673, 33674, 33675] + group_stage_url = 'https://www.uefa.com/uefaeuro-2020/fixtures-results/#/md/' + group_stages_ids.each { |id| @urls << "#{group_stage_url}#{id}" } + + # TODO: Knockout stages have placeholders for winners of groups, not team names yet. + # knockout_ids = [2001025, 2001026, 2001027, 2001028] + # knockout_url = 'https://www.uefa.com/uefaeuro-2020/fixtures-results/#/rd/' + # knockout_ids.each { |id| @urls << "#{knockout_url}#{id}" } + end + + def call + @browser = Watir::Browser.new :chrome, args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] + urls.each do |url| + scrape(url) + end + end + + def scrape(url) + browser.goto url + sleep(10) + html_doc = Nokogiri::HTML.parse(browser.html) + + # The HTML is so crazily organized + # for each match row, there is an h3 with the date (some are hidden) / groups + dates = html_doc.search('.matches-match_date') + puts "Found #{dates.count} dates (should be 12)" + groups = html_doc.search('.match-row_group .match-group') + puts "Found #{groups.count} groups (should be 12)" + + html_doc.search('.match-row_link').each_with_index do |match_row, index| + # Tried about 100 ways before I got to this. Others weren't loading in time(?) + epoch = JSON.parse(match_row.search('.match-row_match').first.attributes["data-options"].value)['match']["MatchDateTime"].delete('/Date()/') + puts 'Epoch:' + p epoch + + kickoff_time = DateTime.strptime(epoch, '%Q') + puts 'Kickoff time:' + p kickoff_time + + home_name = match_row.search('.team-home .team-name').text.strip + team_home = Team.find_by(name: home_name) + puts 'Home team:' + p team_home + + away_name = match_row.search('.team-away .team-name').text.strip + team_away = Team.find_by(name: away_name) + puts 'Away team:' + p team_away + + group_name = groups[index].attributes['title'].value + group = Group.find_by(name: group_name) + puts 'Group:' + p group + + p Match.find_or_create_by( + kickoff_time: kickoff_time, + team_home: team_home, + team_away: team_away, + group: group + ) + end + end +end diff --git a/db/seeds.rb b/db/seeds.rb index b7dc3f8..62f16e4 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -8,6 +8,7 @@ puts 'Creating test users...' 20.times do User.create( + # fake emails for testing purposes email: Faker::Internet.safe_email, password: '123123' ) From 62fc7680ddc367b796a57a07b0a2c3157f5c6c14 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 21:30:42 +0900 Subject: [PATCH 033/286] updated comment --- app/services/scrape_matches_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/scrape_matches_service.rb b/app/services/scrape_matches_service.rb index a493106..e815ff1 100644 --- a/app/services/scrape_matches_service.rb +++ b/app/services/scrape_matches_service.rb @@ -11,7 +11,7 @@ def initialize group_stage_url = 'https://www.uefa.com/uefaeuro-2020/fixtures-results/#/md/' group_stages_ids.each { |id| @urls << "#{group_stage_url}#{id}" } - # TODO: Knockout stages have placeholders for winners of groups, not team names yet. + # TODO: Knockout stages have placeholders for winners of groups, not team names. # knockout_ids = [2001025, 2001026, 2001027, 2001028] # knockout_url = 'https://www.uefa.com/uefaeuro-2020/fixtures-results/#/rd/' # knockout_ids.each { |id| @urls << "#{knockout_url}#{id}" } From 0c759594a01388008e5fed44356625e2bf17235d Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 21:42:31 +0900 Subject: [PATCH 034/286] refactored service into multiple methods --- app/services/scrape_matches_service.rb | 60 +++++++++++++------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/app/services/scrape_matches_service.rb b/app/services/scrape_matches_service.rb index e815ff1..9df4cff 100644 --- a/app/services/scrape_matches_service.rb +++ b/app/services/scrape_matches_service.rb @@ -29,44 +29,42 @@ def scrape(url) sleep(10) html_doc = Nokogiri::HTML.parse(browser.html) - # The HTML is so crazily organized - # for each match row, there is an h3 with the date (some are hidden) / groups - dates = html_doc.search('.matches-match_date') - puts "Found #{dates.count} dates (should be 12)" + # The HTML is crazily organized groups = html_doc.search('.match-row_group .match-group') puts "Found #{groups.count} groups (should be 12)" html_doc.search('.match-row_link').each_with_index do |match_row, index| - # Tried about 100 ways before I got to this. Others weren't loading in time(?) - epoch = JSON.parse(match_row.search('.match-row_match').first.attributes["data-options"].value)['match']["MatchDateTime"].delete('/Date()/') - puts 'Epoch:' - p epoch - - kickoff_time = DateTime.strptime(epoch, '%Q') - puts 'Kickoff time:' - p kickoff_time + p Match.find_or_create_by( + kickoff_time: get_kickoff_time(match_row), + team_home: get_team_home(match_row), + team_away: get_team_away(match_row), + group: get_group(groups[index]) + ) + end + end - home_name = match_row.search('.team-home .team-name').text.strip - team_home = Team.find_by(name: home_name) - puts 'Home team:' - p team_home + def get_team_home(match_row) + home_name = match_row.search('.team-home .team-name').text.strip + puts "Home team: #{home_name}" + Team.find_by(name: home_name) + end - away_name = match_row.search('.team-away .team-name').text.strip - team_away = Team.find_by(name: away_name) - puts 'Away team:' - p team_away + def get_team_away(match_row) + away_name = match_row.search('.team-away .team-name').text.strip + puts "Away team: #{away_name}" + Team.find_by(name: away_name) + end - group_name = groups[index].attributes['title'].value - group = Group.find_by(name: group_name) - puts 'Group:' - p group + def get_kickoff_time(match_row) + # Tried about 100 ways before I got to this. Others weren't loading in time(?) + epoch = JSON.parse(match_row.search('.match-row_match').first.attributes["data-options"].value)['match']["MatchDateTime"].delete('/Date()/') + puts "Epoch: #{epoch}" + DateTime.strptime(epoch, '%Q') + end - p Match.find_or_create_by( - kickoff_time: kickoff_time, - team_home: team_home, - team_away: team_away, - group: group - ) - end + def get_group(group_element) + group_name = group_element.attributes['title'].value + puts "Group: #{group_name}" + Group.find_by(name: group_name) end end From ad1782c818a60f460c89e03f6e3a9aa1d5653cdb Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 7 Apr 2021 22:28:15 +0900 Subject: [PATCH 035/286] added gem and rescuing errors in application controller --- Gemfile | 1 + Gemfile.lock | 3 ++ app/controllers/application_controller.rb | 25 ++++++++++++ app/policies/application_policy.rb | 49 +++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 app/policies/application_policy.rb diff --git a/Gemfile b/Gemfile index 0fe1973..23aab48 100644 --- a/Gemfile +++ b/Gemfile @@ -29,6 +29,7 @@ gem 'rack-cors' gem 'cloudinary', '~> 1.16.0' gem 'devise' gem 'faker' +gem 'pundit' gem 'watir', '6.16.5' gem 'webdrivers' diff --git a/Gemfile.lock b/Gemfile.lock index 3f99a50..4b36434 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -128,6 +128,8 @@ GEM pry (~> 0.13.0) puma (5.2.2) nio4r (~> 2.0) + pundit (2.1.0) + activesupport (>= 3.0.0) racc (1.5.2) rack (2.2.3) rack-cors (1.1.1) @@ -219,6 +221,7 @@ DEPENDENCIES pg (~> 1.1) pry-byebug puma (~> 5.0) + pundit rack-cors rails (~> 6.1.3, >= 6.1.3.1) spring diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4ac8823..064cdb4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,27 @@ class ApplicationController < ActionController::API + before_action :authenticate_user! + include Pundit + + # Pundit: white-list approach. + after_action :verify_authorized, except: :index, unless: :skip_pundit? + after_action :verify_policy_scoped, only: :index, unless: :skip_pundit? + + rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized + rescue_from ActiveRecord::RecordNotFound, with: :not_found + + private + + def user_not_authorized(exception) + render json: { + error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}" + }, status: :unauthorized + end + + def not_found(exception) + render json: { error: exception.message }, status: :not_found + end + + def skip_pundit? + devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/ + end end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 0000000..eefe976 --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,49 @@ +class ApplicationPolicy + attr_reader :user, :record + + def initialize(user, record) + @user = user + @record = record + end + + def index? + false + end + + def show? + false + end + + def create? + false + end + + def new? + create? + end + + def update? + false + end + + def edit? + update? + end + + def destroy? + false + end + + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + scope.all + end + end +end From c2b89e9425eef8c17f813ab9d0d63c9fdc8c2c5b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 8 Apr 2021 15:28:11 +0900 Subject: [PATCH 036/286] Added omniauth and devise token. needed to change user table migration. Also added the matches at the end of seeds. --- Gemfile | 2 + Gemfile.lock | 15 +++++ app/controllers/application_controller.rb | 1 + app/models/user.rb | 5 ++ config/initializers/devise_token_auth.rb | 60 +++++++++++++++++++ config/routes.rb | 3 +- .../20210407055611_devise_create_users.rb | 48 --------------- ...07055612_devise_token_auth_create_users.rb | 53 ++++++++++++++++ db/schema.rb | 12 +++- db/seeds.rb | 2 + 10 files changed, 150 insertions(+), 51 deletions(-) create mode 100644 config/initializers/devise_token_auth.rb delete mode 100644 db/migrate/20210407055611_devise_create_users.rb create mode 100644 db/migrate/20210407055612_devise_token_auth_create_users.rb diff --git a/Gemfile b/Gemfile index 23aab48..b13af2a 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,9 @@ gem 'rack-cors' # Added gem 'cloudinary', '~> 1.16.0' gem 'devise' +gem 'devise_token_auth', github: 'lynndylanhurley/devise_token_auth' gem 'faker' +gem 'omniauth' gem 'pundit' gem 'watir', '6.16.5' gem 'webdrivers' diff --git a/Gemfile.lock b/Gemfile.lock index 4b36434..f402a9e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/lynndylanhurley/devise_token_auth.git + revision: 5c0baba8aaf005d03daeaf425d555739d3215603 + specs: + devise_token_auth (1.1.4) + bcrypt (~> 3.0) + devise (> 3.5.2, < 5) + rails (>= 4.2.0, < 6.2) + GEM remote: https://rubygems.org/ specs: @@ -91,6 +100,7 @@ GEM ffi (1.15.0) globalid (0.4.2) activesupport (>= 4.2.0) + hashie (3.5.6) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) @@ -118,6 +128,9 @@ GEM nokogiri (1.11.2) mini_portile2 (~> 2.5.0) racc (~> 1.4) + omniauth (1.6.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) orm_adapter (0.5.0) pg (1.2.3) pry (0.13.1) @@ -215,9 +228,11 @@ DEPENDENCIES byebug cloudinary (~> 1.16.0) devise + devise_token_auth! dotenv-rails faker listen (~> 3.3) + omniauth pg (~> 1.1) pry-byebug puma (~> 5.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 064cdb4..2ce63af 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,5 @@ class ApplicationController < ActionController::API + include DeviseTokenAuth::Concerns::SetUserByToken before_action :authenticate_user! include Pundit diff --git a/app/models/user.rb b/app/models/user.rb index 9af98fe..94e0a14 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,9 @@ class User < ApplicationRecord + # Include default devise modules. + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :trackable, :validatable, + :confirmable, :omniauthable + include DeviseTokenAuth::Concerns::User # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, diff --git a/config/initializers/devise_token_auth.rb b/config/initializers/devise_token_auth.rb new file mode 100644 index 0000000..7e38131 --- /dev/null +++ b/config/initializers/devise_token_auth.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +DeviseTokenAuth.setup do |config| + # By default the authorization headers will change after each request. The + # client is responsible for keeping track of the changing tokens. Change + # this to false to prevent the Authorization header from changing after + # each request. + # config.change_headers_on_each_request = true + + # By default, users will need to re-authenticate after 2 weeks. This setting + # determines how long tokens will remain valid after they are issued. + # config.token_lifespan = 2.weeks + + # Limiting the token_cost to just 4 in testing will increase the performance of + # your test suite dramatically. The possible cost value is within range from 4 + # to 31. It is recommended to not use a value more than 10 in other environments. + config.token_cost = Rails.env.test? ? 4 : 10 + + # Sets the max number of concurrent devices per user, which is 10 by default. + # After this limit is reached, the oldest tokens will be removed. + # config.max_number_of_devices = 10 + + # Sometimes it's necessary to make several requests to the API at the same + # time. In this case, each request in the batch will need to share the same + # auth token. This setting determines how far apart the requests can be while + # still using the same auth token. + # config.batch_request_buffer_throttle = 5.seconds + + # This route will be the prefix for all oauth2 redirect callbacks. For + # example, using the default '/omniauth', the github oauth2 provider will + # redirect successful authentications to '/omniauth/github/callback' + # config.omniauth_prefix = "/omniauth" + + # By default sending current password is not needed for the password update. + # Uncomment to enforce current_password param to be checked before all + # attribute updates. Set it to :password if you want it to be checked only if + # password is updated. + # config.check_current_password_before_update = :attributes + + # By default we will use callbacks for single omniauth. + # It depends on fields like email, provider and uid. + # config.default_callbacks = true + + # Makes it possible to change the headers names + # config.headers_names = {:'access-token' => 'access-token', + # :'client' => 'client', + # :'expiry' => 'expiry', + # :'uid' => 'uid', + # :'token-type' => 'token-type' } + + # By default, only Bearer Token authentication is implemented out of the box. + # If, however, you wish to integrate with legacy Devise authentication, you can + # do so by enabling this flag. NOTE: This feature is highly experimental! + # config.enable_standard_devise_support = false + + # By default DeviseTokenAuth will not send confirmation email, even when including + # devise confirmable module. If you want to use devise confirmable module and + # send email, set it to true. (This is a setting for compatibility) + # config.send_confirmation_email = true +end diff --git a/config/routes.rb b/config/routes.rb index 54b04d7..cc49ad9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,3 @@ Rails.application.routes.draw do - devise_for :users - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + mount_devise_token_auth_for 'User', at: 'auth' end diff --git a/db/migrate/20210407055611_devise_create_users.rb b/db/migrate/20210407055611_devise_create_users.rb deleted file mode 100644 index b5910db..0000000 --- a/db/migrate/20210407055611_devise_create_users.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -class DeviseCreateUsers < ActiveRecord::Migration[6.1] - def change - create_table :users do |t| - ## Database authenticatable - t.string :email, null: false, default: "" - t.string :encrypted_password, null: false, default: "" - - ## Recoverable - t.string :reset_password_token - t.datetime :reset_password_sent_at - - ## Rememberable - t.datetime :remember_created_at - - ## Trackable - # t.integer :sign_in_count, default: 0, null: false - # t.datetime :current_sign_in_at - # t.datetime :last_sign_in_at - # t.string :current_sign_in_ip - # t.string :last_sign_in_ip - - ## Confirmable - # t.string :confirmation_token - # t.datetime :confirmed_at - # t.datetime :confirmation_sent_at - # t.string :unconfirmed_email # Only if using reconfirmable - - ## Lockable - # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts - # t.string :unlock_token # Only if unlock strategy is :email or :both - # t.datetime :locked_at - - ## Manually Added - t.string :name - t.boolean :admin, default: false - t.string :timezone - - t.timestamps null: false - end - - add_index :users, :email, unique: true - add_index :users, :reset_password_token, unique: true - # add_index :users, :confirmation_token, unique: true - # add_index :users, :unlock_token, unique: true - end -end diff --git a/db/migrate/20210407055612_devise_token_auth_create_users.rb b/db/migrate/20210407055612_devise_token_auth_create_users.rb new file mode 100644 index 0000000..7c62c5b --- /dev/null +++ b/db/migrate/20210407055612_devise_token_auth_create_users.rb @@ -0,0 +1,53 @@ +class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[6.1] + def change + + create_table(:users) do |t| + ## Required + t.string :provider, :null => false, :default => "email" + t.string :uid, :null => false, :default => "" + + ## Database authenticatable + t.string :encrypted_password, :null => false, :default => "" + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + t.boolean :allow_password_change, :default => false + + ## Rememberable + t.datetime :remember_created_at + + ## Confirmable + t.string :confirmation_token + t.datetime :confirmed_at + t.datetime :confirmation_sent_at + t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + ## User Info (these were auto-included but all not necessary) + t.string :email + t.string :name + # t.string :nickname + # t.string :image + + ## Manually Added + t.boolean :admin, default: false + t.string :timezone + + ## Tokens + t.json :tokens + + t.timestamps + end + + add_index :users, :email, unique: true + add_index :users, [:uid, :provider], unique: true + add_index :users, :reset_password_token, unique: true + add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index b395d2e..18ff6dc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -134,18 +134,28 @@ end create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false + t.string "provider", default: "email", null: false + t.string "uid", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" + t.boolean "allow_password_change", default: false t.datetime "remember_created_at" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.string "email" t.string "name" t.boolean "admin", default: false t.string "timezone" + t.json "tokens" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["uid", "provider"], name: "index_users_on_uid_and_provider", unique: true end add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" diff --git a/db/seeds.rb b/db/seeds.rb index 62f16e4..f26b6d9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -96,3 +96,5 @@ [trouni, doug].each do |user| Membership.find_or_create_by!(league: league, user: user) end + +ScrapeMatchesService.new.call From 075b75dfae925b71dfba2ca6211eba80db506728 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 8 Apr 2021 15:46:56 +0900 Subject: [PATCH 037/286] Added mailcatcher and rack cors w/config --- Gemfile | 3 +- Gemfile.lock | 39 ++++ config/application.rb | 10 + config/environments/development.rb | 2 + config/initializers/devise.rb | 309 +---------------------------- 5 files changed, 60 insertions(+), 303 deletions(-) diff --git a/Gemfile b/Gemfile index b13af2a..5188053 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ gem 'puma', '~> 5.0' gem 'bootsnap', '>= 1.4.4', require: false # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible -gem 'rack-cors' +gem 'rack-cors', require: 'rack/cors' # Added gem 'cloudinary', '~> 1.16.0' @@ -39,6 +39,7 @@ group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'dotenv-rails' + gem 'mailcatcher' # need to launch a mailcatcher server to catch dev emails gem 'pry-byebug' end diff --git a/Gemfile.lock b/Gemfile.lock index f402a9e..6aa1306 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,6 +82,7 @@ GEM coderay (1.1.3) concurrent-ruby (1.1.8) crass (1.0.6) + daemons (1.3.1) devise (4.7.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -95,17 +96,22 @@ GEM dotenv (= 2.7.6) railties (>= 3.2) erubi (1.10.0) + eventmachine (1.2.7) faker (2.16.0) i18n (>= 1.6, < 2) ffi (1.15.0) globalid (0.4.2) activesupport (>= 4.2.0) + haml (5.2.1) + temple (>= 0.8.0) + tilt hashie (3.5.6) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) i18n (1.8.10) concurrent-ruby (~> 1.0) + json (2.5.1) listen (3.5.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -114,6 +120,16 @@ GEM nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) + mailcatcher (0.2.4) + eventmachine + haml + i18n + json + mail + sinatra + skinny (>= 0.1.2) + sqlite3-ruby + thin marcel (1.0.1) method_source (1.0.0) mime-types (3.3.1) @@ -123,6 +139,8 @@ GEM mini_portile2 (2.5.0) minitest (5.14.4) msgpack (1.4.2) + mustermann (1.1.1) + ruby2_keywords (~> 0.0.1) netrc (0.11.0) nio4r (2.5.7) nokogiri (1.11.2) @@ -147,6 +165,8 @@ GEM rack (2.2.3) rack-cors (1.1.1) rack (>= 2.0.0) + rack-protection (2.1.0) + rack rack-test (1.1.0) rack (>= 1.0, < 3) rails (6.1.3.1) @@ -188,10 +208,19 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + ruby2_keywords (0.0.4) rubyzip (2.3.0) selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) + sinatra (2.1.0) + mustermann (~> 1.0) + rack (~> 2.2) + rack-protection (= 2.1.0) + tilt (~> 2.0) + skinny (0.2.2) + eventmachine (~> 1.0) + thin spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -200,7 +229,16 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + sqlite3 (1.4.2) + sqlite3-ruby (1.3.3) + sqlite3 (>= 1.3.3) + temple (0.8.2) + thin (1.8.0) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) thor (1.1.0) + tilt (2.0.10) tzinfo (2.0.4) concurrent-ruby (~> 1.0) unf (0.1.4) @@ -232,6 +270,7 @@ DEPENDENCIES dotenv-rails faker listen (~> 3.3) + mailcatcher omniauth pg (~> 1.1) pry-byebug diff --git a/config/application.rb b/config/application.rb index 5d3b657..01c48a8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -36,5 +36,15 @@ class Application < Rails::Application # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. config.api_only = true + # TODO: Finally pick a domain. The following dangerous example will allow cross domain requests from any domain. Make sure to whitelist only the needed domains. + config.middleware.use Rack::Cors do + allow do + origins '*' + resource '*', + headers: :any, + expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'], + methods: [:get, :post, :options, :delete, :put] + end + end end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 79d1002..ab77ccf 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -31,6 +31,8 @@ config.active_storage.service = :cloudinary # Don't care if the mailer can't send. + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 } config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = false diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 1f21bd7..24ed8a2 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,36 +1,7 @@ -# frozen_string_literal: true - -# Assuming you have not yet modified this file, each configuration option below -# is set to its default value. Note that some are commented out while others -# are not: uncommented lines are intended to protect your configuration from -# breaking changes in upgrades (i.e., in the event that future versions of -# Devise change the default values for those options). -# -# Use this hook to configure devise mailer, warden hooks and so forth. -# Many of these configuration options can be set straight in your model. Devise.setup do |config| - # The secret key used by Devise. Devise uses this key to generate - # random tokens. Changing this key will render invalid all existing - # confirmation, reset password and unlock tokens in the database. - # Devise will use the `secret_key_base` as its `secret_key` - # by default. You can change it below and use your own secret key. - # config.secret_key = '2505273ec1e312a38dbca7936d018c72686a981c6a8215998ae782a88ae61579aaf7c8faea0ba1915b0b56e8acd027c769269c68ac520c7c01be3a3f0494d625' - - # ==> Controller configuration - # Configure the parent class to the devise controllers. - # config.parent_controller = 'DeviseController' - - # ==> Mailer Configuration - # Configure the e-mail address which will be shown in Devise::Mailer, - # note that it will be overwritten if you use your own mailer class - # with default "from" parameter. - config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' - - # Configure the class responsible to send e-mails. - # config.mailer = 'Devise::Mailer' - - # Configure the parent class responsible to send e-mails. - # config.parent_mailer = 'ActionMailer::Base' + # The e-mail address that mail will appear to be sent from + # If absent, mail is sent from "please-change-me-at-config-initializers-devise@example.com" + config.mailer_sender = "support@example.com" # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and @@ -38,274 +9,8 @@ # available as additional gems. require 'devise/orm/active_record' - # ==> Configuration for any authentication mechanism - # Configure which keys are used when authenticating a user. The default is - # just :email. You can configure it to use [:username, :subdomain], so for - # authenticating a user, both parameters are required. Remember that those - # parameters are used only when authenticating and not when retrieving from - # session. If you need permissions, you should implement that in a before filter. - # You can also supply a hash where the value is a boolean determining whether - # or not authentication should be aborted when the value is not present. - # config.authentication_keys = [:email] - - # Configure parameters from the request object used for authentication. Each entry - # given should be a request method and it will automatically be passed to the - # find_for_authentication method and considered in your model lookup. For instance, - # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. - # The same considerations mentioned for authentication_keys also apply to request_keys. - # config.request_keys = [] - - # Configure which authentication keys should be case-insensitive. - # These keys will be downcased upon creating or modifying a user and when used - # to authenticate or find a user. Default is :email. - config.case_insensitive_keys = [:email] - - # Configure which authentication keys should have whitespace stripped. - # These keys will have whitespace before and after removed upon creating or - # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [:email] - - # Tell if authentication through request.params is enabled. True by default. - # It can be set to an array that will enable params authentication only for the - # given strategies, for example, `config.params_authenticatable = [:database]` will - # enable it only for database (email + password) authentication. - # config.params_authenticatable = true - - # Tell if authentication through HTTP Auth is enabled. False by default. - # It can be set to an array that will enable http authentication only for the - # given strategies, for example, `config.http_authenticatable = [:database]` will - # enable it only for database authentication. - # For API-only applications to support authentication "out-of-the-box", you will likely want to - # enable this with :database unless you are using a custom strategy. - # The supported strategies are: - # :database = Support basic authentication with authentication key + password - # config.http_authenticatable = false - - # If 401 status code should be returned for AJAX requests. True by default. - # config.http_authenticatable_on_xhr = true - - # The realm used in Http Basic Authentication. 'Application' by default. - # config.http_authentication_realm = 'Application' - - # It will change confirmation, password recovery and other workflows - # to behave the same regardless if the e-mail provided was right or wrong. - # Does not affect registerable. - # config.paranoid = true - - # By default Devise will store the user in session. You can skip storage for - # particular strategies by setting this option. - # Notice that if you are skipping storage for all authentication paths, you - # may want to disable generating routes to Devise's sessions controller by - # passing skip: :sessions to `devise_for` in your config/routes.rb - config.skip_session_storage = [:http_auth] - - # By default, Devise cleans up the CSRF token on authentication to - # avoid CSRF token fixation attacks. This means that, when using AJAX - # requests for sign in and sign up, you need to get a new CSRF token - # from the server. You can disable this option at your own risk. - # config.clean_up_csrf_token_on_authentication = true - - # When false, Devise will not attempt to reload routes on eager load. - # This can reduce the time taken to boot the app but if your application - # requires the Devise mappings to be loaded during boot time the application - # won't boot properly. - # config.reload_routes = true - - # ==> Configuration for :database_authenticatable - # For bcrypt, this is the cost for hashing the password and defaults to 12. If - # using other algorithms, it sets how many times you want the password to be hashed. - # The number of stretches used for generating the hashed password are stored - # with the hashed password. This allows you to change the stretches without - # invalidating existing passwords. - # - # Limiting the stretches to just one in testing will increase the performance of - # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use - # a value less than 10 in other environments. Note that, for bcrypt (the default - # algorithm), the cost increases exponentially with the number of stretches (e.g. - # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). - config.stretches = Rails.env.test? ? 1 : 12 - - # Set up a pepper to generate the hashed password. - # config.pepper = '012475b20535e1594840f0a98f63681154bd03b41438c0032c130ee6fa43ff17959e152d2230c0e851b4bb5064662a21b0623cf926c691be491222a2864cb905' - - # Send a notification to the original email when the user's email is changed. - # config.send_email_changed_notification = false - - # Send a notification email when the user's password is changed. - # config.send_password_change_notification = false - - # ==> Configuration for :confirmable - # A period that the user is allowed to access the website even without - # confirming their account. For instance, if set to 2.days, the user will be - # able to access the website for two days without confirming their account, - # access will be blocked just in the third day. - # You can also set it to nil, which will allow the user to access the website - # without confirming their account. - # Default is 0.days, meaning the user cannot access the website without - # confirming their account. - # config.allow_unconfirmed_access_for = 2.days - - # A period that the user is allowed to confirm their account before their - # token becomes invalid. For example, if set to 3.days, the user can confirm - # their account within 3 days after the mail was sent, but on the fourth day - # their account can't be confirmed with the token any more. - # Default is nil, meaning there is no restriction on how long a user can take - # before confirming their account. - # config.confirm_within = 3.days - - # If true, requires any email changes to be confirmed (exactly the same way as - # initial account confirmation) to be applied. Requires additional unconfirmed_email - # db field (see migrations). Until confirmed, new email is stored in - # unconfirmed_email column, and copied to email column on successful confirmation. - config.reconfirmable = true - - # Defines which key will be used when confirming an account - # config.confirmation_keys = [:email] - - # ==> Configuration for :rememberable - # The time the user will be remembered without asking for credentials again. - # config.remember_for = 2.weeks - - # Invalidates all the remember me tokens when the user signs out. - config.expire_all_remember_me_on_sign_out = true - - # If true, extends the user's remember period when remembered via cookie. - # config.extend_remember_period = false - - # Options to be passed to the created cookie. For instance, you can set - # secure: true in order to force SSL only cookies. - # config.rememberable_options = {} - - # ==> Configuration for :validatable - # Range for password length. - config.password_length = 6..128 - - # Email regex used to validate email formats. It simply asserts that - # one (and only one) @ exists in the given string. This is mainly - # to give user feedback and not to assert the e-mail validity. - config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ - - # ==> Configuration for :timeoutable - # The time you want to timeout the user session without activity. After this - # time the user will be asked for credentials again. Default is 30 minutes. - # config.timeout_in = 30.minutes - - # ==> Configuration for :lockable - # Defines which strategy will be used to lock an account. - # :failed_attempts = Locks an account after a number of failed attempts to sign in. - # :none = No lock strategy. You should handle locking by yourself. - # config.lock_strategy = :failed_attempts - - # Defines which key will be used when locking and unlocking an account - # config.unlock_keys = [:email] - - # Defines which strategy will be used to unlock an account. - # :email = Sends an unlock link to the user email - # :time = Re-enables login after a certain amount of time (see :unlock_in below) - # :both = Enables both strategies - # :none = No unlock strategy. You should handle unlocking by yourself. - # config.unlock_strategy = :both - - # Number of authentication tries before locking an account if lock_strategy - # is failed attempts. - # config.maximum_attempts = 20 - - # Time interval to unlock the account if :time is enabled as unlock_strategy. - # config.unlock_in = 1.hour - - # Warn on the last attempt before the account is locked. - # config.last_attempt_warning = true - - # ==> Configuration for :recoverable - # - # Defines which key will be used when recovering the password for an account - # config.reset_password_keys = [:email] - - # Time interval you can reset your password with a reset password key. - # Don't put a too small interval or your users won't have the time to - # change their passwords. - config.reset_password_within = 6.hours - - # When set to false, does not sign a user in automatically after their password is - # reset. Defaults to true, so a user is signed in automatically after a reset. - # config.sign_in_after_reset_password = true - - # ==> Configuration for :encryptable - # Allow you to use another hashing or encryption algorithm besides bcrypt (default). - # You can use :sha1, :sha512 or algorithms from others authentication tools as - # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 - # for default behavior) and :restful_authentication_sha1 (then you should set - # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). - # - # Require the `devise-encryptable` gem when using anything other than bcrypt - # config.encryptor = :sha512 - - # ==> Scopes configuration - # Turn scoped views on. Before rendering "sessions/new", it will first check for - # "users/sessions/new". It's turned off by default because it's slower if you - # are using only default views. - # config.scoped_views = false - - # Configure the default scope given to Warden. By default it's the first - # devise role declared in your routes (usually :user). - # config.default_scope = :user - - # Set this configuration to false if you want /users/sign_out to sign out - # only the current scope. By default, Devise signs out all scopes. - # config.sign_out_all_scopes = true - - # ==> Navigation configuration - # Lists the formats that should be treated as navigational. Formats like - # :html, should redirect to the sign in page when the user does not have - # access, but formats like :xml or :json, should return 401. - # - # If you have any extra navigational formats, like :iphone or :mobile, you - # should add them to the navigational formats lists. - # - # The "*/*" below is required to match Internet Explorer requests. - # config.navigational_formats = ['*/*', :html] - - # The default HTTP method used to sign out a resource. Default is :delete. - config.sign_out_via = :delete - - # ==> OmniAuth - # Add a new OmniAuth provider. Check the wiki for more information on setting - # up on your models and hooks. - # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' - - # ==> Warden configuration - # If you want to use other strategies, that are not supported by Devise, or - # change the failure app, you can configure them inside the config.warden block. - # - # config.warden do |manager| - # manager.intercept_401 = false - # manager.default_strategies(scope: :user).unshift :some_external_strategy - # end - - # ==> Mountable engine configurations - # When using Devise inside an engine, let's call it `MyEngine`, and this engine - # is mountable, there are some extra configurations to be taken into account. - # The following options are available, assuming the engine is mounted as: - # - # mount MyEngine, at: '/my_engine' - # - # The router that invoked `devise_for`, in the example above, would be: - # config.router_name = :my_engine - # - # When using OmniAuth, Devise cannot automatically set OmniAuth path, - # so you need to do it manually. For the users scope, it would be: - # config.omniauth_path_prefix = '/my_engine/users/auth' - - # ==> Turbolinks configuration - # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: - # - # ActiveSupport.on_load(:devise_failure_app) do - # include Turbolinks::Controller - # end - - # ==> Configuration for :registerable - - # When set to false, does not sign a user in automatically after their password is - # changed. Defaults to true, so a user is signed in automatically after changing a password. - # config.sign_in_after_change_password = true + # If using rails-api, you may want to tell devise to not use ActionDispatch::Flash + # middleware b/c rails-api does not include it. + # See: https://stackoverflow.com/q/19600905/806956 + config.navigational_formats = [:json] end From 330e2292cbc7785d427d15352cfb1bd9f1fdcaec Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 8 Apr 2021 16:06:10 +0900 Subject: [PATCH 038/286] added host url --- app/controllers/application_controller.rb | 3 +-- config/environments/development.rb | 2 ++ config/environments/production.rb | 1 + config/environments/test.rb | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2ce63af..f52164e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,5 @@ class ApplicationController < ActionController::API - include DeviseTokenAuth::Concerns::SetUserByToken - before_action :authenticate_user! + include DeviseTokenAuth::Concerns::SetUserByToken include Pundit # Pundit: white-list approach. diff --git a/config/environments/development.rb b/config/environments/development.rb index ab77ccf..290dd9e 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -30,6 +30,8 @@ # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :cloudinary + config.action_mailer.default_url_options = { host: 'localhost:3000' } + # Don't care if the mailer can't send. config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 } diff --git a/config/environments/production.rb b/config/environments/production.rb index 15f35f9..18748ad 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -55,6 +55,7 @@ # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "predictor_api_production" + config.action_mailer.default_url_options = { host: 'our-host-name' } config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. diff --git a/config/environments/test.rb b/config/environments/test.rb index 93ed4f1..3c692e5 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -38,6 +38,8 @@ config.action_mailer.perform_caching = false + + config.action_mailer.default_url_options = { host: 'localhost:3000' } # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. From 76d7fcd01e5ff58bb30001a5ab037e9eec09fc12 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 8 Apr 2021 16:14:41 +0900 Subject: [PATCH 039/286] removed duplicated from devise gem. but also removed confirmation email --- app/models/user.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 94e0a14..c9c0074 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,13 +1,9 @@ class User < ApplicationRecord - # Include default devise modules. - devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable, - :confirmable, :omniauthable - include DeviseTokenAuth::Concerns::User - # Include default devise modules. Others available are: - # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + # Include default devise modules. devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable + :recoverable, :rememberable, :trackable, :validatable, + :omniauthable # :confirmable + include DeviseTokenAuth::Concerns::User has_many :memberships, dependent: :destroy has_many :competitions, through: :leagues has_many :predictions, dependent: :destroy From 7a9bed92c4415928c73c355eab2959d2dce1a74f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 8 Apr 2021 20:18:09 +0900 Subject: [PATCH 040/286] added api routes --- config/routes.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index cc49ad9..d75473f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,14 @@ Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'auth' + namespace :v1 do + resources :competitions, only: [] do + resources :matches, only: [:index] + resources :leagues, only: [:index, :create, :destroy] + resources :users, only: [:show] + resources :memberships, only: [:create, :destroy] + end + resources :matches, only: [] do + resources :predictions, only: [:create, :update] + end + end end From 6cf8eca454749d1ad882f0815976da7192c54d4f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 9 Apr 2021 18:02:25 +0900 Subject: [PATCH 041/286] fixed routes thant don't need to be nested --- config/routes.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index d75473f..c712c33 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,12 +3,15 @@ namespace :v1 do resources :competitions, only: [] do resources :matches, only: [:index] - resources :leagues, only: [:index, :create, :destroy] + resources :leagues, only: [:index, :create] resources :users, only: [:show] - resources :memberships, only: [:create, :destroy] + resources :memberships, only: [:create] end resources :matches, only: [] do - resources :predictions, only: [:create, :update] + resources :predictions, only: [:create] end + resources :predictions, only: [:update] + resources :leagues, only: [:destroy] + resources :memberships, only: [:destroy] end end From 17b63510cace11784bae123127f91f59537e57bc Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 9 Apr 2021 18:08:55 +0900 Subject: [PATCH 042/286] trackable was enabled but not added to the users table --- .../20210409090454_add_trackable_fields_to_user.rb | 11 +++++++++++ db/schema.rb | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210409090454_add_trackable_fields_to_user.rb diff --git a/db/migrate/20210409090454_add_trackable_fields_to_user.rb b/db/migrate/20210409090454_add_trackable_fields_to_user.rb new file mode 100644 index 0000000..41212bf --- /dev/null +++ b/db/migrate/20210409090454_add_trackable_fields_to_user.rb @@ -0,0 +1,11 @@ +class AddTrackableFieldsToUser < ActiveRecord::Migration[6.1] + def change + change_table :users do |t| + t.integer :sign_in_count, default: 0, null: false + t.datetime :current_sign_in_at + t.datetime :last_sign_in_at + t.inet :current_sign_in_ip + t.inet :last_sign_in_ip + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 18ff6dc..31de334 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: 2021_04_07_082013) do +ActiveRecord::Schema.define(version: 2021_04_09_090454) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -152,6 +152,11 @@ t.json "tokens" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.inet "current_sign_in_ip" + t.inet "last_sign_in_ip" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From 593e2bb71cfad0ea4b5da0d9488efa22159a779b Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 9 Apr 2021 19:48:02 +0900 Subject: [PATCH 043/286] Protect all actions behind authentication --- app/controllers/application_controller.rb | 6 ++++++ .../auth/devise_token_auth/sessions_controller.rb | 9 +++++++++ config/routes.rb | 4 +++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 app/controllers/auth/devise_token_auth/sessions_controller.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f52164e..2734f4b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,8 @@ class ApplicationController < ActionController::API include DeviseTokenAuth::Concerns::SetUserByToken include Pundit + before_action :authenticate_user!, unless: :token_auth_controller? + # Pundit: white-list approach. after_action :verify_authorized, except: :index, unless: :skip_pundit? after_action :verify_policy_scoped, only: :index, unless: :skip_pundit? @@ -24,4 +26,8 @@ def not_found(exception) def skip_pundit? devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/ end + + def token_auth_controller? + params[:controller].split('/').include? 'devise_token_auth' + end end diff --git a/app/controllers/auth/devise_token_auth/sessions_controller.rb b/app/controllers/auth/devise_token_auth/sessions_controller.rb new file mode 100644 index 0000000..6aaa1b8 --- /dev/null +++ b/app/controllers/auth/devise_token_auth/sessions_controller.rb @@ -0,0 +1,9 @@ +module Auth + module DeviseTokenAuth + class SessionsController < ::DeviseTokenAuth::SessionsController + # Prevent session parameter from being passed + # Unpermitted parameter: session + wrap_parameters format: [] + end + end +end diff --git a/config/routes.rb b/config/routes.rb index cc49ad9..24c1b33 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,5 @@ Rails.application.routes.draw do - mount_devise_token_auth_for 'User', at: 'auth' + mount_devise_token_auth_for 'User', at: 'auth', controllers: { + sessions: 'auth/devise_token_auth/sessions' + } end From 584322b4fe854c6450040274d503471abf58b191 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 9 Apr 2021 19:49:22 +0900 Subject: [PATCH 044/286] Move and update CORS configuration --- config/application.rb | 10 ---------- config/initializers/cors.rb | 25 ++++++++++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/config/application.rb b/config/application.rb index 01c48a8..5d3b657 100644 --- a/config/application.rb +++ b/config/application.rb @@ -36,15 +36,5 @@ class Application < Rails::Application # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. config.api_only = true - # TODO: Finally pick a domain. The following dangerous example will allow cross domain requests from any domain. Make sure to whitelist only the needed domains. - config.middleware.use Rack::Cors do - allow do - origins '*' - resource '*', - headers: :any, - expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'], - methods: [:get, :post, :options, :delete, :put] - end - end end end diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 3b1c1b5..6dbe531 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -5,12 +5,19 @@ # Read more: https://github.com/cyu/rack-cors -# Rails.application.config.middleware.insert_before 0, Rack::Cors do -# allow do -# origins 'example.com' -# -# resource '*', -# headers: :any, -# methods: [:get, :post, :put, :patch, :delete, :options, :head] -# end -# end +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins [ + %r(\Ahttps?:\/\/localhost:\d{4}) + # TODO: Add production domain and Netlify urls for staging/previews: + # %r(\Ahttps?:\/\/.+\.app-name\.com), + # %r(\Ahttps?:\/\/.*app-name\.netlify\.app), + ] + + resource '*', + headers: :any, + expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'], + methods: [:get, :post, :options, :delete, :put], + credentials: true + end +end From bcc6d679fd97c2bbdc36220522568a1a26e1ae01 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 9 Apr 2021 22:37:36 +0900 Subject: [PATCH 045/286] Add Netlify app domain to CORS settings --- config/initializers/cors.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 6dbe531..7fec9c7 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -8,16 +8,18 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins [ - %r(\Ahttps?:\/\/localhost:\d{4}) - # TODO: Add production domain and Netlify urls for staging/previews: + # Local server + %r{\Ahttps?://localhost:\d{4}}, + # Netlify app and preview deploys + %r{\Ahttps?://(.+--)?soccer-predictor.\.netlify\.app} + # TODO: Add production domain url: # %r(\Ahttps?:\/\/.+\.app-name\.com), - # %r(\Ahttps?:\/\/.*app-name\.netlify\.app), ] resource '*', - headers: :any, - expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'], - methods: [:get, :post, :options, :delete, :put], - credentials: true + headers: :any, + expose: %w[access-token expiry token-type uid client], + methods: %i[get post options delete put], + credentials: true end end From 23f6ae96f0e2b5fed83b26b484a833196ddd0064 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 10 Apr 2021 14:19:13 +0900 Subject: [PATCH 046/286] updated to nest matches inside of user --- config/routes.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index c712c33..977d87e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,17 +1,19 @@ Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'auth' namespace :v1 do - resources :competitions, only: [] do + resources :users, only: [:show] do resources :matches, only: [:index] + end + resources :competitions, only: [:show] do resources :leagues, only: [:index, :create] - resources :users, only: [:show] - resources :memberships, only: [:create] end resources :matches, only: [] do resources :predictions, only: [:create] end + resources :leagues, only: [:destroy] do + resources :memberships, only: [:create] + end resources :predictions, only: [:update] - resources :leagues, only: [:destroy] resources :memberships, only: [:destroy] end end From 6b732ea850f0fd6f087d12f3c354777585001fba Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 13 Apr 2021 20:37:00 +0900 Subject: [PATCH 047/286] updated routes for matches --- config/routes.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 977d87e..e8d076d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,6 @@ Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'auth' namespace :v1 do - resources :users, only: [:show] do - resources :matches, only: [:index] - end resources :competitions, only: [:show] do resources :leagues, only: [:index, :create] end @@ -13,7 +10,9 @@ resources :leagues, only: [:destroy] do resources :memberships, only: [:create] end + resources :matches, only: [:index] resources :predictions, only: [:update] resources :memberships, only: [:destroy] + resources :users, only: [:show] end end From 5d675748bc7bcf9183cf9f380326e91f3377c40e Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 13 Apr 2021 22:07:38 +0900 Subject: [PATCH 048/286] refactored routes to add shallow nesting --- config/routes.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index e8d076d..0fd36b3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,15 +4,12 @@ resources :competitions, only: [:show] do resources :leagues, only: [:index, :create] end - resources :matches, only: [] do - resources :predictions, only: [:create] + resources :matches, only: [:index], shallow: true do + resources :predictions, only: [:create, :update] end - resources :leagues, only: [:destroy] do - resources :memberships, only: [:create] + resources :leagues, only: [:destroy], shallow: true do + resources :memberships, only: [:create, :destroy] end - resources :matches, only: [:index] - resources :predictions, only: [:update] - resources :memberships, only: [:destroy] resources :users, only: [:show] end end From 654d4e10671d88fec026178ce255ac60fd600a7a Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 11:09:31 +0900 Subject: [PATCH 049/286] Fix missing round_id and null constraint on group_id --- db/migrate/20210415013709_add_round_to_matches.rb | 6 ++++++ db/schema.rb | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20210415013709_add_round_to_matches.rb diff --git a/db/migrate/20210415013709_add_round_to_matches.rb b/db/migrate/20210415013709_add_round_to_matches.rb new file mode 100644 index 0000000..367f8c1 --- /dev/null +++ b/db/migrate/20210415013709_add_round_to_matches.rb @@ -0,0 +1,6 @@ +class AddRoundToMatches < ActiveRecord::Migration[6.1] + def change + add_reference :matches, :round, null: true, foreign_key: true + change_column_null :matches, :group_id, true + end +end diff --git a/db/schema.rb b/db/schema.rb index 31de334..9257320 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: 2021_04_09_090454) do +ActiveRecord::Schema.define(version: 2021_04_15_013709) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -86,14 +86,16 @@ t.integer "team_home_score" t.integer "team_away_score" t.integer "status", default: 0 - t.bigint "group_id", null: false + t.bigint "group_id" t.bigint "team_away_id" t.bigint "team_home_id" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.bigint "next_match_id" + t.bigint "round_id" t.index ["group_id"], name: "index_matches_on_group_id" t.index ["next_match_id"], name: "index_matches_on_next_match_id" + t.index ["round_id"], name: "index_matches_on_round_id" t.index ["team_away_id"], name: "index_matches_on_team_away_id" t.index ["team_home_id"], name: "index_matches_on_team_home_id" end @@ -173,6 +175,7 @@ add_foreign_key "leagues", "users" add_foreign_key "matches", "groups" add_foreign_key "matches", "matches", column: "next_match_id" + add_foreign_key "matches", "rounds" add_foreign_key "matches", "teams", column: "team_away_id" add_foreign_key "matches", "teams", column: "team_home_id" add_foreign_key "memberships", "leagues" From 7489c25350b27e127f0a3521177021430887de4c Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 11:09:41 +0900 Subject: [PATCH 050/286] Implement instance methods --- app/models/match.rb | 28 ++++++++++++++++++++++++++++ app/models/prediction.rb | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/app/models/match.rb b/app/models/match.rb index bda999a..b004ecb 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -2,6 +2,7 @@ class Match < ApplicationRecord belongs_to :team_away, class_name: 'Team' belongs_to :team_home, class_name: 'Team' belongs_to :group, optional: true + belongs_to :round, optional: true belongs_to :next_match, class_name: 'Match', optional: true has_many :predictions, dependent: :destroy has_many :users, through: :predictions @@ -9,4 +10,31 @@ class Match < ApplicationRecord validates :status, presence: true enum status: %i[upcoming started finished] validates_uniqueness_of :kickoff_time, scope: [:team_home, :team_away] + validate :round_xor_group + + def draw? + team_home_score == team_away_score + end + + def round + super || group&.round + end + + def winner + return nil if draw? + + winner_side == 'home' ? team_home : team_away + end + + def winner_side + return 'draw' if draw? + + team_home_score > team_away_score ? 'home' : 'away' + end + + private + + def round_xor_group + errors.add(:round_id, 'either round_id or group_id needs a value') unless group_id.present? ^ round_id.present? + end end diff --git a/app/models/prediction.rb b/app/models/prediction.rb index 5725364..4ca3ad7 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -4,4 +4,8 @@ class Prediction < ApplicationRecord validates_uniqueness_of :user, scope: :match validates :choice, presence: true enum choice: %i[home away draw] + + def correct? + choice == match.winner_side + end end From 5a5a1af44a2052dc54949e346cebb712c3eb3892 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 11:31:58 +0900 Subject: [PATCH 051/286] Add guard clause in case match is not finished --- app/models/match.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/match.rb b/app/models/match.rb index b004ecb..67bbd30 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -13,6 +13,8 @@ class Match < ApplicationRecord validate :round_xor_group def draw? + return unless finished? + team_home_score == team_away_score end @@ -21,12 +23,14 @@ def round end def winner - return nil if draw? + return unless finished? + return if draw? winner_side == 'home' ? team_home : team_away end def winner_side + return unless finished? return 'draw' if draw? team_home_score > team_away_score ? 'home' : 'away' From 594aa81c6c59dff5328cfc3b11f154260e50d636 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 11:34:08 +0900 Subject: [PATCH 052/286] Add guard clause to pred if match not finished --- app/models/prediction.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/prediction.rb b/app/models/prediction.rb index 4ca3ad7..9e04d17 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -6,6 +6,8 @@ class Prediction < ApplicationRecord enum choice: %i[home away draw] def correct? + return unless match.finished? + choice == match.winner_side end end From 0a8da8a470a5238e6d0abec5f233d537e37fdba8 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 11:34:47 +0900 Subject: [PATCH 053/286] Remove spaces --- app/models/prediction.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/prediction.rb b/app/models/prediction.rb index 9e04d17..4396e08 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -7,7 +7,7 @@ class Prediction < ApplicationRecord def correct? return unless match.finished? - + choice == match.winner_side end end From 553acceaa1bd8c1a62fbc2f65bf2da1ba706906c Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 11:44:15 +0900 Subject: [PATCH 054/286] Create custom PATCH route --- config/routes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index b828096..fe66d16 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,8 @@ resources :leagues, only: [:index, :create] end resources :matches, only: [:index], shallow: true do - resources :predictions, only: [:create, :update] + resources :predictions, only: [:create] + patch :predictions, to: 'predictions#update', as: :prediction end resources :leagues, only: [:destroy], shallow: true do resources :memberships, only: [:create, :destroy] From e1bbe6598daa8e9b1af69395a4af13adac033a7d Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 12:47:08 +0900 Subject: [PATCH 055/286] Enable `reload!` method in Rails console --- .pryrc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .pryrc diff --git a/.pryrc b/.pryrc new file mode 100644 index 0000000..257566d --- /dev/null +++ b/.pryrc @@ -0,0 +1,3 @@ +# Enable the `reload!` method in the Rails console +require 'rails/console/app' +include Rails::ConsoleMethods From eeb2b44b7052f452d9886dcfe84ec56dd5d20a0c Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 15 Apr 2021 12:50:24 +0900 Subject: [PATCH 056/286] Fix error message --- app/models/match.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/match.rb b/app/models/match.rb index 67bbd30..2841b48 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -39,6 +39,6 @@ def winner_side private def round_xor_group - errors.add(:round_id, 'either round_id or group_id needs a value') unless group_id.present? ^ round_id.present? + errors.add(:round_id, 'or Group (not both) must exist') unless group_id.present? ^ round_id.present? end end From f8a9a0ee6767cc7edbd9a099b0c5336aca339d0f Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 16 Apr 2021 18:03:10 +0900 Subject: [PATCH 057/286] Add point calculations for affiliations --- app/models/affiliation.rb | 9 +++++++++ app/models/team.rb | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/app/models/affiliation.rb b/app/models/affiliation.rb index a3186bc..af51d7e 100644 --- a/app/models/affiliation.rb +++ b/app/models/affiliation.rb @@ -2,4 +2,13 @@ class Affiliation < ApplicationRecord belongs_to :team belongs_to :group validates_uniqueness_of :team, scope: :group + + def points + # Could probably be optimized into a single query + team.victories.count * 3 + team.draws.count + end + + def goal_diff + # TODO + end end diff --git a/app/models/team.rb b/app/models/team.rb index db15e05..6eb174f 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -9,4 +9,25 @@ def matches # teams can either be home or away Match.where('team_home_id = :id OR team_away_id = :id', id: id) end + + def victories + matches.finished.where(<<-SQL, id: id) + (team_home_id = :id AND team_home_score > team_away_score) OR + (team_away_id = :id AND team_home_score < team_away_score) + SQL + end + + def defeats + matches.finished.where(<<-SQL, id: id) + (team_home_id = :id AND team_home_score < team_away_score) OR + (team_away_id = :id AND team_home_score > team_away_score) + SQL + end + + def draws + matches.finished.where(<<-SQL, id: id) + (team_home_id = :id OR team_away_id = :id) AND + team_home_score = team_away_score + SQL + end end From da5340bf929ab540838702efaf2e9537b90c5f45 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 16 Apr 2021 18:26:17 +0900 Subject: [PATCH 058/286] Add goal_diff calculation --- app/models/affiliation.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/models/affiliation.rb b/app/models/affiliation.rb index af51d7e..5e5225d 100644 --- a/app/models/affiliation.rb +++ b/app/models/affiliation.rb @@ -4,11 +4,24 @@ class Affiliation < ApplicationRecord validates_uniqueness_of :team, scope: :group def points - # Could probably be optimized into a single query - team.victories.count * 3 + team.draws.count + victories.count * 3 + draws.count end def goal_diff - # TODO + victories.sum('ABS(team_home_score - team_away_score)') - defeats.sum('ABS(team_home_score - team_away_score)') + end + + private + + def victories + team.victories.where(group: group) + end + + def defeats + team.defeats.where(group: group) + end + + def draws + team.draws.where(group: group) end end From 28443bcf4e51bd98d3e20402766362eb009cdacf Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 16 Apr 2021 22:32:31 +0900 Subject: [PATCH 059/286] Move points calculation to Group --- app/models/affiliation.rb | 22 --------------------- app/models/application_record.rb | 8 ++++++++ app/models/group.rb | 33 ++++++++++++++++++++++++++++++++ app/models/team.rb | 1 + 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/app/models/affiliation.rb b/app/models/affiliation.rb index 5e5225d..a3186bc 100644 --- a/app/models/affiliation.rb +++ b/app/models/affiliation.rb @@ -2,26 +2,4 @@ class Affiliation < ApplicationRecord belongs_to :team belongs_to :group validates_uniqueness_of :team, scope: :group - - def points - victories.count * 3 + draws.count - end - - def goal_diff - victories.sum('ABS(team_home_score - team_away_score)') - defeats.sum('ABS(team_home_score - team_away_score)') - end - - private - - def victories - team.victories.where(group: group) - end - - def defeats - team.defeats.where(group: group) - end - - def draws - team.draws.where(group: group) - end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 10a4cba..46c0498 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,11 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + + def self.execute_sql(query, args = {}) + query = sanitize_sql([query, args]) + results = ActiveRecord::Base.connection.execute(query) + return unless results.present? + + results + end end diff --git a/app/models/group.rb b/app/models/group.rb index 3888ab3..996110d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -5,4 +5,37 @@ class Group < ApplicationRecord has_many :teams, through: :affiliations validates :name, presence: true validates_uniqueness_of :name, scope: :round + + RANKING_SQL = <<-SQL.freeze + WITH match_results AS ( + SELECT teams.id AS team_id, matches.id AS match_id, + CASE -- Calculating points earned per match + WHEN + (team_home_id = teams.id AND team_home_score > team_away_score) + OR (team_away_id = teams.id AND team_home_score < team_away_score) + THEN 3 + WHEN + (team_home_id = teams.id AND team_home_score < team_away_score) OR + (team_away_id = teams.id AND team_home_score > team_away_score) + THEN 0 + ELSE 1 + END AS result, + CASE -- Calculating goal difference + WHEN team_home_id = teams.id THEN team_home_score - team_away_score + ELSE team_away_score - team_home_score + END AS goal_diff + FROM teams + JOIN matches ON team_home_id = teams.id OR team_away_id = teams.id + WHERE status = 'finished' AND group_id = :group_id + ) + SELECT teams.*, SUM(result) AS points, SUM(goal_diff) AS total_goal_diff, COUNT(*) AS matches_count + FROM match_results + JOIN teams ON team_id = teams.id + GROUP BY teams.id + ORDER BY points DESC, total_goal_diff DESC + SQL + + def ranking + Group.execute_sql(RANKING_SQL, group_id: id) + end end diff --git a/app/models/team.rb b/app/models/team.rb index 6eb174f..2b2b997 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -10,6 +10,7 @@ def matches Match.where('team_home_id = :id OR team_away_id = :id', id: id) end + # These methods are not used for Group rankings, but could be used for individual team stats def victories matches.finished.where(<<-SQL, id: id) (team_home_id = :id AND team_home_score > team_away_score) OR From 5ee26d4445219b49876db9cb676f0261a3b7aff3 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 16 Apr 2021 22:32:50 +0900 Subject: [PATCH 060/286] Change status type from integer to string --- app/models/match.rb | 4 ++-- db/migrate/20210416124931_change_status_to_string.rb | 9 +++++++++ db/schema.rb | 4 ++-- db/seeds.rb | 11 +++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20210416124931_change_status_to_string.rb diff --git a/app/models/match.rb b/app/models/match.rb index 2841b48..b472c6b 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -8,9 +8,9 @@ class Match < ApplicationRecord has_many :users, through: :predictions validates :kickoff_time, presence: true validates :status, presence: true - enum status: %i[upcoming started finished] - validates_uniqueness_of :kickoff_time, scope: [:team_home, :team_away] + validates_uniqueness_of :kickoff_time, scope: %i[team_home team_away] validate :round_xor_group + enum status: { upcoming: 'upcoming', started: 'started', finished: 'finished' }, _default: :upcoming def draw? return unless finished? diff --git a/db/migrate/20210416124931_change_status_to_string.rb b/db/migrate/20210416124931_change_status_to_string.rb new file mode 100644 index 0000000..a253fdb --- /dev/null +++ b/db/migrate/20210416124931_change_status_to_string.rb @@ -0,0 +1,9 @@ +class ChangeStatusToString < ActiveRecord::Migration[6.1] + def up + change_column :matches, :status, :string, default: nil + end + + def down + change_column :matches, :status, :integer, using: 'status::integer', default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 9257320..89967fa 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: 2021_04_15_013709) do +ActiveRecord::Schema.define(version: 2021_04_16_124931) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -85,7 +85,7 @@ t.datetime "kickoff_time" t.integer "team_home_score" t.integer "team_away_score" - t.integer "status", default: 0 + t.string "status" t.bigint "group_id" t.bigint "team_away_id" t.bigint "team_home_id" diff --git a/db/seeds.rb b/db/seeds.rb index f26b6d9..628a7d6 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -98,3 +98,14 @@ end ScrapeMatchesService.new.call + +puts 'Assigning random scores to matches before June 22nd' +Match.all.each do |match| + if match.kickoff_time < Date.new(2021, 06, 22) + match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) + else + # Needed when migrating status enum from integer to string + match.update(status: :upcoming) + end +end +puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" \ No newline at end of file From e02470ce2d48def27508dde81f5b098c37dad90c Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 16 Apr 2021 22:44:11 +0900 Subject: [PATCH 061/286] Add leader and runner_up methods --- app/models/group.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index 996110d..0ec7dba 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -23,19 +23,27 @@ class Group < ApplicationRecord CASE -- Calculating goal difference WHEN team_home_id = teams.id THEN team_home_score - team_away_score ELSE team_away_score - team_home_score - END AS goal_diff + END AS match_goal_diff FROM teams JOIN matches ON team_home_id = teams.id OR team_away_id = teams.id WHERE status = 'finished' AND group_id = :group_id ) - SELECT teams.*, SUM(result) AS points, SUM(goal_diff) AS total_goal_diff, COUNT(*) AS matches_count + SELECT teams.*, SUM(result) AS points, SUM(match_goal_diff) AS goal_diff, COUNT(*) AS matches_count FROM match_results JOIN teams ON team_id = teams.id GROUP BY teams.id - ORDER BY points DESC, total_goal_diff DESC + ORDER BY points DESC, goal_diff DESC SQL def ranking Group.execute_sql(RANKING_SQL, group_id: id) end + + def leader + Team.find(ranking.first['id']) + end + + def runner_up + Team.find(ranking[1]['id']) + end end From ecbc963a23cad7c406dd273ac746ae4685db80d7 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 17 Apr 2021 01:05:00 +0900 Subject: [PATCH 062/286] Refactor random score assignment --- db/seeds.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 628a7d6..ffc4548 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -100,12 +100,9 @@ ScrapeMatchesService.new.call puts 'Assigning random scores to matches before June 22nd' -Match.all.each do |match| - if match.kickoff_time < Date.new(2021, 06, 22) - match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) - else - # Needed when migrating status enum from integer to string - match.update(status: :upcoming) - end +Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| + match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) end +# Needed when migrating status enum from integer to string: +Match.where('kickoff_time >= ?', Date.new(2021, 6, 22)).update(status: :upcoming) puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" \ No newline at end of file From a7c3cb32dcc65a3dda8a9bc9fad8c960317a9c37 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 18:59:11 +0900 Subject: [PATCH 063/286] created create and update action for predictions controller --- app/controllers/v1/matches_controller.rb | 2 ++ app/controllers/v1/predictions_controller.rb | 34 +++++++++++++++++++ app/views/v1/predictions/show.json.jbuilder | 1 + .../controllers/v1/matches_controller_test.rb | 7 ++++ .../v1/predictions_controller_test.rb | 7 ++++ 5 files changed, 51 insertions(+) create mode 100644 app/controllers/v1/matches_controller.rb create mode 100644 app/controllers/v1/predictions_controller.rb create mode 100644 app/views/v1/predictions/show.json.jbuilder create mode 100644 test/controllers/v1/matches_controller_test.rb create mode 100644 test/controllers/v1/predictions_controller_test.rb diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb new file mode 100644 index 0000000..cde16b9 --- /dev/null +++ b/app/controllers/v1/matches_controller.rb @@ -0,0 +1,2 @@ +class V1::MatchesController < ApplicationController +end diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb new file mode 100644 index 0000000..6110482 --- /dev/null +++ b/app/controllers/v1/predictions_controller.rb @@ -0,0 +1,34 @@ +class V1::PredictionsController < ApplicationController + + def create + @match = Match.find(params[:match_id]) + @predition = Predicition.new(prediction_params) + @prediction.match = @match + @prediction.user = current_user + if @prediction.save + render :show, status: :created + else + render_error + end + end + + def update + @predition = Predicition.find_by(user: current_user, match: params[:match_id]) + if @prediction.update(prediction_params) + render :show + else + render_error + end + end + + private + + def prediction_params + params.require(:prediction).permit(:choice) + end + + def render_error + render json: { errors: @prediction.errors.full_messages }, + status: :unprocessable_entity + end +end diff --git a/app/views/v1/predictions/show.json.jbuilder b/app/views/v1/predictions/show.json.jbuilder new file mode 100644 index 0000000..732ef6b --- /dev/null +++ b/app/views/v1/predictions/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @prediction, :id, :choice, :match_id, :user_id diff --git a/test/controllers/v1/matches_controller_test.rb b/test/controllers/v1/matches_controller_test.rb new file mode 100644 index 0000000..95ecb63 --- /dev/null +++ b/test/controllers/v1/matches_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class V1::MatchesControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/v1/predictions_controller_test.rb b/test/controllers/v1/predictions_controller_test.rb new file mode 100644 index 0000000..174393c --- /dev/null +++ b/test/controllers/v1/predictions_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class V1::PredictionsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From ca3e2ab656a19fbf6bbc49bc016a38c0412bc353 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:16:48 +0900 Subject: [PATCH 064/286] matches index --- app/controllers/application_controller.rb | 1 - app/controllers/v1/matches_controller.rb | 7 +++++++ app/views/v1/matches/index.json.jbuilder | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 app/views/v1/matches/index.json.jbuilder diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2734f4b..29e78bf 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,7 +4,6 @@ class ApplicationController < ActionController::API before_action :authenticate_user!, unless: :token_auth_controller? - # Pundit: white-list approach. after_action :verify_authorized, except: :index, unless: :skip_pundit? after_action :verify_policy_scoped, only: :index, unless: :skip_pundit? diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index cde16b9..38a45e9 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -1,2 +1,9 @@ class V1::MatchesController < ApplicationController + + # /matches?competition_id=:id&user_id=:id + def index + @user = User.find_by(id: params[:user_id]) || current_user + # @competition = Competition.find_by(id: params[:competition_id]) + @matches = policy_scope(Match) + end end diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder new file mode 100644 index 0000000..cc76825 --- /dev/null +++ b/app/views/v1/matches/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @matches do |match| + json.extract! match, :id, :kickoff_time, :team_home_score, :team_away_score, :status, :group_id, :team_away_id, :team_home_id, :next_match_id, :round_id +end From 54445a610c90df30e192bc07db758787b0e217dd Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:17:14 +0900 Subject: [PATCH 065/286] generated policies --- app/policies/match_policy.rb | 7 +++++++ app/policies/prediction_policy.rb | 7 +++++++ test/policies/match_policy_test.rb | 18 ++++++++++++++++++ test/policies/prediction_policy_test.rb | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 app/policies/match_policy.rb create mode 100644 app/policies/prediction_policy.rb create mode 100644 test/policies/match_policy_test.rb create mode 100644 test/policies/prediction_policy_test.rb diff --git a/app/policies/match_policy.rb b/app/policies/match_policy.rb new file mode 100644 index 0000000..fad5381 --- /dev/null +++ b/app/policies/match_policy.rb @@ -0,0 +1,7 @@ +class MatchPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end +end diff --git a/app/policies/prediction_policy.rb b/app/policies/prediction_policy.rb new file mode 100644 index 0000000..1a31cc8 --- /dev/null +++ b/app/policies/prediction_policy.rb @@ -0,0 +1,7 @@ +class PredictionPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end +end diff --git a/test/policies/match_policy_test.rb b/test/policies/match_policy_test.rb new file mode 100644 index 0000000..e449d02 --- /dev/null +++ b/test/policies/match_policy_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class MatchPolicyTest < ActiveSupport::TestCase + def test_scope + end + + def test_show + end + + def test_create + end + + def test_update + end + + def test_destroy + end +end diff --git a/test/policies/prediction_policy_test.rb b/test/policies/prediction_policy_test.rb new file mode 100644 index 0000000..bebc40f --- /dev/null +++ b/test/policies/prediction_policy_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class PredictionPolicyTest < ActiveSupport::TestCase + def test_scope + end + + def test_show + end + + def test_create + end + + def test_update + end + + def test_destroy + end +end From dc64c0e9060d7eb4564a4521559d83232f36f32c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:20:37 +0900 Subject: [PATCH 066/286] updated prediction policy --- app/controllers/v1/predictions_controller.rb | 2 ++ app/policies/prediction_policy.rb | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index 6110482..47dc8af 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -5,6 +5,7 @@ def create @predition = Predicition.new(prediction_params) @prediction.match = @match @prediction.user = current_user + authorize @prediction if @prediction.save render :show, status: :created else @@ -14,6 +15,7 @@ def create def update @predition = Predicition.find_by(user: current_user, match: params[:match_id]) + authorize @prediction if @prediction.update(prediction_params) render :show else diff --git a/app/policies/prediction_policy.rb b/app/policies/prediction_policy.rb index 1a31cc8..af256ef 100644 --- a/app/policies/prediction_policy.rb +++ b/app/policies/prediction_policy.rb @@ -4,4 +4,18 @@ def resolve scope.all end end + + def create? + true + end + + def update? + owner_or_admin? + end + + private + + def owner_or_admin? + record.user == user || user.admin? + end end From 14fc09f46ea7f83778f3348c23ba84a918b44d43 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:24:09 +0900 Subject: [PATCH 067/286] fixed typo --- app/controllers/v1/predictions_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index 47dc8af..d28a73c 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -2,7 +2,7 @@ class V1::PredictionsController < ApplicationController def create @match = Match.find(params[:match_id]) - @predition = Predicition.new(prediction_params) + @predition = Prediction.new(prediction_params) @prediction.match = @match @prediction.user = current_user authorize @prediction @@ -14,7 +14,7 @@ def create end def update - @predition = Predicition.find_by(user: current_user, match: params[:match_id]) + @predition = Prediction.find_by(user: current_user, match: params[:match_id]) authorize @prediction if @prediction.update(prediction_params) render :show From e2e3c68df62aaee96fd8e6c09fff6b6b125bb663 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:28:48 +0900 Subject: [PATCH 068/286] fixed typo --- app/controllers/v1/predictions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index d28a73c..d144f24 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -2,7 +2,7 @@ class V1::PredictionsController < ApplicationController def create @match = Match.find(params[:match_id]) - @predition = Prediction.new(prediction_params) + @prediction = Prediction.new(prediction_params) @prediction.match = @match @prediction.user = current_user authorize @prediction From 604dc1610b23640d3625d062239e32d3607ed320 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:31:44 +0900 Subject: [PATCH 069/286] fixed typo --- app/controllers/v1/predictions_controller.rb | 2 +- app/views/v1/predictions/show.json.jbuilder | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index d144f24..1fe6a9d 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -14,7 +14,7 @@ def create end def update - @predition = Prediction.find_by(user: current_user, match: params[:match_id]) + @prediction = Prediction.find_by(user: current_user, match: params[:match_id]) authorize @prediction if @prediction.update(prediction_params) render :show diff --git a/app/views/v1/predictions/show.json.jbuilder b/app/views/v1/predictions/show.json.jbuilder index 732ef6b..908d63b 100644 --- a/app/views/v1/predictions/show.json.jbuilder +++ b/app/views/v1/predictions/show.json.jbuilder @@ -1 +1,2 @@ json.extract! @prediction, :id, :choice, :match_id, :user_id + From d852169ef15cbdfba16c4b4f078b77f55a49105c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 18 Apr 2021 20:45:18 +0900 Subject: [PATCH 070/286] uncommented jbuilder --- Gemfile | 2 +- Gemfile.lock | 3 +++ config/routes.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 5188053..f3b525b 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem 'pg', '~> 1.1' # Use Puma as the app server gem 'puma', '~> 5.0' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -# gem 'jbuilder', '~> 2.7' +gem 'jbuilder', '~> 2.7' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use Active Model has_secure_password diff --git a/Gemfile.lock b/Gemfile.lock index 6aa1306..b4520d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -111,6 +111,8 @@ GEM domain_name (~> 0.5) i18n (1.8.10) concurrent-ruby (~> 1.0) + jbuilder (2.11.2) + activesupport (>= 5.0.0) json (2.5.1) listen (3.5.1) rb-fsevent (~> 0.10, >= 0.10.3) @@ -269,6 +271,7 @@ DEPENDENCIES devise_token_auth! dotenv-rails faker + jbuilder (~> 2.7) listen (~> 3.3) mailcatcher omniauth diff --git a/config/routes.rb b/config/routes.rb index fe66d16..9b4228f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,7 @@ mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'auth/devise_token_auth/sessions' } - namespace :v1 do + namespace :v1, defaults: { format: :json } do resources :competitions, only: [:show] do resources :leagues, only: [:index, :create] end From 3d1db44a20f4a9976f7c755b61079dbc166f75cb Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 20:03:34 +0900 Subject: [PATCH 071/286] generated migration --- ...0210421110232_change_leagues_to_leaderboards.rb | 5 +++++ db/schema.rb | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20210421110232_change_leagues_to_leaderboards.rb diff --git a/db/migrate/20210421110232_change_leagues_to_leaderboards.rb b/db/migrate/20210421110232_change_leagues_to_leaderboards.rb new file mode 100644 index 0000000..3d27b22 --- /dev/null +++ b/db/migrate/20210421110232_change_leagues_to_leaderboards.rb @@ -0,0 +1,5 @@ +class ChangeLeaguesToLeaderboards < ActiveRecord::Migration[6.1] + def change + rename_table :leagues, :leaderboards + end +end diff --git a/db/schema.rb b/db/schema.rb index 89967fa..22b92a9 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: 2021_04_16_124931) do +ActiveRecord::Schema.define(version: 2021_04_21_110232) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -70,15 +70,15 @@ t.index ["round_id"], name: "index_groups_on_round_id" end - create_table "leagues", force: :cascade do |t| + create_table "leaderboards", force: :cascade do |t| t.string "name" t.string "password" t.bigint "user_id", null: false t.bigint "competition_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false - t.index ["competition_id"], name: "index_leagues_on_competition_id" - t.index ["user_id"], name: "index_leagues_on_user_id" + t.index ["competition_id"], name: "index_leaderboards_on_competition_id" + t.index ["user_id"], name: "index_leaderboards_on_user_id" end create_table "matches", force: :cascade do |t| @@ -171,14 +171,14 @@ add_foreign_key "affiliations", "teams" add_foreign_key "competitions", "rounds", column: "current_round_id" add_foreign_key "groups", "rounds" - add_foreign_key "leagues", "competitions" - add_foreign_key "leagues", "users" + add_foreign_key "leaderboards", "competitions" + add_foreign_key "leaderboards", "users" add_foreign_key "matches", "groups" add_foreign_key "matches", "matches", column: "next_match_id" add_foreign_key "matches", "rounds" add_foreign_key "matches", "teams", column: "team_away_id" add_foreign_key "matches", "teams", column: "team_home_id" - add_foreign_key "memberships", "leagues" + add_foreign_key "memberships", "leaderboards", column: "league_id" add_foreign_key "memberships", "users" add_foreign_key "predictions", "matches" add_foreign_key "predictions", "users" From a4b904642b9fad4486c796a2e3b97af04d0da77d Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 20:20:04 +0900 Subject: [PATCH 072/286] renamed league to leaderboard --- app/models/competition.rb | 5 +---- app/models/{league.rb => leaderboard.rb} | 2 +- app/models/membership.rb | 4 ++-- app/models/user.rb | 9 +++++---- config/routes.rb | 4 ++-- .../20210421110232_change_leagues_to_leaderboards.rb | 2 ++ db/schema.rb | 6 +++--- db/seeds.rb | 12 ++++++------ 8 files changed, 22 insertions(+), 22 deletions(-) rename app/models/{league.rb => leaderboard.rb} (86%) diff --git a/app/models/competition.rb b/app/models/competition.rb index 53c3b62..14749ce 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -4,10 +4,7 @@ class Competition < ApplicationRecord has_many :groups, through: :rounds has_many :affiliations, through: :groups has_many :teams, through: :affiliations - has_many :leagues, dependent: :destroy - # TODO: Technically a User doesn't need to join a league to be involved in a competition - # has_many :memberships, through: :leagues - # has_many :users, through: :memberships + has_many :leaderboards, dependent: :destroy validates :name, presence: true validates :start_date, presence: true validates :end_date, presence: true diff --git a/app/models/league.rb b/app/models/leaderboard.rb similarity index 86% rename from app/models/league.rb rename to app/models/leaderboard.rb index d22c6cc..2385747 100644 --- a/app/models/league.rb +++ b/app/models/leaderboard.rb @@ -1,4 +1,4 @@ -class League < ApplicationRecord +class Leaderboard < ApplicationRecord belongs_to :user belongs_to :competition has_many :memberships, dependent: :destroy diff --git a/app/models/membership.rb b/app/models/membership.rb index a04f362..312f476 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -1,5 +1,5 @@ class Membership < ApplicationRecord - belongs_to :league + belongs_to :leaderboard belongs_to :user - validates_uniqueness_of :user, scope: :league + validates_uniqueness_of :user, scope: :leaderboard end diff --git a/app/models/user.rb b/app/models/user.rb index c9c0074..fe67160 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,12 +5,13 @@ class User < ApplicationRecord :omniauthable # :confirmable include DeviseTokenAuth::Concerns::User has_many :memberships, dependent: :destroy - has_many :competitions, through: :leagues + # TODO: Fix this + # has_many :competitions, through: :leaderboards has_many :predictions, dependent: :destroy has_many :matches, through: :predictions - def leagues - # this includes creator or league and members - League.includes(:memberships).where(memberships: { user: self }).or(League.where(user: self)) + def leaderboards + # this includes creator or leaderboard and members + Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) end end diff --git a/config/routes.rb b/config/routes.rb index fe66d16..cd1e7d0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,13 +4,13 @@ } namespace :v1 do resources :competitions, only: [:show] do - resources :leagues, only: [:index, :create] + resources :leaderboards, only: [:index, :create] end resources :matches, only: [:index], shallow: true do resources :predictions, only: [:create] patch :predictions, to: 'predictions#update', as: :prediction end - resources :leagues, only: [:destroy], shallow: true do + resources :leaderboards, only: [:destroy], shallow: true do resources :memberships, only: [:create, :destroy] end resources :users, only: [:show] diff --git a/db/migrate/20210421110232_change_leagues_to_leaderboards.rb b/db/migrate/20210421110232_change_leagues_to_leaderboards.rb index 3d27b22..072d94a 100644 --- a/db/migrate/20210421110232_change_leagues_to_leaderboards.rb +++ b/db/migrate/20210421110232_change_leagues_to_leaderboards.rb @@ -1,5 +1,7 @@ class ChangeLeaguesToLeaderboards < ActiveRecord::Migration[6.1] def change + remove_reference :memberships, :league, index: true, foreign_key: true rename_table :leagues, :leaderboards + add_reference :memberships, :leaderboard, foreign_key: true end end diff --git a/db/schema.rb b/db/schema.rb index 22b92a9..53a4135 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -101,11 +101,11 @@ end create_table "memberships", force: :cascade do |t| - t.bigint "league_id", null: false t.bigint "user_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false - t.index ["league_id"], name: "index_memberships_on_league_id" + t.bigint "leaderboard_id" + t.index ["leaderboard_id"], name: "index_memberships_on_leaderboard_id" t.index ["user_id"], name: "index_memberships_on_user_id" end @@ -178,7 +178,7 @@ add_foreign_key "matches", "rounds" add_foreign_key "matches", "teams", column: "team_away_id" add_foreign_key "matches", "teams", column: "team_home_id" - add_foreign_key "memberships", "leaderboards", column: "league_id" + add_foreign_key "memberships", "leaderboards" add_foreign_key "memberships", "users" add_foreign_key "predictions", "matches" add_foreign_key "predictions", "users" diff --git a/db/seeds.rb b/db/seeds.rb index ffc4548..e45edd1 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -85,16 +85,16 @@ puts team.badge.attached? ? 'Success' : 'Failed' end -puts 'Creating a test league w/ James as creator' -league = League.find_or_create_by!( - name: 'Admin League', +puts 'Creating a test leaderboard w/ James as creator' +leaderboard = Leaderboard.find_or_create_by!( + name: 'Admin Leaderboard', competition: euros, user: james ) -puts 'Adding Trouni and Doug to the league' +puts 'Adding Trouni and Doug to the leaderboard' [trouni, doug].each do |user| - Membership.find_or_create_by!(league: league, user: user) + Membership.find_or_create_by!(leaderboard: leaderboard, user: user) end ScrapeMatchesService.new.call @@ -105,4 +105,4 @@ end # Needed when migrating status enum from integer to string: Match.where('kickoff_time >= ?', Date.new(2021, 6, 22)).update(status: :upcoming) -puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" \ No newline at end of file +puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" From 1e7dd26610c3f5b801ba7e37786ff8b0c6bb28e9 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 20:34:51 +0900 Subject: [PATCH 073/286] Added procfile to auto migrate --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..8d36be1 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +release: rake db:migrate From ed5191b07291ead773e2b07a0b5ffc4d8884bc04 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 20:46:43 +0900 Subject: [PATCH 074/286] avoiding refresh of token in development --- config/initializers/devise_token_auth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/devise_token_auth.rb b/config/initializers/devise_token_auth.rb index 7e38131..9d5ea52 100644 --- a/config/initializers/devise_token_auth.rb +++ b/config/initializers/devise_token_auth.rb @@ -5,7 +5,7 @@ # client is responsible for keeping track of the changing tokens. Change # this to false to prevent the Authorization header from changing after # each request. - # config.change_headers_on_each_request = true + config.change_headers_on_each_request = !Rails.env.development? # By default, users will need to re-authenticate after 2 weeks. This setting # determines how long tokens will remain valid after they are issued. From f34def623cb8bbe65478ad84092f089969738b0a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 21:42:39 +0900 Subject: [PATCH 075/286] doing it the hard way first. --- app/views/v1/matches/index.json.jbuilder | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index cc76825..e40e661 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,3 +1,12 @@ json.array! @matches do |match| json.extract! match, :id, :kickoff_time, :team_home_score, :team_away_score, :status, :group_id, :team_away_id, :team_home_id, :next_match_id, :round_id + prediction = match.predictions.find_by(user: @user) + if prediction + json.prediction do + json.id prediction.id + json.choice prediction.choice + json.user_id prediction.user.id + json.match_id prediction.match.id + end + end end From 7698a86646822c7d3fb8d5cb9742a5c9089b9da3 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 22:06:11 +0900 Subject: [PATCH 076/286] updated controller so you can scope by competition --- app/controllers/v1/matches_controller.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 38a45e9..5428ff4 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -3,7 +3,12 @@ class V1::MatchesController < ApplicationController # /matches?competition_id=:id&user_id=:id def index @user = User.find_by(id: params[:user_id]) || current_user - # @competition = Competition.find_by(id: params[:competition_id]) - @matches = policy_scope(Match) + @competition = Competition.find_by(id: params[:competition_id]) + @matches = + if @competition + policy_scope(Match).where(group: @competition.groups) + else + policy_scope(Match) + end end end From fc73728e8c7cb069ddc09512fb0950451baa329e Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 22:25:40 +0900 Subject: [PATCH 077/286] added new policy to handle user matches --- app/controllers/v1/matches_controller.rb | 2 +- app/policies/user/match_policy.rb | 7 +++++++ config/initializers/devise_token_auth.rb | 2 +- test/policies/user/match_policy_test.rb | 18 ++++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 app/policies/user/match_policy.rb create mode 100644 test/policies/user/match_policy_test.rb diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 5428ff4..54b3606 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -8,7 +8,7 @@ def index if @competition policy_scope(Match).where(group: @competition.groups) else - policy_scope(Match) + policy_scope([:user, Match]) end end end diff --git a/app/policies/user/match_policy.rb b/app/policies/user/match_policy.rb new file mode 100644 index 0000000..5bc9d59 --- /dev/null +++ b/app/policies/user/match_policy.rb @@ -0,0 +1,7 @@ +class User::MatchPolicy < ApplicationPolicy + class Scope < Scope + def resolve + user.matches + end + end +end diff --git a/config/initializers/devise_token_auth.rb b/config/initializers/devise_token_auth.rb index 7e38131..9d5ea52 100644 --- a/config/initializers/devise_token_auth.rb +++ b/config/initializers/devise_token_auth.rb @@ -5,7 +5,7 @@ # client is responsible for keeping track of the changing tokens. Change # this to false to prevent the Authorization header from changing after # each request. - # config.change_headers_on_each_request = true + config.change_headers_on_each_request = !Rails.env.development? # By default, users will need to re-authenticate after 2 weeks. This setting # determines how long tokens will remain valid after they are issued. diff --git a/test/policies/user/match_policy_test.rb b/test/policies/user/match_policy_test.rb new file mode 100644 index 0000000..8c6eb3d --- /dev/null +++ b/test/policies/user/match_policy_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class User::MatchPolicyTest < ActiveSupport::TestCase + def test_scope + end + + def test_show + end + + def test_create + end + + def test_update + end + + def test_destroy + end +end From 6c345918e327a6825ef1386df4ffaa874d59aab2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 21 Apr 2021 22:26:44 +0900 Subject: [PATCH 078/286] ordering matches by kickoff time --- app/controllers/v1/matches_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 54b3606..6e92d8e 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -6,9 +6,9 @@ def index @competition = Competition.find_by(id: params[:competition_id]) @matches = if @competition - policy_scope(Match).where(group: @competition.groups) + policy_scope(Match).where(group: @competition.groups).order(:kickoff_time) else - policy_scope([:user, Match]) + policy_scope([:user, Match]).order(:kickoff_time) end end end From a7c1b404c577072d0261d391864389b9f6b45d6a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 18:18:58 +0900 Subject: [PATCH 079/286] generated controller --- app/controllers/application_controller.rb | 5 +++++ app/controllers/v1/leaderboards_controller.rb | 22 +++++++++++++++++++ app/controllers/v1/predictions_controller.rb | 9 ++------ .../v1/leaderboards_controller_test.rb | 7 ++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 app/controllers/v1/leaderboards_controller.rb create mode 100644 test/controllers/v1/leaderboards_controller_test.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 29e78bf..79f989c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,4 +29,9 @@ def skip_pundit? def token_auth_controller? params[:controller].split('/').include? 'devise_token_auth' end + + def render_error(resource) + render json: { errors: resource.errors.full_messages }, + status: :unprocessable_entity + end end diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb new file mode 100644 index 0000000..861d86c --- /dev/null +++ b/app/controllers/v1/leaderboards_controller.rb @@ -0,0 +1,22 @@ +class V1::LeaderboardsController < ApplicationController + + def create + @match = Match.find(params[:match_id]) + @prediction = Prediction.new(prediction_params) + @prediction.match = @match + @prediction.user = current_user + authorize @prediction + if @leaderboard.save + render :show, status: :created + else + render_error(@leaderboard) + end + end + + private + + def leaderboard_params + params.require(:leaderboard).permit(:) + end + +end diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index 1fe6a9d..f51c480 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -9,7 +9,7 @@ def create if @prediction.save render :show, status: :created else - render_error + render_error(@prediction) end end @@ -19,7 +19,7 @@ def update if @prediction.update(prediction_params) render :show else - render_error + render_error(@prediction) end end @@ -28,9 +28,4 @@ def update def prediction_params params.require(:prediction).permit(:choice) end - - def render_error - render json: { errors: @prediction.errors.full_messages }, - status: :unprocessable_entity - end end diff --git a/test/controllers/v1/leaderboards_controller_test.rb b/test/controllers/v1/leaderboards_controller_test.rb new file mode 100644 index 0000000..89fea76 --- /dev/null +++ b/test/controllers/v1/leaderboards_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class V1::LeaderboardsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From 47087a3b1e648f8604a35f1c68c4f2b989c70251 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 18:23:01 +0900 Subject: [PATCH 080/286] completed create action --- app/controllers/v1/leaderboards_controller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 861d86c..ce6367d 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -1,10 +1,10 @@ class V1::LeaderboardsController < ApplicationController def create - @match = Match.find(params[:match_id]) - @prediction = Prediction.new(prediction_params) - @prediction.match = @match - @prediction.user = current_user + @competition = Competition.find(params[:competition_id]) + @leaderboard = Leaderboard.new(leaderboard_params) + @leaderboard.competition = @competition + @leaderboard.user = current_user authorize @prediction if @leaderboard.save render :show, status: :created @@ -16,7 +16,7 @@ def create private def leaderboard_params - params.require(:leaderboard).permit(:) + params.require(:leaderboard).permit(:name) end end From f16ea204885df643e33c8e3eef1d81710f6d71c3 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 18:23:54 +0900 Subject: [PATCH 081/286] created leaderboard policy --- app/policies/leaderboard_policy.rb | 11 +++++++++++ test/policies/leaderboard_policy_test.rb | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 app/policies/leaderboard_policy.rb create mode 100644 test/policies/leaderboard_policy_test.rb diff --git a/app/policies/leaderboard_policy.rb b/app/policies/leaderboard_policy.rb new file mode 100644 index 0000000..e7698cb --- /dev/null +++ b/app/policies/leaderboard_policy.rb @@ -0,0 +1,11 @@ +class LeaderboardPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def create? + true + end +end diff --git a/test/policies/leaderboard_policy_test.rb b/test/policies/leaderboard_policy_test.rb new file mode 100644 index 0000000..49b79ff --- /dev/null +++ b/test/policies/leaderboard_policy_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class LeaderboardPolicyTest < ActiveSupport::TestCase + def test_scope + end + + def test_show + end + + def test_create + end + + def test_update + end + + def test_destroy + end +end From fadf7241bfc23a77d57bc15e4443466621b67326 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 18:57:58 +0900 Subject: [PATCH 082/286] added secure token to password --- app/models/leaderboard.rb | 1 + config/initializers/active_record/secure_token.rb | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 config/initializers/active_record/secure_token.rb diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 2385747..843bca8 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -6,4 +6,5 @@ class Leaderboard < ApplicationRecord validates :name, presence: true # TODO: Think about how users join groups # validates :password, presence: true + has_secure_token :password end diff --git a/config/initializers/active_record/secure_token.rb b/config/initializers/active_record/secure_token.rb new file mode 100644 index 0000000..cbcde13 --- /dev/null +++ b/config/initializers/active_record/secure_token.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module ActiveRecord + module SecureToken + MINIMUM_TOKEN_LENGTH = 10 + end +end From 4a69eb4102351667ef58a51df014fc09bc83eac1 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 19:07:49 +0900 Subject: [PATCH 083/286] added view and fixed typo --- app/controllers/v1/leaderboards_controller.rb | 2 +- app/views/v1/leaderboards/show.json.jbuilder | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 app/views/v1/leaderboards/show.json.jbuilder diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index ce6367d..4d62d18 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -5,7 +5,7 @@ def create @leaderboard = Leaderboard.new(leaderboard_params) @leaderboard.competition = @competition @leaderboard.user = current_user - authorize @prediction + authorize @leaderboard if @leaderboard.save render :show, status: :created else diff --git a/app/views/v1/leaderboards/show.json.jbuilder b/app/views/v1/leaderboards/show.json.jbuilder new file mode 100644 index 0000000..849f082 --- /dev/null +++ b/app/views/v1/leaderboards/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @leaderboard, :id, :name, :password, :user_id, :competition_id From 08e7dcc8a51802af9442403eb4dbf4b50b76b972 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 19:12:47 +0900 Subject: [PATCH 084/286] added unique constraint on password --- db/migrate/20210424101146_add_uniqueness_to_leagues.rb | 5 +++++ db/schema.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210424101146_add_uniqueness_to_leagues.rb diff --git a/db/migrate/20210424101146_add_uniqueness_to_leagues.rb b/db/migrate/20210424101146_add_uniqueness_to_leagues.rb new file mode 100644 index 0000000..6cc452b --- /dev/null +++ b/db/migrate/20210424101146_add_uniqueness_to_leagues.rb @@ -0,0 +1,5 @@ +class AddUniquenessToLeagues < ActiveRecord::Migration[6.1] + def change + change_column :leaderboards, :password, :string, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 53a4135..8e7302a 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: 2021_04_21_110232) do +ActiveRecord::Schema.define(version: 2021_04_24_101146) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From 4af706c3743117c57e4b1b01342a074ea935c7ec Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 19:41:51 +0900 Subject: [PATCH 085/286] added create method --- app/controllers/v1/memberships_controller.rb | 14 ++++++++++++++ config/routes.rb | 1 + test/controllers/v1/memberships_controller_test.rb | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100644 app/controllers/v1/memberships_controller.rb create mode 100644 test/controllers/v1/memberships_controller_test.rb diff --git a/app/controllers/v1/memberships_controller.rb b/app/controllers/v1/memberships_controller.rb new file mode 100644 index 0000000..2410952 --- /dev/null +++ b/app/controllers/v1/memberships_controller.rb @@ -0,0 +1,14 @@ +class V1::MembershipsController < ApplicationController + def create + @leaderboard = Leaderboard.find_by(password: params[:password]) + @membership = Membership.new + @membership.leaderboard = @leaderboard + @membership.user = current_user + authorize @membership + if @membership.save + render :show, status: :created + else + render_error(@membership) + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 7c5ba0d..18822ba 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,5 +14,6 @@ resources :memberships, only: [:create, :destroy] end resources :users, only: [:show] + get 'join/:password', to: 'memberships#create' end end diff --git a/test/controllers/v1/memberships_controller_test.rb b/test/controllers/v1/memberships_controller_test.rb new file mode 100644 index 0000000..a41b5d8 --- /dev/null +++ b/test/controllers/v1/memberships_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class V1::MembershipsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From 5f1525e9e65eee6c14278287cf75767363b88184 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 19:42:07 +0900 Subject: [PATCH 086/286] created policy --- app/policies/membership_policy.rb | 7 +++++++ test/policies/membership_policy_test.rb | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 app/policies/membership_policy.rb create mode 100644 test/policies/membership_policy_test.rb diff --git a/app/policies/membership_policy.rb b/app/policies/membership_policy.rb new file mode 100644 index 0000000..bb87df7 --- /dev/null +++ b/app/policies/membership_policy.rb @@ -0,0 +1,7 @@ +class MembershipPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end +end diff --git a/test/policies/membership_policy_test.rb b/test/policies/membership_policy_test.rb new file mode 100644 index 0000000..ed1600f --- /dev/null +++ b/test/policies/membership_policy_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class MembershipPolicyTest < ActiveSupport::TestCase + def test_scope + end + + def test_show + end + + def test_create + end + + def test_update + end + + def test_destroy + end +end From b844320a14d2a0d7f0b5c9725b828b202489e5a0 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 19:43:11 +0900 Subject: [PATCH 087/286] updated policy --- app/policies/membership_policy.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/policies/membership_policy.rb b/app/policies/membership_policy.rb index bb87df7..d561680 100644 --- a/app/policies/membership_policy.rb +++ b/app/policies/membership_policy.rb @@ -4,4 +4,8 @@ def resolve scope.all end end + + def create? + record.leaderboard.user != user + end end From 8ce287b1e9ae3333cf5cb0d309a3166051649a1c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 24 Apr 2021 19:46:11 +0900 Subject: [PATCH 088/286] created membershiop json view --- app/views/v1/memberships/show.json.jbuilder | 1 + app/views/v1/predictions/show.json.jbuilder | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 app/views/v1/memberships/show.json.jbuilder diff --git a/app/views/v1/memberships/show.json.jbuilder b/app/views/v1/memberships/show.json.jbuilder new file mode 100644 index 0000000..73f7b56 --- /dev/null +++ b/app/views/v1/memberships/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @membership, :id, :leaderboard_id, :user_id diff --git a/app/views/v1/predictions/show.json.jbuilder b/app/views/v1/predictions/show.json.jbuilder index 908d63b..732ef6b 100644 --- a/app/views/v1/predictions/show.json.jbuilder +++ b/app/views/v1/predictions/show.json.jbuilder @@ -1,2 +1 @@ json.extract! @prediction, :id, :choice, :match_id, :user_id - From 03028314c94a405a204d484f2c540309022d181b Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 20:48:25 +0900 Subject: [PATCH 089/286] Add teams and refactor view --- app/controllers/v1/matches_controller.rb | 8 ++++++-- app/views/v1/matches/index.json.jbuilder | 12 ++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 6e92d8e..964bb7b 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -1,12 +1,16 @@ class V1::MatchesController < ApplicationController - # /matches?competition_id=:id&user_id=:id def index @user = User.find_by(id: params[:user_id]) || current_user @competition = Competition.find_by(id: params[:competition_id]) @matches = if @competition - policy_scope(Match).where(group: @competition.groups).order(:kickoff_time) + policy_scope(Match).joins(:team_away, :team_home) + .left_outer_joins(:predictions) + .includes(:team_away, :team_home, :predictions) + .where(group: @competition.groups) + .where('predictions.user_id = ? OR predictions.user_id IS NULL', @user.id) + .order(:kickoff_time) else policy_scope([:user, Match]).order(:kickoff_time) end diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index e40e661..60b396f 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,12 +1,4 @@ json.array! @matches do |match| - json.extract! match, :id, :kickoff_time, :team_home_score, :team_away_score, :status, :group_id, :team_away_id, :team_home_id, :next_match_id, :round_id - prediction = match.predictions.find_by(user: @user) - if prediction - json.prediction do - json.id prediction.id - json.choice prediction.choice - json.user_id prediction.user.id - json.match_id prediction.match.id - end - end + json.extract! match, :id, :kickoff_time, :team_home_score, :team_away_score, :status, :group_id, :next_match_id, :round_id, :team_away, :team_home + json.prediction match.predictions.first if match.predictions.any? end From ec22607523eca3fa0a21f5261945b34871a3e51f Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 20:48:38 +0900 Subject: [PATCH 090/286] Convert UpperCamelCase keys to lower_snake_case --- app/controllers/application_controller.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 29e78bf..5b6a82e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,8 +10,15 @@ class ApplicationController < ActionController::API rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized rescue_from ActiveRecord::RecordNotFound, with: :not_found + # Allows JSON compliant key names in the front-end and Ruby compliant key names in the back-end + before_action :underscore_params! + private + def underscore_params! + params.deep_transform_keys!(&:underscore) + end + def user_not_authorized(exception) render json: { error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}" From 26dda3182a6437ae5b012978c1279c4547f8dabe Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 20:50:10 +0900 Subject: [PATCH 091/286] Clarify comment --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5b6a82e..78ad667 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,7 +10,7 @@ class ApplicationController < ActionController::API rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized rescue_from ActiveRecord::RecordNotFound, with: :not_found - # Allows JSON compliant key names in the front-end and Ruby compliant key names in the back-end + # Transforms JSON key names (UpperCamelCase) to lower_snake_case before_action :underscore_params! private From b58ce4d7097f79317c9b8e05f802f64a496160a3 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 21:58:11 +0900 Subject: [PATCH 092/286] Refactor with partials --- app/views/v1/matches/index.json.jbuilder | 16 ++++++++++++++-- .../v1/predictions/_prediction.json.jbuilder | 1 + app/views/v1/predictions/show.json.jbuilder | 3 +-- app/views/v1/teams/_team.json.jbuilder | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 app/views/v1/predictions/_prediction.json.jbuilder create mode 100644 app/views/v1/teams/_team.json.jbuilder diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 60b396f..80d5909 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,4 +1,16 @@ json.array! @matches do |match| - json.extract! match, :id, :kickoff_time, :team_home_score, :team_away_score, :status, :group_id, :next_match_id, :round_id, :team_away, :team_home - json.prediction match.predictions.first if match.predictions.any? + json.extract! match, :id, :kickoff_time, :status, :group_id, :next_match_id, :round_id + json.team_home do + json.partial! match.team_home, partial: 'teams/team', as: :team + json.score match.team_home_score if match.team_home_score + end + json.team_away do + json.partial! match.team_away, partial: 'teams/team', as: :team + json.score match.team_away_score if match.team_away_score + end + if match.predictions.any? + json.prediction do + json.partial! match.predictions.first, partial: 'predictions/prediction', as: :prediction + end + end end diff --git a/app/views/v1/predictions/_prediction.json.jbuilder b/app/views/v1/predictions/_prediction.json.jbuilder new file mode 100644 index 0000000..919f96c --- /dev/null +++ b/app/views/v1/predictions/_prediction.json.jbuilder @@ -0,0 +1 @@ +json.extract! prediction, :id, :choice, :match_id, :user_id diff --git a/app/views/v1/predictions/show.json.jbuilder b/app/views/v1/predictions/show.json.jbuilder index 908d63b..9edd400 100644 --- a/app/views/v1/predictions/show.json.jbuilder +++ b/app/views/v1/predictions/show.json.jbuilder @@ -1,2 +1 @@ -json.extract! @prediction, :id, :choice, :match_id, :user_id - +json.partial! @prediction, partial: 'predictions/prediction', as: :prediction diff --git a/app/views/v1/teams/_team.json.jbuilder b/app/views/v1/teams/_team.json.jbuilder new file mode 100644 index 0000000..6683380 --- /dev/null +++ b/app/views/v1/teams/_team.json.jbuilder @@ -0,0 +1 @@ +json.extract! team, :id, :name, :abbrev From 47f06ab51ea1bbf3c23267c00d3b969f12a55ddf Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 22:01:54 +0900 Subject: [PATCH 093/286] Simplify partial rendering syntax --- app/views/v1/matches/index.json.jbuilder | 6 +++--- app/views/v1/predictions/show.json.jbuilder | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 80d5909..c972c71 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,16 +1,16 @@ json.array! @matches do |match| json.extract! match, :id, :kickoff_time, :status, :group_id, :next_match_id, :round_id json.team_home do - json.partial! match.team_home, partial: 'teams/team', as: :team + json.partial! match.team_home json.score match.team_home_score if match.team_home_score end json.team_away do - json.partial! match.team_away, partial: 'teams/team', as: :team + json.partial! match.team_away json.score match.team_away_score if match.team_away_score end if match.predictions.any? json.prediction do - json.partial! match.predictions.first, partial: 'predictions/prediction', as: :prediction + json.partial! match.predictions.first end end end diff --git a/app/views/v1/predictions/show.json.jbuilder b/app/views/v1/predictions/show.json.jbuilder index 9edd400..15e8435 100644 --- a/app/views/v1/predictions/show.json.jbuilder +++ b/app/views/v1/predictions/show.json.jbuilder @@ -1 +1 @@ -json.partial! @prediction, partial: 'predictions/prediction', as: :prediction +json.partial! @prediction From 19477430813192ecda7061cde688b8fdfa96deb5 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 22:06:54 +0900 Subject: [PATCH 094/286] Refactor prediction --- app/views/v1/matches/index.json.jbuilder | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index c972c71..0aa346c 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -8,9 +8,5 @@ json.array! @matches do |match| json.partial! match.team_away json.score match.team_away_score if match.team_away_score end - if match.predictions.any? - json.prediction do - json.partial! match.predictions.first - end - end + json.prediction { json.partial! match.predictions.first } if match.predictions.any? end From 7bd8545e5fb6546472f7a522d0cdc9d4980d9436 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 22:13:51 +0900 Subject: [PATCH 095/286] Use match status to decide score inclusion --- app/views/v1/matches/index.json.jbuilder | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 0aa346c..8bea0fd 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -2,11 +2,11 @@ json.array! @matches do |match| json.extract! match, :id, :kickoff_time, :status, :group_id, :next_match_id, :round_id json.team_home do json.partial! match.team_home - json.score match.team_home_score if match.team_home_score + json.score match.team_home_score if match.finished? end json.team_away do json.partial! match.team_away - json.score match.team_away_score if match.team_away_score + json.score match.team_away_score if match.finished? end json.prediction { json.partial! match.predictions.first } if match.predictions.any? end From 5c91da76722cc6355164de351afc1ac83225ab4f Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 22:17:28 +0900 Subject: [PATCH 096/286] Camelize JSON keys in API rendering --- config/environment.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/environment.rb b/config/environment.rb index cac5315..ef2d4c8 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,9 @@ # Load the Rails application. -require_relative "application" +require_relative 'application' # Initialize the Rails application. Rails.application.initialize! + +# Configure Jbuilder +Jbuilder.key_format camelize: :lower +Jbuilder.deep_format_keys true From d76164ca0f8ba3ed4a9658158f1a75b4d168baf0 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 22:22:59 +0900 Subject: [PATCH 097/286] Remove keys transform from branch --- app/controllers/application_controller.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 78ad667..29e78bf 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,15 +10,8 @@ class ApplicationController < ActionController::API rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized rescue_from ActiveRecord::RecordNotFound, with: :not_found - # Transforms JSON key names (UpperCamelCase) to lower_snake_case - before_action :underscore_params! - private - def underscore_params! - params.deep_transform_keys!(&:underscore) - end - def user_not_authorized(exception) render json: { error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}" From 49e03338bc3b82ff8f9c2df3d1d77ab571af9a23 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 24 Apr 2021 22:24:01 +0900 Subject: [PATCH 098/286] Convert JSON keys from params to lower_snake_case --- app/controllers/application_controller.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 29e78bf..c4ada29 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,8 +10,15 @@ class ApplicationController < ActionController::API rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized rescue_from ActiveRecord::RecordNotFound, with: :not_found + # Transforms JSON key names (UpperCamelCase) to lower_snake_case + before_action :underscore_params! + private + def underscore_params! + params.deep_transform_keys!(&:underscore) + end + def user_not_authorized(exception) render json: { error: "Unauthorized #{exception.policy.class.to_s.underscore.camelize}.#{exception.query}" From ddef39ee59b1a67ddeba4fd725b1b0bf211c17ac Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 5 May 2021 13:23:39 +0900 Subject: [PATCH 099/286] added membership partial --- app/views/v1/memberships/_membership.json.jbuilder | 1 + app/views/v1/memberships/show.json.jbuilder | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 app/views/v1/memberships/_membership.json.jbuilder diff --git a/app/views/v1/memberships/_membership.json.jbuilder b/app/views/v1/memberships/_membership.json.jbuilder new file mode 100644 index 0000000..e54b2c5 --- /dev/null +++ b/app/views/v1/memberships/_membership.json.jbuilder @@ -0,0 +1 @@ +json.extract! membership, :id, :leaderboard_id, :user_id diff --git a/app/views/v1/memberships/show.json.jbuilder b/app/views/v1/memberships/show.json.jbuilder index 73f7b56..d6f8e70 100644 --- a/app/views/v1/memberships/show.json.jbuilder +++ b/app/views/v1/memberships/show.json.jbuilder @@ -1 +1 @@ -json.extract! @membership, :id, :leaderboard_id, :user_id +json.partial! @membership From a77c394e5bb02e7d49f2b53d923a9e5c5e8c4d0e Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 15 May 2021 16:38:43 +0900 Subject: [PATCH 100/286] Add badge_url to team --- app/views/v1/teams/_team.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/v1/teams/_team.json.jbuilder b/app/views/v1/teams/_team.json.jbuilder index 6683380..ed729fc 100644 --- a/app/views/v1/teams/_team.json.jbuilder +++ b/app/views/v1/teams/_team.json.jbuilder @@ -1 +1,2 @@ json.extract! team, :id, :name, :abbrev +json.badge_url cl_image_path(team.badge.key) if team.badge.attached? \ No newline at end of file From 22c70de5ccdcc1d91c2172faa57b3cbffee92104 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sun, 16 May 2021 19:23:52 +0900 Subject: [PATCH 101/286] Remove . from regular expression --- config/initializers/cors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 7fec9c7..b33e006 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -11,7 +11,7 @@ # Local server %r{\Ahttps?://localhost:\d{4}}, # Netlify app and preview deploys - %r{\Ahttps?://(.+--)?soccer-predictor.\.netlify\.app} + %r{\Ahttps?://(.+--)?soccer-predictor\.netlify\.app} # TODO: Add production domain url: # %r(\Ahttps?:\/\/.+\.app-name\.com), ] From eea09f377a998a8d48c862eab1a0065fafa73c2b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 09:24:33 +0900 Subject: [PATCH 102/286] added controller action --- app/controllers/v1/leaderboards_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 4d62d18..a26bfbf 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -1,5 +1,10 @@ class V1::LeaderboardsController < ApplicationController + def index + @competition = Competition.find(params[:competition_id]) + @leaderboards = current_user.leaderboards + end + def create @competition = Competition.find(params[:competition_id]) @leaderboard = Leaderboard.new(leaderboard_params) From fca95c47a15ce378a43cbd27d1bc7aa9a36ea558 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 09:27:16 +0900 Subject: [PATCH 103/286] Added index view --- app/views/v1/leaderboards/index.json.jbuilder | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/views/v1/leaderboards/index.json.jbuilder diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder new file mode 100644 index 0000000..1171af5 --- /dev/null +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @leaderboards do |leaderboard| + json.extract! leaderboard, :id, :name, :password, :user_id, :competition_id +end From bdbebaa56028cbf580856e48be605b0e1942b506 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 09:40:36 +0900 Subject: [PATCH 104/286] added a way to get user leaderboards scoped through competition --- app/controllers/v1/leaderboards_controller.rb | 2 +- app/models/user.rb | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index a26bfbf..0a34506 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -2,7 +2,7 @@ class V1::LeaderboardsController < ApplicationController def index @competition = Competition.find(params[:competition_id]) - @leaderboards = current_user.leaderboards + @leaderboards = current_user.leaderboards(@competition) end def create diff --git a/app/models/user.rb b/app/models/user.rb index fe67160..2cbdb74 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,8 +10,18 @@ class User < ApplicationRecord has_many :predictions, dependent: :destroy has_many :matches, through: :predictions - def leaderboards + def leaderboards(competition = nil) # this includes creator or leaderboard and members - Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) + if competition + Leaderboard.includes(:memberships).where( + competition: competition, + memberships: { user: self } + ).or(Leaderboard.where( + user: self, + competition: competition + )) + else + Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) + end end end From 619674b59a82a491561125af0f46deac35bfe8aa Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 09:42:56 +0900 Subject: [PATCH 105/286] added policy scope for Leaderboard --- app/controllers/v1/leaderboards_controller.rb | 3 ++- app/policies/leaderboard_policy.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 0a34506..08066a5 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -2,7 +2,8 @@ class V1::LeaderboardsController < ApplicationController def index @competition = Competition.find(params[:competition_id]) - @leaderboards = current_user.leaderboards(@competition) + # @leaderboards = current_user.leaderboards(@competition) + @leaderboards = policy_scope(Leaderboard).where(competition: @competition) end def create diff --git a/app/policies/leaderboard_policy.rb b/app/policies/leaderboard_policy.rb index e7698cb..4dc41ea 100644 --- a/app/policies/leaderboard_policy.rb +++ b/app/policies/leaderboard_policy.rb @@ -1,7 +1,7 @@ class LeaderboardPolicy < ApplicationPolicy class Scope < Scope def resolve - scope.all + user.leaderboards end end From 2d135b1dd4aae2d815c894776991ba3d06010032 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 10:04:08 +0900 Subject: [PATCH 106/286] added score --- app/controllers/v1/leaderboards_controller.rb | 1 + app/views/v1/leaderboards/index.json.jbuilder | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 08066a5..78539fe 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -3,6 +3,7 @@ class V1::LeaderboardsController < ApplicationController def index @competition = Competition.find(params[:competition_id]) # @leaderboards = current_user.leaderboards(@competition) + @ranking = 0 @leaderboards = policy_scope(Leaderboard).where(competition: @competition) end diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 1171af5..7660a4f 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -1,3 +1,10 @@ json.array! @leaderboards do |leaderboard| json.extract! leaderboard, :id, :name, :password, :user_id, :competition_id + json.users leaderboard.users do |user| + json.user_id user.id + json.points user.predictions.count(&:correct?) * 3 + end end + +# TODO: add 🔼 or 🔽 +# TODO: user predictions should be scoped by competition From 5a0125ad9591a4a36299a320c7e705b76bcfafe0 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 10:05:06 +0900 Subject: [PATCH 107/286] Added learderboard partial --- app/views/v1/leaderboards/_leaderboard.json.jbuilder | 1 + app/views/v1/leaderboards/index.json.jbuilder | 2 +- app/views/v1/leaderboards/show.json.jbuilder | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 app/views/v1/leaderboards/_leaderboard.json.jbuilder diff --git a/app/views/v1/leaderboards/_leaderboard.json.jbuilder b/app/views/v1/leaderboards/_leaderboard.json.jbuilder new file mode 100644 index 0000000..5a913e8 --- /dev/null +++ b/app/views/v1/leaderboards/_leaderboard.json.jbuilder @@ -0,0 +1 @@ +json.extract! leaderboard, :id, :name, :password, :user_id, :competition_id diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 7660a4f..ac4b608 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -1,5 +1,5 @@ json.array! @leaderboards do |leaderboard| - json.extract! leaderboard, :id, :name, :password, :user_id, :competition_id + json.partial! leaderboard json.users leaderboard.users do |user| json.user_id user.id json.points user.predictions.count(&:correct?) * 3 diff --git a/app/views/v1/leaderboards/show.json.jbuilder b/app/views/v1/leaderboards/show.json.jbuilder index 849f082..cd4e1f3 100644 --- a/app/views/v1/leaderboards/show.json.jbuilder +++ b/app/views/v1/leaderboards/show.json.jbuilder @@ -1 +1 @@ -json.extract! @leaderboard, :id, :name, :password, :user_id, :competition_id +json.partial! @leaderboard From 8b6b619ff378c56d52838a90982e01881ac5ff34 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 10:06:39 +0900 Subject: [PATCH 108/286] Added name on user --- app/models/user.rb | 4 ++++ app/views/v1/leaderboards/index.json.jbuilder | 1 + 2 files changed, 5 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 2cbdb74..a0f48bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,4 +24,8 @@ def leaderboards(competition = nil) Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) end end + + def display_name + name || email + end end diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index ac4b608..a87ad4e 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -2,6 +2,7 @@ json.array! @leaderboards do |leaderboard| json.partial! leaderboard json.users leaderboard.users do |user| json.user_id user.id + json.name user.display_name json.points user.predictions.count(&:correct?) * 3 end end From 2af5252fef8ea56cfd277e315cc231fd1b954e26 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 10:36:11 +0900 Subject: [PATCH 109/286] moved the score logic outside of the view --- app/models/user.rb | 6 ++++++ app/views/v1/leaderboards/index.json.jbuilder | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index a0f48bd..3f941b6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,4 +28,10 @@ def leaderboards(competition = nil) def display_name name || email end + + def score(competition) + # TODO: user predictions should be scoped by competition => prediction -> match -> group -> round -> competition + # predictions.where(competition: competition).count(&:correct?) * 3 + predictions.count(&:correct?) * 3 + end end diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index a87ad4e..91b2074 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -3,9 +3,8 @@ json.array! @leaderboards do |leaderboard| json.users leaderboard.users do |user| json.user_id user.id json.name user.display_name - json.points user.predictions.count(&:correct?) * 3 + json.points user.score(leaderboard.competition) end end # TODO: add 🔼 or 🔽 -# TODO: user predictions should be scoped by competition From 4440def832712506de38f56c4beb967a05fbb8c9 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 14:24:41 +0900 Subject: [PATCH 110/286] Added controller action --- app/controllers/v1/leaderboards_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 4d62d18..2b1045d 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -13,6 +13,12 @@ def create end end + def destroy + @leaderboard = Leaderboard.find(params[:id]) + @leaderboard.destroy + head :no_content + end + private def leaderboard_params From 6226b7132d337701ac2baf14aaa31fdf2d54f6cc Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 25 May 2021 14:25:56 +0900 Subject: [PATCH 111/286] added policy to destroy leaderboard --- app/controllers/v1/leaderboards_controller.rb | 1 + app/policies/leaderboard_policy.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 2b1045d..c0bab6f 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -15,6 +15,7 @@ def create def destroy @leaderboard = Leaderboard.find(params[:id]) + authorize @leaderboard @leaderboard.destroy head :no_content end diff --git a/app/policies/leaderboard_policy.rb b/app/policies/leaderboard_policy.rb index e7698cb..c07ee1a 100644 --- a/app/policies/leaderboard_policy.rb +++ b/app/policies/leaderboard_policy.rb @@ -8,4 +8,8 @@ def resolve def create? true end + + def destroy? + record.user == user + end end From d41d9e75144c2ba42a37af7a2820897baab0a7a7 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 26 May 2021 16:45:12 +0900 Subject: [PATCH 112/286] refactored leaderboards method --- app/models/user.rb | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 3f941b6..0dd3204 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,17 +12,9 @@ class User < ApplicationRecord def leaderboards(competition = nil) # this includes creator or leaderboard and members - if competition - Leaderboard.includes(:memberships).where( - competition: competition, - memberships: { user: self } - ).or(Leaderboard.where( - user: self, - competition: competition - )) - else - Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) - end + leaderboards = Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) + leaderboards = leaderboards.where(competition: competition) if competition + leaderboards end def display_name From f69d00f6874efe221f6805d2a4e037cba84ca602 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 26 May 2021 16:45:56 +0900 Subject: [PATCH 113/286] removed unncessary controller lines --- app/controllers/v1/leaderboards_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 78539fe..6a66e7a 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -2,8 +2,6 @@ class V1::LeaderboardsController < ApplicationController def index @competition = Competition.find(params[:competition_id]) - # @leaderboards = current_user.leaderboards(@competition) - @ranking = 0 @leaderboards = policy_scope(Leaderboard).where(competition: @competition) end From 8f7e0b7aaf60ccfb54e07f6a001c29b0c1102f1d Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 27 May 2021 01:26:18 +0900 Subject: [PATCH 114/286] Add PATCH to CORS methods --- config/initializers/cors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index b33e006..3bcd24f 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -19,7 +19,7 @@ resource '*', headers: :any, expose: %w[access-token expiry token-type uid client], - methods: %i[get post options delete put], + methods: %i[get post options delete put patch], credentials: true end end From 51c972c02bf4cdc54ad437348b988f93b008fe76 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 27 May 2021 17:57:50 +0900 Subject: [PATCH 115/286] Added more memberships --- db/seeds.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index e45edd1..af021a6 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,4 +1,5 @@ require 'open-uri' +Leaderboard.destroy_all puts 'Getting Admin users...' doug = User.find_by(email: 'douglasmberkley@gmail.com') || User.create(email: 'douglasmberkley@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) @@ -92,8 +93,8 @@ user: james ) -puts 'Adding Trouni and Doug to the leaderboard' -[trouni, doug].each do |user| +puts 'Adding Users to the leaderboard' +([doug, trouni] + User.all.take(20)).each do |user| Membership.find_or_create_by!(leaderboard: leaderboard, user: user) end From 1b357a6199fadc01470d26719ae4fcec1203e458 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 28 May 2021 16:34:00 +0900 Subject: [PATCH 116/286] Remove initializer file --- config/initializers/active_record/secure_token.rb | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 config/initializers/active_record/secure_token.rb diff --git a/config/initializers/active_record/secure_token.rb b/config/initializers/active_record/secure_token.rb deleted file mode 100644 index cbcde13..0000000 --- a/config/initializers/active_record/secure_token.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -module ActiveRecord - module SecureToken - MINIMUM_TOKEN_LENGTH = 10 - end -end From 51552c7acb29a376ab9e34182cf714d4b28c1776 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 29 May 2021 13:51:05 +0900 Subject: [PATCH 117/286] Addded more leaderboards --- app/models/competition.rb | 2 +- db/seeds.rb | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index 14749ce..c16d4a3 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -5,7 +5,7 @@ class Competition < ApplicationRecord has_many :affiliations, through: :groups has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy - validates :name, presence: true + validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true end diff --git a/db/seeds.rb b/db/seeds.rb index af021a6..00801d4 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -90,15 +90,29 @@ leaderboard = Leaderboard.find_or_create_by!( name: 'Admin Leaderboard', competition: euros, - user: james + user: trouni ) +puts 'Creating test users...' +5.times do + Leaderboard.create!( + # fake emails for testing purposes + name: Faker::Sports::Football.team, + password: '123123', + competition: euros, + user: trouni + ) +end +puts "... #{User.count} Total Users" + puts 'Adding Users to the leaderboard' -([doug, trouni] + User.all.take(20)).each do |user| - Membership.find_or_create_by!(leaderboard: leaderboard, user: user) +Leaderboard.find_each do |ldbrd| + ([doug] + User.last(20)).each do |user| + Membership.find_or_create_by!(leaderboard: ldbrd, user: user) + end end -ScrapeMatchesService.new.call +# ScrapeMatchesService.new.call puts 'Assigning random scores to matches before June 22nd' Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| From 224b6bb1a532ecc14e3e1d17f4aaaab1120920df Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 29 May 2021 15:59:58 +0900 Subject: [PATCH 118/286] let password get auto generated --- db/seeds.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 00801d4..b548a9d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -98,7 +98,6 @@ Leaderboard.create!( # fake emails for testing purposes name: Faker::Sports::Football.team, - password: '123123', competition: euros, user: trouni ) @@ -112,7 +111,7 @@ end end -# ScrapeMatchesService.new.call +ScrapeMatchesService.new.call puts 'Assigning random scores to matches before June 22nd' Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| From 12d14c04f445157a506640b4bcdff6ee530404bd Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 29 May 2021 21:35:24 +0900 Subject: [PATCH 119/286] added user associations since leaderboard wasnt including owner --- app/models/leaderboard.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 843bca8..4e8207e 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -2,9 +2,11 @@ class Leaderboard < ApplicationRecord belongs_to :user belongs_to :competition has_many :memberships, dependent: :destroy - has_many :users, through: :memberships validates :name, presence: true - # TODO: Think about how users join groups - # validates :password, presence: true has_secure_token :password + + def users + User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: users)) + end end + From 5707a1035b8a97b3cc6b9beb5747adbc9367e012 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 29 May 2021 21:38:53 +0900 Subject: [PATCH 120/286] fixed typo on user's' --- app/models/leaderboard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 4e8207e..7da0072 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -6,7 +6,7 @@ class Leaderboard < ApplicationRecord has_secure_token :password def users - User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: users)) + User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: user)) end end From 97a7eb3629081f846dc16fc8df65d8b1d8ee26f4 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 29 May 2021 22:01:54 +0900 Subject: [PATCH 121/286] rendering the leaderboard when joining one" --- app/controllers/v1/memberships_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/v1/memberships_controller.rb b/app/controllers/v1/memberships_controller.rb index 2410952..0bc248a 100644 --- a/app/controllers/v1/memberships_controller.rb +++ b/app/controllers/v1/memberships_controller.rb @@ -6,7 +6,7 @@ def create @membership.user = current_user authorize @membership if @membership.save - render :show, status: :created + render 'leaderboards/show', status: :created else render_error(@membership) end From ea3adfbaefdd4ec946539739ab4a46c0e1624736 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 30 May 2021 12:09:23 +0900 Subject: [PATCH 122/286] added form and action --- app/controllers/v1/memberships_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/v1/memberships_controller.rb b/app/controllers/v1/memberships_controller.rb index 0bc248a..94413ab 100644 --- a/app/controllers/v1/memberships_controller.rb +++ b/app/controllers/v1/memberships_controller.rb @@ -6,7 +6,7 @@ def create @membership.user = current_user authorize @membership if @membership.save - render 'leaderboards/show', status: :created + render 'v1/leaderboards/show', status: :created else render_error(@membership) end From 794274b5ce55228a178df1d7ee6b830c43d1e96b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 30 May 2021 12:21:26 +0900 Subject: [PATCH 123/286] added users controller --- app/controllers/v1/users_controller.rb | 17 +++++++++++++++++ test/controllers/v1/users_controller_test.rb | 7 +++++++ 2 files changed, 24 insertions(+) create mode 100644 app/controllers/v1/users_controller.rb create mode 100644 test/controllers/v1/users_controller_test.rb diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb new file mode 100644 index 0000000..0c68646 --- /dev/null +++ b/app/controllers/v1/users_controller.rb @@ -0,0 +1,17 @@ +class V1::UsersController < ApplicationController + def update + @user = current_user + authorize @user + if @user.update(prediction_params) + render :show + else + render_error(@user) + end + end + + private + + def prediction_params + params.require(:user).permit(:name, :timezone) + end +end diff --git a/test/controllers/v1/users_controller_test.rb b/test/controllers/v1/users_controller_test.rb new file mode 100644 index 0000000..201c212 --- /dev/null +++ b/test/controllers/v1/users_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class V1::UsersControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From e60c025aa2845ef3a85a8dec064db9c2e72521cd Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 30 May 2021 12:23:06 +0900 Subject: [PATCH 124/286] added user views --- app/views/v1/users/_user.json.jbuilder | 1 + app/views/v1/users/show.json.jbuilder | 1 + 2 files changed, 2 insertions(+) create mode 100644 app/views/v1/users/_user.json.jbuilder create mode 100644 app/views/v1/users/show.json.jbuilder diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder new file mode 100644 index 0000000..9ebb910 --- /dev/null +++ b/app/views/v1/users/_user.json.jbuilder @@ -0,0 +1 @@ +json.extract! user, :id, :name, :email, :timezone, :admin diff --git a/app/views/v1/users/show.json.jbuilder b/app/views/v1/users/show.json.jbuilder new file mode 100644 index 0000000..51a8538 --- /dev/null +++ b/app/views/v1/users/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! @user From 2951aa618e9b2f831d040f0a227a46e9e0a3bb06 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 30 May 2021 12:36:09 +0900 Subject: [PATCH 125/286] added user policy --- app/policies/user_policy.rb | 11 +++++++++++ config/routes.rb | 2 +- test/policies/user_policy_test.rb | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 app/policies/user_policy.rb create mode 100644 test/policies/user_policy_test.rb diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 0000000..38d94c8 --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,11 @@ +class UserPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end + + def update? + user == record + end +end diff --git a/config/routes.rb b/config/routes.rb index 18822ba..04e2bf5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,7 +13,7 @@ resources :leaderboards, only: [:destroy], shallow: true do resources :memberships, only: [:create, :destroy] end - resources :users, only: [:show] + resources :users, only: [:show, :update] get 'join/:password', to: 'memberships#create' end end diff --git a/test/policies/user_policy_test.rb b/test/policies/user_policy_test.rb new file mode 100644 index 0000000..577ac60 --- /dev/null +++ b/test/policies/user_policy_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class UserPolicyTest < ActiveSupport::TestCase + def test_scope + end + + def test_show + end + + def test_create + end + + def test_update + end + + def test_destroy + end +end From 56b6828b79fbca032533e5beef0e2a6f3ea90e8e Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 1 Jun 2021 16:18:47 +0900 Subject: [PATCH 126/286] Update README for Live-Score API --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 4e9dd76..7ad5044 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,34 @@ bundle install ``` rails s ``` + + +## Live-score API +#### Competition List +https://livescore-api.com/api-client/competitions/list.json?key=REPLACE_ME&secret=REPLACE_ME + +#### Matches for Euros +https://livescore-api.com/api-client/fixtures/matches.json?key=REPLACE_ME&secret=REPLACE_ME&competition_id=387 + +To retreive the H2H info, you can access it in the matches list: +Screen Shot 2021-06-01 at 15 45 21 + +#### Single Fixture Info +https://livescore-api.com/api-client/teams/head2head.json?key=REPLACE_ME&secret=REPLACE_ME&team1_id=1744&team2_id=1740 + +Screen Shot 2021-06-01 at 15 57 25 + +#### Getting Groups in Euros +https://livescore-api.com/api-client/competitions/groups.json?key=REPLACE_ME&secret=REPLACE_ME&competition_id=387 +Screen Shot 2021-06-01 at 16 02 54 + +#### Getting Country Flag +https://livescore-api.com/api-client/countries/flag.json?key=REPLACE_ME&secret=REPLACE_ME&team_id=1440 + +#### Getting Live Scores +http://livescore-api.com/api-client/scores/live.json?key=REPLACE_ME&secret=REPLACE_ME&competition_id=387 + +#### Getting Results +http://livescore-api.com/api-client/scores/history.json?key=REPLACE_ME&secret=REPLACE_ME&competition_id=387 + +Screen Shot 2021-06-01 at 16 17 48 From bfc0a74937233253197328a4474ac8ab77c9b757 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 1 Jun 2021 17:49:04 +0900 Subject: [PATCH 127/286] fixed membership create to render the membership --- app/controllers/v1/memberships_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/v1/memberships_controller.rb b/app/controllers/v1/memberships_controller.rb index 94413ab..2410952 100644 --- a/app/controllers/v1/memberships_controller.rb +++ b/app/controllers/v1/memberships_controller.rb @@ -6,7 +6,7 @@ def create @membership.user = current_user authorize @membership if @membership.save - render 'v1/leaderboards/show', status: :created + render :show, status: :created else render_error(@membership) end From b799eee4c48c1642b1ee4e589a5305a988cbe730 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 1 Jun 2021 17:49:50 +0900 Subject: [PATCH 128/286] updated leaderboard users --- app/models/leaderboard.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 7da0072..41abea3 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -2,11 +2,11 @@ class Leaderboard < ApplicationRecord belongs_to :user belongs_to :competition has_many :memberships, dependent: :destroy + has_many :users, through: :memberships validates :name, presence: true has_secure_token :password def users - User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: user)) + super().or(User.where(id: user)) end end - From 703cd2cc1103fc10392617cd1533607e5ebe915f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 1 Jun 2021 21:12:58 +0900 Subject: [PATCH 129/286] give all users who have memberships and owner --- app/models/leaderboard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 41abea3..0be68a3 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -7,6 +7,6 @@ class Leaderboard < ApplicationRecord has_secure_token :password def users - super().or(User.where(id: user)) + User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: user)) end end From abeeda76cb5eb5af1d88328c27c784e2c42e13bc Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 19:32:05 +0900 Subject: [PATCH 130/286] created rake task --- lib/tasks/competition.rake | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lib/tasks/competition.rake diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake new file mode 100644 index 0000000..57745e9 --- /dev/null +++ b/lib/tasks/competition.rake @@ -0,0 +1,10 @@ +namespace :competition do + desc "Copy the first competition and start it today" + task copy: :environment do + euros = Competition.first + competition = euros.dup + competition.save + + end + +end From b182fe6fc6d8102abd85823df5fcc8efe49106ab Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 20:24:46 +0900 Subject: [PATCH 131/286] created comptition task logic --- app/models/competition.rb | 1 + lib/tasks/competition.rake | 69 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index c16d4a3..5169ccb 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -2,6 +2,7 @@ class Competition < ApplicationRecord belongs_to :current_round, class_name: 'Round', optional: true has_many :rounds, dependent: :destroy has_many :groups, through: :rounds + has_many :matches, through: :groups has_many :affiliations, through: :groups has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 57745e9..d85f2f9 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -1,9 +1,72 @@ namespace :competition do desc "Copy the first competition and start it today" task copy: :environment do - euros = Competition.first - competition = euros.dup - competition.save + euros = Competition.find_or_create_by!(name: 'Euro 2020') + + groups = { + 'Group A' => [ + { name: 'Italy', abbrev: 'ITA' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Turkey', abbrev: 'TUR' }, + { name: 'Wales', abbrev: 'WAL' } + ], + 'Group B' => [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Finland', abbrev: 'FIN' }, + { name: 'Russia', abbrev: 'RUS' } + ], + 'Group C' => [ + { name: 'Austria', abbrev: 'AUT' }, + { name: 'Netherlands', abbrev: 'NED' }, + { name: 'North Macedonia', abbrev: 'MKD' }, + { name: 'Ukraine', abbrev: 'UKR' } + ], + 'Group D' => [ + { name: 'Croatia', abbrev: 'CRO' }, + { name: 'Czech Republic', abbrev: 'CZE' }, + { name: 'England', abbrev: 'ENG' }, + { name: 'Scotland', abbrev: 'SCO' } + ], + 'Group E' => [ + { name: 'Poland', abbrev: 'POL' }, + { name: 'Slovakia', abbrev: 'SVK' }, + { name: 'Spain', abbrev: 'ESP' }, + { name: 'Sweden', abbrev: 'SWE' } + ], + 'Group F' => [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Germany', abbrev: 'GER' }, + { name: 'Hungary', abbrev: 'HUN' }, + { name: 'Portugal', abbrev: 'POR' } + ] + } + + puts 'Creating the Euros...' + euros_test = Competition.find_or_create_by!(name: 'Euro Test 2020', start_date: Date.today, end_date: Date.today + 35.days) + puts '.. created the Euros_test' + + puts 'Creating or finding first round...' + first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros_test) + puts "...#{Round.count} Total Rounds" + + puts 'Creating or finding groups...' + groups.each_key do |group_name| + puts "...#{group_name}..." + group = Group.find_or_create_by!(name: group_name, round: first_round) + groups[group_name].each do |team_hash| + puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" + team = Team.find_or_create_by!(team_hash) + Affiliation.find_or_create_by!(team: team, group: group) + end + end + + euros.matches.each do |match| + new_match = match.dup + new_match.group = euros_test.find_by(name: match.group.name) + new_match.kickoff_time = match.kickoff_time - 9.days + new_match.save + end end From 95f9e1f3d0aa6e44c454def71fb5e403f1113d2a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 20:29:14 +0900 Subject: [PATCH 132/286] fixed match creation bug. all games in future --- lib/tasks/competition.rake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index d85f2f9..823ee9f 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -63,11 +63,13 @@ namespace :competition do euros.matches.each do |match| new_match = match.dup - new_match.group = euros_test.find_by(name: match.group.name) + new_match.group = euros_test.groups.find_by(name: match.group.name) new_match.kickoff_time = match.kickoff_time - 9.days + new_match.team_home_score = nil + new_match.team_away_score = nil + new_match.status = "upcoming" new_match.save end - end end From db659e282a254a4461f46acf9a3b35301f0f729b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 21:20:17 +0900 Subject: [PATCH 133/286] Added rake task to get api id and flag --- Gemfile | 1 + Gemfile.lock | 4 +++ app/models/team.rb | 1 + app/views/v1/teams/_team.json.jbuilder | 3 +- .../20210602120404_add_api_id_to_teams.rb | 5 +++ db/schema.rb | 3 +- lib/tasks/team.rake | 33 +++++++++++++++++++ 7 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20210602120404_add_api_id_to_teams.rb create mode 100644 lib/tasks/team.rake diff --git a/Gemfile b/Gemfile index f3b525b..4022364 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'cloudinary', '~> 1.16.0' gem 'devise' gem 'devise_token_auth', github: 'lynndylanhurley/devise_token_auth' gem 'faker' +gem 'httparty' gem 'omniauth' gem 'pundit' gem 'watir', '6.16.5' diff --git a/Gemfile.lock b/Gemfile.lock index b4520d6..74e3870 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,6 +109,8 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) + httparty (0.15.5) + multi_xml (>= 0.5.2) i18n (1.8.10) concurrent-ruby (~> 1.0) jbuilder (2.11.2) @@ -141,6 +143,7 @@ GEM mini_portile2 (2.5.0) minitest (5.14.4) msgpack (1.4.2) + multi_xml (0.6.0) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) netrc (0.11.0) @@ -271,6 +274,7 @@ DEPENDENCIES devise_token_auth! dotenv-rails faker + httparty jbuilder (~> 2.7) listen (~> 3.3) mailcatcher diff --git a/app/models/team.rb b/app/models/team.rb index 2b2b997..d5f83e8 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -4,6 +4,7 @@ class Team < ApplicationRecord validates :name, presence: true, uniqueness: true validates :abbrev, presence: true, uniqueness: true has_one_attached :badge + has_one_attached :flag def matches # teams can either be home or away diff --git a/app/views/v1/teams/_team.json.jbuilder b/app/views/v1/teams/_team.json.jbuilder index ed729fc..c773cd6 100644 --- a/app/views/v1/teams/_team.json.jbuilder +++ b/app/views/v1/teams/_team.json.jbuilder @@ -1,2 +1,3 @@ json.extract! team, :id, :name, :abbrev -json.badge_url cl_image_path(team.badge.key) if team.badge.attached? \ No newline at end of file +json.badge_url cl_image_path(team.badge.key) if team.badge.attached? +json.flag_url cl_image_path(team.flag.key) if team.flag.attached? diff --git a/db/migrate/20210602120404_add_api_id_to_teams.rb b/db/migrate/20210602120404_add_api_id_to_teams.rb new file mode 100644 index 0000000..c998539 --- /dev/null +++ b/db/migrate/20210602120404_add_api_id_to_teams.rb @@ -0,0 +1,5 @@ +class AddApiIdToTeams < ActiveRecord::Migration[6.1] + def change + add_column :teams, :api_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 8e7302a..e0f4fb2 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: 2021_04_24_101146) do +ActiveRecord::Schema.define(version: 2021_06_02_120404) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -133,6 +133,7 @@ t.string "abbrev" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.integer "api_id" end create_table "users", force: :cascade do |t| diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake new file mode 100644 index 0000000..07f8afb --- /dev/null +++ b/lib/tasks/team.rake @@ -0,0 +1,33 @@ +namespace :team do + desc "Calls Live-Score API, saves API id and gets the flag" + task add_flag: :environment do + competition_id = 387 + response = HTTParty.get("http://livescore-api.com/api-client/countries/list.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}").body + countries = JSON.parse(response)['data']['country'] + + not_found = [] + Team.find_each do |team| + country = countries.find { |country| country['name'] == team.name } + if country + team.api_id = country['id'] + team.save + + next if team.flag.attached? + + scrape_flag(team) + else + not_found << team.name + end + end + + puts "Teams not found: #{not_found.join(', ')}" + end + + def scrape_flag(team) + url = "https://livescore-api.com/api-client/countries/flag.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&team_id=#{team.api_id}" + puts "#{team.name}: #{url}" + file = URI.open(url) + team.flag.attach(io: file, filename: 'flag.png', content_type: 'image/png') + puts team.flag.attached? ? 'Success' : 'Failed' + end +end From ce67cd7a056645915a1e710aec9cb6ec1ad632d8 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 21:23:16 +0900 Subject: [PATCH 134/286] choosing the national team id --- lib/tasks/team.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index 07f8afb..72da13e 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -9,7 +9,7 @@ namespace :team do Team.find_each do |team| country = countries.find { |country| country['name'] == team.name } if country - team.api_id = country['id'] + team.api_id = country['national_team']['id'] team.save next if team.flag.attached? From 9a2e6ca8ea6a3fafa3f4fa59c2ec2ee516c02f7f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 21:25:57 +0900 Subject: [PATCH 135/286] updated seed feedback --- lib/tasks/team.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index 72da13e..a8d34b1 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -20,7 +20,7 @@ namespace :team do end end - puts "Teams not found: #{not_found.join(', ')}" + puts not_found.any? ? "Teams not found: #{not_found.join(', ')}" : 'Found all teams' end def scrape_flag(team) From 9fcd209c6f17aee44d063aef6da31194e5f4eff2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 21:59:56 +0900 Subject: [PATCH 136/286] Added sidekiq gems --- Gemfile | 2 ++ Gemfile.lock | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/Gemfile b/Gemfile index 4022364..65a644e 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,8 @@ gem 'faker' gem 'httparty' gem 'omniauth' gem 'pundit' +gem 'sidekiq' +gem 'sidekiq-failures', '~> 1.0' gem 'watir', '6.16.5' gem 'webdrivers' diff --git a/Gemfile.lock b/Gemfile.lock index 74e3870..99b15d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,6 +81,7 @@ GEM rest-client coderay (1.1.3) concurrent-ruby (1.1.8) + connection_pool (2.2.5) crass (1.0.6) daemons (1.3.1) devise (4.7.3) @@ -204,6 +205,7 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) + redis (4.2.5) regexp_parser (1.8.2) responders (3.0.1) actionpack (>= 5.0) @@ -218,6 +220,12 @@ GEM selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) + sidekiq (6.2.1) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) + sidekiq-failures (1.0.0) + sidekiq (>= 4.0.0) sinatra (2.1.0) mustermann (~> 1.0) rack (~> 2.2) @@ -285,6 +293,8 @@ DEPENDENCIES pundit rack-cors rails (~> 6.1.3, >= 6.1.3.1) + sidekiq + sidekiq-failures (~> 1.0) spring tzinfo-data watir (= 6.16.5) From 34816ced3ed2851ed3994270c3c1a25e1efbeefe Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 22:01:53 +0900 Subject: [PATCH 137/286] added sidekiq confi --- bin/sidekiq | 29 +++++++++++++++++++++++++++++ bin/sidekiqmon | 29 +++++++++++++++++++++++++++++ config/application.rb | 1 + config/routes.rb | 4 ++++ config/sidekiq.yml | 8 ++++++++ 5 files changed, 71 insertions(+) create mode 100755 bin/sidekiq create mode 100755 bin/sidekiqmon create mode 100644 config/sidekiq.yml diff --git a/bin/sidekiq b/bin/sidekiq new file mode 100755 index 0000000..9e75499 --- /dev/null +++ b/bin/sidekiq @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'sidekiq' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("sidekiq", "sidekiq") diff --git a/bin/sidekiqmon b/bin/sidekiqmon new file mode 100755 index 0000000..fedda51 --- /dev/null +++ b/bin/sidekiqmon @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'sidekiqmon' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("sidekiq", "sidekiqmon") diff --git a/config/application.rb b/config/application.rb index 5d3b657..c89e186 100644 --- a/config/application.rb +++ b/config/application.rb @@ -36,5 +36,6 @@ class Application < Rails::Application # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. config.api_only = true + config.active_job.queue_adapter = :sidekiq end end diff --git a/config/routes.rb b/config/routes.rb index 04e2bf5..a294608 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,10 @@ mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'auth/devise_token_auth/sessions' } + require "sidekiq/web" + authenticate :user, ->(user) { user.admin? } do + mount Sidekiq::Web => '/sidekiq' + end namespace :v1, defaults: { format: :json } do resources :competitions, only: [:show] do resources :leaderboards, only: [:index, :create] diff --git a/config/sidekiq.yml b/config/sidekiq.yml new file mode 100644 index 0000000..5c836ef --- /dev/null +++ b/config/sidekiq.yml @@ -0,0 +1,8 @@ +:concurrency: 3 +:timeout: 60 +:verbose: true +:queues: + - default + - mailers + - active_storage_analysis + - active_storage_purge From 73d0708ac4d831897844c92fff5cac7e9ed15a53 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 22:02:54 +0900 Subject: [PATCH 138/286] added redi --- config/initializers/redis.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 config/initializers/redis.rb diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb new file mode 100644 index 0000000..5cd2827 --- /dev/null +++ b/config/initializers/redis.rb @@ -0,0 +1,14 @@ +$redis = Redis.new + +url = ENV["REDISCLOUD_URL"] + +if url + Sidekiq.configure_server do |config| + config.redis = { url: url } + end + + Sidekiq.configure_client do |config| + config.redis = { url: url } + end + $redis = Redis.new(:url => url) +end From 5d518d5a63c13f05a71df4c2a07848d3718d2b28 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 22:03:20 +0900 Subject: [PATCH 139/286] updated procfile --- Procfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Procfile b/Procfile index 8d36be1..7a10729 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,3 @@ release: rake db:migrate +web: bundle exec puma -C config/puma.rb +worker: bundle exec sidekiq -C config/sidekiq.yml From 381390cb85c1432fc7b829db5d6067c3d2a77875 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 22:10:03 +0900 Subject: [PATCH 140/286] created rake task to radomly assign scores to matches in the past --- lib/tasks/match.rake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/tasks/match.rake diff --git a/lib/tasks/match.rake b/lib/tasks/match.rake new file mode 100644 index 0000000..ef9e34a --- /dev/null +++ b/lib/tasks/match.rake @@ -0,0 +1,14 @@ +namespace :match do + desc "Checks for matches in the past and randomly assigns a score" + task add_fake_results: :environment do + completed_matches = Match.where('kickoff_time < :date', date: DateTime.now) + completed_matches.each do |match| + next if match.finished? + + match.finished! + match.team_home_score = rand(0..3) + match.team_away_score = rand(0..3) + match.save + end + end +end From 9bd2391cb6a449a16fd0cb73c2f5346c98e1d598 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 22:12:04 +0900 Subject: [PATCH 141/286] added todo comment --- lib/tasks/match.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/match.rake b/lib/tasks/match.rake index ef9e34a..04b0b83 100644 --- a/lib/tasks/match.rake +++ b/lib/tasks/match.rake @@ -1,6 +1,7 @@ namespace :match do desc "Checks for matches in the past and randomly assigns a score" task add_fake_results: :environment do + # TODO: Turn off before Friday completed_matches = Match.where('kickoff_time < :date', date: DateTime.now) completed_matches.each do |match| next if match.finished? From 77667139a2af096dfb416a5172357278a80b30ab Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 2 Jun 2021 22:14:43 +0900 Subject: [PATCH 142/286] udpdated cable URL also just in case --- config/cable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/cable.yml b/config/cable.yml index 0b3553b..1ae16c5 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -6,5 +6,5 @@ test: production: adapter: redis - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + url: <%= ENV.fetch("REDISCLOUD_URL") { "redis://localhost:6379/1" } %> channel_prefix: predictor_api_production From 5dfbc1ae0bba9f9548b29291bf88831976200a53 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Wed, 2 Jun 2021 23:09:52 +0900 Subject: [PATCH 143/286] Add octacle.app to CORS settings --- config/initializers/cors.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 3bcd24f..d8f1bc0 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -11,9 +11,9 @@ # Local server %r{\Ahttps?://localhost:\d{4}}, # Netlify app and preview deploys - %r{\Ahttps?://(.+--)?soccer-predictor\.netlify\.app} - # TODO: Add production domain url: - # %r(\Ahttps?:\/\/.+\.app-name\.com), + %r{\Ahttps?://(.+--)?octacle\.netlify\.app}, + # Production app + %r{\Ahttps?:\/\/.+\.octacle\.app} ] resource '*', From 9e38f87428b3c10fdaf2d6c04b723fe27cdd9f47 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 3 Jun 2021 15:13:38 +0900 Subject: [PATCH 144/286] Prevent crash if already joined --- app/controllers/v1/memberships_controller.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/v1/memberships_controller.rb b/app/controllers/v1/memberships_controller.rb index 2410952..9776056 100644 --- a/app/controllers/v1/memberships_controller.rb +++ b/app/controllers/v1/memberships_controller.rb @@ -1,9 +1,7 @@ class V1::MembershipsController < ApplicationController def create @leaderboard = Leaderboard.find_by(password: params[:password]) - @membership = Membership.new - @membership.leaderboard = @leaderboard - @membership.user = current_user + @membership = Membership.find_or_initialize_by(user: current_user, leaderboard: @leaderboard) authorize @membership if @membership.save render :show, status: :created From d09ba9705a425c7cc2f5555ee1e8aa8cd228e9ee Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 3 Jun 2021 15:14:10 +0900 Subject: [PATCH 145/286] Add competition_id to partial --- app/models/membership.rb | 1 + app/views/v1/memberships/_membership.json.jbuilder | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/membership.rb b/app/models/membership.rb index 312f476..80b304d 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -1,5 +1,6 @@ class Membership < ApplicationRecord belongs_to :leaderboard belongs_to :user + has_one :competition, through: :leaderboard validates_uniqueness_of :user, scope: :leaderboard end diff --git a/app/views/v1/memberships/_membership.json.jbuilder b/app/views/v1/memberships/_membership.json.jbuilder index e54b2c5..20c1280 100644 --- a/app/views/v1/memberships/_membership.json.jbuilder +++ b/app/views/v1/memberships/_membership.json.jbuilder @@ -1 +1,2 @@ json.extract! membership, :id, :leaderboard_id, :user_id +json.competition_id membership.competition.id From c3abf10b89a417aaa7d795172fabb20d711c58e5 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 3 Jun 2021 15:14:48 +0900 Subject: [PATCH 146/286] Add associations for access via console --- app/models/competition.rb | 1 + app/models/prediction.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/competition.rb b/app/models/competition.rb index 5169ccb..47c6eb5 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -6,6 +6,7 @@ class Competition < ApplicationRecord has_many :affiliations, through: :groups has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy + has_many :predictions, through: :matches, dependent: :destroy validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true diff --git a/app/models/prediction.rb b/app/models/prediction.rb index 4396e08..b55f6bd 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -1,5 +1,6 @@ class Prediction < ApplicationRecord belongs_to :match + has_one :competition, through: :match belongs_to :user validates_uniqueness_of :user, scope: :match validates :choice, presence: true From ef4768d57e34995d872a64a0c8cf2812006d1875 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 3 Jun 2021 19:44:09 +0900 Subject: [PATCH 147/286] Add competitions#index --- app/controllers/v1/competitions_controller.rb | 7 +++++++ app/policies/competition_policy.rb | 7 +++++++ app/views/v1/competitions/_competition.json.jbuilder | 1 + app/views/v1/competitions/index.json.jbuilder | 3 +++ app/views/v1/competitions/show.json.jbuilder | 1 + config/routes.rb | 2 +- 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 app/controllers/v1/competitions_controller.rb create mode 100644 app/policies/competition_policy.rb create mode 100644 app/views/v1/competitions/_competition.json.jbuilder create mode 100644 app/views/v1/competitions/index.json.jbuilder create mode 100644 app/views/v1/competitions/show.json.jbuilder diff --git a/app/controllers/v1/competitions_controller.rb b/app/controllers/v1/competitions_controller.rb new file mode 100644 index 0000000..8d6872a --- /dev/null +++ b/app/controllers/v1/competitions_controller.rb @@ -0,0 +1,7 @@ +class V1::CompetitionsController < ApplicationController + skip_before_action :authenticate_user!, only: :index + + def index + @competitions = policy_scope(Competition) + end +end diff --git a/app/policies/competition_policy.rb b/app/policies/competition_policy.rb new file mode 100644 index 0000000..ebcd7c5 --- /dev/null +++ b/app/policies/competition_policy.rb @@ -0,0 +1,7 @@ +class CompetitionPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope.all + end + end +end diff --git a/app/views/v1/competitions/_competition.json.jbuilder b/app/views/v1/competitions/_competition.json.jbuilder new file mode 100644 index 0000000..52cdebf --- /dev/null +++ b/app/views/v1/competitions/_competition.json.jbuilder @@ -0,0 +1 @@ +json.extract! competition, :id, :name, :start_date, :end_date, :current_round_id diff --git a/app/views/v1/competitions/index.json.jbuilder b/app/views/v1/competitions/index.json.jbuilder new file mode 100644 index 0000000..8d8ccb7 --- /dev/null +++ b/app/views/v1/competitions/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @competitions do |competition| + json.partial! competition +end diff --git a/app/views/v1/competitions/show.json.jbuilder b/app/views/v1/competitions/show.json.jbuilder new file mode 100644 index 0000000..e6f12ac --- /dev/null +++ b/app/views/v1/competitions/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! @competition diff --git a/config/routes.rb b/config/routes.rb index a294608..3e82843 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,7 @@ mount Sidekiq::Web => '/sidekiq' end namespace :v1, defaults: { format: :json } do - resources :competitions, only: [:show] do + resources :competitions, only: [:index] do resources :leaderboards, only: [:index, :create] end resources :matches, only: [:index], shallow: true do From fdf9c2ac526f4b74364d626626d7e2441c36f4ad Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 4 Jun 2021 21:06:44 +0900 Subject: [PATCH 148/286] added attached photo --- app/models/user.rb | 1 + app/views/v1/users/_user.json.jbuilder | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 0dd3204..0da6181 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,7 @@ class User < ApplicationRecord # has_many :competitions, through: :leaderboards has_many :predictions, dependent: :destroy has_many :matches, through: :predictions + has_one_attached :photo def leaderboards(competition = nil) # this includes creator or leaderboard and members diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder index 9ebb910..a47d8e8 100644 --- a/app/views/v1/users/_user.json.jbuilder +++ b/app/views/v1/users/_user.json.jbuilder @@ -1 +1,2 @@ json.extract! user, :id, :name, :email, :timezone, :admin +json.photo_url cl_image_path(user.photo.key) if user.photo.attached? From e6de301a52338311c1be30091c49fca7f06717ed Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 4 Jun 2021 21:54:53 +0900 Subject: [PATCH 149/286] added repo actions --- app/views/v1/users/_user.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder index a47d8e8..b596806 100644 --- a/app/views/v1/users/_user.json.jbuilder +++ b/app/views/v1/users/_user.json.jbuilder @@ -1,2 +1,2 @@ json.extract! user, :id, :name, :email, :timezone, :admin -json.photo_url cl_image_path(user.photo.key) if user.photo.attached? +json.photo_key user.photo.key if user.photo.attached? From 51c378f86f68e35040c290e352c46529da10e72b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 4 Jun 2021 22:22:22 +0900 Subject: [PATCH 150/286] upload working --- app/controllers/v1/users_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index 0c68646..3f921b0 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -1,8 +1,15 @@ +require 'open-uri' + class V1::UsersController < ApplicationController def update @user = current_user authorize @user - if @user.update(prediction_params) + + if params[:user][:photo_url] + file = URI.open(params[:user][:photo_url]) + @user.photo.attach(io: file, filename: 'profile.png', content_type: 'image/png') + render :show + elsif @user.update(prediction_params) render :show else render_error(@user) From f5ef128036408889580e5fdcd1e74ae17915beee Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 4 Jun 2021 23:22:09 +0900 Subject: [PATCH 151/286] added cl_key to prediction user --- app/views/v1/leaderboards/index.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 91b2074..7db3384 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -4,6 +4,7 @@ json.array! @leaderboards do |leaderboard| json.user_id user.id json.name user.display_name json.points user.score(leaderboard.competition) + json.photo_key user.photo.key if user.photo.attached? end end From 3158e48ed11eedd9188c8f3ba83e8022a41be8d6 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 4 Jun 2021 23:24:50 +0900 Subject: [PATCH 152/286] Fix selecting all matches --- app/controllers/v1/matches_controller.rb | 30 ++++++++++++++++++++++-- app/views/v1/matches/index.json.jbuilder | 2 +- db/seeds.rb | 14 +++++------ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 964bb7b..7588f65 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -9,8 +9,34 @@ def index .left_outer_joins(:predictions) .includes(:team_away, :team_home, :predictions) .where(group: @competition.groups) - .where('predictions.user_id = ? OR predictions.user_id IS NULL', @user.id) - .order(:kickoff_time) + + # TODO: Rewrite the query to avoid iterating over each match to get the predictions + + # # ORIGINAL ATTEMPT + # # The following query doesn't work because if any use makes a prediction on a match, + # # the predictions.user_id in the joined table is neither the current user's id, nor + # # is it NULL. The solution would be to make a SQL UNION query (not possible in AR). + # policy_scope(Match).joins(:team_away, :team_home) + # .left_outer_joins(:predictions) + # .includes(:team_away, :team_home, :predictions) + # .where(group: @competition.groups) + # .where('predictions.user_id = ? OR predictions.user_id IS NULL', @user.id) + # .order(:kickoff_time) + + # # NEW ATTEMPT + # matches_with_predictions = policy_scope(Match).joins(:team_away, :team_home) + # .joins(:predictions) + # .includes(:team_away, :team_home, :predictions) + # .where(group: @competition.groups) + # .where('predictions.user_id = ?', @user.id) + + # matches_without_predictions = policy_scope(Match).joins(:team_away, :team_home) + # .includes(:team_away, :team_home, :predictions) + # .where(group: @competition.groups) + # .where.not(matches: { id: matches_with_predictions }) + + # sql_query = "(#{matches_with_predictions.to_sql}) UNION (#{matches_without_predictions.to_sql})" + # policy_scope(Match).execute_sql(sql_query) else policy_scope([:user, Match]).order(:kickoff_time) end diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 8bea0fd..b05cc12 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -8,5 +8,5 @@ json.array! @matches do |match| json.partial! match.team_away json.score match.team_away_score if match.finished? end - json.prediction { json.partial! match.predictions.first } if match.predictions.any? + json.prediction { json.partial! match.predictions.find_by(user: @user) } if match.predictions.find_by(user: @user) end diff --git a/db/seeds.rb b/db/seeds.rb index b548a9d..31ee880 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -113,10 +113,10 @@ ScrapeMatchesService.new.call -puts 'Assigning random scores to matches before June 22nd' -Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| - match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) -end -# Needed when migrating status enum from integer to string: -Match.where('kickoff_time >= ?', Date.new(2021, 6, 22)).update(status: :upcoming) -puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" +# puts 'Assigning random scores to matches before June 22nd' +# Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| +# match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) +# end +# # Needed when migrating status enum from integer to string: +# Match.where('kickoff_time >= ?', Date.new(2021, 6, 22)).update(status: :upcoming) +# puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" From bcb609e9cb5b983581064fdb44225d94e321d8c0 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 4 Jun 2021 23:29:14 +0900 Subject: [PATCH 153/286] Restore ordering of matches --- app/controllers/v1/matches_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 7588f65..65c65f3 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -9,6 +9,7 @@ def index .left_outer_joins(:predictions) .includes(:team_away, :team_home, :predictions) .where(group: @competition.groups) + .order(:kickoff_time) # TODO: Rewrite the query to avoid iterating over each match to get the predictions From fd97ced4b673b4f13d8cd823dcaeb16ef26a2ade Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 4 Jun 2021 23:51:20 +0900 Subject: [PATCH 154/286] added photo url to users --- app/controllers/v1/users_controller.rb | 8 ++------ app/models/user.rb | 1 - db/migrate/20210604145056_add_photo_url_to_users.rb | 5 +++++ db/schema.rb | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20210604145056_add_photo_url_to_users.rb diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index 3f921b0..42bf302 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -5,11 +5,7 @@ def update @user = current_user authorize @user - if params[:user][:photo_url] - file = URI.open(params[:user][:photo_url]) - @user.photo.attach(io: file, filename: 'profile.png', content_type: 'image/png') - render :show - elsif @user.update(prediction_params) + if @user.update(prediction_params) render :show else render_error(@user) @@ -19,6 +15,6 @@ def update private def prediction_params - params.require(:user).permit(:name, :timezone) + params.require(:user).permit(:name, :timezone, :photo_url) end end diff --git a/app/models/user.rb b/app/models/user.rb index 0da6181..0dd3204 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,7 +9,6 @@ class User < ApplicationRecord # has_many :competitions, through: :leaderboards has_many :predictions, dependent: :destroy has_many :matches, through: :predictions - has_one_attached :photo def leaderboards(competition = nil) # this includes creator or leaderboard and members diff --git a/db/migrate/20210604145056_add_photo_url_to_users.rb b/db/migrate/20210604145056_add_photo_url_to_users.rb new file mode 100644 index 0000000..4c5d9ba --- /dev/null +++ b/db/migrate/20210604145056_add_photo_url_to_users.rb @@ -0,0 +1,5 @@ +class AddPhotoUrlToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :photo_url, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e0f4fb2..4337f25 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: 2021_06_02_120404) do +ActiveRecord::Schema.define(version: 2021_06_04_145056) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -160,6 +160,7 @@ t.datetime "last_sign_in_at" t.inet "current_sign_in_ip" t.inet "last_sign_in_ip" + t.string "photo_url" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From fc499c5ea5d03a32518520e5d9caa0c6cef2b442 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 4 Jun 2021 23:52:27 +0900 Subject: [PATCH 155/286] updated user json --- app/views/v1/leaderboards/index.json.jbuilder | 2 +- app/views/v1/users/_user.json.jbuilder | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 7db3384..661d922 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -4,7 +4,7 @@ json.array! @leaderboards do |leaderboard| json.user_id user.id json.name user.display_name json.points user.score(leaderboard.competition) - json.photo_key user.photo.key if user.photo.attached? + json.photo_url user.photo_url end end diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder index b596806..64eaae3 100644 --- a/app/views/v1/users/_user.json.jbuilder +++ b/app/views/v1/users/_user.json.jbuilder @@ -1,2 +1 @@ -json.extract! user, :id, :name, :email, :timezone, :admin -json.photo_key user.photo.key if user.photo.attached? +json.extract! user, :id, :name, :email, :timezone, :admin, :photo_url From ad4d357a6944eac19b2423c3eff9d942597f34af Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 5 Jun 2021 00:40:58 +0900 Subject: [PATCH 156/286] sending photo key instead of photo url --- app/controllers/v1/users_controller.rb | 2 +- app/views/v1/leaderboards/index.json.jbuilder | 2 +- app/views/v1/users/_user.json.jbuilder | 2 +- db/migrate/20210604145056_add_photo_url_to_users.rb | 5 ----- db/migrate/20210604153807_add_photo_key_to_users.rb | 5 +++++ db/schema.rb | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 db/migrate/20210604145056_add_photo_url_to_users.rb create mode 100644 db/migrate/20210604153807_add_photo_key_to_users.rb diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index 42bf302..8fb6c32 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -15,6 +15,6 @@ def update private def prediction_params - params.require(:user).permit(:name, :timezone, :photo_url) + params.require(:user).permit(:name, :timezone, :photo_key) end end diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 661d922..7483d58 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -4,7 +4,7 @@ json.array! @leaderboards do |leaderboard| json.user_id user.id json.name user.display_name json.points user.score(leaderboard.competition) - json.photo_url user.photo_url + json.photo_key user.photo_key end end diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder index 64eaae3..d2f7dbf 100644 --- a/app/views/v1/users/_user.json.jbuilder +++ b/app/views/v1/users/_user.json.jbuilder @@ -1 +1 @@ -json.extract! user, :id, :name, :email, :timezone, :admin, :photo_url +json.extract! user, :id, :name, :email, :timezone, :admin, :photo_key diff --git a/db/migrate/20210604145056_add_photo_url_to_users.rb b/db/migrate/20210604145056_add_photo_url_to_users.rb deleted file mode 100644 index 4c5d9ba..0000000 --- a/db/migrate/20210604145056_add_photo_url_to_users.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddPhotoUrlToUsers < ActiveRecord::Migration[6.1] - def change - add_column :users, :photo_url, :string - end -end diff --git a/db/migrate/20210604153807_add_photo_key_to_users.rb b/db/migrate/20210604153807_add_photo_key_to_users.rb new file mode 100644 index 0000000..c9de259 --- /dev/null +++ b/db/migrate/20210604153807_add_photo_key_to_users.rb @@ -0,0 +1,5 @@ +class AddPhotoKeyToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :photo_key, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 4337f25..25ad7ca 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: 2021_06_04_145056) do +ActiveRecord::Schema.define(version: 2021_06_04_153807) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -160,7 +160,7 @@ t.datetime "last_sign_in_at" t.inet "current_sign_in_ip" t.inet "last_sign_in_ip" - t.string "photo_url" + t.string "photo_key" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From f83650bced92192cc16b2daf419e8dec6b86c4ac Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sat, 5 Jun 2021 09:15:40 +0900 Subject: [PATCH 157/286] Refactor user prediction --- app/views/v1/matches/index.json.jbuilder | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index b05cc12..7ef412a 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -8,5 +8,6 @@ json.array! @matches do |match| json.partial! match.team_away json.score match.team_away_score if match.finished? end - json.prediction { json.partial! match.predictions.find_by(user: @user) } if match.predictions.find_by(user: @user) + user_prediction = match.predictions.find_by(user: @user) + json.prediction { json.partial! user_prediction } if user_prediction end From 5c22a8c001596e053faa4242f9cd829027d652a4 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 18:39:08 +0900 Subject: [PATCH 158/286] created migration to add api_id to competition --- app/jobs/match_update_all_job.rb | 9 +++++++++ .../20210606093702_add_api_id_to_competition.rb | 12 ++++++++++++ test/jobs/match_update_all_job_test.rb | 7 +++++++ 3 files changed, 28 insertions(+) create mode 100644 app/jobs/match_update_all_job.rb create mode 100644 db/migrate/20210606093702_add_api_id_to_competition.rb create mode 100644 test/jobs/match_update_all_job_test.rb diff --git a/app/jobs/match_update_all_job.rb b/app/jobs/match_update_all_job.rb new file mode 100644 index 0000000..b957a06 --- /dev/null +++ b/app/jobs/match_update_all_job.rb @@ -0,0 +1,9 @@ +class MatchUpdateAllJob < ApplicationJob + queue_as :default + + def perform + competition_id = 387 + response = HTTParty.get("http://livescore-api.com/api-client/countries/list.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}").body + countries = JSON.parse(response)['data']['country'] + end +end diff --git a/db/migrate/20210606093702_add_api_id_to_competition.rb b/db/migrate/20210606093702_add_api_id_to_competition.rb new file mode 100644 index 0000000..4382c92 --- /dev/null +++ b/db/migrate/20210606093702_add_api_id_to_competition.rb @@ -0,0 +1,12 @@ +class AddApiIdToCompetition < ActiveRecord::Migration[6.1] + def change + add_column :competitions, :api_id, :integer + Competition.find_each do |competition| + next if api_id + + # Live-Score API ID for the the Euros + competition.api_id = 387 + competition.save + end + end +end diff --git a/test/jobs/match_update_all_job_test.rb b/test/jobs/match_update_all_job_test.rb new file mode 100644 index 0000000..06db8bf --- /dev/null +++ b/test/jobs/match_update_all_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MatchUpdateAllJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From ccedddd70cdd06b98bcec762c327c1018b4bbdb9 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 18:49:48 +0900 Subject: [PATCH 159/286] added api_id to competition --- app/jobs/match_update_all_job.rb | 8 +++++--- app/models/competition.rb | 1 + db/migrate/20210606093702_add_api_id_to_competition.rb | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/jobs/match_update_all_job.rb b/app/jobs/match_update_all_job.rb index b957a06..b1246cb 100644 --- a/app/jobs/match_update_all_job.rb +++ b/app/jobs/match_update_all_job.rb @@ -2,8 +2,10 @@ class MatchUpdateAllJob < ApplicationJob queue_as :default def perform - competition_id = 387 - response = HTTParty.get("http://livescore-api.com/api-client/countries/list.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}").body - countries = JSON.parse(response)['data']['country'] + competitions = Competition.on_going + competitions.each do |competition| + response = HTTParty.get("http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}").body + countries = JSON.parse(response)['data']['country'] + end end end diff --git a/app/models/competition.rb b/app/models/competition.rb index 47c6eb5..5d1e945 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -10,4 +10,5 @@ class Competition < ApplicationRecord validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true + scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today - 1, end: Date.today + 1) } end diff --git a/db/migrate/20210606093702_add_api_id_to_competition.rb b/db/migrate/20210606093702_add_api_id_to_competition.rb index 4382c92..8f3e57c 100644 --- a/db/migrate/20210606093702_add_api_id_to_competition.rb +++ b/db/migrate/20210606093702_add_api_id_to_competition.rb @@ -2,7 +2,7 @@ class AddApiIdToCompetition < ActiveRecord::Migration[6.1] def change add_column :competitions, :api_id, :integer Competition.find_each do |competition| - next if api_id + next if competition.api_id # Live-Score API ID for the the Euros competition.api_id = 387 From 298b964918919b63dab362819f90e18d47c919b7 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:00:43 +0900 Subject: [PATCH 160/286] iterating over the matches --- app/jobs/match_update_all_job.rb | 6 +++++- db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/jobs/match_update_all_job.rb b/app/jobs/match_update_all_job.rb index b1246cb..8bd88f7 100644 --- a/app/jobs/match_update_all_job.rb +++ b/app/jobs/match_update_all_job.rb @@ -5,7 +5,11 @@ def perform competitions = Competition.on_going competitions.each do |competition| response = HTTParty.get("http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}").body - countries = JSON.parse(response)['data']['country'] + matches = JSON.parse(response)['data']['match'] + matches.each do |match| + p match + puts + end end end end diff --git a/db/schema.rb b/db/schema.rb index 25ad7ca..8f34b21 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: 2021_06_04_153807) do +ActiveRecord::Schema.define(version: 2021_06_06_093702) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -59,6 +59,7 @@ t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.bigint "current_round_id" + t.integer "api_id" t.index ["current_round_id"], name: "index_competitions_on_current_round_id" end From fb0c8cfc43985579469653acb1ce99bbb33dffb7 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:02:02 +0900 Subject: [PATCH 161/286] added api_id to match --- db/migrate/20210606100056_add_api_id_to_match.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20210606100056_add_api_id_to_match.rb diff --git a/db/migrate/20210606100056_add_api_id_to_match.rb b/db/migrate/20210606100056_add_api_id_to_match.rb new file mode 100644 index 0000000..7fe6315 --- /dev/null +++ b/db/migrate/20210606100056_add_api_id_to_match.rb @@ -0,0 +1,5 @@ +class AddApiIdToMatch < ActiveRecord::Migration[6.1] + def change + add_column :matches, :api_id, :integer + end +end From 45d467aaae9c9300c96cc90316bdf94dc5b300ec Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:07:48 +0900 Subject: [PATCH 162/286] created job to update matches --- app/jobs/match_update_job.rb | 14 ++++++++++++++ db/migrate/20210606100056_add_api_id_to_match.rb | 1 + test/jobs/match_update_job_test.rb | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100644 app/jobs/match_update_job.rb create mode 100644 test/jobs/match_update_job_test.rb diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb new file mode 100644 index 0000000..2ab78d8 --- /dev/null +++ b/app/jobs/match_update_job.rb @@ -0,0 +1,14 @@ +class MatchUpdateJob < ApplicationJob + queue_as :default + + def perform + Competition.find_each do |competition| + response = HTTParty.get("https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}").body + matches = JSON.parse(response)['data']['match'] + matches.each do |match| + p match + puts + end + end + end +end diff --git a/db/migrate/20210606100056_add_api_id_to_match.rb b/db/migrate/20210606100056_add_api_id_to_match.rb index 7fe6315..118bc6a 100644 --- a/db/migrate/20210606100056_add_api_id_to_match.rb +++ b/db/migrate/20210606100056_add_api_id_to_match.rb @@ -1,5 +1,6 @@ class AddApiIdToMatch < ActiveRecord::Migration[6.1] def change add_column :matches, :api_id, :integer + MatchUpdateJob.perform_now end end diff --git a/test/jobs/match_update_job_test.rb b/test/jobs/match_update_job_test.rb new file mode 100644 index 0000000..a6c451b --- /dev/null +++ b/test/jobs/match_update_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MatchUpdateJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From 76dd9b366395dc013f05528dac2456da0c659a15 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:27:59 +0900 Subject: [PATCH 163/286] put update actions in own methods --- app/jobs/match_update_job.rb | 39 ++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb index 2ab78d8..943bc2a 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_job.rb @@ -1,14 +1,37 @@ class MatchUpdateJob < ApplicationJob queue_as :default - def perform - Competition.find_each do |competition| - response = HTTParty.get("https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}").body - matches = JSON.parse(response)['data']['match'] - matches.each do |match| - p match - puts - end + def perform(competition_id) + counter = 0 + competition = Competition.find(competition_id) + url_to_update = "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}" + while url_to_update + url_to_update = update_matches(url_to_update) + counter += 1 end + counter + end + + def get_home_team(match) + Team.find_by(name: match['home_name']) + end + + def get_away_team(match) + Team.find_by(name: match['away_name']) + end + + def update_matches(url) + response = HTTParty.get(url).body + matches = JSON.parse(response)['data']['fixtures'] + matches.each do |match| + match = Match.find_by(id: match['id']) || Match.find_by(team_home: get_home_team(match), team_away: get_away_team(match)) + p match + # match.id = match['id']) + # match.kickoff_time = + # team_home_score = nil + # team_away_score = nil + # status = "upcoming" + end + return response['data']['next_page'] end end From ced79a44ddf19a125cf9e034ea8f42fba740fdf7 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:33:35 +0900 Subject: [PATCH 164/286] added rake task to update macedonia --- db/seeds.rb | 2 +- lib/tasks/team.rake | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index 31ee880..808602e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -32,7 +32,7 @@ 'Group C' => [ { name: 'Austria', abbrev: 'AUT' }, { name: 'Netherlands', abbrev: 'NED' }, - { name: 'North Macedonia', abbrev: 'MKD' }, + { name: 'FYR Macedonia', abbrev: 'MKD' }, { name: 'Ukraine', abbrev: 'UKR' } ], 'Group D' => [ diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index a8d34b1..fd01dfd 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -30,4 +30,11 @@ namespace :team do team.flag.attach(io: file, filename: 'flag.png', content_type: 'image/png') puts team.flag.attached? ? 'Success' : 'Failed' end + + desc "Changes North Macedonia to FYR Macedonia" + task update_macedonia: :environment do + team = Team.find_by(name: 'North Macedonia') + team.name = 'FYR Macedonia' if team + team.save + end end From 88d21d84346fb7b2e4e1d2ebf9d17518312a453a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:39:44 +0900 Subject: [PATCH 165/286] iterating over matches from api --- app/jobs/match_update_job.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb index 943bc2a..e91a727 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_job.rb @@ -12,26 +12,29 @@ def perform(competition_id) counter end - def get_home_team(match) - Team.find_by(name: match['home_name']) + def get_home_team(match_info) + Team.find_by(name: match_info['home_name']) end - def get_away_team(match) - Team.find_by(name: match['away_name']) + def get_away_team(match_info) + Team.find_by(name: match_info['away_name']) end def update_matches(url) response = HTTParty.get(url).body - matches = JSON.parse(response)['data']['fixtures'] - matches.each do |match| - match = Match.find_by(id: match['id']) || Match.find_by(team_home: get_home_team(match), team_away: get_away_team(match)) - p match + parsed_response = JSON.parse(response)['data'] + matches = parsed_response['fixtures'] + matches.each do |match_info| + match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_home_team(match_info), team_away: get_away_team(match_info)) + next unless match # knock-out rounds with no teams + + p match['id'] # match.id = match['id']) # match.kickoff_time = # team_home_score = nil # team_away_score = nil # status = "upcoming" end - return response['data']['next_page'] + p parsed_response['next_page'] end end From 46eaff7cce690f6ef3d9e052132067e390baf7ab Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:49:03 +0900 Subject: [PATCH 166/286] updating matches in job --- app/jobs/match_update_job.rb | 15 +++++---------- db/migrate/20210606100056_add_api_id_to_match.rb | 1 + 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb index e91a727..b1fc68e 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_job.rb @@ -2,14 +2,11 @@ class MatchUpdateJob < ApplicationJob queue_as :default def perform(competition_id) - counter = 0 competition = Competition.find(competition_id) url_to_update = "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}" while url_to_update url_to_update = update_matches(url_to_update) - counter += 1 end - counter end def get_home_team(match_info) @@ -28,13 +25,11 @@ def update_matches(url) match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_home_team(match_info), team_away: get_away_team(match_info)) next unless match # knock-out rounds with no teams - p match['id'] - # match.id = match['id']) - # match.kickoff_time = - # team_home_score = nil - # team_away_score = nil - # status = "upcoming" + match.id = match_info['id'] + match.location = match_info['location'] + match.kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") + match.save end - p parsed_response['next_page'] + return parsed_response['next_page'] end end diff --git a/db/migrate/20210606100056_add_api_id_to_match.rb b/db/migrate/20210606100056_add_api_id_to_match.rb index 118bc6a..34d521d 100644 --- a/db/migrate/20210606100056_add_api_id_to_match.rb +++ b/db/migrate/20210606100056_add_api_id_to_match.rb @@ -1,6 +1,7 @@ class AddApiIdToMatch < ActiveRecord::Migration[6.1] def change add_column :matches, :api_id, :integer + add_column :matches, :location, :string MatchUpdateJob.perform_now end end From ef1c8c91325125cf768e9ac4a3a976e24fd4d771 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:55:49 +0900 Subject: [PATCH 167/286] created a rake task to update match by competition --- db/migrate/20210606100056_add_api_id_to_match.rb | 1 - db/schema.rb | 4 +++- lib/tasks/competition.rake | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/db/migrate/20210606100056_add_api_id_to_match.rb b/db/migrate/20210606100056_add_api_id_to_match.rb index 34d521d..d9124f5 100644 --- a/db/migrate/20210606100056_add_api_id_to_match.rb +++ b/db/migrate/20210606100056_add_api_id_to_match.rb @@ -2,6 +2,5 @@ class AddApiIdToMatch < ActiveRecord::Migration[6.1] def change add_column :matches, :api_id, :integer add_column :matches, :location, :string - MatchUpdateJob.perform_now end end diff --git a/db/schema.rb b/db/schema.rb index 8f34b21..ffdb22a 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: 2021_06_06_093702) do +ActiveRecord::Schema.define(version: 2021_06_06_100056) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -94,6 +94,8 @@ t.datetime "updated_at", precision: 6, null: false t.bigint "next_match_id" t.bigint "round_id" + t.integer "api_id" + t.string "location" t.index ["group_id"], name: "index_matches_on_group_id" t.index ["next_match_id"], name: "index_matches_on_next_match_id" t.index ["round_id"], name: "index_matches_on_round_id" diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 823ee9f..700727a 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -1,4 +1,10 @@ namespace :competition do + desc "Update upcoming fixtures for a competition" + task :update_matches, [:competition_id] => :environment do |t, args| + competition = Competition.find(args[:competition_id]) + MatchUpdateJob.perform_now(competition.id) + end + desc "Copy the first competition and start it today" task copy: :environment do euros = Competition.find_or_create_by!(name: 'Euro 2020') From ecdd10292f37544d05e5608d37e60b80a1a93b51 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 19:59:14 +0900 Subject: [PATCH 168/286] updated wrong attribute being updated --- app/jobs/match_update_job.rb | 3 ++- db/migrate/20210606100056_add_api_id_to_match.rb | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb index b1fc68e..4f23895 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_job.rb @@ -22,10 +22,11 @@ def update_matches(url) parsed_response = JSON.parse(response)['data'] matches = parsed_response['fixtures'] matches.each do |match_info| + puts "Updating the matches between : #{match_info['home_name']} v #{match_info['away_name']}" match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_home_team(match_info), team_away: get_away_team(match_info)) next unless match # knock-out rounds with no teams - match.id = match_info['id'] + match.api_id = match_info['id'] match.location = match_info['location'] match.kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") match.save diff --git a/db/migrate/20210606100056_add_api_id_to_match.rb b/db/migrate/20210606100056_add_api_id_to_match.rb index d9124f5..4485065 100644 --- a/db/migrate/20210606100056_add_api_id_to_match.rb +++ b/db/migrate/20210606100056_add_api_id_to_match.rb @@ -2,5 +2,8 @@ class AddApiIdToMatch < ActiveRecord::Migration[6.1] def change add_column :matches, :api_id, :integer add_column :matches, :location, :string + Competition.find_each do |competition| + MatchUpdateJob.perform_now(competition.id) + end end end From e56cffca877aecfe1dd18549ef95ae8ea1b28f17 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 20:07:12 +0900 Subject: [PATCH 169/286] updated the job to find by id instead of name --- app/jobs/match_update_job.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb index 4f23895..e2fdfe0 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_job.rb @@ -9,12 +9,8 @@ def perform(competition_id) end end - def get_home_team(match_info) - Team.find_by(name: match_info['home_name']) - end - - def get_away_team(match_info) - Team.find_by(name: match_info['away_name']) + def get_team(id) + Team.find_by(api_id: id) end def update_matches(url) @@ -22,14 +18,15 @@ def update_matches(url) parsed_response = JSON.parse(response)['data'] matches = parsed_response['fixtures'] matches.each do |match_info| - puts "Updating the matches between : #{match_info['home_name']} v #{match_info['away_name']}" - match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_home_team(match_info), team_away: get_away_team(match_info)) + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']}" + match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id'])) next unless match # knock-out rounds with no teams match.api_id = match_info['id'] match.location = match_info['location'] match.kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") match.save + puts 'Match Update' end return parsed_response['next_page'] end From dd926f768eb1c1b5a37c56ebfd81c7948aeb6346 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 20:13:18 +0900 Subject: [PATCH 170/286] Updated job names for the match api --- ...{match_update_job.rb => match_update_future_job.rb} | 2 +- ...h_update_all_job.rb => match_update_history_job.rb} | 6 +++--- db/migrate/20210606100056_add_api_id_to_match.rb | 2 +- lib/tasks/competition.rake | 10 ++++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) rename app/jobs/{match_update_job.rb => match_update_future_job.rb} (96%) rename app/jobs/{match_update_all_job.rb => match_update_history_job.rb} (74%) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_future_job.rb similarity index 96% rename from app/jobs/match_update_job.rb rename to app/jobs/match_update_future_job.rb index e2fdfe0..015d9d8 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -1,4 +1,4 @@ -class MatchUpdateJob < ApplicationJob +class MatchUpdateFutureJob < ApplicationJob queue_as :default def perform(competition_id) diff --git a/app/jobs/match_update_all_job.rb b/app/jobs/match_update_history_job.rb similarity index 74% rename from app/jobs/match_update_all_job.rb rename to app/jobs/match_update_history_job.rb index 8bd88f7..9b33036 100644 --- a/app/jobs/match_update_all_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -1,4 +1,4 @@ -class MatchUpdateAllJob < ApplicationJob +class MatchUpdateHistoryJob < ApplicationJob queue_as :default def perform @@ -6,8 +6,8 @@ def perform competitions.each do |competition| response = HTTParty.get("http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}").body matches = JSON.parse(response)['data']['match'] - matches.each do |match| - p match + matches.each do |match_info| + match = Match.find_by(api_id: match_info['id']) puts end end diff --git a/db/migrate/20210606100056_add_api_id_to_match.rb b/db/migrate/20210606100056_add_api_id_to_match.rb index 4485065..3b3086e 100644 --- a/db/migrate/20210606100056_add_api_id_to_match.rb +++ b/db/migrate/20210606100056_add_api_id_to_match.rb @@ -3,7 +3,7 @@ def change add_column :matches, :api_id, :integer add_column :matches, :location, :string Competition.find_each do |competition| - MatchUpdateJob.perform_now(competition.id) + MatchUpdateFutureJob.perform_now(competition.id) end end end diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 700727a..d6dc731 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -1,8 +1,14 @@ namespace :competition do desc "Update upcoming fixtures for a competition" - task :update_matches, [:competition_id] => :environment do |t, args| + task :update_matches_future, [:competition_id] => :environment do |t, args| competition = Competition.find(args[:competition_id]) - MatchUpdateJob.perform_now(competition.id) + MatchUpdateFutureJob.perform_now(competition.id) + end + + desc "Update upcoming fixtures for a competition" + task :update_matches_history, [:competition_id] => :environment do |t, args| + competition = Competition.find(args[:competition_id]) + MatchUpdateHistoryJob.perform_now(competition.id) end desc "Copy the first competition and start it today" From 17f150394e7111d466034dcb0fc2263531e1f663 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 20:37:51 +0900 Subject: [PATCH 171/286] refactor job --- app/jobs/match_update_future_job.rb | 4 +-- app/jobs/match_update_history_job.rb | 37 +++++++++++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index 015d9d8..5495e94 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -5,7 +5,7 @@ def perform(competition_id) competition = Competition.find(competition_id) url_to_update = "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}" while url_to_update - url_to_update = update_matches(url_to_update) + url_to_update = update_matches_future(url_to_update) end end @@ -13,7 +13,7 @@ def get_team(id) Team.find_by(api_id: id) end - def update_matches(url) + def update_matches_future(url) response = HTTParty.get(url).body parsed_response = JSON.parse(response)['data'] matches = parsed_response['fixtures'] diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 9b33036..7c97d27 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -1,15 +1,34 @@ class MatchUpdateHistoryJob < ApplicationJob queue_as :default - def perform - competitions = Competition.on_going - competitions.each do |competition| - response = HTTParty.get("http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}").body - matches = JSON.parse(response)['data']['match'] - matches.each do |match_info| - match = Match.find_by(api_id: match_info['id']) - puts - end + def perform(competition_id) + competition = Competition.find(competition_id) + url_to_update = "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}" + while url_to_update + url_to_update = update_matches_history(url_to_update) end end + + def get_team(id) + Team.find_by(api_id: id) + end + + def update_matches_history(url) + response = HTTParty.get(url).body + parsed_response = JSON.parse(response)['data'] + matches = parsed_response['match'] + matches.each do |match_info| + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']}" + match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id'])) + next unless match # knock-out rounds with no teams + + match.finished! + p scores = match_info['score'].split(' - ') + match.team_home_score = scores.first + match.team_away_score = score.second + match.save + puts 'Match Update' + end + return parsed_response['next_page'] + end end From 3a26e8c7dd95da865e35c725c59a12e803b65665 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 20:51:55 +0900 Subject: [PATCH 172/286] refactored the API endpoints to avoid copying and pasting --- app/jobs/match_update_future_job.rb | 2 +- app/jobs/match_update_history_job.rb | 2 +- app/services/live_score_api.rb | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 app/services/live_score_api.rb diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index 5495e94..be24f90 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -3,7 +3,7 @@ class MatchUpdateFutureJob < ApplicationJob def perform(competition_id) competition = Competition.find(competition_id) - url_to_update = "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}" + url_to_update = LiveScoreApi.matches_future_url(competition.api_id) while url_to_update url_to_update = update_matches_future(url_to_update) end diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 7c97d27..399ab59 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -3,7 +3,7 @@ class MatchUpdateHistoryJob < ApplicationJob def perform(competition_id) competition = Competition.find(competition_id) - url_to_update = "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition.api_id}" + url_to_update = LiveScoreApi.matches_history_url(competition.api_id) while url_to_update url_to_update = update_matches_history(url_to_update) end diff --git a/app/services/live_score_api.rb b/app/services/live_score_api.rb new file mode 100644 index 0000000..0e2da1b --- /dev/null +++ b/app/services/live_score_api.rb @@ -0,0 +1,9 @@ +class LiveScoreApi + def self.matches_future_url(competition_id) + "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}" + end + + def self.matches_history_url(competition_id) + "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}" + end +end From 3c8fc714ea58c9aebb3a5d2485b1cb3ec5d947ac Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 21:15:13 +0900 Subject: [PATCH 173/286] making sure the match matches the kickoff_time --- app/jobs/match_update_future_job.rb | 7 ++++--- app/jobs/match_update_history_job.rb | 7 ++++--- lib/tasks/competition.rake | 13 +++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index be24f90..d9c0db9 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -18,13 +18,14 @@ def update_matches_future(url) parsed_response = JSON.parse(response)['data'] matches = parsed_response['fixtures'] matches.each do |match_info| - puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']}" - match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id'])) + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" + match = Match.find_by(api_id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match # knock-out rounds with no teams match.api_id = match_info['id'] match.location = match_info['location'] - match.kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") + match.kickoff_time = kickoff_time match.save puts 'Match Update' end diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 399ab59..95d3485 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -18,14 +18,15 @@ def update_matches_history(url) parsed_response = JSON.parse(response)['data'] matches = parsed_response['match'] matches.each do |match_info| - puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']}" - match = Match.find_by(id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id'])) + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" + match = Match.find_by(api_id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match # knock-out rounds with no teams match.finished! p scores = match_info['score'].split(' - ') match.team_home_score = scores.first - match.team_away_score = score.second + match.team_away_score = scores.second match.save puts 'Match Update' end diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index d6dc731..76cb32d 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -1,14 +1,23 @@ namespace :competition do + desc "Update upcoming fixtures for on-going competitions" + task update_ongoing_matches: :environment do + competitions = Competition.on_going + competitions.each do |competition| + MatchUpdateFutureJob.perform_later(competition.id) + MatchUpdateHistoryJob.perform_later(competition.id) + end + end + desc "Update upcoming fixtures for a competition" task :update_matches_future, [:competition_id] => :environment do |t, args| competition = Competition.find(args[:competition_id]) - MatchUpdateFutureJob.perform_now(competition.id) + MatchUpdateFutureJob.perform_later(competition.id) end desc "Update upcoming fixtures for a competition" task :update_matches_history, [:competition_id] => :environment do |t, args| competition = Competition.find(args[:competition_id]) - MatchUpdateHistoryJob.perform_now(competition.id) + MatchUpdateHistoryJob.perform_later(competition.id) end desc "Copy the first competition and start it today" From c3f60573adbe235746f4921eff8fc6216038cc1d Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 6 Jun 2021 21:27:19 +0900 Subject: [PATCH 174/286] updated comments in job --- app/jobs/match_update_history_job.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 95d3485..f4d46c7 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -21,14 +21,14 @@ def update_matches_history(url) kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" match = Match.find_by(api_id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) - next unless match # knock-out rounds with no teams + next unless match match.finished! - p scores = match_info['score'].split(' - ') - match.team_home_score = scores.first - match.team_away_score = scores.second + scores = match_info['score'].split(' - ') + match.team_home_score = scores&.first + match.team_away_score = scores&.second match.save - puts 'Match Update' + puts "Match Update: #{match_info['score']}" end return parsed_response['next_page'] end From 8d7311be66b09c0946550d8d3ccc8950fe06fd8c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Mon, 7 Jun 2021 16:35:07 +0900 Subject: [PATCH 175/286] added a rake task to copy the production DB to development --- lib/tasks/heroku.rake | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/tasks/heroku.rake diff --git a/lib/tasks/heroku.rake b/lib/tasks/heroku.rake new file mode 100644 index 0000000..9de28cf --- /dev/null +++ b/lib/tasks/heroku.rake @@ -0,0 +1,18 @@ +namespace :heroku do + desc 'Drops development DB and replaces it with the production DB' + task pg_pull: :environment do + puts '-----> Setting the environment...' + run 'RAILS_ENV=development rails db:environment:set' + + puts '-----> dropping DB…' + run 'rails db:drop' + + puts '-----> pulling the DB...' + run 'heroku pg:pull postgresql-cubic-90889 predictor_api_development' + end + + def run(*cmd) + system(*cmd) + raise "Command #{cmd.inspect} failed!" unless $?.success? + end +end From a2cee6fb030b3cb213e1f9b58c76a19c7f49e50c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 9 Jun 2021 14:27:23 +0900 Subject: [PATCH 176/286] user can delete an empty leaderboard or remove their membership from a non-empty one --- app/controllers/v1/leaderboards_controller.rb | 2 +- app/models/leaderboard.rb | 17 +++++++++++++++++ app/policies/leaderboard_policy.rb | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index f9584e7..d5d2b64 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -21,7 +21,7 @@ def create def destroy @leaderboard = Leaderboard.find(params[:id]) authorize @leaderboard - @leaderboard.destroy + @leaderboard.leave(current_user) head :no_content end diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 0be68a3..7fd6f0c 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -9,4 +9,21 @@ class Leaderboard < ApplicationRecord def users User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: user)) end + + def leave(current_user) + if user == current_user + # remove if empty OR transfer + memberships.any? ? transfer_ownership : destroy + elsif (membership = memberships.find_by(user: current_user)) + # remove membership if just a regular member + membership.destroy + end + end + + def transfer_ownership + membership = memberships.first + membership.destroy + self.user = membership.user + save + end end diff --git a/app/policies/leaderboard_policy.rb b/app/policies/leaderboard_policy.rb index ec641fd..0b49f5e 100644 --- a/app/policies/leaderboard_policy.rb +++ b/app/policies/leaderboard_policy.rb @@ -10,6 +10,6 @@ def create? end def destroy? - record.user == user + record.user == user || record.memberships.find_by(user: user) end end From c7ac3622dc64a0741fb31768f79f7817569c6686 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 9 Jun 2021 21:41:12 +0900 Subject: [PATCH 177/286] scoping predictions by competition --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 0dd3204..76d3cb6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,6 +24,6 @@ def display_name def score(competition) # TODO: user predictions should be scoped by competition => prediction -> match -> group -> round -> competition # predictions.where(competition: competition).count(&:correct?) * 3 - predictions.count(&:correct?) * 3 + competition.predictions.count(&:correct?) * 3 end end From f940a52340f34f431673ddc91a54a00fed0976e3 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 10 Jun 2021 15:19:49 +0900 Subject: [PATCH 178/286] fixed score tally --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 76d3cb6..7776c14 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,6 +24,6 @@ def display_name def score(competition) # TODO: user predictions should be scoped by competition => prediction -> match -> group -> round -> competition # predictions.where(competition: competition).count(&:correct?) * 3 - competition.predictions.count(&:correct?) * 3 + competition.predictions.where(user: self).count(&:correct?) * 3 end end From f3bdd64c74a739b4291e29f02974a38093bce54f Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Thu, 10 Jun 2021 22:48:59 +0900 Subject: [PATCH 179/286] Access past predictions from other users --- app/controllers/v1/matches_controller.rb | 1 + app/controllers/v1/users_controller.rb | 6 ++++++ app/policies/prediction_policy.rb | 4 +++- app/policies/user_policy.rb | 4 ++++ app/views/v1/matches/index.json.jbuilder | 2 +- app/views/v1/users/_user.json.jbuilder | 3 ++- app/views/v1/users/show.json.jbuilder | 1 + 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 65c65f3..b0ec3ec 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -3,6 +3,7 @@ class V1::MatchesController < ApplicationController def index @user = User.find_by(id: params[:user_id]) || current_user @competition = Competition.find_by(id: params[:competition_id]) + @predictions = policy_scope(Prediction) @matches = if @competition policy_scope(Match).joins(:team_away, :team_home) diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index 8fb6c32..c759abf 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -1,6 +1,12 @@ require 'open-uri' class V1::UsersController < ApplicationController + def show + @user = User.find(params[:id]) + @competition = Competition.find(params[:competition_id]) if params[:competition_id] + authorize @user + end + def update @user = current_user authorize @user diff --git a/app/policies/prediction_policy.rb b/app/policies/prediction_policy.rb index af256ef..7787de4 100644 --- a/app/policies/prediction_policy.rb +++ b/app/policies/prediction_policy.rb @@ -1,7 +1,9 @@ class PredictionPolicy < ApplicationPolicy class Scope < Scope def resolve - scope.all + scope.joins(:match).where(user: user).or( + scope.joins(:match).where.not(user: user).where.not(matches: { status: :upcoming }) + ).distinct end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 38d94c8..be2db90 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -5,6 +5,10 @@ def resolve end end + def show? + true + end + def update? user == record end diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 7ef412a..b5f532e 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -8,6 +8,6 @@ json.array! @matches do |match| json.partial! match.team_away json.score match.team_away_score if match.finished? end - user_prediction = match.predictions.find_by(user: @user) + user_prediction = @predictions.where(match: match).find_by(user: @user) json.prediction { json.partial! user_prediction } if user_prediction end diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder index d2f7dbf..2a6d55a 100644 --- a/app/views/v1/users/_user.json.jbuilder +++ b/app/views/v1/users/_user.json.jbuilder @@ -1 +1,2 @@ -json.extract! user, :id, :name, :email, :timezone, :admin, :photo_key +json.extract! user, :id, :email, :timezone, :admin, :photo_key +json.name user.display_name diff --git a/app/views/v1/users/show.json.jbuilder b/app/views/v1/users/show.json.jbuilder index 51a8538..47dd4a6 100644 --- a/app/views/v1/users/show.json.jbuilder +++ b/app/views/v1/users/show.json.jbuilder @@ -1 +1,2 @@ json.partial! @user +json.points @user.score(@competition) if @competition From 0dfd74c0b6bd994f48e298b9e56678c83e2eab8c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Jun 2021 12:56:46 +0900 Subject: [PATCH 180/286] was reading the wrong value from the api kickofftime --- app/jobs/match_update_history_job.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index f4d46c7..1d4725f 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -4,8 +4,9 @@ class MatchUpdateHistoryJob < ApplicationJob def perform(competition_id) competition = Competition.find(competition_id) url_to_update = LiveScoreApi.matches_history_url(competition.api_id) + while url_to_update - url_to_update = update_matches_history(url_to_update) + url_to_update = update_matches_history(url_to_update, competition) end end @@ -13,14 +14,14 @@ def get_team(id) Team.find_by(api_id: id) end - def update_matches_history(url) + def update_matches_history(url, competition) response = HTTParty.get(url).body parsed_response = JSON.parse(response)['data'] matches = parsed_response['match'] matches.each do |match_info| - kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = Match.find_by(api_id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + match = competition.matches.find_by(api_id: match_info['id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match match.finished! @@ -28,6 +29,7 @@ def update_matches_history(url) match.team_home_score = scores&.first match.team_away_score = scores&.second match.save + puts "Match Update: #{match_info['score']}" end return parsed_response['next_page'] From 754b89670f7233e5562a7e3dd0962977cd39a44a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Jun 2021 12:57:31 +0900 Subject: [PATCH 181/286] removed useless line --- app/jobs/match_update_history_job.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 1d4725f..bb2e459 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -4,7 +4,6 @@ class MatchUpdateHistoryJob < ApplicationJob def perform(competition_id) competition = Competition.find(competition_id) url_to_update = LiveScoreApi.matches_history_url(competition.api_id) - while url_to_update url_to_update = update_matches_history(url_to_update, competition) end From 10d3e804143aab86b8d6c573f581ccd234b51998 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Jun 2021 20:15:22 +0900 Subject: [PATCH 182/286] logic was backwards for on_going compeitions --- app/models/competition.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index 5d1e945..c964e0f 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -10,5 +10,5 @@ class Competition < ApplicationRecord validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true - scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today - 1, end: Date.today + 1) } + scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today + 1, end: Date.today - 1) } end From 43e36f1030e811c7f1abb3befdbe49abef092704 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Jun 2021 21:29:13 +0900 Subject: [PATCH 183/286] added a daily job to check for day events --- app/jobs/match_started_job.rb | 8 ++++++++ app/jobs/schedule_daily_tasks_job.rb | 15 +++++++++++++++ test/jobs/match_started_job_test.rb | 7 +++++++ test/jobs/schedule_daily_tasks_job_test.rb | 7 +++++++ 4 files changed, 37 insertions(+) create mode 100644 app/jobs/match_started_job.rb create mode 100644 app/jobs/schedule_daily_tasks_job.rb create mode 100644 test/jobs/match_started_job_test.rb create mode 100644 test/jobs/schedule_daily_tasks_job_test.rb diff --git a/app/jobs/match_started_job.rb b/app/jobs/match_started_job.rb new file mode 100644 index 0000000..d362de9 --- /dev/null +++ b/app/jobs/match_started_job.rb @@ -0,0 +1,8 @@ +class MatchStartedJob < ApplicationJob + queue_as :default + + def perform(kickoff_time) + matches = Match.where(kickoff_time: kickoff_time) + matches.map(&:started!) + end +end diff --git a/app/jobs/schedule_daily_tasks_job.rb b/app/jobs/schedule_daily_tasks_job.rb new file mode 100644 index 0000000..831338d --- /dev/null +++ b/app/jobs/schedule_daily_tasks_job.rb @@ -0,0 +1,15 @@ +class ScheduleDailyTasksJob < ApplicationJob + queue_as :default + + def perform + competitions = Competition.on_going + competitions.each do |competition| + matches = competition.matches.where(kickoff_time: Date.today.all_day) + matches.pluck(:kickoff_time).uniq.each do |kickoff_time| + + # Update right after kickoff and after the game + MatchStartedJob.set(wait_until: kickoff_time).perform_later(kickoff_time) + MatchUpdateHistoryJob.set(wait_until: kickoff_time + 100.minutes).perform_later(competition.id) + end + end +end diff --git a/test/jobs/match_started_job_test.rb b/test/jobs/match_started_job_test.rb new file mode 100644 index 0000000..a696d00 --- /dev/null +++ b/test/jobs/match_started_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MatchStartedJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/jobs/schedule_daily_tasks_job_test.rb b/test/jobs/schedule_daily_tasks_job_test.rb new file mode 100644 index 0000000..e0ba225 --- /dev/null +++ b/test/jobs/schedule_daily_tasks_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ScheduleDailyTasksJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From 8f16748d0560915eba1693ea4242e16f78136a7b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Jun 2021 21:34:01 +0900 Subject: [PATCH 184/286] Added missing 'end' --- app/jobs/schedule_daily_tasks_job.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/jobs/schedule_daily_tasks_job.rb b/app/jobs/schedule_daily_tasks_job.rb index 831338d..81b14e3 100644 --- a/app/jobs/schedule_daily_tasks_job.rb +++ b/app/jobs/schedule_daily_tasks_job.rb @@ -6,10 +6,10 @@ def perform competitions.each do |competition| matches = competition.matches.where(kickoff_time: Date.today.all_day) matches.pluck(:kickoff_time).uniq.each do |kickoff_time| - - # Update right after kickoff and after the game - MatchStartedJob.set(wait_until: kickoff_time).perform_later(kickoff_time) - MatchUpdateHistoryJob.set(wait_until: kickoff_time + 100.minutes).perform_later(competition.id) + # Update right after kickoff and after the game + MatchStartedJob.set(wait_until: kickoff_time).perform_later(kickoff_time) + MatchUpdateHistoryJob.set(wait_until: kickoff_time + 100.minutes).perform_later(competition.id) + end end end end From 930cec154011d94ad28f68584e8f8bf0f20c6555 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Jun 2021 21:48:07 +0900 Subject: [PATCH 185/286] added the rake task to run the job --- lib/tasks/schedule.rake | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/tasks/schedule.rake diff --git a/lib/tasks/schedule.rake b/lib/tasks/schedule.rake new file mode 100644 index 0000000..3ea0582 --- /dev/null +++ b/lib/tasks/schedule.rake @@ -0,0 +1,7 @@ +namespace :schedule do + desc "Runs at midnight(~) to schedule daily background jobs" + task daily: :environment do + ScheduleDailyTasksJob.perform_later + end + +end From db1707955e2c84e7ec3c789cea8f91c2cccc54d5 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sun, 13 Jun 2021 00:22:25 +0900 Subject: [PATCH 186/286] Add sidekiq route behind BasicAuth --- config/routes.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 3e82843..8394d36 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,10 +2,18 @@ mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'auth/devise_token_auth/sessions' } - require "sidekiq/web" - authenticate :user, ->(user) { user.admin? } do - mount Sidekiq::Web => '/sidekiq' + + # Sidekiq Web UI, only for admins. + Sidekiq::Web.use(Rack::Auth::Basic) do |username, password| + ActiveSupport::SecurityUtils.secure_compare( + username, ENV['SIDEKIQ_USERNAME'] + ) && ActiveSupport::SecurityUtils.secure_compare( + password, ENV['SIDEKIQ_PASSWORD'] + ) end + + mount Sidekiq::Web => '/sidekiq' + namespace :v1, defaults: { format: :json } do resources :competitions, only: [:index] do resources :leaderboards, only: [:index, :create] From c4b8714701b74fcfe9d334d4118ff6b76f6e4784 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sun, 13 Jun 2021 00:29:05 +0900 Subject: [PATCH 187/286] Hide full email address from user --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7776c14..394162b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,7 +18,7 @@ def leaderboards(competition = nil) end def display_name - name || email + name || email.split('@').first end def score(competition) From 5a6c51a67ce65042ad66b76d7965ad71102af03d Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Sun, 13 Jun 2021 00:53:20 +0900 Subject: [PATCH 188/286] Add sessions middleware --- config/application.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/application.rb b/config/application.rb index c89e186..03fc6fc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -37,5 +37,10 @@ class Application < Rails::Application # Skip views, helpers and assets when generating a new resource. config.api_only = true config.active_job.queue_adapter = :sidekiq + + # Adding back session middleware for Sidekiq::Web + config.session_store :cookie_store, key: '_interslice_session' + config.middleware.use ActionDispatch::Cookies + config.middleware.use config.session_store, config.session_options end end From 8e27ad12dd7ee85fcfe4a667f13afcf6abcebc96 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 13 Jun 2021 21:37:12 +0900 Subject: [PATCH 189/286] apparently the ids change to fixuture_id when they go in the past --- app/jobs/match_update_history_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index bb2e459..fd215d6 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -20,7 +20,7 @@ def update_matches_history(url, competition) matches.each do |match_info| kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = competition.matches.find_by(api_id: match_info['id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match match.finished! From c77eff51aeaa2a4ac3597e5e4a42cb24276065a5 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 15 Jun 2021 12:08:00 +0900 Subject: [PATCH 190/286] Added scout gem to track memory --- Gemfile | 1 + Gemfile.lock | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 65a644e..a33203d 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'faker' gem 'httparty' gem 'omniauth' gem 'pundit' +gem 'scout_apm' gem 'sidekiq' gem 'sidekiq-failures', '~> 1.0' gem 'watir', '6.16.5' diff --git a/Gemfile.lock b/Gemfile.lock index 99b15d0..61ebdc1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,7 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) + ast (2.4.2) aws_cf_signer (0.1.3) bcrypt (3.1.16) bootsnap (1.7.3) @@ -156,6 +157,8 @@ GEM hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) orm_adapter (0.5.0) + parser (3.0.1.1) + ast (~> 2.4.1) pg (1.2.3) pry (0.13.1) coderay (~> 1.1) @@ -217,6 +220,8 @@ GEM netrc (~> 0.8) ruby2_keywords (0.0.4) rubyzip (2.3.0) + scout_apm (4.1.1) + parser selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) @@ -293,6 +298,7 @@ DEPENDENCIES pundit rack-cors rails (~> 6.1.3, >= 6.1.3.1) + scout_apm sidekiq sidekiq-failures (~> 1.0) spring @@ -304,4 +310,4 @@ RUBY VERSION ruby 2.6.6p146 BUNDLED WITH - 2.1.4 + 2.2.19 From 5f80b3d4935a7d5aad6600b9da0df4f5d9b546d0 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 15 Jun 2021 12:09:10 +0900 Subject: [PATCH 191/286] changed the bundle version back --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 61ebdc1..3313b02 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -310,4 +310,4 @@ RUBY VERSION ruby 2.6.6p146 BUNDLED WITH - 2.2.19 + 2.1.4 From 736cab07ec792bcf5314a3e3bf82ec9170e3c86d Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Wed, 16 Jun 2021 11:40:47 +0900 Subject: [PATCH 192/286] Add sql query for user matches --- app/models/user.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 7776c14..ba8a1a6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,4 +26,29 @@ def score(competition) # predictions.where(competition: competition).count(&:correct?) * 3 competition.predictions.where(user: self).count(&:correct?) * 3 end + + def matches(competition: nil) + query = <<-SQL.freeze + WITH predictions AS ( + SELECT * + FROM predictions + WHERE user_id = :user_id OR user_id IS NULL + ) + SELECT + matches.*, + team_away.name, team_away.abbrev, + team_home.name, team_home.abbrev, + predictions.choice, + rounds.number, rounds.name + FROM matches + LEFT JOIN groups ON matches.group_id = groups.id + LEFT JOIN rounds ON matches.round_id = rounds.id OR groups.round_id = rounds.id + JOIN teams team_away ON matches.team_away_id = team_away.id + JOIN teams team_home ON matches.team_home_id = team_home.id + LEFT JOIN predictions ON predictions.match_id = matches.id + #{'WHERE rounds.competition_id = :competition_id' if competition} + ORDER BY matches.kickoff_time + SQL + User.execute_sql(query, user_id: id, competition_id: competition&.id) + end end From be57bd28dcf13ebcb16824311dcc59b5eaadf083 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Wed, 16 Jun 2021 12:21:01 +0900 Subject: [PATCH 193/286] Fix controller and json view --- app/controllers/v1/matches_controller.rb | 41 ++---------------------- app/models/user.rb | 8 ++--- app/views/v1/matches/index.json.jbuilder | 25 +++++++++++---- 3 files changed, 24 insertions(+), 50 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index b0ec3ec..67205b4 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -3,44 +3,7 @@ class V1::MatchesController < ApplicationController def index @user = User.find_by(id: params[:user_id]) || current_user @competition = Competition.find_by(id: params[:competition_id]) - @predictions = policy_scope(Prediction) - @matches = - if @competition - policy_scope(Match).joins(:team_away, :team_home) - .left_outer_joins(:predictions) - .includes(:team_away, :team_home, :predictions) - .where(group: @competition.groups) - .order(:kickoff_time) - - # TODO: Rewrite the query to avoid iterating over each match to get the predictions - - # # ORIGINAL ATTEMPT - # # The following query doesn't work because if any use makes a prediction on a match, - # # the predictions.user_id in the joined table is neither the current user's id, nor - # # is it NULL. The solution would be to make a SQL UNION query (not possible in AR). - # policy_scope(Match).joins(:team_away, :team_home) - # .left_outer_joins(:predictions) - # .includes(:team_away, :team_home, :predictions) - # .where(group: @competition.groups) - # .where('predictions.user_id = ? OR predictions.user_id IS NULL', @user.id) - # .order(:kickoff_time) - - # # NEW ATTEMPT - # matches_with_predictions = policy_scope(Match).joins(:team_away, :team_home) - # .joins(:predictions) - # .includes(:team_away, :team_home, :predictions) - # .where(group: @competition.groups) - # .where('predictions.user_id = ?', @user.id) - - # matches_without_predictions = policy_scope(Match).joins(:team_away, :team_home) - # .includes(:team_away, :team_home, :predictions) - # .where(group: @competition.groups) - # .where.not(matches: { id: matches_with_predictions }) - - # sql_query = "(#{matches_with_predictions.to_sql}) UNION (#{matches_without_predictions.to_sql})" - # policy_scope(Match).execute_sql(sql_query) - else - policy_scope([:user, Match]).order(:kickoff_time) - end + skip_policy_scope + @matches = @user.matches(competition: @competition) end end diff --git a/app/models/user.rb b/app/models/user.rb index ba8a1a6..65855f1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,10 +36,10 @@ def matches(competition: nil) ) SELECT matches.*, - team_away.name, team_away.abbrev, - team_home.name, team_home.abbrev, - predictions.choice, - rounds.number, rounds.name + team_away.name AS team_away_name, team_away.abbrev AS team_away_abbrev, + team_home.name AS team_home_name, team_home.abbrev AS team_home_abbrev, + predictions.id AS prediction_id, predictions.choice AS prediction_choice, predictions.user_id AS prediction_user_id, predictions.match_id AS prediction_match_id, + rounds.number AS round_number, rounds.name AS round_name FROM matches LEFT JOIN groups ON matches.group_id = groups.id LEFT JOIN rounds ON matches.round_id = rounds.id OR groups.round_id = rounds.id diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index b5f532e..fc8b448 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,13 +1,24 @@ json.array! @matches do |match| - json.extract! match, :id, :kickoff_time, :status, :group_id, :next_match_id, :round_id + match.symbolize_keys! + json.merge! match.slice(:id, :kickoff_time, :status, :group_id, :next_match_id, :round_id) json.team_home do - json.partial! match.team_home - json.score match.team_home_score if match.finished? + json.id match[:team_home_id] + json.name match[:team_home_name] + json.abbrev match[:team_home_abbrev] + json.score match[:team_home_score] if match[:status] == 'finished' end json.team_away do - json.partial! match.team_away - json.score match.team_away_score if match.finished? + json.id match[:team_away_id] + json.name match[:team_away_name] + json.abbrev match[:team_away_abbrev] + json.score match[:team_away_score] if match[:status] == 'finished' + end + if match[:prediction_choice] + json.prediction do + json.id match[:prediction_id] + json.choice match[:prediction_choice] + json.user_id match[:prediction_user_id] + json.match_id match[:prediction_match_id] + end end - user_prediction = @predictions.where(match: match).find_by(user: @user) - json.prediction { json.partial! user_prediction } if user_prediction end From 76bf51a726960126fda2a8e79572c8e270882679 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Wed, 16 Jun 2021 13:13:40 +0900 Subject: [PATCH 194/286] Add badge and flag image keys --- app/models/user.rb | 43 ++++++++++++++++++++---- app/views/v1/matches/index.json.jbuilder | 4 +++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 65855f1..6885174 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,18 +33,49 @@ def matches(competition: nil) SELECT * FROM predictions WHERE user_id = :user_id OR user_id IS NULL + ), team_badges AS ( + SELECT teams.id AS team_id, asb.key as key + FROM teams + INNER JOIN active_storage_attachments asat ON asat.record_id = teams.id + JOIN active_storage_blobs asb ON asb.id = asat.blob_id + WHERE asat.name = 'badge' AND asat.record_type = 'Team' + ), team_flags AS ( + SELECT teams.id AS team_id, asb.key as key + FROM teams + INNER JOIN active_storage_attachments asat ON asat.record_id = teams.id + JOIN active_storage_blobs asb ON asb.id = asat.blob_id + WHERE asat.name = 'flag' AND asat.record_type = 'Team' + ), team_with_images AS ( + SELECT teams.*, team_badges.key AS badge_key, team_flags.key AS flag_key + FROM teams + LEFT JOIN team_badges ON teams.id = team_badges.team_id + LEFT JOIN team_flags ON teams.id = team_flags.team_id ) SELECT matches.*, - team_away.name AS team_away_name, team_away.abbrev AS team_away_abbrev, - team_home.name AS team_home_name, team_home.abbrev AS team_home_abbrev, - predictions.id AS prediction_id, predictions.choice AS prediction_choice, predictions.user_id AS prediction_user_id, predictions.match_id AS prediction_match_id, - rounds.number AS round_number, rounds.name AS round_name + rounds.number AS round_number, + rounds.name AS round_name, + team_home.name AS team_home_name, + team_home.abbrev AS team_home_abbrev, + team_home.badge_key AS team_home_badge_key, + team_home.flag_key AS team_home_flag_key, + team_away.name AS team_away_name, + team_away.abbrev AS team_away_abbrev, + team_away.badge_key AS team_away_badge_key, + team_away.flag_key AS team_away_flag_key, + predictions.id AS prediction_id, + predictions.user_id AS prediction_user_id, + predictions.match_id AS prediction_match_id, + CASE + WHEN predictions.choice = 0 THEN 'home' + WHEN predictions.choice = 1 THEN 'away' + ELSE 'draw' + END AS prediction_choice FROM matches LEFT JOIN groups ON matches.group_id = groups.id LEFT JOIN rounds ON matches.round_id = rounds.id OR groups.round_id = rounds.id - JOIN teams team_away ON matches.team_away_id = team_away.id - JOIN teams team_home ON matches.team_home_id = team_home.id + JOIN team_with_images team_away ON matches.team_away_id = team_away.id + JOIN team_with_images team_home ON matches.team_home_id = team_home.id LEFT JOIN predictions ON predictions.match_id = matches.id #{'WHERE rounds.competition_id = :competition_id' if competition} ORDER BY matches.kickoff_time diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index fc8b448..8054b1d 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -5,12 +5,16 @@ json.array! @matches do |match| json.id match[:team_home_id] json.name match[:team_home_name] json.abbrev match[:team_home_abbrev] + json.badge_key match[:team_home_badge_key] + json.flag_key match[:team_home_flag_key] json.score match[:team_home_score] if match[:status] == 'finished' end json.team_away do json.id match[:team_away_id] json.name match[:team_away_name] json.abbrev match[:team_away_abbrev] + json.badge_key match[:team_away_badge_key] + json.flag_key match[:team_away_flag_key] json.score match[:team_away_score] if match[:status] == 'finished' end if match[:prediction_choice] From e45d780592c99e1c92d6bc72fcd2c2b017bf86ef Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Wed, 16 Jun 2021 13:25:45 +0900 Subject: [PATCH 195/286] Replace key with image url from cloudinary --- app/views/v1/matches/index.json.jbuilder | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 8054b1d..1bc6332 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -5,16 +5,16 @@ json.array! @matches do |match| json.id match[:team_home_id] json.name match[:team_home_name] json.abbrev match[:team_home_abbrev] - json.badge_key match[:team_home_badge_key] - json.flag_key match[:team_home_flag_key] + json.badge_url cl_image_path(match[:team_home_badge_key]) + json.flag_url cl_image_path(match[:team_home_flag_key]) json.score match[:team_home_score] if match[:status] == 'finished' end json.team_away do json.id match[:team_away_id] json.name match[:team_away_name] json.abbrev match[:team_away_abbrev] - json.badge_key match[:team_away_badge_key] - json.flag_key match[:team_away_flag_key] + json.badge_url cl_image_path(match[:team_away_badge_key]) + json.flag_url cl_image_path(match[:team_away_flag_key]) json.score match[:team_away_score] if match[:status] == 'finished' end if match[:prediction_choice] From 3ef1993a840f768959728388e0eb6089de9e2942 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Wed, 16 Jun 2021 13:57:54 +0900 Subject: [PATCH 196/286] Fix prediction case statement --- app/models/user.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index b05087d..45bc93e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -69,7 +69,8 @@ def matches(competition: nil) CASE WHEN predictions.choice = 0 THEN 'home' WHEN predictions.choice = 1 THEN 'away' - ELSE 'draw' + WHEN predictions.choice = 2 THEN 'draw' + ELSE NULL END AS prediction_choice FROM matches LEFT JOIN groups ON matches.group_id = groups.id From 48a4c0ef0678f1a4a86a2e1792724eb5b1de5241 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 17:15:34 +0900 Subject: [PATCH 197/286] increasing the score by 1 each round --- app/models/round.rb | 4 ++++ app/models/user.rb | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/round.rb b/app/models/round.rb index 1064cb4..acaacd0 100644 --- a/app/models/round.rb +++ b/app/models/round.rb @@ -3,4 +3,8 @@ class Round < ApplicationRecord has_many :groups, dependent: :destroy has_many :matches, through: :groups validates :name, presence: true + + def points + number + 2 + end end diff --git a/app/models/user.rb b/app/models/user.rb index 45bc93e..143b233 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,7 +24,9 @@ def display_name def score(competition) # TODO: user predictions should be scoped by competition => prediction -> match -> group -> round -> competition # predictions.where(competition: competition).count(&:correct?) * 3 - competition.predictions.where(user: self).count(&:correct?) * 3 + competition.predictions.includes(match: [:round, :group]).where(user: self).sum do |prediction| + prediction.correct? ? prediction.match.round.points : 0 + end end def matches(competition: nil) From 302d60d4d6534aa04e2a6753479eff3443519474 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 17:31:29 +0900 Subject: [PATCH 198/286] generated rake task --- lib/tasks/round.rake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/tasks/round.rake diff --git a/lib/tasks/round.rake b/lib/tasks/round.rake new file mode 100644 index 0000000..17d9614 --- /dev/null +++ b/lib/tasks/round.rake @@ -0,0 +1,15 @@ +namespace :round do + desc 'Creating the rounds for the Euros' + task create_all: :environment do + euros = Competition.find_by(name: 'Euro 2020') + + puts 'Creating or finding first round...' + # Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros) + Round.find_or_create_by!(name: 'Round of 16', number: 2, competition: euros) + Round.find_or_create_by!(name: 'Quarter-finals', number: 3, competition: euros) + Round.find_or_create_by!(name: 'Semi-finals', number: 4, competition: euros) + Round.find_or_create_by!(name: 'Final', number: 5, competition: euros) + puts "...#{euros.rounds.count} Total Rounds" + end + +end From 5f867eed4a8b6d33a0f38192a34f2655bf847e61 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 17:34:06 +0900 Subject: [PATCH 199/286] Added api_name to rounds - migration --- db/migrate/20210622083343_add_api_name_to_rounds.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20210622083343_add_api_name_to_rounds.rb diff --git a/db/migrate/20210622083343_add_api_name_to_rounds.rb b/db/migrate/20210622083343_add_api_name_to_rounds.rb new file mode 100644 index 0000000..37f7695 --- /dev/null +++ b/db/migrate/20210622083343_add_api_name_to_rounds.rb @@ -0,0 +1,5 @@ +class AddApiNameToRounds < ActiveRecord::Migration[6.1] + def change + add_column :rounds, :api_name, :string + end +end From f22ea81c1f6abfeb934f07d39136deea9e550a30 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 17:35:21 +0900 Subject: [PATCH 200/286] added api_name to euro rounds --- lib/tasks/round.rake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/tasks/round.rake b/lib/tasks/round.rake index 17d9614..82729c9 100644 --- a/lib/tasks/round.rake +++ b/lib/tasks/round.rake @@ -4,11 +4,11 @@ namespace :round do euros = Competition.find_by(name: 'Euro 2020') puts 'Creating or finding first round...' - # Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros) - Round.find_or_create_by!(name: 'Round of 16', number: 2, competition: euros) - Round.find_or_create_by!(name: 'Quarter-finals', number: 3, competition: euros) - Round.find_or_create_by!(name: 'Semi-finals', number: 4, competition: euros) - Round.find_or_create_by!(name: 'Final', number: 5, competition: euros) + # Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: '3') + Round.find_or_create_by!(name: 'Round of 16', number: 2, competition: euros, api_name: 'R16') + Round.find_or_create_by!(name: 'Quarter-finals', number: 3, competition: euros, api_name: 'QF') + Round.find_or_create_by!(name: 'Semi-finals', number: 4, competition: euros, api_name: 'SF') + Round.find_or_create_by!(name: 'Final', number: 5, competition: euros, api_name: 'F') puts "...#{euros.rounds.count} Total Rounds" end From d0ff5566192c6d813bdfd59e28f0887ad6bbc702 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 17:36:32 +0900 Subject: [PATCH 201/286] fixed seeds --- db/schema.rb | 3 ++- db/seeds.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index ffdb22a..887f2c8 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: 2021_06_06_100056) do +ActiveRecord::Schema.define(version: 2021_06_22_083343) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -128,6 +128,7 @@ t.bigint "competition_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "api_name" t.index ["competition_id"], name: "index_rounds_on_competition_id" end diff --git a/db/seeds.rb b/db/seeds.rb index 808602e..680832e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -60,7 +60,7 @@ puts '.. created the Euros' puts 'Creating or finding first round...' -first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros) +first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: '3') puts "...#{Round.count} Total Rounds" puts 'Creating or finding groups...' From 0dd6539449d0749fc1b2f40470353d77054263b5 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 17:39:13 +0900 Subject: [PATCH 202/286] Added name validation for rounds on a competition --- app/models/round.rb | 2 +- lib/tasks/round.rake | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/round.rb b/app/models/round.rb index 1064cb4..5cf6b9d 100644 --- a/app/models/round.rb +++ b/app/models/round.rb @@ -2,5 +2,5 @@ class Round < ApplicationRecord belongs_to :competition has_many :groups, dependent: :destroy has_many :matches, through: :groups - validates :name, presence: true + validates :name, presence: true, uniqueness: { scope: :competition } end diff --git a/lib/tasks/round.rake b/lib/tasks/round.rake index 82729c9..bfc2b48 100644 --- a/lib/tasks/round.rake +++ b/lib/tasks/round.rake @@ -11,5 +11,4 @@ namespace :round do Round.find_or_create_by!(name: 'Final', number: 5, competition: euros, api_name: 'F') puts "...#{euros.rounds.count} Total Rounds" end - end From 1022e248851e243f7c298240e95ce5bb729a2176 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Jun 2021 18:09:05 +0900 Subject: [PATCH 203/286] api will now create new matches from further rounds --- app/jobs/match_update_future_job.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index d9c0db9..16a216d 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -9,10 +9,6 @@ def perform(competition_id) end end - def get_team(id) - Team.find_by(api_id: id) - end - def update_matches_future(url) response = HTTParty.get(url).body parsed_response = JSON.parse(response)['data'] @@ -20,9 +16,13 @@ def update_matches_future(url) matches.each do |match_info| kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = Match.find_by(api_id: match_info['id']) || Match.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) - next unless match # knock-out rounds with no teams + match = Match.find_by(api_id: match_info['id']) || Match.new + match.team_home ||= Team.find_by(api_id: match_info['home_id']) + match.team_away ||= Team.find_by(api_id: match_info['away_id']) + next unless match.team_home && match.team_away # knock-out rounds with no teams yet + # Only adding a round for knockout stages, group isn't provided by API :/ + match.round = Round.find_by(api_name: match_info['round']) unless match_info['round'] == '3' match.api_id = match_info['id'] match.location = match_info['location'] match.kickoff_time = kickoff_time From 56763b459a6b71f9cb67ea453ae296d6b1d6a692 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 24 Jun 2021 16:10:54 +0900 Subject: [PATCH 204/286] added location to match --- app/views/v1/matches/index.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 1bc6332..bccf6d0 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,6 +1,6 @@ json.array! @matches do |match| match.symbolize_keys! - json.merge! match.slice(:id, :kickoff_time, :status, :group_id, :next_match_id, :round_id) + json.merge! match.slice(:id, :kickoff_time, :status, :group_id, :next_match_id, :round_id, :location) json.team_home do json.id match[:team_home_id] json.name match[:team_home_name] From 9392acde220c40970c5de4bd6a3fdd9d0aabf256 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 24 Jun 2021 18:37:21 +0900 Subject: [PATCH 205/286] added uniq on api_id --- app/models/match.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/match.rb b/app/models/match.rb index b472c6b..260f34b 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -8,6 +8,7 @@ class Match < ApplicationRecord has_many :users, through: :predictions validates :kickoff_time, presence: true validates :status, presence: true + validates :api_id, uniqueness: { allow_nil: true } validates_uniqueness_of :kickoff_time, scope: %i[team_home team_away] validate :round_xor_group enum status: { upcoming: 'upcoming', started: 'started', finished: 'finished' }, _default: :upcoming From 4cbcd149f96d263abca4f2e17f185ab695fab3a3 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 24 Jun 2021 18:52:41 +0900 Subject: [PATCH 206/286] added round number to the match --- app/views/v1/matches/index.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index bccf6d0..c64a743 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,6 +1,6 @@ json.array! @matches do |match| match.symbolize_keys! - json.merge! match.slice(:id, :kickoff_time, :status, :group_id, :next_match_id, :round_id, :location) + json.merge! match.slice(:id, :kickoff_time, :status, :group_id, :next_match_id, :round_id, :location, :round_number) json.team_home do json.id match[:team_home_id] json.name match[:team_home_name] From 1ec4d1986b9404581112672ef971d0c51d3a2ebf Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Mon, 28 Jun 2021 00:51:30 +0900 Subject: [PATCH 207/286] fix bug in match job --- app/jobs/match_update_history_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index fd215d6..a0e9493 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -20,7 +20,7 @@ def update_matches_history(url, competition) matches.each do |match_info| kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + match = Match.find_by(api_id: match_info['fixture_id']) || Match.find_by(api_id: match_info['fixture_id']).find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match match.finished! From ade4e35c1b45dba4f635ac88984cc939e9bc8f99 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Mon, 28 Jun 2021 01:53:51 +0900 Subject: [PATCH 208/286] fixed the competition matches query --- app/jobs/match_update_history_job.rb | 2 +- app/models/competition.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index a0e9493..fd215d6 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -20,7 +20,7 @@ def update_matches_history(url, competition) matches.each do |match_info| kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = Match.find_by(api_id: match_info['fixture_id']) || Match.find_by(api_id: match_info['fixture_id']).find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match match.finished! diff --git a/app/models/competition.rb b/app/models/competition.rb index c964e0f..042d2d6 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -11,4 +11,8 @@ class Competition < ApplicationRecord validates :start_date, presence: true validates :end_date, presence: true scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today + 1, end: Date.today - 1) } + + def matches + Match.where(group: groups).or(Match.where(round: rounds)) + end end From f53fd3673cb03d078a18a50b87c3dd8a2e70eb05 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Mon, 28 Jun 2021 02:22:36 +0900 Subject: [PATCH 209/286] added one more history check in case of penalties --- app/jobs/schedule_daily_tasks_job.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/jobs/schedule_daily_tasks_job.rb b/app/jobs/schedule_daily_tasks_job.rb index 81b14e3..320e44d 100644 --- a/app/jobs/schedule_daily_tasks_job.rb +++ b/app/jobs/schedule_daily_tasks_job.rb @@ -9,6 +9,7 @@ def perform # Update right after kickoff and after the game MatchStartedJob.set(wait_until: kickoff_time).perform_later(kickoff_time) MatchUpdateHistoryJob.set(wait_until: kickoff_time + 100.minutes).perform_later(competition.id) + MatchUpdateHistoryJob.set(wait_until: kickoff_time + 200.minutes).perform_later(competition.id) end end end From 9106fa6ffeab1e21722d3d417c6e0b1144fbdbed Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 4 Jul 2021 17:59:34 +0900 Subject: [PATCH 210/286] fixed scoring on knockout rounds --- app/models/competition.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/competition.rb b/app/models/competition.rb index 042d2d6..340e498 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -15,4 +15,8 @@ class Competition < ApplicationRecord def matches Match.where(group: groups).or(Match.where(round: rounds)) end + + def predictions + Prediction.where(match: matches) + end end From a63ebfcc1034867b68958da61a3f8f18a4447c05 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 5 Jul 2021 11:53:45 +0900 Subject: [PATCH 211/286] Add ET and PS scores logic --- app/jobs/match_update_history_job.rb | 11 +++++++---- app/models/match.rb | 11 +++++++++-- .../20210705023732_add_more_scores_to_matches.rb | 8 ++++++++ db/schema.rb | 6 +++++- 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20210705023732_add_more_scores_to_matches.rb diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index fd215d6..c33b72f 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -24,12 +24,15 @@ def update_matches_history(url, competition) next unless match match.finished! - scores = match_info['score'].split(' - ') - match.team_home_score = scores&.first - match.team_away_score = scores&.second + match.team_home_score, match.team_away_score = match_info['score']&.split(' - ') + match.team_home_et_score, match.team_away_et_score = match_info['et_score']&.split(' - ') + match.team_home_ps_score, match.team_away_ps_score = match_info['ps_score']&.split(' - ') match.save - puts "Match Update: #{match_info['score']}" + scores = ["FT Score > #{match_info['score']}"] + scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score'].blank? + scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score'].blank? + puts "Match Update:\n#{scores.join("\n")}" end return parsed_response['next_page'] end diff --git a/app/models/match.rb b/app/models/match.rb index b472c6b..c058d5d 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -15,7 +15,9 @@ class Match < ApplicationRecord def draw? return unless finished? - team_home_score == team_away_score + team_home_score == team_away_score && + team_home_et_score == team_away_et_score && + team_home_ps_score == team_away_ps_score end def round @@ -33,7 +35,12 @@ def winner_side return unless finished? return 'draw' if draw? - team_home_score > team_away_score ? 'home' : 'away' + home_wins = ( + team_home_score > team_away_score || + team_home_et_score > team_away_et_score || + team_home_ps_score > team_away_ps_score + ) + home_wins ? 'home' : 'away' end private diff --git a/db/migrate/20210705023732_add_more_scores_to_matches.rb b/db/migrate/20210705023732_add_more_scores_to_matches.rb new file mode 100644 index 0000000..e2b36d6 --- /dev/null +++ b/db/migrate/20210705023732_add_more_scores_to_matches.rb @@ -0,0 +1,8 @@ +class AddMoreScoresToMatches < ActiveRecord::Migration[6.1] + def change + add_column :matches, :team_home_et_score, :integer + add_column :matches, :team_away_et_score, :integer + add_column :matches, :team_home_ps_score, :integer + add_column :matches, :team_away_ps_score, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index ffdb22a..466d7d3 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: 2021_06_06_100056) do +ActiveRecord::Schema.define(version: 2021_07_05_023732) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -96,6 +96,10 @@ t.bigint "round_id" t.integer "api_id" t.string "location" + t.integer "team_home_et_score" + t.integer "team_away_et_score" + t.integer "team_home_ps_score" + t.integer "team_away_ps_score" t.index ["group_id"], name: "index_matches_on_group_id" t.index ["next_match_id"], name: "index_matches_on_next_match_id" t.index ["round_id"], name: "index_matches_on_round_id" From f85779069e9a37bed168cf3dc3be37d020c2cdac Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 5 Jul 2021 12:18:37 +0900 Subject: [PATCH 212/286] Protect in case no ET or PS score key --- app/jobs/match_update_history_job.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index c33b72f..16db0f3 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -30,8 +30,8 @@ def update_matches_history(url, competition) match.save scores = ["FT Score > #{match_info['score']}"] - scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score'].blank? - scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score'].blank? + scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score']&.blank? + scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score']&.blank? puts "Match Update:\n#{scores.join("\n")}" end return parsed_response['next_page'] From 9dfd547c75d5c5b04407191082f87ffc6c2b3cd1 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 5 Jul 2021 12:33:40 +0900 Subject: [PATCH 213/286] Fix winner when no ET or PS --- app/models/match.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/models/match.rb b/app/models/match.rb index c058d5d..841f631 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -31,15 +31,22 @@ def winner winner_side == 'home' ? team_home : team_away end + def extra_time? + team_home_et_score.present? && team_away_et_score.present? + end + + def penalties? + team_home_ps_score.present? && team_away_ps_score.present? + end + def winner_side return unless finished? return 'draw' if draw? - home_wins = ( - team_home_score > team_away_score || - team_home_et_score > team_away_et_score || - team_home_ps_score > team_away_ps_score - ) + home_wins = team_home_score > team_away_score + home_wins = team_home_et_score > team_away_et_score if extra_time? + home_wins = team_home_ps_score > team_away_ps_score if penalties? + home_wins ? 'home' : 'away' end From 6fd9cab031c677017e67963f381cc33c20fb1ff0 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 5 Jul 2021 18:13:23 +0900 Subject: [PATCH 214/286] Expose correct? attribute in view --- app/views/v1/predictions/_prediction.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/v1/predictions/_prediction.json.jbuilder b/app/views/v1/predictions/_prediction.json.jbuilder index 919f96c..83075f4 100644 --- a/app/views/v1/predictions/_prediction.json.jbuilder +++ b/app/views/v1/predictions/_prediction.json.jbuilder @@ -1 +1 @@ -json.extract! prediction, :id, :choice, :match_id, :user_id +json.extract! prediction, :id, :choice, :match_id, :user_id, :correct? From 66f93de5dab05761d94ee1ee5b50fdf40990ed01 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 5 Jul 2021 18:34:34 +0900 Subject: [PATCH 215/286] Add fields to matches/index.json --- app/views/v1/matches/index.json.jbuilder | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index c64a743..a96827c 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -7,7 +7,11 @@ json.array! @matches do |match| json.abbrev match[:team_home_abbrev] json.badge_url cl_image_path(match[:team_home_badge_key]) json.flag_url cl_image_path(match[:team_home_flag_key]) - json.score match[:team_home_score] if match[:status] == 'finished' + if match[:status] == 'finished' + json.score match[:team_home_score] + json.score match[:team_home_et_score] + json.score match[:team_home_ps_score] + end end json.team_away do json.id match[:team_away_id] @@ -15,7 +19,11 @@ json.array! @matches do |match| json.abbrev match[:team_away_abbrev] json.badge_url cl_image_path(match[:team_away_badge_key]) json.flag_url cl_image_path(match[:team_away_flag_key]) - json.score match[:team_away_score] if match[:status] == 'finished' + if match[:status] == 'finished' + json.score match[:team_away_score] + json.score match[:team_away_et_score] + json.score match[:team_away_ps_score] + end end if match[:prediction_choice] json.prediction do From 0e306e3fe54e1ff428f9f6c8bf2c91e1685da21f Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 5 Jul 2021 18:37:06 +0900 Subject: [PATCH 216/286] Fix key names --- app/views/v1/matches/index.json.jbuilder | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index a96827c..75567f5 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -9,8 +9,8 @@ json.array! @matches do |match| json.flag_url cl_image_path(match[:team_home_flag_key]) if match[:status] == 'finished' json.score match[:team_home_score] - json.score match[:team_home_et_score] - json.score match[:team_home_ps_score] + json.et_score match[:team_home_et_score] + json.ps_score match[:team_home_ps_score] end end json.team_away do @@ -21,8 +21,8 @@ json.array! @matches do |match| json.flag_url cl_image_path(match[:team_away_flag_key]) if match[:status] == 'finished' json.score match[:team_away_score] - json.score match[:team_away_et_score] - json.score match[:team_away_ps_score] + json.et_score match[:team_away_et_score] + json.ps_score match[:team_away_ps_score] end end if match[:prediction_choice] From 7d26ce5c5eccd7843187521702e79c1586eeddb7 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 3 Nov 2022 14:03:29 +0900 Subject: [PATCH 217/286] added world cup task --- lib/tasks/competition.rake | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 76cb32d..30f249b 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -1,4 +1,46 @@ namespace :competition do + desc "Create World Cup 2022" + task copy: :environment do + groups = { + 'Group A' => [ + { name: 'Italy', abbrev: 'ITA' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Turkey', abbrev: 'TUR' }, + { name: 'Wales', abbrev: 'WAL' } + ], + 'Group B' => [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Finland', abbrev: 'FIN' }, + { name: 'Russia', abbrev: 'RUS' } + ], + 'Group C' => [ + { name: 'Austria', abbrev: 'AUT' }, + { name: 'Netherlands', abbrev: 'NED' }, + { name: 'FYR Macedonia', abbrev: 'MKD' }, + { name: 'Ukraine', abbrev: 'UKR' } + ], + 'Group D' => [ + { name: 'Croatia', abbrev: 'CRO' }, + { name: 'Czech Republic', abbrev: 'CZE' }, + { name: 'England', abbrev: 'ENG' }, + { name: 'Scotland', abbrev: 'SCO' } + ], + 'Group E' => [ + { name: 'Poland', abbrev: 'POL' }, + { name: 'Slovakia', abbrev: 'SVK' }, + { name: 'Spain', abbrev: 'ESP' }, + { name: 'Sweden', abbrev: 'SWE' } + ], + 'Group F' => [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Germany', abbrev: 'GER' }, + { name: 'Hungary', abbrev: 'HUN' }, + { name: 'Portugal', abbrev: 'POR' } + ] +} + end + desc "Update upcoming fixtures for on-going competitions" task update_ongoing_matches: :environment do competitions = Competition.on_going From da935920c3995a73ef04ae89f19d772dc916e2e6 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 3 Nov 2022 14:11:29 +0900 Subject: [PATCH 218/286] added competition and rounds creation --- lib/tasks/competition.rake | 81 +++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 30f249b..ad104a9 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -2,43 +2,50 @@ namespace :competition do desc "Create World Cup 2022" task copy: :environment do groups = { - 'Group A' => [ - { name: 'Italy', abbrev: 'ITA' }, - { name: 'Switzerland', abbrev: 'SUI' }, - { name: 'Turkey', abbrev: 'TUR' }, - { name: 'Wales', abbrev: 'WAL' } - ], - 'Group B' => [ - { name: 'Belgium', abbrev: 'BEL' }, - { name: 'Denmark', abbrev: 'DEN' }, - { name: 'Finland', abbrev: 'FIN' }, - { name: 'Russia', abbrev: 'RUS' } - ], - 'Group C' => [ - { name: 'Austria', abbrev: 'AUT' }, - { name: 'Netherlands', abbrev: 'NED' }, - { name: 'FYR Macedonia', abbrev: 'MKD' }, - { name: 'Ukraine', abbrev: 'UKR' } - ], - 'Group D' => [ - { name: 'Croatia', abbrev: 'CRO' }, - { name: 'Czech Republic', abbrev: 'CZE' }, - { name: 'England', abbrev: 'ENG' }, - { name: 'Scotland', abbrev: 'SCO' } - ], - 'Group E' => [ - { name: 'Poland', abbrev: 'POL' }, - { name: 'Slovakia', abbrev: 'SVK' }, - { name: 'Spain', abbrev: 'ESP' }, - { name: 'Sweden', abbrev: 'SWE' } - ], - 'Group F' => [ - { name: 'France', abbrev: 'FRA' }, - { name: 'Germany', abbrev: 'GER' }, - { name: 'Hungary', abbrev: 'HUN' }, - { name: 'Portugal', abbrev: 'POR' } - ] -} + 'Group A' => [ + { name: 'Italy', abbrev: 'ITA' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Turkey', abbrev: 'TUR' }, + { name: 'Wales', abbrev: 'WAL' } + ], + 'Group B' => [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Finland', abbrev: 'FIN' }, + { name: 'Russia', abbrev: 'RUS' } + ], + 'Group C' => [ + { name: 'Austria', abbrev: 'AUT' }, + { name: 'Netherlands', abbrev: 'NED' }, + { name: 'FYR Macedonia', abbrev: 'MKD' }, + { name: 'Ukraine', abbrev: 'UKR' } + ], + 'Group D' => [ + { name: 'Croatia', abbrev: 'CRO' }, + { name: 'Czech Republic', abbrev: 'CZE' }, + { name: 'England', abbrev: 'ENG' }, + { name: 'Scotland', abbrev: 'SCO' } + ], + 'Group E' => [ + { name: 'Poland', abbrev: 'POL' }, + { name: 'Slovakia', abbrev: 'SVK' }, + { name: 'Spain', abbrev: 'ESP' }, + { name: 'Sweden', abbrev: 'SWE' } + ], + 'Group F' => [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Germany', abbrev: 'GER' }, + { name: 'Hungary', abbrev: 'HUN' }, + { name: 'Portugal', abbrev: 'POR' } + ] + } + puts 'Creating the World Cup...' + world_cup = Competition.find_or_create_by!(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18)) + puts '.. created the World Cup' + + puts 'Creating or finding first round...' + first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') + puts "...#{world_cup.rounds.count} Total Rounds" end desc "Update upcoming fixtures for on-going competitions" From d5374530c77a2db008871bc9832a77b4e015e191 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 3 Nov 2022 14:12:49 +0900 Subject: [PATCH 219/286] added groups creation --- lib/tasks/competition.rake | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index ad104a9..c68e8a2 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -46,6 +46,19 @@ namespace :competition do puts 'Creating or finding first round...' first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') puts "...#{world_cup.rounds.count} Total Rounds" + + puts 'Creating or finding groups...' + groups.each_key do |group_name| + puts "...#{group_name}..." + group = Group.find_or_create_by!(name: group_name, round: first_round) + groups[group_name].each do |team_hash| + puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" + team = Team.find_or_create_by!(team_hash) + Affiliation.find_or_create_by!(team: team, group: group) + end + end + puts "...#{world_cup.teams.count} Total Teams" + puts "...#{world_cup.groups.count} Total Groups" end desc "Update upcoming fixtures for on-going competitions" From 001b2c0477ae5e370e357e4ea673e85c510b26ce Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 3 Nov 2022 16:10:01 +0900 Subject: [PATCH 220/286] updated job to with all the correct WC teams --- lib/tasks/competition.rake | 85 +++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index c68e8a2..8d42295 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -1,50 +1,62 @@ namespace :competition do desc "Create World Cup 2022" - task copy: :environment do + task world_cup: :environment do groups = { 'Group A' => [ - { name: 'Italy', abbrev: 'ITA' }, - { name: 'Switzerland', abbrev: 'SUI' }, - { name: 'Turkey', abbrev: 'TUR' }, - { name: 'Wales', abbrev: 'WAL' } + { name: 'Qatar', abbrev: 'QAT' }, + { name: 'Ecuador', abbrev: 'ECU' }, + { name: 'Senegal', abbrev: 'SEN' }, + { name: 'Netherlands', abbrev: 'NED' } ], 'Group B' => [ - { name: 'Belgium', abbrev: 'BEL' }, - { name: 'Denmark', abbrev: 'DEN' }, - { name: 'Finland', abbrev: 'FIN' }, - { name: 'Russia', abbrev: 'RUS' } + { name: 'England', abbrev: 'ENG' }, + { name: 'IR Iran', abbrev: 'IRN' }, + { name: 'USA', abbrev: 'USA' }, + { name: 'Wales', abbrev: 'WAL' } ], 'Group C' => [ - { name: 'Austria', abbrev: 'AUT' }, - { name: 'Netherlands', abbrev: 'NED' }, - { name: 'FYR Macedonia', abbrev: 'MKD' }, - { name: 'Ukraine', abbrev: 'UKR' } + { name: 'Argentina', abbrev: 'ARG' }, + { name: 'Saudi Arabia', abbrev: 'KSA' }, + { name: 'Mexico', abbrev: 'MEX' }, + { name: 'Poland', abbrev: 'POL' } ], 'Group D' => [ - { name: 'Croatia', abbrev: 'CRO' }, - { name: 'Czech Republic', abbrev: 'CZE' }, - { name: 'England', abbrev: 'ENG' }, - { name: 'Scotland', abbrev: 'SCO' } + { name: 'France', abbrev: 'FRA' }, + { name: 'Australia', abbrev: 'AUS' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Tunisia', abbrev: 'TUN' } ], 'Group E' => [ - { name: 'Poland', abbrev: 'POL' }, - { name: 'Slovakia', abbrev: 'SVK' }, { name: 'Spain', abbrev: 'ESP' }, - { name: 'Sweden', abbrev: 'SWE' } + { name: 'Costa Rica', abbrev: 'CRC' }, + { name: 'Germany', abbrev: 'GER' }, + { name: 'Japan', abbrev: 'JPN' } ], 'Group F' => [ - { name: 'France', abbrev: 'FRA' }, - { name: 'Germany', abbrev: 'GER' }, - { name: 'Hungary', abbrev: 'HUN' }, - { name: 'Portugal', abbrev: 'POR' } + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Canada', abbrev: 'CAN' }, + { name: 'Morocco', abbrev: 'MAR' }, + { name: 'Croatia', abbrev: 'CRO' } + ], + 'Group G' => [ + { name: 'Brazil', abbrev: 'BRA' }, + { name: 'Serbia', abbrev: 'SRB' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Camerooon', abbrev: 'CMR' } + ], + 'Group H' => [ + { name: 'Portugal', abbrev: 'POR' }, + { name: 'Ghana', abbrev: 'GHA' }, + { name: 'Uruguay', abbrev: 'URU' }, + { name: 'Korea Republic', abbrev: 'KOR' } ] } puts 'Creating the World Cup...' - world_cup = Competition.find_or_create_by!(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18)) + world_cup = Competition.find_or_create_by(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18)) puts '.. created the World Cup' puts 'Creating or finding first round...' - first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') + first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') puts "...#{world_cup.rounds.count} Total Rounds" puts 'Creating or finding groups...' @@ -53,12 +65,29 @@ namespace :competition do group = Group.find_or_create_by!(name: group_name, round: first_round) groups[group_name].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" - team = Team.find_or_create_by!(team_hash) - Affiliation.find_or_create_by!(team: team, group: group) + team = Team.find_or_create_by(team_hash) + Affiliation.find_or_create_by(team: team, group: group) end end puts "...#{world_cup.teams.count} Total Teams" puts "...#{world_cup.groups.count} Total Groups" + + doug = User.find_by(email: 'douglasmberkley@gmail.com') + trouni = User.find_by(email: 'trouni@gmail.com') + + puts 'Creating a test leaderboards' + leaderboard = Leaderboard.find_or_create_by( + name: 'Admin Leaderboard', + competition: world_cup, + user: trouni + ) + Membership.find_or_create_by(leaderboard: leaderboard, user: doug) + leaderboard = Leaderboard.find_or_create_by( + name: 'Admin Leaderboard', + competition: world_cup, + user: doug + ) + Membership.find_or_create_by(leaderboard: leaderboard, user: trouni) end desc "Update upcoming fixtures for on-going competitions" From 57f222f7cf46671ca86d4bfeb13d11a78b9bf1c6 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 16:25:47 +0900 Subject: [PATCH 221/286] updated job to fetch country ids and flags --- lib/tasks/team.rake | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index fd01dfd..c353d10 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -1,15 +1,16 @@ namespace :team do desc "Calls Live-Score API, saves API id and gets the flag" task add_flag: :environment do - competition_id = 387 - response = HTTParty.get("http://livescore-api.com/api-client/countries/list.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}").body - countries = JSON.parse(response)['data']['country'] + competition_id = 362 + season = 2022 + response = HTTParty.get("http://livescore-api.com/api-client/countries/list.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}&season=#{season}").body + countries = JSON.parse(response)['data'] not_found = [] Team.find_each do |team| country = countries.find { |country| country['name'] == team.name } if country - team.api_id = country['national_team']['id'] + team.api_id = country['id'] team.save next if team.flag.attached? From acd07644a22c30d159b1b411635ddb64433dffa9 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 16:26:14 +0900 Subject: [PATCH 222/286] updated job to fetch country ids and flags --- lib/tasks/team.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index c353d10..41b632a 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -15,7 +15,7 @@ namespace :team do next if team.flag.attached? - scrape_flag(team) + fetch_flag(team) else not_found << team.name end @@ -24,7 +24,7 @@ namespace :team do puts not_found.any? ? "Teams not found: #{not_found.join(', ')}" : 'Found all teams' end - def scrape_flag(team) + def fetch_flag(team) url = "https://livescore-api.com/api-client/countries/flag.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&team_id=#{team.api_id}" puts "#{team.name}: #{url}" file = URI.open(url) From 793345bab4638678871ed5f92723c227b934e21f Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 16:31:07 +0900 Subject: [PATCH 223/286] updated teams in competition api url --- lib/tasks/competition.rake | 4 ++-- lib/tasks/team.rake | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 8d42295..018cbd7 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -10,7 +10,7 @@ namespace :competition do ], 'Group B' => [ { name: 'England', abbrev: 'ENG' }, - { name: 'IR Iran', abbrev: 'IRN' }, + { name: 'Iran', abbrev: 'IRN' }, { name: 'USA', abbrev: 'USA' }, { name: 'Wales', abbrev: 'WAL' } ], @@ -48,7 +48,7 @@ namespace :competition do { name: 'Portugal', abbrev: 'POR' }, { name: 'Ghana', abbrev: 'GHA' }, { name: 'Uruguay', abbrev: 'URU' }, - { name: 'Korea Republic', abbrev: 'KOR' } + { name: 'South Korea', abbrev: 'KOR' } ] } puts 'Creating the World Cup...' diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index 41b632a..fb73f2a 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -3,7 +3,7 @@ namespace :team do task add_flag: :environment do competition_id = 362 season = 2022 - response = HTTParty.get("http://livescore-api.com/api-client/countries/list.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}&season=#{season}").body + response = HTTParty.get("https://livescore-api.com/api-client/competitions/participants.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}&season=#{season}").body countries = JSON.parse(response)['data'] not_found = [] From 97be8a6df643c7360fa6b0cef50cb40ea0439397 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 16:41:02 +0900 Subject: [PATCH 224/286] refactored match creation job --- app/jobs/match_update_future_job.rb | 8 ++++---- app/models/competition.rb | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index 16a216d..227340f 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -2,8 +2,8 @@ class MatchUpdateFutureJob < ApplicationJob queue_as :default def perform(competition_id) - competition = Competition.find(competition_id) - url_to_update = LiveScoreApi.matches_future_url(competition.api_id) + @competition = Competition.find(competition_id) + url_to_update = LiveScoreApi.matches_future_url(@competition.api_id) while url_to_update url_to_update = update_matches_future(url_to_update) end @@ -16,13 +16,13 @@ def update_matches_future(url) matches.each do |match_info| kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = Match.find_by(api_id: match_info['id']) || Match.new + match = @competition.matches.find_by(api_id: match_info['id']) || Match.new match.team_home ||= Team.find_by(api_id: match_info['home_id']) match.team_away ||= Team.find_by(api_id: match_info['away_id']) next unless match.team_home && match.team_away # knock-out rounds with no teams yet # Only adding a round for knockout stages, group isn't provided by API :/ - match.round = Round.find_by(api_name: match_info['round']) unless match_info['round'] == '3' + match.round = @competition.rounds.find_by(api_name: match_info['round']) unless match_info['round'] == '3' match.api_id = match_info['id'] match.location = match_info['location'] match.kickoff_time = kickoff_time diff --git a/app/models/competition.rb b/app/models/competition.rb index 340e498..75a59b4 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -13,10 +13,12 @@ class Competition < ApplicationRecord scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today + 1, end: Date.today - 1) } def matches + # TODO: by competition, right??? Match.where(group: groups).or(Match.where(round: rounds)) end def predictions + # TODO: by competition, right??? Prediction.where(match: matches) end end From 30fd53e26a35da3d211bcd43e13f85472822f951 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 16:42:53 +0900 Subject: [PATCH 225/286] added todo --- lib/tasks/competition.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 018cbd7..1426ed8 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -55,6 +55,7 @@ namespace :competition do world_cup = Competition.find_or_create_by(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18)) puts '.. created the World Cup' + # TODO: How do we create the rounds?? puts 'Creating or finding first round...' first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') puts "...#{world_cup.rounds.count} Total Rounds" From 82cc7ec784b70c8c964c7f52d350d978687fab24 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 21:31:38 +0900 Subject: [PATCH 226/286] removed unnessary comments --- app/models/competition.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index 75a59b4..340e498 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -13,12 +13,10 @@ class Competition < ApplicationRecord scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today + 1, end: Date.today - 1) } def matches - # TODO: by competition, right??? Match.where(group: groups).or(Match.where(round: rounds)) end def predictions - # TODO: by competition, right??? Prediction.where(match: matches) end end From c29a532c5fab0e5d7a91a2283281b82b62353e8a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 21:37:58 +0900 Subject: [PATCH 227/286] added world cup api_id --- lib/tasks/competition.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 1426ed8..deb5e47 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -52,7 +52,7 @@ namespace :competition do ] } puts 'Creating the World Cup...' - world_cup = Competition.find_or_create_by(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18)) + world_cup = Competition.find_or_create_by(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18), api_id: 362) puts '.. created the World Cup' # TODO: How do we create the rounds?? From 1dab6f52d5d47aeb3788b4456b8a5687484872b2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 22:17:57 +0900 Subject: [PATCH 228/286] Added api id to groups --- app/jobs/match_update_future_job.rb | 7 +- .../20221108131452_add_api_id_to_groups.rb | 5 + db/schema.rb | 3 +- lib/tasks/competition.rake | 127 +++++++++++------- 4 files changed, 88 insertions(+), 54 deletions(-) create mode 100644 db/migrate/20221108131452_add_api_id_to_groups.rb diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index 227340f..fd242f5 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -22,11 +22,16 @@ def update_matches_future(url) next unless match.team_home && match.team_away # knock-out rounds with no teams yet # Only adding a round for knockout stages, group isn't provided by API :/ - match.round = @competition.rounds.find_by(api_name: match_info['round']) unless match_info['round'] == '3' + if %w[1 2 3].include?(match_info['round']) + match.group = @competition.groups.find_by(api_id: match_info["group_id"]) + else + match.round = @competition.rounds.find_by(api_name: match_info['round']) + end match.api_id = match_info['id'] match.location = match_info['location'] match.kickoff_time = kickoff_time match.save + p match.errors.full_messages if match.errors.any? puts 'Match Update' end return parsed_response['next_page'] diff --git a/db/migrate/20221108131452_add_api_id_to_groups.rb b/db/migrate/20221108131452_add_api_id_to_groups.rb new file mode 100644 index 0000000..c9e6daa --- /dev/null +++ b/db/migrate/20221108131452_add_api_id_to_groups.rb @@ -0,0 +1,5 @@ +class AddApiIdToGroups < ActiveRecord::Migration[6.1] + def change + add_column :groups, :api_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 52d0b76..a5b171f 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: 2021_07_05_023732) do +ActiveRecord::Schema.define(version: 2022_11_08_131452) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -68,6 +68,7 @@ t.bigint "round_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.integer "api_id" t.index ["round_id"], name: "index_groups_on_round_id" end diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index deb5e47..f8ef9a2 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -2,69 +2,92 @@ namespace :competition do desc "Create World Cup 2022" task world_cup: :environment do groups = { - 'Group A' => [ - { name: 'Qatar', abbrev: 'QAT' }, - { name: 'Ecuador', abbrev: 'ECU' }, - { name: 'Senegal', abbrev: 'SEN' }, - { name: 'Netherlands', abbrev: 'NED' } - ], - 'Group B' => [ - { name: 'England', abbrev: 'ENG' }, - { name: 'Iran', abbrev: 'IRN' }, - { name: 'USA', abbrev: 'USA' }, - { name: 'Wales', abbrev: 'WAL' } - ], - 'Group C' => [ - { name: 'Argentina', abbrev: 'ARG' }, - { name: 'Saudi Arabia', abbrev: 'KSA' }, - { name: 'Mexico', abbrev: 'MEX' }, - { name: 'Poland', abbrev: 'POL' } - ], - 'Group D' => [ - { name: 'France', abbrev: 'FRA' }, - { name: 'Australia', abbrev: 'AUS' }, - { name: 'Denmark', abbrev: 'DEN' }, - { name: 'Tunisia', abbrev: 'TUN' } - ], - 'Group E' => [ - { name: 'Spain', abbrev: 'ESP' }, - { name: 'Costa Rica', abbrev: 'CRC' }, - { name: 'Germany', abbrev: 'GER' }, - { name: 'Japan', abbrev: 'JPN' } - ], - 'Group F' => [ - { name: 'Belgium', abbrev: 'BEL' }, - { name: 'Canada', abbrev: 'CAN' }, - { name: 'Morocco', abbrev: 'MAR' }, - { name: 'Croatia', abbrev: 'CRO' } - ], - 'Group G' => [ - { name: 'Brazil', abbrev: 'BRA' }, - { name: 'Serbia', abbrev: 'SRB' }, - { name: 'Switzerland', abbrev: 'SUI' }, - { name: 'Camerooon', abbrev: 'CMR' } - ], - 'Group H' => [ - { name: 'Portugal', abbrev: 'POR' }, - { name: 'Ghana', abbrev: 'GHA' }, - { name: 'Uruguay', abbrev: 'URU' }, - { name: 'South Korea', abbrev: 'KOR' } - ] + 'Group A' => { + api_id: 1913, + teams: [ + { name: 'Qatar', abbrev: 'QAT' }, + { name: 'Ecuador', abbrev: 'ECU' }, + { name: 'Senegal', abbrev: 'SEN' }, + { name: 'Netherlands', abbrev: 'NED' } + ] + }, + 'Group B' => { + api_id: 1914, + teams: [ + { name: 'England', abbrev: 'ENG' }, + { name: 'Iran', abbrev: 'IRN' }, + { name: 'USA', abbrev: 'USA' }, + { name: 'Wales', abbrev: 'WAL' } + ] + }, + 'Group C' => { + api_id: 1915, + teams: [ + { name: 'Argentina', abbrev: 'ARG' }, + { name: 'Saudi Arabia', abbrev: 'KSA' }, + { name: 'Mexico', abbrev: 'MEX' }, + { name: 'Poland', abbrev: 'POL' } + ] + }, + 'Group D' => { + api_id: 1916, + teams: [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Australia', abbrev: 'AUS' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Tunisia', abbrev: 'TUN' } + ] + }, + 'Group E' => { + api_id: 1917, + teams: [ + { name: 'Spain', abbrev: 'ESP' }, + { name: 'Costa Rica', abbrev: 'CRC' }, + { name: 'Germany', abbrev: 'GER' }, + { name: 'Japan', abbrev: 'JPN' } + ] + }, + 'Group F' => { + api_id: 1918, + teams: [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Canada', abbrev: 'CAN' }, + { name: 'Morocco', abbrev: 'MAR' }, + { name: 'Croatia', abbrev: 'CRO' } + ] + }, + 'Group G' => { + api_id: 1919, + teams: [ + { name: 'Brazil', abbrev: 'BRA' }, + { name: 'Serbia', abbrev: 'SRB' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Cameroon', abbrev: 'CMR' } + ] + }, + 'Group H' => { + api_id: 1920, + teams: [ + { name: 'Portugal', abbrev: 'POR' }, + { name: 'Ghana', abbrev: 'GHA' }, + { name: 'Uruguay', abbrev: 'URU' }, + { name: 'South Korea', abbrev: 'KOR' } + ] + } } puts 'Creating the World Cup...' world_cup = Competition.find_or_create_by(name: 'World Cup 2022', start_date: Date.new(2022, 11, 20), end_date: Date.new(2022, 12, 18), api_id: 362) puts '.. created the World Cup' - # TODO: How do we create the rounds?? puts 'Creating or finding first round...' - first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') + first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: world_cup, api_name: '1') puts "...#{world_cup.rounds.count} Total Rounds" puts 'Creating or finding groups...' groups.each_key do |group_name| puts "...#{group_name}..." - group = Group.find_or_create_by!(name: group_name, round: first_round) - groups[group_name].each do |team_hash| + group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id]) + groups[group_name][:teams].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" team = Team.find_or_create_by(team_hash) Affiliation.find_or_create_by(team: team, group: group) From 04d68b5a0eeecf6aeba8a0f9d528073b0112fc7b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 22:41:17 +0900 Subject: [PATCH 229/286] added badges to teams --- app/controllers/v1/matches_controller.rb | 4 +++- lib/tasks/team.rake | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 67205b4..ba2c620 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -2,7 +2,9 @@ class V1::MatchesController < ApplicationController # /matches?competition_id=:id&user_id=:id def index @user = User.find_by(id: params[:user_id]) || current_user - @competition = Competition.find_by(id: params[:competition_id]) + # p @competition = Competition.find_by(id: params[:competition_id]) + # TODO: Why doesn't this work ☝️. It's only giving me the Euros + @competition = Competition.order(id: :desc).first skip_policy_scope @matches = @user.matches(competition: @competition) end diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index fb73f2a..253c5b5 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -7,13 +7,13 @@ namespace :team do countries = JSON.parse(response)['data'] not_found = [] - Team.find_each do |team| + competition = Competition.find_by(api_id: competition_id) + competition.teams.find_each do |team| country = countries.find { |country| country['name'] == team.name } if country team.api_id = country['id'] team.save - - next if team.flag.attached? + next if team.flag.attached? && team.badge.attached? fetch_flag(team) else @@ -28,7 +28,8 @@ namespace :team do url = "https://livescore-api.com/api-client/countries/flag.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&team_id=#{team.api_id}" puts "#{team.name}: #{url}" file = URI.open(url) - team.flag.attach(io: file, filename: 'flag.png', content_type: 'image/png') + team.flag.attach(io: file, filename: 'flag.png', content_type: 'image/png') unless team.flag.attached? + team.badge.attach(io: file, filename: 'badge.png', content_type: 'image/png') unless team.badge.attached? puts team.flag.attached? ? 'Success' : 'Failed' end From b1785e38e6b6da3ed596cf6aed0606efb4ac23e6 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 8 Nov 2022 22:51:13 +0900 Subject: [PATCH 230/286] updated TODO notes --- app/controllers/v1/matches_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index ba2c620..66b5103 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -2,8 +2,8 @@ class V1::MatchesController < ApplicationController # /matches?competition_id=:id&user_id=:id def index @user = User.find_by(id: params[:user_id]) || current_user + # TODO: Why is the FE sending competition_id: '1'??? # p @competition = Competition.find_by(id: params[:competition_id]) - # TODO: Why doesn't this work ☝️. It's only giving me the Euros @competition = Competition.order(id: :desc).first skip_policy_scope @matches = @user.matches(competition: @competition) From c2b24b01ea02d394bbc953a9a2d9875c529b80ee Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 12 Nov 2022 14:02:43 +0900 Subject: [PATCH 231/286] went back to the original version of fetching the competition from the FE --- app/controllers/v1/matches_controller.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index 66b5103..a89f793 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -2,9 +2,7 @@ class V1::MatchesController < ApplicationController # /matches?competition_id=:id&user_id=:id def index @user = User.find_by(id: params[:user_id]) || current_user - # TODO: Why is the FE sending competition_id: '1'??? - # p @competition = Competition.find_by(id: params[:competition_id]) - @competition = Competition.order(id: :desc).first + p @competition = Competition.find_by(id: params[:competition_id]) skip_policy_scope @matches = @user.matches(competition: @competition) end From a9f0c999ba80738c1e60202cdf04d6e9f33969ba Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 14 Nov 2022 16:10:25 +0900 Subject: [PATCH 232/286] Update ruby version to 3.1.2 --- .ruby-version | 2 +- Gemfile | 8 +- Gemfile.lock | 311 ++++++++++++++++++++++-------------------- lib/tasks/heroku.rake | 2 +- 4 files changed, 171 insertions(+), 152 deletions(-) diff --git a/.ruby-version b/.ruby-version index 338a5b5..ef538c2 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.6 +3.1.2 diff --git a/Gemfile b/Gemfile index a33203d..40136a4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,10 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.6.6' +ruby '3.1.2' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' -gem 'rails', '~> 6.1.3', '>= 6.1.3.1' +gem 'rails', '~> 6.1.7' # Use postgresql as the database for Active Record gem 'pg', '~> 1.1' # Use Puma as the app server @@ -12,7 +12,7 @@ gem 'puma', '~> 5.0' # 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' +gem 'redis', '~> 4.0' # Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -32,7 +32,9 @@ gem 'devise_token_auth', github: 'lynndylanhurley/devise_token_auth' gem 'faker' gem 'httparty' gem 'omniauth' +gem 'net-smtp' gem 'pundit' +gem 'rexml' gem 'scout_apm' gem 'sidekiq' gem 'sidekiq-failures', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 3313b02..786f2e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,69 +1,69 @@ GIT remote: https://github.com/lynndylanhurley/devise_token_auth.git - revision: 5c0baba8aaf005d03daeaf425d555739d3215603 + revision: ec68e47f2a3e743bd51293369d059508974aed14 specs: - devise_token_auth (1.1.4) + devise_token_auth (1.2.1) bcrypt (~> 3.0) devise (> 3.5.2, < 5) - rails (>= 4.2.0, < 6.2) + rails (>= 4.2.0, < 7.1) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.3.1) - actionpack (= 6.1.3.1) - activesupport (= 6.1.3.1) + actioncable (6.1.7) + actionpack (= 6.1.7) + activesupport (= 6.1.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.3.1) - actionpack (= 6.1.3.1) - activejob (= 6.1.3.1) - activerecord (= 6.1.3.1) - activestorage (= 6.1.3.1) - activesupport (= 6.1.3.1) + actionmailbox (6.1.7) + actionpack (= 6.1.7) + activejob (= 6.1.7) + activerecord (= 6.1.7) + activestorage (= 6.1.7) + activesupport (= 6.1.7) mail (>= 2.7.1) - actionmailer (6.1.3.1) - actionpack (= 6.1.3.1) - actionview (= 6.1.3.1) - activejob (= 6.1.3.1) - activesupport (= 6.1.3.1) + actionmailer (6.1.7) + actionpack (= 6.1.7) + actionview (= 6.1.7) + activejob (= 6.1.7) + activesupport (= 6.1.7) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.3.1) - actionview (= 6.1.3.1) - activesupport (= 6.1.3.1) + actionpack (6.1.7) + actionview (= 6.1.7) + activesupport (= 6.1.7) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.3.1) - actionpack (= 6.1.3.1) - activerecord (= 6.1.3.1) - activestorage (= 6.1.3.1) - activesupport (= 6.1.3.1) + actiontext (6.1.7) + actionpack (= 6.1.7) + activerecord (= 6.1.7) + activestorage (= 6.1.7) + activesupport (= 6.1.7) nokogiri (>= 1.8.5) - actionview (6.1.3.1) - activesupport (= 6.1.3.1) + actionview (6.1.7) + activesupport (= 6.1.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.3.1) - activesupport (= 6.1.3.1) + activejob (6.1.7) + activesupport (= 6.1.7) globalid (>= 0.3.6) - activemodel (6.1.3.1) - activesupport (= 6.1.3.1) - activerecord (6.1.3.1) - activemodel (= 6.1.3.1) - activesupport (= 6.1.3.1) - activestorage (6.1.3.1) - actionpack (= 6.1.3.1) - activejob (= 6.1.3.1) - activerecord (= 6.1.3.1) - activesupport (= 6.1.3.1) - marcel (~> 1.0.0) - mini_mime (~> 1.0.2) - activesupport (6.1.3.1) + activemodel (6.1.7) + activesupport (= 6.1.7) + activerecord (6.1.7) + activemodel (= 6.1.7) + activesupport (= 6.1.7) + activestorage (6.1.7) + actionpack (= 6.1.7) + activejob (= 6.1.7) + activerecord (= 6.1.7) + activesupport (= 6.1.7) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (6.1.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -71,9 +71,9 @@ GEM zeitwerk (~> 2.3) ast (2.4.2) aws_cf_signer (0.1.3) - bcrypt (3.1.16) - bootsnap (1.7.3) - msgpack (~> 1.0) + bcrypt (3.1.18) + bootsnap (1.13.0) + msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) childprocess (3.0.0) @@ -81,47 +81,51 @@ GEM aws_cf_signer rest-client coderay (1.1.3) - concurrent-ruby (1.1.8) - connection_pool (2.2.5) + concurrent-ruby (1.1.10) + connection_pool (2.3.0) crass (1.0.6) - daemons (1.3.1) - devise (4.7.3) + daemons (1.4.1) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) + digest (3.1.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) - dotenv-rails (2.7.6) - dotenv (= 2.7.6) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) railties (>= 3.2) - erubi (1.10.0) + erubi (1.11.0) eventmachine (1.2.7) - faker (2.16.0) - i18n (>= 1.6, < 2) - ffi (1.15.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - haml (5.2.1) - temple (>= 0.8.0) + faker (3.0.0) + i18n (>= 1.8.11, < 2) + ffi (1.15.5) + globalid (1.0.0) + activesupport (>= 5.0) + haml (6.0.10) + temple (>= 0.8.2) + thor tilt - hashie (3.5.6) + hashie (5.0.0) http-accept (1.7.0) - http-cookie (1.0.3) + http-cookie (1.0.5) domain_name (~> 0.5) - httparty (0.15.5) + httparty (0.20.0) + mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.10) + i18n (1.12.0) concurrent-ruby (~> 1.0) - jbuilder (2.11.2) + jbuilder (2.11.5) + actionview (>= 5.0.0) activesupport (>= 5.0.0) - json (2.5.1) - listen (3.5.1) + json (2.6.2) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.9.0) + loofah (2.19.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -136,79 +140,86 @@ GEM skinny (>= 0.1.2) sqlite3-ruby thin - marcel (1.0.1) + marcel (1.0.2) method_source (1.0.0) - mime-types (3.3.1) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0212) - mini_mime (1.0.3) - mini_portile2 (2.5.0) - minitest (5.14.4) - msgpack (1.4.2) + mime-types-data (3.2022.0105) + mini_mime (1.1.2) + minitest (5.16.3) + msgpack (1.6.0) multi_xml (0.6.0) - mustermann (1.1.1) + mustermann (3.0.0) ruby2_keywords (~> 0.0.1) + net-protocol (0.1.3) + timeout + net-smtp (0.3.1) + digest + net-protocol + timeout netrc (0.11.0) - nio4r (2.5.7) - nokogiri (1.11.2) - mini_portile2 (~> 2.5.0) + nio4r (2.5.8) + nokogiri (1.13.9-arm64-darwin) racc (~> 1.4) - omniauth (1.6.1) - hashie (>= 3.4.6, < 3.6.0) - rack (>= 1.6.2, < 3) + omniauth (2.1.0) + hashie (>= 3.4.6) + rack (>= 2.2.3) + rack-protection orm_adapter (0.5.0) - parser (3.0.1.1) + parser (3.1.2.1) ast (~> 2.4.1) - pg (1.2.3) - pry (0.13.1) + pg (1.4.4) + pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.9.0) + pry-byebug (3.10.1) byebug (~> 11.0) - pry (~> 0.13.0) - puma (5.2.2) + pry (>= 0.13, < 0.15) + puma (5.6.5) nio4r (~> 2.0) - pundit (2.1.0) + pundit (2.2.0) activesupport (>= 3.0.0) - racc (1.5.2) - rack (2.2.3) + racc (1.6.0) + rack (2.2.4) rack-cors (1.1.1) rack (>= 2.0.0) - rack-protection (2.1.0) + rack-protection (3.0.3) rack - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.1.3.1) - actioncable (= 6.1.3.1) - actionmailbox (= 6.1.3.1) - actionmailer (= 6.1.3.1) - actionpack (= 6.1.3.1) - actiontext (= 6.1.3.1) - actionview (= 6.1.3.1) - activejob (= 6.1.3.1) - activemodel (= 6.1.3.1) - activerecord (= 6.1.3.1) - activestorage (= 6.1.3.1) - activesupport (= 6.1.3.1) + rack-test (2.0.2) + rack (>= 1.3) + rails (6.1.7) + actioncable (= 6.1.7) + actionmailbox (= 6.1.7) + actionmailer (= 6.1.7) + actionpack (= 6.1.7) + actiontext (= 6.1.7) + actionview (= 6.1.7) + activejob (= 6.1.7) + activemodel (= 6.1.7) + activerecord (= 6.1.7) + activestorage (= 6.1.7) + activesupport (= 6.1.7) bundler (>= 1.15.0) - railties (= 6.1.3.1) + railties (= 6.1.7) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.3) loofah (~> 2.3) - railties (6.1.3.1) - actionpack (= 6.1.3.1) - activesupport (= 6.1.3.1) + railties (6.1.7) + actionpack (= 6.1.7) + activesupport (= 6.1.7) method_source - rake (>= 0.8.7) + rake (>= 12.2) thor (~> 1.0) - rake (13.0.3) - rb-fsevent (0.10.4) + rake (13.0.6) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - redis (4.2.5) + redis (4.7.1) + redis-client (0.11.1) + connection_pool regexp_parser (1.8.2) responders (3.0.1) actionpack (>= 5.0) @@ -218,66 +229,69 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - ruby2_keywords (0.0.4) - rubyzip (2.3.0) - scout_apm (4.1.1) + rexml (3.2.5) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + scout_apm (5.3.2) parser selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - sidekiq (6.2.1) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - sidekiq-failures (1.0.0) + sidekiq (7.0.1) + concurrent-ruby (< 2) + connection_pool (>= 2.3.0) + rack (>= 2.2.4) + redis-client (>= 0.9.0) + sidekiq-failures (1.0.4) sidekiq (>= 4.0.0) - sinatra (2.1.0) - mustermann (~> 1.0) - rack (~> 2.2) - rack-protection (= 2.1.0) + sinatra (3.0.3) + mustermann (~> 3.0) + rack (~> 2.2, >= 2.2.4) + rack-protection (= 3.0.3) tilt (~> 2.0) skinny (0.2.2) eventmachine (~> 1.0) thin - spring (2.1.1) - sprockets (4.0.2) + spring (4.1.0) + sprockets (4.1.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) - sqlite3 (1.4.2) + sqlite3 (1.5.3-arm64-darwin) sqlite3-ruby (1.3.3) sqlite3 (>= 1.3.3) - temple (0.8.2) - thin (1.8.0) + temple (0.9.1) + thin (1.8.1) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (1.1.0) - tilt (2.0.10) - tzinfo (2.0.4) + thor (1.2.1) + tilt (2.0.11) + timeout (0.3.0) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) + unf_ext (0.0.8.2) warden (1.2.9) rack (>= 2.0.9) watir (6.16.5) regexp_parser (~> 1.2) selenium-webdriver (~> 3.6) - webdrivers (4.6.0) + webdrivers (4.7.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (>= 3.0, < 4.0) - websocket-driver (0.7.3) + selenium-webdriver (> 3.141, < 5.0) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.4.2) + zeitwerk (2.6.6) PLATFORMS - ruby + arm64-darwin-21 DEPENDENCIES bootsnap (>= 1.4.4) @@ -291,13 +305,16 @@ DEPENDENCIES jbuilder (~> 2.7) listen (~> 3.3) mailcatcher + net-smtp omniauth pg (~> 1.1) pry-byebug puma (~> 5.0) pundit rack-cors - rails (~> 6.1.3, >= 6.1.3.1) + rails (~> 6.1.7) + redis (~> 4.0) + rexml scout_apm sidekiq sidekiq-failures (~> 1.0) @@ -307,7 +324,7 @@ DEPENDENCIES webdrivers RUBY VERSION - ruby 2.6.6p146 + ruby 3.1.2p20 BUNDLED WITH - 2.1.4 + 2.3.19 diff --git a/lib/tasks/heroku.rake b/lib/tasks/heroku.rake index 9de28cf..7d2795a 100644 --- a/lib/tasks/heroku.rake +++ b/lib/tasks/heroku.rake @@ -8,7 +8,7 @@ namespace :heroku do run 'rails db:drop' puts '-----> pulling the DB...' - run 'heroku pg:pull postgresql-cubic-90889 predictor_api_development' + run 'heroku pg:pull postgresql-cubic-90889 predictor_api_development -a predict-to-win' end def run(*cmd) From 3630ba9d4bd589309d7e9dd88510e0133320a609 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Mon, 14 Nov 2022 16:30:46 +0900 Subject: [PATCH 233/286] Added my platforms --- Gemfile.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 786f2e7..591aac1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -161,6 +161,8 @@ GEM nio4r (2.5.8) nokogiri (1.13.9-arm64-darwin) racc (~> 1.4) + nokogiri (1.13.9-x86_64-darwin) + racc (~> 1.4) omniauth (2.1.0) hashie (>= 3.4.6) rack (>= 2.2.3) @@ -261,6 +263,7 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.5.3-arm64-darwin) + sqlite3 (1.5.3-x86_64-darwin) sqlite3-ruby (1.3.3) sqlite3 (>= 1.3.3) temple (0.9.1) @@ -292,6 +295,7 @@ GEM PLATFORMS arm64-darwin-21 + x86_64-darwin-20 DEPENDENCIES bootsnap (>= 1.4.4) From 173c1170200f4b6de67198836e61d2b0c0abb8b4 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 17 Nov 2022 18:37:20 +0900 Subject: [PATCH 234/286] somehow lost the bundle platform --- Gemfile.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 591aac1..c4900a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -163,6 +163,8 @@ GEM racc (~> 1.4) nokogiri (1.13.9-x86_64-darwin) racc (~> 1.4) + nokogiri (1.13.9-x86_64-linux) + racc (~> 1.4) omniauth (2.1.0) hashie (>= 3.4.6) rack (>= 2.2.3) @@ -264,6 +266,7 @@ GEM sprockets (>= 3.0.0) sqlite3 (1.5.3-arm64-darwin) sqlite3 (1.5.3-x86_64-darwin) + sqlite3 (1.5.3-x86_64-linux) sqlite3-ruby (1.3.3) sqlite3 (>= 1.3.3) temple (0.9.1) @@ -296,6 +299,7 @@ GEM PLATFORMS arm64-darwin-21 x86_64-darwin-20 + x86_64-linux DEPENDENCIES bootsnap (>= 1.4.4) From 153b69bd303a86b084f9b14e4b6d97875ac32330 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 18 Nov 2022 09:18:53 +0900 Subject: [PATCH 235/286] added needed gems for Redis --- Gemfile | 4 +++- Gemfile.lock | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 40136a4..68ede59 100644 --- a/Gemfile +++ b/Gemfile @@ -32,7 +32,9 @@ gem 'devise_token_auth', github: 'lynndylanhurley/devise_token_auth' gem 'faker' gem 'httparty' gem 'omniauth' -gem 'net-smtp' +gem 'net-smtp', require: false +gem 'net-imap', require: false +gem 'net-pop', require: false gem 'pundit' gem 'rexml' gem 'scout_apm' diff --git a/Gemfile.lock b/Gemfile.lock index c4900a6..98e15cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -151,6 +151,10 @@ GEM multi_xml (0.6.0) mustermann (3.0.0) ruby2_keywords (~> 0.0.1) + net-imap (0.3.1) + net-protocol + net-pop (0.1.2) + net-protocol net-protocol (0.1.3) timeout net-smtp (0.3.1) @@ -313,6 +317,8 @@ DEPENDENCIES jbuilder (~> 2.7) listen (~> 3.3) mailcatcher + net-imap + net-pop net-smtp omniauth pg (~> 1.1) From 4ba298015b1311f720a9181bd54298b569a1c7a9 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 20 Nov 2022 17:05:13 +0900 Subject: [PATCH 236/286] downgraded sidekiq and redis --- Gemfile | 4 ++-- Gemfile.lock | 18 ++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 68ede59..d279440 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'puma', '~> 5.0' # 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' +gem 'redis', '~> 3.3.3' # Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -38,7 +38,7 @@ gem 'net-pop', require: false gem 'pundit' gem 'rexml' gem 'scout_apm' -gem 'sidekiq' +gem 'sidekiq', '~> 5.0.4' gem 'sidekiq-failures', '~> 1.0' gem 'watir', '6.16.5' gem 'webdrivers' diff --git a/Gemfile.lock b/Gemfile.lock index 98e15cd..5156c1e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,9 +225,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - redis (4.7.1) - redis-client (0.11.1) - connection_pool + redis (3.3.5) regexp_parser (1.8.2) responders (3.0.1) actionpack (>= 5.0) @@ -245,11 +243,11 @@ GEM selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - sidekiq (7.0.1) - concurrent-ruby (< 2) - connection_pool (>= 2.3.0) - rack (>= 2.2.4) - redis-client (>= 0.9.0) + sidekiq (5.0.5) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) + rack-protection (>= 1.5.0) + redis (>= 3.3.4, < 5) sidekiq-failures (1.0.4) sidekiq (>= 4.0.0) sinatra (3.0.3) @@ -327,10 +325,10 @@ DEPENDENCIES pundit rack-cors rails (~> 6.1.7) - redis (~> 4.0) + redis (~> 3.3.3) rexml scout_apm - sidekiq + sidekiq (~> 5.0.4) sidekiq-failures (~> 1.0) spring tzinfo-data From d2d52c630b1dbfde83745e5593cce09f140af5d2 Mon Sep 17 00:00:00 2001 From: Trouni <34345789+trouni@users.noreply.github.com> Date: Sun, 20 Nov 2022 20:55:50 +0900 Subject: [PATCH 237/286] Add sendgrid SMTP (#92) * Add sendgrid SMTP * Add letter opener and update sender email --- Gemfile | 1 + Gemfile.lock | 8 ++++++++ config/environments/development.rb | 5 +++-- config/environments/production.rb | 3 ++- config/initializers/devise.rb | 2 +- config/initializers/smtp.rb | 11 +++++++++++ lib/tasks/heroku.rake | 2 +- 7 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 config/initializers/smtp.rb diff --git a/Gemfile b/Gemfile index d279440..ea188fe 100644 --- a/Gemfile +++ b/Gemfile @@ -52,6 +52,7 @@ group :development, :test do end group :development do + gem 'letter_opener' gem 'listen', '~> 3.3' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' diff --git a/Gemfile.lock b/Gemfile.lock index 5156c1e..aee14d4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,8 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) aws_cf_signer (0.1.3) bcrypt (3.1.18) @@ -122,6 +124,10 @@ GEM actionview (>= 5.0.0) activesupport (>= 5.0.0) json (2.6.2) + launchy (2.5.0) + addressable (~> 2.7) + letter_opener (1.8.1) + launchy (>= 2.2, < 3) listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -183,6 +189,7 @@ GEM pry-byebug (3.10.1) byebug (~> 11.0) pry (>= 0.13, < 0.15) + public_suffix (5.0.0) puma (5.6.5) nio4r (~> 2.0) pundit (2.2.0) @@ -313,6 +320,7 @@ DEPENDENCIES faker httparty jbuilder (~> 2.7) + letter_opener listen (~> 3.3) mailcatcher net-imap diff --git a/config/environments/development.rb b/config/environments/development.rb index 290dd9e..1cd8f51 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -33,8 +33,9 @@ config.action_mailer.default_url_options = { host: 'localhost:3000' } # Don't care if the mailer can't send. - config.action_mailer.delivery_method = :smtp - config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 } + config.action_mailer.delivery_method = :letter_opener + config.action_mailer.perform_deliveries = true + # config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025 } config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 18748ad..43259b5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -55,7 +55,8 @@ # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "predictor_api_production" - config.action_mailer.default_url_options = { host: 'our-host-name' } + config.action_mailer.delivery_method = :smtp + config.action_mailer.default_url_options = { host: 'http://octacle.app' } config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 24ed8a2..b96b787 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,7 +1,7 @@ Devise.setup do |config| # The e-mail address that mail will appear to be sent from # If absent, mail is sent from "please-change-me-at-config-initializers-devise@example.com" - config.mailer_sender = "support@example.com" + config.mailer_sender = "hello@octacle.app" # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and diff --git a/config/initializers/smtp.rb b/config/initializers/smtp.rb new file mode 100644 index 0000000..cbc8447 --- /dev/null +++ b/config/initializers/smtp.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +ActionMailer::Base.smtp_settings = { + user_name: 'apikey', + password: ENV['SENDGRID_API_KEY'], + domain: 'octacle.app', + address: 'smtp.sendgrid.net', + port: 587, + authentication: :plain, + enable_starttls_auto: true +} diff --git a/lib/tasks/heroku.rake b/lib/tasks/heroku.rake index 7d2795a..e315b1d 100644 --- a/lib/tasks/heroku.rake +++ b/lib/tasks/heroku.rake @@ -8,7 +8,7 @@ namespace :heroku do run 'rails db:drop' puts '-----> pulling the DB...' - run 'heroku pg:pull postgresql-cubic-90889 predictor_api_development -a predict-to-win' + run 'heroku pg:pull DATABASE_URL predictor_api_development -a predict-to-win' end def run(*cmd) From c84c5c20d473db92c891c18461a490e6bb7f43fd Mon Sep 17 00:00:00 2001 From: Trouni <34345789+trouni@users.noreply.github.com> Date: Sun, 20 Nov 2022 22:05:03 +0900 Subject: [PATCH 238/286] Fix host url (#93) --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 43259b5..2eaff2e 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -56,7 +56,7 @@ # config.active_job.queue_name_prefix = "predictor_api_production" config.action_mailer.delivery_method = :smtp - config.action_mailer.default_url_options = { host: 'http://octacle.app' } + config.action_mailer.default_url_options = { host: 'http://predict-to-win.herokuapp.com' } config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. From 57143f899bb74a693d5041b76c922a710ed7f143 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 20 Nov 2022 22:08:05 +0900 Subject: [PATCH 239/286] prevent user from giving an empty name --- app/models/user.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/user.rb b/app/models/user.rb index 143b233..63d9e35 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,7 @@ class User < ApplicationRecord # has_many :competitions, through: :leaderboards has_many :predictions, dependent: :destroy has_many :matches, through: :predictions + validates :name, presence: true, on: :update, if: :name_changed? def leaderboards(competition = nil) # this includes creator or leaderboard and members From 945ebdc2bd3ee7ee528e07f202f4408b2af74342 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Mon, 21 Nov 2022 16:14:13 +0900 Subject: [PATCH 240/286] removed unnecessary match updating --- app/jobs/schedule_daily_tasks_job.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/jobs/schedule_daily_tasks_job.rb b/app/jobs/schedule_daily_tasks_job.rb index 320e44d..3df356f 100644 --- a/app/jobs/schedule_daily_tasks_job.rb +++ b/app/jobs/schedule_daily_tasks_job.rb @@ -6,10 +6,7 @@ def perform competitions.each do |competition| matches = competition.matches.where(kickoff_time: Date.today.all_day) matches.pluck(:kickoff_time).uniq.each do |kickoff_time| - # Update right after kickoff and after the game MatchStartedJob.set(wait_until: kickoff_time).perform_later(kickoff_time) - MatchUpdateHistoryJob.set(wait_until: kickoff_time + 100.minutes).perform_later(competition.id) - MatchUpdateHistoryJob.set(wait_until: kickoff_time + 200.minutes).perform_later(competition.id) end end end From 5804160b8641e848235f5e975affa4f03f9b8aa0 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 22 Nov 2022 15:40:34 +0900 Subject: [PATCH 241/286] passing possible points to the user --- app/models/user.rb | 6 ++++++ app/views/v1/users/show.json.jbuilder | 1 + 2 files changed, 7 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 143b233..fd6a56d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -29,6 +29,12 @@ def score(competition) end end + def possible_score(competition) + competition.matches.where(status: 'finished').sum do |match| + match.round.points + end + end + def matches(competition: nil) query = <<-SQL.freeze WITH predictions AS ( diff --git a/app/views/v1/users/show.json.jbuilder b/app/views/v1/users/show.json.jbuilder index 47dd4a6..4da5a88 100644 --- a/app/views/v1/users/show.json.jbuilder +++ b/app/views/v1/users/show.json.jbuilder @@ -1,2 +1,3 @@ json.partial! @user json.points @user.score(@competition) if @competition +json.possible_points @user.possible_score(@competition) if @competition From c632b7a2a065c8f8be51b329b9e722ffc58a8485 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 23 Nov 2022 19:52:48 +0900 Subject: [PATCH 242/286] created a rake task to upload random photos of people who havent uploaded a photo --- Gemfile | 2 +- Gemfile.lock | 21 +++++++++++-------- app/models/competition.rb | 1 + app/services/scrape_photo_service.rb | 31 ++++++++++++++++++++++++++++ lib/tasks/user.rake | 11 ++++++++++ 5 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 app/services/scrape_photo_service.rb create mode 100644 lib/tasks/user.rake diff --git a/Gemfile b/Gemfile index ea188fe..223e8aa 100644 --- a/Gemfile +++ b/Gemfile @@ -40,7 +40,7 @@ gem 'rexml' gem 'scout_apm' gem 'sidekiq', '~> 5.0.4' gem 'sidekiq-failures', '~> 1.0' -gem 'watir', '6.16.5' +gem "watir", "~> 7.1" gem 'webdrivers' group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index aee14d4..eee6191 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,7 +78,7 @@ GEM msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) - childprocess (3.0.0) + childprocess (4.1.0) cloudinary (1.16.1) aws_cf_signer rest-client @@ -233,7 +233,7 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redis (3.3.5) - regexp_parser (1.8.2) + regexp_parser (2.6.1) responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) @@ -247,9 +247,11 @@ GEM rubyzip (2.3.2) scout_apm (5.3.2) parser - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) + selenium-webdriver (4.6.1) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) @@ -293,13 +295,14 @@ GEM unf_ext (0.0.8.2) warden (1.2.9) rack (>= 2.0.9) - watir (6.16.5) - regexp_parser (~> 1.2) - selenium-webdriver (~> 3.6) + watir (7.1.0) + regexp_parser (>= 1.2, < 3) + selenium-webdriver (~> 4.0) webdrivers (4.7.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (> 3.141, < 5.0) + websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -340,7 +343,7 @@ DEPENDENCIES sidekiq-failures (~> 1.0) spring tzinfo-data - watir (= 6.16.5) + watir (~> 7.1) webdrivers RUBY VERSION diff --git a/app/models/competition.rb b/app/models/competition.rb index 340e498..e008dc7 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -7,6 +7,7 @@ class Competition < ApplicationRecord has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy has_many :predictions, through: :matches, dependent: :destroy + has_many :users, through: :predictions validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true diff --git a/app/services/scrape_photo_service.rb b/app/services/scrape_photo_service.rb new file mode 100644 index 0000000..a2e6fd7 --- /dev/null +++ b/app/services/scrape_photo_service.rb @@ -0,0 +1,31 @@ +require 'watir' + +class ScrapePhotoService + attr_reader :user, :competition + + def initialize(attrs = {}) + @user = attrs[:user] + @competition = attrs[:competition] + end + + def call + url = "https://www.fifa.com/fifaplus/en/tournaments/mens/worldcup/qatar2022/teams/#{competition.teams.sample.name.split.join('-').downcase}/squad" + browser = Watir::Browser.new :chrome, headless: true + browser.goto url + sleep(15) + html_doc = Nokogiri::HTML.parse(browser.html) + main_div = html_doc.search('main section')[2] + return unless main_div + + forwards = main_div.search('.entire-squad_container__3W4Hl')[3] + images = forwards.search('.player-badge-card_playerImage__301X0') + image_url = images[rand(0...images.length)].attribute("style").value.gsub('background-image: url(', '').delete('\"());') + file = URI.open(image_url) + puts "#{user.display_name}: \nUploading #{image_url} ..." + cl_response = Cloudinary::Uploader.upload(file) + user.photo_key = cl_response['public_id'] + user.save + # TODO: Most likely the ending numbers and names might change... + # document.querySelectorAll('main section')[2].querySelectorAll('.entire-squad_container__3W4Hl')[3].querySelectorAll('.player-badge-card_playerImage__301X0')[0].style.backgroundImage + end +end diff --git a/lib/tasks/user.rake b/lib/tasks/user.rake new file mode 100644 index 0000000..0427322 --- /dev/null +++ b/lib/tasks/user.rake @@ -0,0 +1,11 @@ +namespace :user do + desc "Scrapes a photo from worlcup.com and attaches to users who don't have an image" + task :attach_photos, [:competition_id] => :environment do |t, args| + competition = Competition.find(args[:competition_id]) + competition.users.uniq.each do |user| + next if user.photo_key + + ScrapePhotoService.new(user: user, competition: competition).call + end + end +end From 68f32a1f6a3258625fa842e9ab3af7e306133bbf Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 24 Nov 2022 21:02:43 +0900 Subject: [PATCH 243/286] comp has many users through leaderboards --- app/models/competition.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index e008dc7..d45fc64 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -7,7 +7,7 @@ class Competition < ApplicationRecord has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy has_many :predictions, through: :matches, dependent: :destroy - has_many :users, through: :predictions + has_many :users, through: :leaderboards validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true From 9bc874a203fc9a26728a8543bbbff338ebbd7245 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 25 Nov 2022 10:49:43 +0900 Subject: [PATCH 244/286] competition has many users --- app/models/competition.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index d45fc64..83f0281 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -7,7 +7,8 @@ class Competition < ApplicationRecord has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy has_many :predictions, through: :matches, dependent: :destroy - has_many :users, through: :leaderboards + has_many :memberships, through: :leaderboards + has_many :users, through: :memberships validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true From a8595f2032d2d5b8b15245d843082723cb2a1ed5 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 27 Nov 2022 14:31:08 +0900 Subject: [PATCH 245/286] removed memberships association --- app/models/competition.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index 83f0281..3db4972 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -7,8 +7,7 @@ class Competition < ApplicationRecord has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy has_many :predictions, through: :matches, dependent: :destroy - has_many :memberships, through: :leaderboards - has_many :users, through: :memberships + has_many :users, through: :leaderboards, source: :users validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true From d17af5cfbb623fd4ae2631d900c1b1f927b627f5 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 27 Nov 2022 14:39:53 +0900 Subject: [PATCH 246/286] added api url --- app/services/live_score_api.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/services/live_score_api.rb b/app/services/live_score_api.rb index 0e2da1b..9ef1607 100644 --- a/app/services/live_score_api.rb +++ b/app/services/live_score_api.rb @@ -1,9 +1,14 @@ class LiveScoreApi - def self.matches_future_url(competition_id) - "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}" + def self.matches_future_url(competition_api_id) + "https://livescore-api.com/api-client/fixtures/matches.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_api_id}" end - def self.matches_history_url(competition_id) - "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}" + def self.matches_history_url(competition_api_id) + "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_api_id}" + end + + def self.matches_live_url(competition_api_id) + "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_api_id}" + "https://livescore-api.com/api-client/scores/live.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_api_id}" end end From acecb1dde3640b1f8070134d77412ee5d2a5af22 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 27 Nov 2022 14:59:03 +0900 Subject: [PATCH 247/286] added a job to update the matches live and added to rake task --- app/jobs/match_update_live_job.rb | 39 +++++++++++++++++++ app/services/live_score_api.rb | 1 - db/live_score_example.json | 50 +++++++++++++++++++++++++ lib/tasks/competition.rake | 1 + test/jobs/match_update_live_job_test.rb | 7 ++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 app/jobs/match_update_live_job.rb create mode 100644 db/live_score_example.json create mode 100644 test/jobs/match_update_live_job_test.rb diff --git a/app/jobs/match_update_live_job.rb b/app/jobs/match_update_live_job.rb new file mode 100644 index 0000000..cf6106a --- /dev/null +++ b/app/jobs/match_update_live_job.rb @@ -0,0 +1,39 @@ +class MatchUpdateLiveJob < ApplicationJob + queue_as :default + + def perform(competition_id) + competition = Competition.find(competition_id) + url_to_update = LiveScoreApi.matches_live_url(competition.api_id) + update_matches_live(url_to_update, competition) + end + + def get_team(id) + Team.find_by(api_id: id) + end + + def update_matches_live(url, competition) + # To test it locally, switch out the response: + # response = File.open('db/live_score_example.json').read + response = HTTParty.get(url).body + parsed_response = JSON.parse(response)['data'] + matches = parsed_response['match'] + matches.each do |match_info| + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" + match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + next unless match + + match.finished! if match_info['status'] == 'FINISHED' + match.started! if match_info['status'] == 'IN PLAY' + match.team_home_score, match.team_away_score = match_info['score']&.split(' - ') + match.team_home_et_score, match.team_away_et_score = match_info['et_score']&.split(' - ') + match.team_home_ps_score, match.team_away_ps_score = match_info['ps_score']&.split(' - ') + match.save + + scores = ["FT Score > #{match_info['score']}"] + scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score']&.blank? + scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score']&.blank? + puts "Match Update:\n#{scores.join("\n")}" + end + end +end diff --git a/app/services/live_score_api.rb b/app/services/live_score_api.rb index 9ef1607..7a5fe84 100644 --- a/app/services/live_score_api.rb +++ b/app/services/live_score_api.rb @@ -8,7 +8,6 @@ def self.matches_history_url(competition_api_id) end def self.matches_live_url(competition_api_id) - "http://livescore-api.com/api-client/scores/history.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_api_id}" "https://livescore-api.com/api-client/scores/live.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_api_id}" end end diff --git a/db/live_score_example.json b/db/live_score_example.json new file mode 100644 index 0000000..c598fb2 --- /dev/null +++ b/db/live_score_example.json @@ -0,0 +1,50 @@ +{"success":true,"data":{"match":[ +{ +"ht_score": "0 - 0", +"has_lineups": true, +"location": "Lusail Iconic Stadium, Lusail", +"events": "https://livescore-api.com/api-client/scores/events.json?key=ApDugiN21K7nGZRl&secret=uNR7nJIwjpFLurX5RBA7TnaV8hjOhaaL&id=382953", +"score": "2 - 0", +"league_id": 0, +"scheduled": "19:00", +"odds": { +"pre": { +"1": 1.55, +"2": 7, +"X": 3.8 +}, +"live": { +"1": 1.04, +"2": 151, +"X": 13 +} +}, +"competition_name": "FIFA World Cup", +"id": 382953, +"country": null, +"status": "IN PLAY", +"last_changed": "2022-11-26 20:53:03", +"et_score": "", +"competition_id": 362, +"fixture_id": 1527784, +"away_id": 1450, +"home_id": 1443, +"h2h": "https://livescore-api.com/api-client/teams/head2head.json?key=ApDugiN21K7nGZRl&secret=uNR7nJIwjpFLurX5RBA7TnaV8hjOhaaL&team1_id=1443&team2_id=1450", +"home_name": "Argentina", +"ps_score": "", +"time": "90+", +"league_name": "", +"away_name": "Mexico", +"ft_score": "", +"federation": { +"id": 1, +"name": "FIFA" +}, +"added": "2022-11-26 18:45:18", +"outcomes": { +"half_time": "X", +"full_time": null, +"extra_time": null +} +} +]}} diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index f8ef9a2..3a53137 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -120,6 +120,7 @@ namespace :competition do competitions.each do |competition| MatchUpdateFutureJob.perform_later(competition.id) MatchUpdateHistoryJob.perform_later(competition.id) + MatchUpdateLiveJob.perform_later(competition.id) end end diff --git a/test/jobs/match_update_live_job_test.rb b/test/jobs/match_update_live_job_test.rb new file mode 100644 index 0000000..d36eafd --- /dev/null +++ b/test/jobs/match_update_live_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class MatchUpdateLiveJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From 16f643933cdff0e7c1ee99c0bec8b07a569ee71c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 27 Nov 2022 15:05:50 +0900 Subject: [PATCH 248/286] refactored the api jobs to share match updating --- app/jobs/match_update_history_job.rb | 11 +---------- app/jobs/match_update_live_job.rb | 12 +----------- app/models/match.rb | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 16db0f3..88e4061 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -23,16 +23,7 @@ def update_matches_history(url, competition) match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match - match.finished! - match.team_home_score, match.team_away_score = match_info['score']&.split(' - ') - match.team_home_et_score, match.team_away_et_score = match_info['et_score']&.split(' - ') - match.team_home_ps_score, match.team_away_ps_score = match_info['ps_score']&.split(' - ') - match.save - - scores = ["FT Score > #{match_info['score']}"] - scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score']&.blank? - scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score']&.blank? - puts "Match Update:\n#{scores.join("\n")}" + match.update_with_api(match_info) end return parsed_response['next_page'] end diff --git a/app/jobs/match_update_live_job.rb b/app/jobs/match_update_live_job.rb index cf6106a..48ce912 100644 --- a/app/jobs/match_update_live_job.rb +++ b/app/jobs/match_update_live_job.rb @@ -23,17 +23,7 @@ def update_matches_live(url, competition) match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) next unless match - match.finished! if match_info['status'] == 'FINISHED' - match.started! if match_info['status'] == 'IN PLAY' - match.team_home_score, match.team_away_score = match_info['score']&.split(' - ') - match.team_home_et_score, match.team_away_et_score = match_info['et_score']&.split(' - ') - match.team_home_ps_score, match.team_away_ps_score = match_info['ps_score']&.split(' - ') - match.save - - scores = ["FT Score > #{match_info['score']}"] - scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score']&.blank? - scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score']&.blank? - puts "Match Update:\n#{scores.join("\n")}" + match.update_with_api(match_info) end end end diff --git a/app/models/match.rb b/app/models/match.rb index 59ecbf8..33e0d18 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -51,6 +51,20 @@ def winner_side home_wins ? 'home' : 'away' end + def update_with_api(match_info) + finished! if match_info['status'] == 'FINISHED' + started! if match_info['status'] == 'IN PLAY' + self.team_home_score, self.team_away_score = match_info['score']&.split(' - ') + self.team_home_et_score, self.team_away_et_score = match_info['et_score']&.split(' - ') + self.team_home_ps_score, self.team_away_ps_score = match_info['ps_score']&.split(' - ') + save + + scores = ["FT Score > #{match_info['score']}"] + scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score']&.blank? + scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score']&.blank? + puts "Match Update:\n#{scores.join("\n")}" + end + private def round_xor_group From 6019c165336513a37855532fe7d7d81a61e4b584 Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Mon, 28 Nov 2022 19:43:27 +0900 Subject: [PATCH 249/286] Send scores if match is finished or started --- app/views/v1/matches/index.json.jbuilder | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 75567f5..0d94e17 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -7,7 +7,7 @@ json.array! @matches do |match| json.abbrev match[:team_home_abbrev] json.badge_url cl_image_path(match[:team_home_badge_key]) json.flag_url cl_image_path(match[:team_home_flag_key]) - if match[:status] == 'finished' + if %w[finished started].include?(match[:status]) json.score match[:team_home_score] json.et_score match[:team_home_et_score] json.ps_score match[:team_home_ps_score] @@ -19,7 +19,7 @@ json.array! @matches do |match| json.abbrev match[:team_away_abbrev] json.badge_url cl_image_path(match[:team_away_badge_key]) json.flag_url cl_image_path(match[:team_away_flag_key]) - if match[:status] == 'finished' + if %w[finished started].include?(match[:status]) json.score match[:team_away_score] json.et_score match[:team_away_et_score] json.ps_score match[:team_away_ps_score] From bccb7cbc70cecda812ca3b09da6da79439e5fa4e Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 30 Nov 2022 13:49:07 +0900 Subject: [PATCH 250/286] updated round creation for the world cup" --- lib/tasks/round.rake | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/tasks/round.rake b/lib/tasks/round.rake index bfc2b48..706bfe4 100644 --- a/lib/tasks/round.rake +++ b/lib/tasks/round.rake @@ -1,14 +1,16 @@ namespace :round do desc 'Creating the rounds for the Euros' task create_all: :environment do - euros = Competition.find_by(name: 'Euro 2020') + world_cup = Competition.find_by(name: 'World Cup 2022') puts 'Creating or finding first round...' - # Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: '3') - Round.find_or_create_by!(name: 'Round of 16', number: 2, competition: euros, api_name: 'R16') - Round.find_or_create_by!(name: 'Quarter-finals', number: 3, competition: euros, api_name: 'QF') - Round.find_or_create_by!(name: 'Semi-finals', number: 4, competition: euros, api_name: 'SF') - Round.find_or_create_by!(name: 'Final', number: 5, competition: euros, api_name: 'F') - puts "...#{euros.rounds.count} Total Rounds" + # First round was created when the competition was created. Next time, run all together + # Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: world_cup, api_name: '3') + Round.find_or_create_by!(name: 'Round of 16', number: 2, competition: world_cup, api_name: 'R16') + Round.find_or_create_by!(name: 'Quarter-finals', number: 3, competition: world_cup, api_name: 'QF') + Round.find_or_create_by!(name: 'Semi-finals', number: 4, competition: world_cup, api_name: 'SF') + Round.find_or_create_by!(name: 'Third Place', number: 5, competition: world_cup, api_name: '3PPO') + Round.find_or_create_by!(name: 'Final', number: 6, competition: world_cup, api_name: 'F') + puts "...#{world_cup.rounds.count} Total Rounds" end end From 5424f3d0d7279eccc322b9dae55e9b90cf90fea2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 30 Nov 2022 19:02:26 +0900 Subject: [PATCH 251/286] updated browser --- app/services/scrape_photo_service.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/scrape_photo_service.rb b/app/services/scrape_photo_service.rb index a2e6fd7..409b895 100644 --- a/app/services/scrape_photo_service.rb +++ b/app/services/scrape_photo_service.rb @@ -9,8 +9,9 @@ def initialize(attrs = {}) end def call - url = "https://www.fifa.com/fifaplus/en/tournaments/mens/worldcup/qatar2022/teams/#{competition.teams.sample.name.split.join('-').downcase}/squad" - browser = Watir::Browser.new :chrome, headless: true + url = "https://www.fifa.com/fifaplus/en/tournaments/mgstens/worldcup/qatar2022/teams/#{competition.teams.sample.name.split.join('-').downcase}/squad" + # browser = Watir::Browser.new :chrome, args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] + browser = Watir::Browser.new :chrome, options: { args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] } browser.goto url sleep(15) html_doc = Nokogiri::HTML.parse(browser.html) From bc9a2a3590e8c9162a54ee46978de1d2f2adaa59 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Wed, 30 Nov 2022 19:14:05 +0900 Subject: [PATCH 252/286] added options to the browser. upgraded webdrivers. changed the url --- Gemfile.lock | 6 +++--- app/services/scrape_photo_service.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index eee6191..1831586 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -298,10 +298,10 @@ GEM watir (7.1.0) regexp_parser (>= 1.2, < 3) selenium-webdriver (~> 4.0) - webdrivers (4.7.0) + webdrivers (5.2.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (> 3.141, < 5.0) + selenium-webdriver (~> 4.0) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) @@ -350,4 +350,4 @@ RUBY VERSION ruby 3.1.2p20 BUNDLED WITH - 2.3.19 + 2.3.25 diff --git a/app/services/scrape_photo_service.rb b/app/services/scrape_photo_service.rb index 409b895..f8514fc 100644 --- a/app/services/scrape_photo_service.rb +++ b/app/services/scrape_photo_service.rb @@ -9,10 +9,10 @@ def initialize(attrs = {}) end def call - url = "https://www.fifa.com/fifaplus/en/tournaments/mgstens/worldcup/qatar2022/teams/#{competition.teams.sample.name.split.join('-').downcase}/squad" - # browser = Watir::Browser.new :chrome, args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] + url = "https://www.fifa.com/fifaplus/en/tournaments/mens/worldcup/qatar2022/teams/#{competition.teams.sample.name.split.join('-').downcase}/squad" browser = Watir::Browser.new :chrome, options: { args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] } browser.goto url + puts "Going to: #{url}" sleep(15) html_doc = Nokogiri::HTML.parse(browser.html) main_div = html_doc.search('main section')[2] From 0abe3bd29810f2f481d2a9c1f723be2faccd3a13 Mon Sep 17 00:00:00 2001 From: Trouni <34345789+trouni@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:24:12 +0900 Subject: [PATCH 253/286] Add results to leaderboards endpoint (#101) * Refactor leaderboard owner to have normal membership * Fix deprecation warning * Remove inspect * Add local IP addresses to CORS for dev * Add results to `leaderboards#index` * Setting policy to true since owner will now have a membership * Fix leaderboards association * Fix leaderboard policy * Refactor using Database Views (#110) * Create scenic views * Make views materialized * Fix queries in views * Update views and associations * Fix match results view * Refactor matches controller * Add missing lines at EOF * Fix leaderboard rankings and remove unnecessary associations * Clean up unused code * Refactor views into partials * Add accuracy to users#show response * Add missing lines at EOF * Add refresh views callback to most models --- Gemfile | 3 + Gemfile.lock | 15 +++ app/controllers/application_controller.rb | 2 +- app/controllers/v1/leaderboards_controller.rb | 8 +- app/controllers/v1/matches_controller.rb | 9 +- app/models/application_record.rb | 9 ++ app/models/competition.rb | 14 +- app/models/group.rb | 41 ------ app/models/leaderboard.rb | 28 ++-- app/models/leaderboard_ranking.rb | 5 + app/models/match.rb | 49 ++----- app/models/match_result.rb | 10 ++ app/models/membership.rb | 10 ++ app/models/prediction.rb | 13 +- app/models/round.rb | 9 +- app/models/scenic_view_record.rb | 7 + app/models/team.rb | 22 --- app/models/user.rb | 85 +----------- app/models/user_score.rb | 4 + app/policies/leaderboard_policy.rb | 2 +- app/policies/membership_policy.rb | 2 +- app/policies/user/match_policy.rb | 7 - .../leaderboards/_predictions.json.jbuilder | 5 + .../v1/leaderboards/_ranking.json.jbuilder | 9 ++ app/views/v1/leaderboards/index.json.jbuilder | 14 +- app/views/v1/matches/_match.json.jbuilder | 18 +++ app/views/v1/matches/index.json.jbuilder | 37 +---- .../v1/predictions/_prediction.json.jbuilder | 2 +- app/views/v1/users/_user.json.jbuilder | 3 +- app/views/v1/users/show.json.jbuilder | 7 +- config/environments/development.rb | 6 + config/initializers/cors.rb | 1 + ...07_add_competition_and_round_to_matches.rb | 22 +++ .../20221204123021_add_points_to_rounds.rb | 15 +++ ...938_change_predictions_choice_to_string.rb | 33 +++++ .../20221204123940_create_match_results.rb | 5 + .../20221204124451_create_user_scores.rb | 5 + ...21204153322_create_leaderboard_rankings.rb | 5 + db/schema.rb | 126 +++++++++++++++++- db/views/leaderboard_rankings_v01.sql | 11 ++ db/views/match_results_v01.sql | 45 +++++++ db/views/user_scores_v01.sql | 64 +++++++++ 42 files changed, 513 insertions(+), 274 deletions(-) create mode 100644 app/models/leaderboard_ranking.rb create mode 100644 app/models/match_result.rb create mode 100644 app/models/scenic_view_record.rb create mode 100644 app/models/user_score.rb delete mode 100644 app/policies/user/match_policy.rb create mode 100644 app/views/v1/leaderboards/_predictions.json.jbuilder create mode 100644 app/views/v1/leaderboards/_ranking.json.jbuilder create mode 100644 app/views/v1/matches/_match.json.jbuilder create mode 100644 db/migrate/20221204113607_add_competition_and_round_to_matches.rb create mode 100644 db/migrate/20221204123021_add_points_to_rounds.rb create mode 100644 db/migrate/20221204123938_change_predictions_choice_to_string.rb create mode 100644 db/migrate/20221204123940_create_match_results.rb create mode 100644 db/migrate/20221204124451_create_user_scores.rb create mode 100644 db/migrate/20221204153322_create_leaderboard_rankings.rb create mode 100644 db/views/leaderboard_rankings_v01.sql create mode 100644 db/views/match_results_v01.sql create mode 100644 db/views/user_scores_v01.sql diff --git a/Gemfile b/Gemfile index 223e8aa..7f17724 100644 --- a/Gemfile +++ b/Gemfile @@ -35,10 +35,12 @@ gem 'omniauth' gem 'net-smtp', require: false gem 'net-imap', require: false gem 'net-pop', require: false +gem 'progress_bar' gem 'pundit' gem 'rexml' gem 'scout_apm' gem 'sidekiq', '~> 5.0.4' +gem 'scenic' gem 'sidekiq-failures', '~> 1.0' gem "watir", "~> 7.1" gem 'webdrivers' @@ -52,6 +54,7 @@ group :development, :test do end group :development do + gem 'bullet' gem 'letter_opener' gem 'listen', '~> 3.3' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring diff --git a/Gemfile.lock b/Gemfile.lock index 1831586..930c510 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,6 +77,9 @@ GEM bootsnap (1.13.0) msgpack (~> 1.2) builder (3.2.4) + bullet (7.0.3) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) byebug (11.1.3) childprocess (4.1.0) cloudinary (1.16.1) @@ -112,6 +115,7 @@ GEM thor tilt hashie (5.0.0) + highline (2.0.3) http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) @@ -179,10 +183,14 @@ GEM hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection + options (2.3.2) orm_adapter (0.5.0) parser (3.1.2.1) ast (~> 2.4.1) pg (1.4.4) + progress_bar (1.3.3) + highline (>= 1.6, < 3) + options (~> 2.3.0) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) @@ -245,6 +253,9 @@ GEM rexml (3.2.5) ruby2_keywords (0.0.5) rubyzip (2.3.2) + scenic (1.6.0) + activerecord (>= 4.0.0) + railties (>= 4.0.0) scout_apm (5.3.2) parser selenium-webdriver (4.6.1) @@ -293,6 +304,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.8.2) + uniform_notifier (1.16.0) warden (1.2.9) rack (>= 2.0.9) watir (7.1.0) @@ -315,6 +327,7 @@ PLATFORMS DEPENDENCIES bootsnap (>= 1.4.4) + bullet byebug cloudinary (~> 1.16.0) devise @@ -331,6 +344,7 @@ DEPENDENCIES net-smtp omniauth pg (~> 1.1) + progress_bar pry-byebug puma (~> 5.0) pundit @@ -338,6 +352,7 @@ DEPENDENCIES rails (~> 6.1.7) redis (~> 3.3.3) rexml + scenic scout_apm sidekiq (~> 5.0.4) sidekiq-failures (~> 1.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 08107e1..8f03d68 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,6 @@ class ApplicationController < ActionController::API include DeviseTokenAuth::Concerns::SetUserByToken - include Pundit + include Pundit::Authorization before_action :authenticate_user!, unless: :token_auth_controller? diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index d5d2b64..87bcaab 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -1,8 +1,8 @@ class V1::LeaderboardsController < ApplicationController - def index @competition = Competition.find(params[:competition_id]) - @leaderboards = policy_scope(Leaderboard).where(competition: @competition) + @leaderboards = policy_scope(Leaderboard).includes(:match_results, rankings: %i[user]) + .where(competition: @competition) end def create @@ -21,7 +21,8 @@ def create def destroy @leaderboard = Leaderboard.find(params[:id]) authorize @leaderboard - @leaderboard.leave(current_user) + membership = @leaderboard.memberships.find_by!(user: current_user) + membership.destroy head :no_content end @@ -30,5 +31,4 @@ def destroy def leaderboard_params params.require(:leaderboard).permit(:name) end - end diff --git a/app/controllers/v1/matches_controller.rb b/app/controllers/v1/matches_controller.rb index a89f793..257b7ca 100644 --- a/app/controllers/v1/matches_controller.rb +++ b/app/controllers/v1/matches_controller.rb @@ -2,8 +2,11 @@ class V1::MatchesController < ApplicationController # /matches?competition_id=:id&user_id=:id def index @user = User.find_by(id: params[:user_id]) || current_user - p @competition = Competition.find_by(id: params[:competition_id]) - skip_policy_scope - @matches = @user.matches(competition: @competition) + competition = Competition.find_by(id: params[:competition_id]) + @matches = policy_scope(Match).includes( + :round, + team_home: [badge_attachment: :blob, flag_attachment: :blob], + team_away: [badge_attachment: :blob ,flag_attachment: :blob] + ).where(competition: competition) end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 46c0498..f5b4a3d 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,6 +1,8 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + private + def self.execute_sql(query, args = {}) query = sanitize_sql([query, args]) results = ActiveRecord::Base.connection.execute(query) @@ -8,4 +10,11 @@ def self.execute_sql(query, args = {}) results end + + def refresh_materialized_views + # The materialized views need to be refreshed in this order + MatchResult.refresh + UserScore.refresh + LeaderboardRanking.refresh + end end diff --git a/app/models/competition.rb b/app/models/competition.rb index 3db4972..8ff8cb3 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1,23 +1,23 @@ class Competition < ApplicationRecord belongs_to :current_round, class_name: 'Round', optional: true - has_many :rounds, dependent: :destroy + has_many :matches + has_many :rounds, -> { distinct }, through: :matches has_many :groups, through: :rounds - has_many :matches, through: :groups has_many :affiliations, through: :groups has_many :teams, through: :affiliations has_many :leaderboards, dependent: :destroy has_many :predictions, through: :matches, dependent: :destroy has_many :users, through: :leaderboards, source: :users + validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true validates :end_date, presence: true + scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today + 1, end: Date.today - 1) } - def matches - Match.where(group: groups).or(Match.where(round: rounds)) - end + after_commit :refresh_materialized_views - def predictions - Prediction.where(match: matches) + def max_possible_score + matches.finished.joins(:round).sum('rounds.points') end end diff --git a/app/models/group.rb b/app/models/group.rb index 0ec7dba..3888ab3 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -5,45 +5,4 @@ class Group < ApplicationRecord has_many :teams, through: :affiliations validates :name, presence: true validates_uniqueness_of :name, scope: :round - - RANKING_SQL = <<-SQL.freeze - WITH match_results AS ( - SELECT teams.id AS team_id, matches.id AS match_id, - CASE -- Calculating points earned per match - WHEN - (team_home_id = teams.id AND team_home_score > team_away_score) - OR (team_away_id = teams.id AND team_home_score < team_away_score) - THEN 3 - WHEN - (team_home_id = teams.id AND team_home_score < team_away_score) OR - (team_away_id = teams.id AND team_home_score > team_away_score) - THEN 0 - ELSE 1 - END AS result, - CASE -- Calculating goal difference - WHEN team_home_id = teams.id THEN team_home_score - team_away_score - ELSE team_away_score - team_home_score - END AS match_goal_diff - FROM teams - JOIN matches ON team_home_id = teams.id OR team_away_id = teams.id - WHERE status = 'finished' AND group_id = :group_id - ) - SELECT teams.*, SUM(result) AS points, SUM(match_goal_diff) AS goal_diff, COUNT(*) AS matches_count - FROM match_results - JOIN teams ON team_id = teams.id - GROUP BY teams.id - ORDER BY points DESC, goal_diff DESC - SQL - - def ranking - Group.execute_sql(RANKING_SQL, group_id: id) - end - - def leader - Team.find(ranking.first['id']) - end - - def runner_up - Team.find(ranking[1]['id']) - end end diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 7fd6f0c..3b99b23 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -3,22 +3,16 @@ class Leaderboard < ApplicationRecord belongs_to :competition has_many :memberships, dependent: :destroy has_many :users, through: :memberships - validates :name, presence: true - has_secure_token :password + has_many :locked_predictions, -> { locked }, through: :users, source: :predictions - def users - User.includes(:memberships).where(memberships: { leaderboard: self }).or(User.where(id: user)) - end + # Scenic views + has_many :match_results, -> { distinct } + has_many :rankings, class_name: 'LeaderboardRanking' - def leave(current_user) - if user == current_user - # remove if empty OR transfer - memberships.any? ? transfer_ownership : destroy - elsif (membership = memberships.find_by(user: current_user)) - # remove membership if just a regular member - membership.destroy - end - end + validates :name, presence: true + has_secure_token :password + after_create :create_owner_membership + after_commit :refresh_materialized_views def transfer_ownership membership = memberships.first @@ -26,4 +20,10 @@ def transfer_ownership self.user = membership.user save end + + private + + def create_owner_membership + memberships.create(user: user) + end end diff --git a/app/models/leaderboard_ranking.rb b/app/models/leaderboard_ranking.rb new file mode 100644 index 0000000..6b9b3bd --- /dev/null +++ b/app/models/leaderboard_ranking.rb @@ -0,0 +1,5 @@ +class LeaderboardRanking < ScenicViewRecord + belongs_to :leaderboard + belongs_to :competition + belongs_to :user +end diff --git a/app/models/match.rb b/app/models/match.rb index 33e0d18..ed41be6 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -1,8 +1,9 @@ class Match < ApplicationRecord + belongs_to :competition belongs_to :team_away, class_name: 'Team' belongs_to :team_home, class_name: 'Team' belongs_to :group, optional: true - belongs_to :round, optional: true + belongs_to :round belongs_to :next_match, class_name: 'Match', optional: true has_many :predictions, dependent: :destroy has_many :users, through: :predictions @@ -10,46 +11,13 @@ class Match < ApplicationRecord validates :status, presence: true validates :api_id, uniqueness: { allow_nil: true } validates_uniqueness_of :kickoff_time, scope: %i[team_home team_away] - validate :round_xor_group enum status: { upcoming: 'upcoming', started: 'started', finished: 'finished' }, _default: :upcoming - def draw? - return unless finished? + # Scenic views + has_many :results, class_name: 'MatchResult' - team_home_score == team_away_score && - team_home_et_score == team_away_et_score && - team_home_ps_score == team_away_ps_score - end - - def round - super || group&.round - end - - def winner - return unless finished? - return if draw? - - winner_side == 'home' ? team_home : team_away - end - - def extra_time? - team_home_et_score.present? && team_away_et_score.present? - end - - def penalties? - team_home_ps_score.present? && team_away_ps_score.present? - end - - def winner_side - return unless finished? - return 'draw' if draw? - - home_wins = team_home_score > team_away_score - home_wins = team_home_et_score > team_away_et_score if extra_time? - home_wins = team_home_ps_score > team_away_ps_score if penalties? - - home_wins ? 'home' : 'away' - end + before_validation :set_round_and_competition, on: :create + after_commit :refresh_materialized_views def update_with_api(match_info) finished! if match_info['status'] == 'FINISHED' @@ -67,7 +35,8 @@ def update_with_api(match_info) private - def round_xor_group - errors.add(:round_id, 'or Group (not both) must exist') unless group_id.present? ^ round_id.present? + def set_round_and_competition + self.round ||= group&.round + self.competition ||= round&.competition end end diff --git a/app/models/match_result.rb b/app/models/match_result.rb new file mode 100644 index 0000000..c3ba600 --- /dev/null +++ b/app/models/match_result.rb @@ -0,0 +1,10 @@ +class MatchResult < ScenicViewRecord + belongs_to :match + belongs_to :group, optional: true + belongs_to :round + belongs_to :competition + belongs_to :team_home, class_name: 'Team' + belongs_to :team_away, class_name: 'Team' + + enum status: { upcoming: 'upcoming', started: 'started', finished: 'finished' } +end diff --git a/app/models/membership.rb b/app/models/membership.rb index 80b304d..5b09bd6 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -3,4 +3,14 @@ class Membership < ApplicationRecord belongs_to :user has_one :competition, through: :leaderboard validates_uniqueness_of :user, scope: :leaderboard + after_destroy :update_leaderboard_ownership + after_commit :refresh_materialized_views + + private + + def update_leaderboard_ownership + return unless user == leaderboard.user + + leaderboard.memberships.any? ? leaderboard.transfer_ownership : leaderboard.destroy + end end diff --git a/app/models/prediction.rb b/app/models/prediction.rb index b55f6bd..441157a 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -4,11 +4,16 @@ class Prediction < ApplicationRecord belongs_to :user validates_uniqueness_of :user, scope: :match validates :choice, presence: true - enum choice: %i[home away draw] + enum choice: { home: 'home', away: 'away', draw: 'draw' } - def correct? - return unless match.finished? + scope :locked, -> { joins(:match).where.not(matches: { status: :upcoming }) } - choice == match.winner_side + after_commit :refresh_materialized_views + + private + + def refresh_materialized_views + UserScore.refresh + LeaderboardRanking.refresh end end diff --git a/app/models/round.rb b/app/models/round.rb index 5785743..5d8736e 100644 --- a/app/models/round.rb +++ b/app/models/round.rb @@ -4,7 +4,12 @@ class Round < ApplicationRecord has_many :matches, through: :groups validates :name, presence: true, uniqueness: { scope: :competition } - def points - number + 2 + before_validation :set_points, on: :create + after_commit :refresh_materialized_views + + private + + def set_points + self.points ||= number + 2 end end diff --git a/app/models/scenic_view_record.rb b/app/models/scenic_view_record.rb new file mode 100644 index 0000000..a846f9b --- /dev/null +++ b/app/models/scenic_view_record.rb @@ -0,0 +1,7 @@ +class ScenicViewRecord < ApplicationRecord + self.abstract_class = true + + def self.refresh + Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false) + end +end diff --git a/app/models/team.rb b/app/models/team.rb index d5f83e8..26405d1 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -10,26 +10,4 @@ def matches # teams can either be home or away Match.where('team_home_id = :id OR team_away_id = :id', id: id) end - - # These methods are not used for Group rankings, but could be used for individual team stats - def victories - matches.finished.where(<<-SQL, id: id) - (team_home_id = :id AND team_home_score > team_away_score) OR - (team_away_id = :id AND team_home_score < team_away_score) - SQL - end - - def defeats - matches.finished.where(<<-SQL, id: id) - (team_home_id = :id AND team_home_score < team_away_score) OR - (team_away_id = :id AND team_home_score > team_away_score) - SQL - end - - def draws - matches.finished.where(<<-SQL, id: id) - (team_home_id = :id OR team_away_id = :id) AND - team_home_score = team_away_score - SQL - end end diff --git a/app/models/user.rb b/app/models/user.rb index a62082a..c41cb18 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,91 +5,18 @@ class User < ApplicationRecord :omniauthable # :confirmable include DeviseTokenAuth::Concerns::User has_many :memberships, dependent: :destroy + has_many :leaderboards, through: :memberships # TODO: Fix this # has_many :competitions, through: :leaderboards has_many :predictions, dependent: :destroy has_many :matches, through: :predictions - validates :name, presence: true, on: :update, if: :name_changed? - - def leaderboards(competition = nil) - # this includes creator or leaderboard and members - leaderboards = Leaderboard.includes(:memberships).where(memberships: { user: self }).or(Leaderboard.where(user: self)) - leaderboards = leaderboards.where(competition: competition) if competition - leaderboards - end - - def display_name - name || email.split('@').first - end - def score(competition) - # TODO: user predictions should be scoped by competition => prediction -> match -> group -> round -> competition - # predictions.where(competition: competition).count(&:correct?) * 3 - competition.predictions.includes(match: [:round, :group]).where(user: self).sum do |prediction| - prediction.correct? ? prediction.match.round.points : 0 - end - end + # Scenic views + has_many :scores, class_name: 'UserScore' - def possible_score(competition) - competition.matches.where(status: 'finished').sum do |match| - match.round.points - end - end + validates :name, presence: true, on: :update, if: :name_changed? - def matches(competition: nil) - query = <<-SQL.freeze - WITH predictions AS ( - SELECT * - FROM predictions - WHERE user_id = :user_id OR user_id IS NULL - ), team_badges AS ( - SELECT teams.id AS team_id, asb.key as key - FROM teams - INNER JOIN active_storage_attachments asat ON asat.record_id = teams.id - JOIN active_storage_blobs asb ON asb.id = asat.blob_id - WHERE asat.name = 'badge' AND asat.record_type = 'Team' - ), team_flags AS ( - SELECT teams.id AS team_id, asb.key as key - FROM teams - INNER JOIN active_storage_attachments asat ON asat.record_id = teams.id - JOIN active_storage_blobs asb ON asb.id = asat.blob_id - WHERE asat.name = 'flag' AND asat.record_type = 'Team' - ), team_with_images AS ( - SELECT teams.*, team_badges.key AS badge_key, team_flags.key AS flag_key - FROM teams - LEFT JOIN team_badges ON teams.id = team_badges.team_id - LEFT JOIN team_flags ON teams.id = team_flags.team_id - ) - SELECT - matches.*, - rounds.number AS round_number, - rounds.name AS round_name, - team_home.name AS team_home_name, - team_home.abbrev AS team_home_abbrev, - team_home.badge_key AS team_home_badge_key, - team_home.flag_key AS team_home_flag_key, - team_away.name AS team_away_name, - team_away.abbrev AS team_away_abbrev, - team_away.badge_key AS team_away_badge_key, - team_away.flag_key AS team_away_flag_key, - predictions.id AS prediction_id, - predictions.user_id AS prediction_user_id, - predictions.match_id AS prediction_match_id, - CASE - WHEN predictions.choice = 0 THEN 'home' - WHEN predictions.choice = 1 THEN 'away' - WHEN predictions.choice = 2 THEN 'draw' - ELSE NULL - END AS prediction_choice - FROM matches - LEFT JOIN groups ON matches.group_id = groups.id - LEFT JOIN rounds ON matches.round_id = rounds.id OR groups.round_id = rounds.id - JOIN team_with_images team_away ON matches.team_away_id = team_away.id - JOIN team_with_images team_home ON matches.team_home_id = team_home.id - LEFT JOIN predictions ON predictions.match_id = matches.id - #{'WHERE rounds.competition_id = :competition_id' if competition} - ORDER BY matches.kickoff_time - SQL - User.execute_sql(query, user_id: id, competition_id: competition&.id) + def name + super || email.split('@').first end end diff --git a/app/models/user_score.rb b/app/models/user_score.rb new file mode 100644 index 0000000..4ce3964 --- /dev/null +++ b/app/models/user_score.rb @@ -0,0 +1,4 @@ +class UserScore < ScenicViewRecord + belongs_to :user + belongs_to :competition +end diff --git a/app/policies/leaderboard_policy.rb b/app/policies/leaderboard_policy.rb index 0b49f5e..46b4d18 100644 --- a/app/policies/leaderboard_policy.rb +++ b/app/policies/leaderboard_policy.rb @@ -1,7 +1,7 @@ class LeaderboardPolicy < ApplicationPolicy class Scope < Scope def resolve - user.leaderboards + scope.joins(:memberships).where(memberships: { user: user }) end end diff --git a/app/policies/membership_policy.rb b/app/policies/membership_policy.rb index d561680..1d3d9c8 100644 --- a/app/policies/membership_policy.rb +++ b/app/policies/membership_policy.rb @@ -6,6 +6,6 @@ def resolve end def create? - record.leaderboard.user != user + true end end diff --git a/app/policies/user/match_policy.rb b/app/policies/user/match_policy.rb deleted file mode 100644 index 5bc9d59..0000000 --- a/app/policies/user/match_policy.rb +++ /dev/null @@ -1,7 +0,0 @@ -class User::MatchPolicy < ApplicationPolicy - class Scope < Scope - def resolve - user.matches - end - end -end diff --git a/app/views/v1/leaderboards/_predictions.json.jbuilder b/app/views/v1/leaderboards/_predictions.json.jbuilder new file mode 100644 index 0000000..c079557 --- /dev/null +++ b/app/views/v1/leaderboards/_predictions.json.jbuilder @@ -0,0 +1,5 @@ +json.set! result.match_id do + json.home result.predicted_home + json.draw result.predicted_draw + json.away result.predicted_away +end diff --git a/app/views/v1/leaderboards/_ranking.json.jbuilder b/app/views/v1/leaderboards/_ranking.json.jbuilder new file mode 100644 index 0000000..9b9e156 --- /dev/null +++ b/app/views/v1/leaderboards/_ranking.json.jbuilder @@ -0,0 +1,9 @@ +json.user_id ranking.user_id +json.name ranking.user.name +json.photo_key ranking.user.photo_key +json.points ranking.score +json.total_predictions ranking.total_predictions +json.completed_predictions ranking.completed_predictions +json.correct_predictions ranking.correct_predictions +json.accuracy ranking.accuracy +json.rank ranking.user_rank diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 7483d58..1323b64 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -1,11 +1,11 @@ json.array! @leaderboards do |leaderboard| json.partial! leaderboard - json.users leaderboard.users do |user| - json.user_id user.id - json.name user.display_name - json.points user.score(leaderboard.competition) - json.photo_key user.photo_key + json.users leaderboard.rankings do |ranking| + json.partial! 'v1/leaderboards/ranking', ranking: ranking + end + json.results do + leaderboard.match_results.each do |result| + json.partial! 'v1/leaderboards/predictions', result: result + end end end - -# TODO: add 🔼 or 🔽 diff --git a/app/views/v1/matches/_match.json.jbuilder b/app/views/v1/matches/_match.json.jbuilder new file mode 100644 index 0000000..f5d9efb --- /dev/null +++ b/app/views/v1/matches/_match.json.jbuilder @@ -0,0 +1,18 @@ +json.extract! match, :id, :kickoff_time, :status, :group_id, :next_match_id, :round_id, :location +json.round_number match.round.number +json.team_home do + json.partial! match.team_home + if %w[finished started].include?(match[:status]) + json.score match.team_home_score + json.et_score match.team_home_et_score + json.ps_score match.team_home_ps_score + end +end +json.team_away do + json.partial! match.team_away + if %w[finished started].include?(match[:status]) + json.score match.team_away_score + json.et_score match.team_away_et_score + json.ps_score match.team_away_ps_score + end +end diff --git a/app/views/v1/matches/index.json.jbuilder b/app/views/v1/matches/index.json.jbuilder index 0d94e17..dfd4d59 100644 --- a/app/views/v1/matches/index.json.jbuilder +++ b/app/views/v1/matches/index.json.jbuilder @@ -1,36 +1,7 @@ json.array! @matches do |match| - match.symbolize_keys! - json.merge! match.slice(:id, :kickoff_time, :status, :group_id, :next_match_id, :round_id, :location, :round_number) - json.team_home do - json.id match[:team_home_id] - json.name match[:team_home_name] - json.abbrev match[:team_home_abbrev] - json.badge_url cl_image_path(match[:team_home_badge_key]) - json.flag_url cl_image_path(match[:team_home_flag_key]) - if %w[finished started].include?(match[:status]) - json.score match[:team_home_score] - json.et_score match[:team_home_et_score] - json.ps_score match[:team_home_ps_score] - end - end - json.team_away do - json.id match[:team_away_id] - json.name match[:team_away_name] - json.abbrev match[:team_away_abbrev] - json.badge_url cl_image_path(match[:team_away_badge_key]) - json.flag_url cl_image_path(match[:team_away_flag_key]) - if %w[finished started].include?(match[:status]) - json.score match[:team_away_score] - json.et_score match[:team_away_et_score] - json.ps_score match[:team_away_ps_score] - end - end - if match[:prediction_choice] - json.prediction do - json.id match[:prediction_id] - json.choice match[:prediction_choice] - json.user_id match[:prediction_user_id] - json.match_id match[:prediction_match_id] - end + json.partial! match + json.prediction do + prediction = match.predictions.find_by(user: @user) + json.partial! prediction if prediction.present? end end diff --git a/app/views/v1/predictions/_prediction.json.jbuilder b/app/views/v1/predictions/_prediction.json.jbuilder index 83075f4..919f96c 100644 --- a/app/views/v1/predictions/_prediction.json.jbuilder +++ b/app/views/v1/predictions/_prediction.json.jbuilder @@ -1 +1 @@ -json.extract! prediction, :id, :choice, :match_id, :user_id, :correct? +json.extract! prediction, :id, :choice, :match_id, :user_id diff --git a/app/views/v1/users/_user.json.jbuilder b/app/views/v1/users/_user.json.jbuilder index 2a6d55a..fcb41bd 100644 --- a/app/views/v1/users/_user.json.jbuilder +++ b/app/views/v1/users/_user.json.jbuilder @@ -1,2 +1 @@ -json.extract! user, :id, :email, :timezone, :admin, :photo_key -json.name user.display_name +json.extract! user, :id, :email, :timezone, :admin, :photo_key, :name diff --git a/app/views/v1/users/show.json.jbuilder b/app/views/v1/users/show.json.jbuilder index 4da5a88..2b9bb93 100644 --- a/app/views/v1/users/show.json.jbuilder +++ b/app/views/v1/users/show.json.jbuilder @@ -1,3 +1,6 @@ json.partial! @user -json.points @user.score(@competition) if @competition -json.possible_points @user.possible_score(@competition) if @competition +if @competition + json.points @user.scores.find_by(competition: @competition).score + json.accuracy @user.scores.find_by(competition: @competition).accuracy.to_f + json.possible_points @competition.max_possible_score +end diff --git a/config/environments/development.rb b/config/environments/development.rb index 1cd8f51..6f09d00 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,6 +1,12 @@ require "active_support/core_ext/integer/time" Rails.application.configure do + config.after_initialize do + Bullet.enable = true + Bullet.console = true + Bullet.rails_logger = true + end + # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded any time diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index d8f1bc0..89b2fe1 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -10,6 +10,7 @@ origins [ # Local server %r{\Ahttps?://localhost:\d{4}}, + %r{\Ahttps?://192\.168\.\d\.\d{1,3}:\d{4}}, # Netlify app and preview deploys %r{\Ahttps?://(.+--)?octacle\.netlify\.app}, # Production app diff --git a/db/migrate/20221204113607_add_competition_and_round_to_matches.rb b/db/migrate/20221204113607_add_competition_and_round_to_matches.rb new file mode 100644 index 0000000..c08e7e7 --- /dev/null +++ b/db/migrate/20221204113607_add_competition_and_round_to_matches.rb @@ -0,0 +1,22 @@ +class AddCompetitionAndRoundToMatches < ActiveRecord::Migration[6.1] + def up + add_reference :matches, :competition, foreign_key: true + + Match.reset_column_information + Match.find_each do |match| + match.update_columns(round_id: match.group.round.id) if match.round.blank? + match.update_columns(competition_id: match.round.competition.id) if match.competition.blank? + end + + change_column_null :matches, :competition_id, false + change_column_null :matches, :round_id, false + end + + def down + remove_reference :matches, :competition, foreign_key: true + change_column_null :matches, :round_id, true + Match.where.not(group: nil).find_each do |match| + match.update_columns(round_id: nil) + end + end +end diff --git a/db/migrate/20221204123021_add_points_to_rounds.rb b/db/migrate/20221204123021_add_points_to_rounds.rb new file mode 100644 index 0000000..b2df0d4 --- /dev/null +++ b/db/migrate/20221204123021_add_points_to_rounds.rb @@ -0,0 +1,15 @@ +class AddPointsToRounds < ActiveRecord::Migration[6.1] + def up + add_column :rounds, :points, :integer + + Round.find_each do |round| + round.update_columns(points: round.number + 2) + end + + change_column_null :rounds, :points, false + end + + def down + remove_column :rounds, :points + end +end diff --git a/db/migrate/20221204123938_change_predictions_choice_to_string.rb b/db/migrate/20221204123938_change_predictions_choice_to_string.rb new file mode 100644 index 0000000..e633c81 --- /dev/null +++ b/db/migrate/20221204123938_change_predictions_choice_to_string.rb @@ -0,0 +1,33 @@ +class ChangePredictionsChoiceToString < ActiveRecord::Migration[6.1] + def up + rename_column :predictions, :choice, :choice_integer + add_column :predictions, :choice, :string + + say_with_time "Converting integer enum to string" do + predictions = Prediction.where.not(choice_integer: nil) + bar = ProgressBar.new(predictions.count) + predictions.find_each do |prediction| + prediction.update_columns(choice: %w[home away draw][prediction.choice_integer]) + bar.increment! + end + end + + remove_column :predictions, :choice_integer + end + + def down + rename_column :predictions, :choice, :choice_string + add_column :predictions, :choice, :integer + + say_with_time "Converting string enum to integer" do + predictions = Prediction.where.not(choice_string: nil) + bar = ProgressBar.new(predictions.count) + predictions.find_each do |prediction| + prediction.update_columns(choice: %w[home away draw].index(prediction.choice_string)) + bar.increment! + end + end + + remove_column :predictions, :choice_string + end +end diff --git a/db/migrate/20221204123940_create_match_results.rb b/db/migrate/20221204123940_create_match_results.rb new file mode 100644 index 0000000..ea77168 --- /dev/null +++ b/db/migrate/20221204123940_create_match_results.rb @@ -0,0 +1,5 @@ +class CreateMatchResults < ActiveRecord::Migration[6.1] + def change + create_view :match_results, materialized: true + end +end diff --git a/db/migrate/20221204124451_create_user_scores.rb b/db/migrate/20221204124451_create_user_scores.rb new file mode 100644 index 0000000..6c92925 --- /dev/null +++ b/db/migrate/20221204124451_create_user_scores.rb @@ -0,0 +1,5 @@ +class CreateUserScores < ActiveRecord::Migration[6.1] + def change + create_view :user_scores, materialized: true + end +end diff --git a/db/migrate/20221204153322_create_leaderboard_rankings.rb b/db/migrate/20221204153322_create_leaderboard_rankings.rb new file mode 100644 index 0000000..933d0c4 --- /dev/null +++ b/db/migrate/20221204153322_create_leaderboard_rankings.rb @@ -0,0 +1,5 @@ +class CreateLeaderboardRankings < ActiveRecord::Migration[6.1] + def change + create_view :leaderboard_rankings, materialized: true + end +end diff --git a/db/schema.rb b/db/schema.rb index a5b171f..1275fcf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,9 +10,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_11_08_131452) do +ActiveRecord::Schema.define(version: 2022_12_04_153322) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" enable_extension "plpgsql" create_table "active_storage_attachments", force: :cascade do |t| @@ -94,13 +95,15 @@ t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.bigint "next_match_id" - t.bigint "round_id" + t.bigint "round_id", null: false t.integer "api_id" t.string "location" t.integer "team_home_et_score" t.integer "team_away_et_score" t.integer "team_home_ps_score" t.integer "team_away_ps_score" + t.bigint "competition_id", null: false + t.index ["competition_id"], name: "index_matches_on_competition_id" t.index ["group_id"], name: "index_matches_on_group_id" t.index ["next_match_id"], name: "index_matches_on_next_match_id" t.index ["round_id"], name: "index_matches_on_round_id" @@ -118,11 +121,11 @@ end create_table "predictions", force: :cascade do |t| - t.integer "choice" t.bigint "match_id", null: false t.bigint "user_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "choice" t.index ["match_id"], name: "index_predictions_on_match_id" t.index ["user_id"], name: "index_predictions_on_user_id" end @@ -134,6 +137,7 @@ t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.string "api_name" + t.integer "points", null: false t.index ["competition_id"], name: "index_rounds_on_competition_id" end @@ -184,6 +188,7 @@ add_foreign_key "groups", "rounds" add_foreign_key "leaderboards", "competitions" add_foreign_key "leaderboards", "users" + add_foreign_key "matches", "competitions" add_foreign_key "matches", "groups" add_foreign_key "matches", "matches", column: "next_match_id" add_foreign_key "matches", "rounds" @@ -194,4 +199,119 @@ add_foreign_key "predictions", "matches" add_foreign_key "predictions", "users" add_foreign_key "rounds", "competitions" + + create_view "match_results", materialized: true, sql_definition: <<-SQL + WITH leaderboard_users AS ( + SELECT leaderboards.id, + mb.user_id + FROM (leaderboards + JOIN memberships mb ON ((mb.leaderboard_id = leaderboards.id))) + ) + SELECT matches.id AS match_id, + matches.round_id, + matches.competition_id, + l.id AS leaderboard_id, + matches.status, + matches.group_id, + matches.team_away_id, + matches.team_home_id, + matches.next_match_id, + CASE + WHEN (((matches.status)::text = 'upcoming'::text) OR ((matches.status)::text = 'started'::text)) THEN NULL::text + WHEN ((matches.team_home_et_score IS NULL) AND (matches.team_away_et_score IS NULL) AND (matches.team_home_ps_score IS NULL) AND (matches.team_away_ps_score IS NULL) AND (matches.team_home_score = matches.team_away_score)) THEN 'draw'::text + WHEN ((matches.team_home_score > matches.team_away_score) OR ((matches.team_home_et_score IS NOT NULL) AND (matches.team_home_et_score > matches.team_away_et_score)) OR ((matches.team_home_ps_score IS NOT NULL) AND (matches.team_home_ps_score > matches.team_away_ps_score))) THEN 'home'::text + ELSE 'away'::text + END AS winning_side, + r.number AS round_number, + r.points, + r.name AS round_name, + ARRAY( SELECT DISTINCT p.user_id + FROM predictions p + WHERE ((p.match_id = matches.id) AND (p.user_id IN ( SELECT lu.user_id + FROM leaderboard_users lu + WHERE (lu.id = l.id))) AND ((p.choice)::text = 'home'::text))) AS predicted_home, + ARRAY( SELECT DISTINCT p.user_id + FROM predictions p + WHERE ((p.match_id = matches.id) AND (p.user_id IN ( SELECT lu.user_id + FROM leaderboard_users lu + WHERE (lu.id = l.id))) AND ((p.choice)::text = 'draw'::text))) AS predicted_draw, + ARRAY( SELECT DISTINCT p.user_id + FROM predictions p + WHERE ((p.match_id = matches.id) AND (p.user_id IN ( SELECT lu.user_id + FROM leaderboard_users lu + WHERE (lu.id = l.id))) AND ((p.choice)::text = 'away'::text))) AS predicted_away + FROM ((matches + JOIN rounds r ON ((matches.round_id = r.id))) + JOIN leaderboards l ON ((l.competition_id = matches.competition_id))) + ORDER BY matches.id; + SQL + create_view "user_scores", materialized: true, sql_definition: <<-SQL + WITH prediction_scores AS ( + SELECT DISTINCT p.id AS prediction_id, + p.user_id, + p.match_id, + r.points, + mr.competition_id, + CASE + WHEN (((mr.status)::text = 'finished'::text) AND (p.choice IS NOT NULL)) THEN true + ELSE false + END AS completed, + CASE + WHEN (((mr.status)::text = 'finished'::text) AND ((p.choice)::text = mr.winning_side)) THEN true + ELSE false + END AS correct, + CASE + WHEN (((mr.status)::text = 'finished'::text) AND ((p.choice)::text = mr.winning_side)) THEN mr.points + ELSE 0 + END AS prediction_score + FROM ((predictions p + LEFT JOIN match_results mr ON ((mr.match_id = p.match_id))) + LEFT JOIN rounds r ON ((r.id = mr.round_id))) + ), prediction_numbers AS ( + SELECT u.id AS user_id, + ps_1.competition_id, + sum(ps_1.prediction_score) AS score, + count(DISTINCT ps_1.prediction_id) AS total_predictions, + ( SELECT count(DISTINCT ps2.prediction_id) AS count + FROM prediction_scores ps2 + WHERE (ps2.completed IS TRUE) + GROUP BY ps2.user_id, ps2.competition_id + HAVING ((ps2.user_id = u.id) AND (ps2.competition_id = ps_1.competition_id))) AS completed_predictions, + ( SELECT count(DISTINCT ps2.prediction_id) AS count + FROM prediction_scores ps2 + WHERE (ps2.correct IS TRUE) + GROUP BY ps2.user_id, ps2.competition_id + HAVING ((ps2.user_id = u.id) AND (ps2.competition_id = ps_1.competition_id))) AS correct_predictions + FROM (users u + LEFT JOIN prediction_scores ps_1 ON ((ps_1.user_id = u.id))) + GROUP BY u.id, ps_1.competition_id + ) + SELECT DISTINCT ON (ps.user_id, ps.competition_id) ps.user_id, + ps.competition_id, + pn.score, + (pn.completed_predictions * ps.points) AS max_possible_score, + pn.total_predictions, + pn.completed_predictions, + pn.correct_predictions, + round((((pn.correct_predictions)::numeric * 1.0) / (NULLIF(pn.total_predictions, 0))::numeric), 3) AS accuracy + FROM (prediction_scores ps + JOIN prediction_numbers pn ON (((pn.user_id = ps.user_id) AND (pn.competition_id = ps.competition_id)))) + ORDER BY ps.user_id; + SQL + create_view "leaderboard_rankings", materialized: true, sql_definition: <<-SQL + SELECT DISTINCT ON ((rank() OVER (PARTITION BY l.id ORDER BY us.score DESC, us.accuracy DESC, us.completed_predictions DESC)), us.score, us.accuracy, us.completed_predictions, l.id, l.competition_id, us.user_id) l.id AS leaderboard_id, + us.user_id, + us.competition_id, + us.score, + us.max_possible_score, + us.total_predictions, + us.completed_predictions, + us.correct_predictions, + us.accuracy, + rank() OVER (PARTITION BY l.id ORDER BY us.score DESC, us.accuracy DESC, us.completed_predictions DESC) AS user_rank + FROM ((leaderboards l + JOIN memberships m ON ((m.leaderboard_id = l.id))) + JOIN user_scores us ON (((us.user_id = m.user_id) AND (us.competition_id = l.competition_id)))) + ORDER BY (rank() OVER (PARTITION BY l.id ORDER BY us.score DESC, us.accuracy DESC, us.completed_predictions DESC)), us.score, us.accuracy, us.completed_predictions; + SQL end diff --git a/db/views/leaderboard_rankings_v01.sql b/db/views/leaderboard_rankings_v01.sql new file mode 100644 index 0000000..e2af4f9 --- /dev/null +++ b/db/views/leaderboard_rankings_v01.sql @@ -0,0 +1,11 @@ +SELECT DISTINCT ON (l.id, l.competition_id, us.user_id, user_rank, us.score, us.accuracy, us.completed_predictions) + l.id AS leaderboard_id, + us.*, + RANK () OVER ( + PARTITION BY l.id + ORDER BY us.score DESC, us.accuracy DESC, us.completed_predictions DESC + ) user_rank +FROM leaderboards l +JOIN memberships m ON m.leaderboard_id = l.id +JOIN user_scores us ON us.user_id = m.user_id AND us.competition_id = l.competition_id +ORDER BY user_rank, us.score, us.accuracy, us.completed_predictions diff --git a/db/views/match_results_v01.sql b/db/views/match_results_v01.sql new file mode 100644 index 0000000..7e9bf6e --- /dev/null +++ b/db/views/match_results_v01.sql @@ -0,0 +1,45 @@ +WITH leaderboard_users AS ( + SELECT + leaderboards.id, + mb.user_id + FROM leaderboards + JOIN memberships mb ON mb.leaderboard_id = leaderboards.id +) +SELECT + matches.id AS match_id, + matches.round_id, + matches.competition_id, + l.id AS leaderboard_id, + matches.status, + matches.group_id, + matches.team_away_id, + matches.team_home_id, + matches.next_match_id, + ( + CASE + WHEN (status = 'upcoming' OR status = 'started') THEN NULL + WHEN ( + team_home_et_score IS NULL AND + team_away_et_score IS NULL AND + team_home_ps_score IS NULL AND + team_away_ps_score IS NULL AND + team_home_score = team_away_score + ) THEN 'draw' + WHEN ( + team_home_score > team_away_score OR + (team_home_et_score IS NOT NULL AND team_home_et_score > team_away_et_score) OR + (team_home_ps_score IS NOT NULL AND team_home_ps_score > team_away_ps_score) + ) THEN 'home' + ELSE 'away' + END + ) AS winning_side, + r.number AS round_number, + r.points AS points, + r.name AS round_name, + ARRAY(SELECT DISTINCT p.user_id FROM predictions p WHERE p.match_id = matches.id AND p.user_id IN (SELECT user_id FROM leaderboard_users lu WHERE lu.id = l.id) AND p.choice = 'home') as predicted_home, + ARRAY(SELECT DISTINCT p.user_id FROM predictions p WHERE p.match_id = matches.id AND p.user_id IN (SELECT user_id FROM leaderboard_users lu WHERE lu.id = l.id) AND p.choice = 'draw') as predicted_draw, + ARRAY(SELECT DISTINCT p.user_id FROM predictions p WHERE p.match_id = matches.id AND p.user_id IN (SELECT user_id FROM leaderboard_users lu WHERE lu.id = l.id) AND p.choice = 'away') as predicted_away +FROM matches +JOIN rounds r ON matches.round_id = r.id +JOIN leaderboards l ON l.competition_id = matches.competition_id +ORDER BY matches.id diff --git a/db/views/user_scores_v01.sql b/db/views/user_scores_v01.sql new file mode 100644 index 0000000..5fcae96 --- /dev/null +++ b/db/views/user_scores_v01.sql @@ -0,0 +1,64 @@ +WITH prediction_scores AS ( + SELECT + DISTINCT p.id AS prediction_id, + p.user_id, + p.match_id, + r.points, + mr.competition_id, + ( + CASE + WHEN mr.status = 'finished' AND p.choice IS NOT NULL THEN TRUE + ELSE FALSE + END + ) AS completed, + ( + CASE + WHEN mr.status = 'finished' AND p.choice = mr.winning_side THEN TRUE + ELSE FALSE + END + ) AS correct, + ( + CASE + WHEN mr.status = 'finished' AND p.choice = mr.winning_side THEN mr.points + ELSE 0 + END + ) AS prediction_score + FROM predictions p + LEFT JOIN match_results mr ON mr.match_id = p.match_id + LEFT JOIN rounds r ON r.id = mr.round_id +), prediction_numbers AS ( + SELECT + u.id AS user_id, + ps.competition_id, + SUM(ps.prediction_score) AS score, + COUNT(DISTINCT ps.prediction_id) AS total_predictions, + ( + SELECT COUNT(DISTINCT ps2.prediction_id) + FROM prediction_scores ps2 + WHERE ps2.completed IS TRUE + GROUP BY ps2.user_id, ps2.competition_id + HAVING ps2.user_id = u.id AND ps2.competition_id = ps.competition_id + ) AS completed_predictions, + ( + SELECT COUNT(DISTINCT ps2.prediction_id) + FROM prediction_scores ps2 + WHERE ps2.correct IS TRUE + GROUP BY ps2.user_id, ps2.competition_id + HAVING ps2.user_id = u.id AND ps2.competition_id = ps.competition_id + ) AS correct_predictions + FROM users u + LEFT JOIN prediction_scores ps ON ps.user_id = u.id + GROUP BY u.id, ps.competition_id +) +SELECT DISTINCT ON (ps.user_id, ps.competition_id) + ps.user_id, + ps.competition_id, + pn.score, + pn.completed_predictions * ps.points AS max_possible_score, + pn.total_predictions, + pn.completed_predictions, + pn.correct_predictions, + ROUND(pn.correct_predictions * 1.0 / NULLIF(pn.total_predictions, 0), 3) AS accuracy +FROM prediction_scores ps +JOIN prediction_numbers pn ON pn.user_id = ps.user_id AND pn.competition_id = ps.competition_id +ORDER BY ps.user_id From f83a02732c4c07812839d9a45c5223726a4a7903 Mon Sep 17 00:00:00 2001 From: Trouni <34345789+trouni@users.noreply.github.com> Date: Wed, 7 Dec 2022 01:56:21 +0900 Subject: [PATCH 254/286] Add Global Top 10 Leaderboard (#111) * Refactor leaderboard owner to have normal membership * Fix deprecation warning * Remove inspect * Add local IP addresses to CORS for dev * Add results to `leaderboards#index` * Setting policy to true since owner will now have a membership * Fix leaderboards association * Fix leaderboard policy * Create scenic views * Make views materialized * Fix queries in views * Update views and associations * Fix match results view * Refactor matches controller * Add missing lines at EOF * Fix leaderboard rankings and remove unnecessary associations * Clean up unused code * Refactor views into partials * Add accuracy to users#show response * Add missing lines at EOF * Add refresh views callback to most models * Add migration and rake task to seed * Add global leaderboards * Add leaderboard description and change name * Create service to deactivate callback on bulk update operations * Move auto join logic to User after_create callback --- app/controllers/v1/leaderboards_controller.rb | 1 + app/jobs/match_update_future_job.rb | 38 +++--- app/jobs/match_update_history_job.rb | 14 +- app/jobs/match_update_live_job.rb | 14 +- app/jobs/refresh_database_views_job.rb | 10 ++ app/models/application_record.rb | 5 +- app/models/leaderboard.rb | 2 + app/models/user.rb | 12 ++ app/policies/membership_policy.rb | 4 + app/services/database_views.rb | 26 ++++ app/services/scrape_matches_service.rb | 8 +- app/services/scrape_photo_service.rb | 2 + .../leaderboards/_leaderboard.json.jbuilder | 2 +- app/views/v1/leaderboards/index.json.jbuilder | 4 +- ...1205114427_add_settings_to_leaderboards.rb | 8 ++ db/schema.rb | 6 +- db/seeds.rb | 124 +---------------- db/seeds/0_default.rb | 127 ++++++++++++++++++ db/seeds/1_leaderboards.rb | 32 +++++ lib/tasks/db.rake | 17 +++ 20 files changed, 295 insertions(+), 161 deletions(-) create mode 100644 app/jobs/refresh_database_views_job.rb create mode 100644 app/services/database_views.rb create mode 100644 db/migrate/20221205114427_add_settings_to_leaderboards.rb create mode 100644 db/seeds/0_default.rb create mode 100644 db/seeds/1_leaderboards.rb create mode 100644 lib/tasks/db.rake diff --git a/app/controllers/v1/leaderboards_controller.rb b/app/controllers/v1/leaderboards_controller.rb index 87bcaab..4dd604a 100644 --- a/app/controllers/v1/leaderboards_controller.rb +++ b/app/controllers/v1/leaderboards_controller.rb @@ -22,6 +22,7 @@ def destroy @leaderboard = Leaderboard.find(params[:id]) authorize @leaderboard membership = @leaderboard.memberships.find_by!(user: current_user) + authorize membership membership.destroy head :no_content end diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index fd242f5..8fa2fe0 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -13,26 +13,28 @@ def update_matches_future(url) response = HTTParty.get(url).body parsed_response = JSON.parse(response)['data'] matches = parsed_response['fixtures'] - matches.each do |match_info| - kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") - puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = @competition.matches.find_by(api_id: match_info['id']) || Match.new - match.team_home ||= Team.find_by(api_id: match_info['home_id']) - match.team_away ||= Team.find_by(api_id: match_info['away_id']) - next unless match.team_home && match.team_away # knock-out rounds with no teams yet + DatabaseViews.run_without_callback(then_refresh: true) do + matches.each do |match_info| + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" + match = @competition.matches.find_by(api_id: match_info['id']) || Match.new + match.team_home ||= Team.find_by(api_id: match_info['home_id']) + match.team_away ||= Team.find_by(api_id: match_info['away_id']) + next unless match.team_home && match.team_away # knock-out rounds with no teams yet - # Only adding a round for knockout stages, group isn't provided by API :/ - if %w[1 2 3].include?(match_info['round']) - match.group = @competition.groups.find_by(api_id: match_info["group_id"]) - else - match.round = @competition.rounds.find_by(api_name: match_info['round']) + # Only adding a round for knockout stages, group isn't provided by API :/ + if %w[1 2 3].include?(match_info['round']) + match.group = @competition.groups.find_by(api_id: match_info["group_id"]) + else + match.round = @competition.rounds.find_by(api_name: match_info['round']) + end + match.api_id = match_info['id'] + match.location = match_info['location'] + match.kickoff_time = kickoff_time + match.save + p match.errors.full_messages if match.errors.any? + puts 'Match Update' end - match.api_id = match_info['id'] - match.location = match_info['location'] - match.kickoff_time = kickoff_time - match.save - p match.errors.full_messages if match.errors.any? - puts 'Match Update' end return parsed_response['next_page'] end diff --git a/app/jobs/match_update_history_job.rb b/app/jobs/match_update_history_job.rb index 88e4061..d6f0f84 100644 --- a/app/jobs/match_update_history_job.rb +++ b/app/jobs/match_update_history_job.rb @@ -17,13 +17,15 @@ def update_matches_history(url, competition) response = HTTParty.get(url).body parsed_response = JSON.parse(response)['data'] matches = parsed_response['match'] - matches.each do |match_info| - kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") - puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) - next unless match + DatabaseViews.run_without_callback(then_refresh: true) do + matches.each do |match_info| + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" + match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + next unless match - match.update_with_api(match_info) + match.update_with_api(match_info) + end end return parsed_response['next_page'] end diff --git a/app/jobs/match_update_live_job.rb b/app/jobs/match_update_live_job.rb index 48ce912..bc6855b 100644 --- a/app/jobs/match_update_live_job.rb +++ b/app/jobs/match_update_live_job.rb @@ -17,13 +17,15 @@ def update_matches_live(url, competition) response = HTTParty.get(url).body parsed_response = JSON.parse(response)['data'] matches = parsed_response['match'] - matches.each do |match_info| - kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") - puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) - next unless match + DatabaseViews.run_without_callback(then_refresh: true) do + matches.each do |match_info| + kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['scheduled']}") + puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" + match = competition.matches.find_by(api_id: match_info['fixture_id']) || competition.matches.find_by(team_home: get_team(match_info['home_id']), team_away: get_team(match_info['away_id']), kickoff_time: kickoff_time) + next unless match - match.update_with_api(match_info) + match.update_with_api(match_info) + end end end end diff --git a/app/jobs/refresh_database_views_job.rb b/app/jobs/refresh_database_views_job.rb new file mode 100644 index 0000000..46c8b97 --- /dev/null +++ b/app/jobs/refresh_database_views_job.rb @@ -0,0 +1,10 @@ +class RefreshDatabaseViewsJob < ApplicationJob + queue_as :default + + def perform + # The materialized views need to be refreshed in this order + MatchResult.refresh + UserScore.refresh + LeaderboardRanking.refresh + end +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index f5b4a3d..ea02fb7 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -12,9 +12,6 @@ def self.execute_sql(query, args = {}) end def refresh_materialized_views - # The materialized views need to be refreshed in this order - MatchResult.refresh - UserScore.refresh - LeaderboardRanking.refresh + DatabaseViews.refresh(async: true) end end diff --git a/app/models/leaderboard.rb b/app/models/leaderboard.rb index 3b99b23..bb36a94 100644 --- a/app/models/leaderboard.rb +++ b/app/models/leaderboard.rb @@ -14,6 +14,8 @@ class Leaderboard < ApplicationRecord after_create :create_owner_membership after_commit :refresh_materialized_views + scope :auto_join, -> { where(auto_join: true) } + def transfer_ownership membership = memberships.first membership.destroy diff --git a/app/models/user.rb b/app/models/user.rb index c41cb18..79c214a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,7 +16,19 @@ class User < ApplicationRecord validates :name, presence: true, on: :update, if: :name_changed? + after_create :auto_join_leaderboards + def name super || email.split('@').first end + + private + + def auto_join_leaderboards + DatabaseViews.run_without_callback(then_refresh: true) do + Leaderboard.auto_join.each do |leaderboard| + leaderboard.memberships.create(user: self) + end + end + end end diff --git a/app/policies/membership_policy.rb b/app/policies/membership_policy.rb index 1d3d9c8..1940528 100644 --- a/app/policies/membership_policy.rb +++ b/app/policies/membership_policy.rb @@ -8,4 +8,8 @@ def resolve def create? true end + + def destroy? + record.user == user && !record.leaderboard.leave_disabled? + end end diff --git a/app/services/database_views.rb b/app/services/database_views.rb new file mode 100644 index 0000000..4cc60cf --- /dev/null +++ b/app/services/database_views.rb @@ -0,0 +1,26 @@ +class DatabaseViews + MODELS = [Membership, Leaderboard, Competition, Match, Prediction, Round] + + def self.refresh(async: true) + async ? RefreshDatabaseViewsJob.perform_later : RefreshDatabaseViewsJob.perform_now + end + + def self.deactivate_callback + MODELS.each do |model| + model.skip_callback(:commit, :after, :refresh_materialized_views) + end + end + + def self.activate_callback(then_refresh: false) + MODELS.each do |model| + model.set_callback(:commit, :after, :refresh_materialized_views) + end + refresh if then_refresh + end + + def self.run_without_callback(then_refresh: false, &block) + deactivate_callback + yield + activate_callback(then_refresh: then_refresh) + end +end diff --git a/app/services/scrape_matches_service.rb b/app/services/scrape_matches_service.rb index 9df4cff..97aabf0 100644 --- a/app/services/scrape_matches_service.rb +++ b/app/services/scrape_matches_service.rb @@ -18,9 +18,11 @@ def initialize end def call - @browser = Watir::Browser.new :chrome, args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] - urls.each do |url| - scrape(url) + DatabaseViews.run_without_callback(then_refresh: true) do + @browser = Watir::Browser.new :chrome, args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] + urls.each do |url| + scrape(url) + end end end diff --git a/app/services/scrape_photo_service.rb b/app/services/scrape_photo_service.rb index f8514fc..8be6c33 100644 --- a/app/services/scrape_photo_service.rb +++ b/app/services/scrape_photo_service.rb @@ -9,6 +9,7 @@ def initialize(attrs = {}) end def call + DatabaseViews.deactivate_callback url = "https://www.fifa.com/fifaplus/en/tournaments/mens/worldcup/qatar2022/teams/#{competition.teams.sample.name.split.join('-').downcase}/squad" browser = Watir::Browser.new :chrome, options: { args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] } browser.goto url @@ -28,5 +29,6 @@ def call user.save # TODO: Most likely the ending numbers and names might change... # document.querySelectorAll('main section')[2].querySelectorAll('.entire-squad_container__3W4Hl')[3].querySelectorAll('.player-badge-card_playerImage__301X0')[0].style.backgroundImage + DatabaseViews.activate_callback end end diff --git a/app/views/v1/leaderboards/_leaderboard.json.jbuilder b/app/views/v1/leaderboards/_leaderboard.json.jbuilder index 5a913e8..2a22348 100644 --- a/app/views/v1/leaderboards/_leaderboard.json.jbuilder +++ b/app/views/v1/leaderboards/_leaderboard.json.jbuilder @@ -1 +1 @@ -json.extract! leaderboard, :id, :name, :password, :user_id, :competition_id +json.extract! leaderboard, :id, :name, :description, :password, :user_id, :competition_id, :auto_join, :leave_disabled, :rankings_top_n diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 1323b64..455fb77 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -1,6 +1,8 @@ json.array! @leaderboards do |leaderboard| json.partial! leaderboard - json.users leaderboard.rankings do |ranking| + rankings = leaderboard.rankings.order(:user_rank) + rankings = rankings.first(leaderboard.rankings_top_n) if leaderboard.rankings_top_n + json.users rankings do |ranking| json.partial! 'v1/leaderboards/ranking', ranking: ranking end json.results do diff --git a/db/migrate/20221205114427_add_settings_to_leaderboards.rb b/db/migrate/20221205114427_add_settings_to_leaderboards.rb new file mode 100644 index 0000000..5d30b82 --- /dev/null +++ b/db/migrate/20221205114427_add_settings_to_leaderboards.rb @@ -0,0 +1,8 @@ +class AddSettingsToLeaderboards < ActiveRecord::Migration[6.1] + def change + add_column :leaderboards, :description, :string + add_column :leaderboards, :auto_join, :boolean, null: false, default: false + add_column :leaderboards, :leave_disabled, :boolean, null: false, default: false + add_column :leaderboards, :rankings_top_n, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 1275fcf..b2ef9e4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_12_04_153322) do +ActiveRecord::Schema.define(version: 2022_12_05_114427) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -80,6 +80,10 @@ t.bigint "competition_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "description" + t.boolean "auto_join", default: false, null: false + t.boolean "leave_disabled", default: false, null: false + t.integer "rankings_top_n" t.index ["competition_id"], name: "index_leaderboards_on_competition_id" t.index ["user_id"], name: "index_leaderboards_on_user_id" end diff --git a/db/seeds.rb b/db/seeds.rb index 680832e..87f5d97 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,122 +1,4 @@ -require 'open-uri' -Leaderboard.destroy_all - -puts 'Getting Admin users...' -doug = User.find_by(email: 'douglasmberkley@gmail.com') || User.create(email: 'douglasmberkley@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) -trouni = User.find_by(email: 'trouni@gmail.com') || User.create(email: 'trouni@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) -james = User.find_by(email: 'devereuxjj@gmail.com') || User.create(email: 'devereuxjj@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) - -puts 'Creating test users...' -20.times do - User.create( - # fake emails for testing purposes - email: Faker::Internet.safe_email, - password: '123123' - ) +Dir[File.join(Rails.root, 'db', 'seeds', '*.rb')].sort.each do |filename| + puts "------> Seeding #{filename}..." + load(filename) end -puts "... #{User.count} Total Users" - -groups = { - 'Group A' => [ - { name: 'Italy', abbrev: 'ITA' }, - { name: 'Switzerland', abbrev: 'SUI' }, - { name: 'Turkey', abbrev: 'TUR' }, - { name: 'Wales', abbrev: 'WAL' } - ], - 'Group B' => [ - { name: 'Belgium', abbrev: 'BEL' }, - { name: 'Denmark', abbrev: 'DEN' }, - { name: 'Finland', abbrev: 'FIN' }, - { name: 'Russia', abbrev: 'RUS' } - ], - 'Group C' => [ - { name: 'Austria', abbrev: 'AUT' }, - { name: 'Netherlands', abbrev: 'NED' }, - { name: 'FYR Macedonia', abbrev: 'MKD' }, - { name: 'Ukraine', abbrev: 'UKR' } - ], - 'Group D' => [ - { name: 'Croatia', abbrev: 'CRO' }, - { name: 'Czech Republic', abbrev: 'CZE' }, - { name: 'England', abbrev: 'ENG' }, - { name: 'Scotland', abbrev: 'SCO' } - ], - 'Group E' => [ - { name: 'Poland', abbrev: 'POL' }, - { name: 'Slovakia', abbrev: 'SVK' }, - { name: 'Spain', abbrev: 'ESP' }, - { name: 'Sweden', abbrev: 'SWE' } - ], - 'Group F' => [ - { name: 'France', abbrev: 'FRA' }, - { name: 'Germany', abbrev: 'GER' }, - { name: 'Hungary', abbrev: 'HUN' }, - { name: 'Portugal', abbrev: 'POR' } - ] -} - -puts 'Creating the Euros...' -euros = Competition.find_or_create_by!(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) -puts '.. created the Euros' - -puts 'Creating or finding first round...' -first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: '3') -puts "...#{Round.count} Total Rounds" - -puts 'Creating or finding groups...' -groups.each_key do |group_name| - puts "...#{group_name}..." - group = Group.find_or_create_by!(name: group_name, round: first_round) - groups[group_name].each do |team_hash| - puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" - team = Team.find_or_create_by!(team_hash) - Affiliation.find_or_create_by!(team: team, group: group) - end -end -puts "...#{Team.count} Total Teams" -puts "...#{Group.count} Total Groups" - -Team.find_each do |team| - next if team.badge.attached? - - url = "https://www.uefa.com/imgml/flags/140x140/#{team.abbrev}.png?imwidth=2048%202048w" - puts "#{team.name}: #{url}" - file = URI.open(url) - team.badge.attach(io: file, filename: 'badge.png', content_type: 'image/png') - puts team.badge.attached? ? 'Success' : 'Failed' -end - -puts 'Creating a test leaderboard w/ James as creator' -leaderboard = Leaderboard.find_or_create_by!( - name: 'Admin Leaderboard', - competition: euros, - user: trouni -) - -puts 'Creating test users...' -5.times do - Leaderboard.create!( - # fake emails for testing purposes - name: Faker::Sports::Football.team, - competition: euros, - user: trouni - ) -end -puts "... #{User.count} Total Users" - -puts 'Adding Users to the leaderboard' -Leaderboard.find_each do |ldbrd| - ([doug] + User.last(20)).each do |user| - Membership.find_or_create_by!(leaderboard: ldbrd, user: user) - end -end - -ScrapeMatchesService.new.call - -# puts 'Assigning random scores to matches before June 22nd' -# Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| -# match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) -# end -# # Needed when migrating status enum from integer to string: -# Match.where('kickoff_time >= ?', Date.new(2021, 6, 22)).update(status: :upcoming) -# puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" diff --git a/db/seeds/0_default.rb b/db/seeds/0_default.rb new file mode 100644 index 0000000..5c34437 --- /dev/null +++ b/db/seeds/0_default.rb @@ -0,0 +1,127 @@ +require 'open-uri' + +DatabaseViews.deactivate_callback + +Leaderboard.destroy_all + +puts 'Getting Admin users...' +doug = User.find_by(email: 'douglasmberkley@gmail.com') || User.create(email: 'douglasmberkley@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) +trouni = User.find_by(email: 'trouni@gmail.com') || User.create(email: 'trouni@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) +james = User.find_by(email: 'devereuxjj@gmail.com') || User.create(email: 'devereuxjj@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) + +puts 'Creating test users...' +20.times do + User.create( + # fake emails for testing purposes + email: Faker::Internet.safe_email, + password: '123123' + ) +end +puts "... #{User.count} Total Users" + +groups = { + 'Group A' => [ + { name: 'Italy', abbrev: 'ITA' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Turkey', abbrev: 'TUR' }, + { name: 'Wales', abbrev: 'WAL' } + ], + 'Group B' => [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'Finland', abbrev: 'FIN' }, + { name: 'Russia', abbrev: 'RUS' } + ], + 'Group C' => [ + { name: 'Austria', abbrev: 'AUT' }, + { name: 'Netherlands', abbrev: 'NED' }, + { name: 'FYR Macedonia', abbrev: 'MKD' }, + { name: 'Ukraine', abbrev: 'UKR' } + ], + 'Group D' => [ + { name: 'Croatia', abbrev: 'CRO' }, + { name: 'Czech Republic', abbrev: 'CZE' }, + { name: 'England', abbrev: 'ENG' }, + { name: 'Scotland', abbrev: 'SCO' } + ], + 'Group E' => [ + { name: 'Poland', abbrev: 'POL' }, + { name: 'Slovakia', abbrev: 'SVK' }, + { name: 'Spain', abbrev: 'ESP' }, + { name: 'Sweden', abbrev: 'SWE' } + ], + 'Group F' => [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Germany', abbrev: 'GER' }, + { name: 'Hungary', abbrev: 'HUN' }, + { name: 'Portugal', abbrev: 'POR' } + ] +} + +puts 'Creating the Euros...' +euros = Competition.find_or_create_by!(name: 'Euro 2020', start_date: Date.new(2021, 6, 12), end_date: Date.new(2021, 7, 12)) +puts '.. created the Euros' + +puts 'Creating or finding first round...' +first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: '3') +puts "...#{Round.count} Total Rounds" + +puts 'Creating or finding groups...' +groups.each_key do |group_name| + puts "...#{group_name}..." + group = Group.find_or_create_by!(name: group_name, round: first_round) + groups[group_name].each do |team_hash| + puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" + team = Team.find_or_create_by!(team_hash) + Affiliation.find_or_create_by!(team: team, group: group) + end +end +puts "...#{Team.count} Total Teams" +puts "...#{Group.count} Total Groups" + +Team.find_each do |team| + next if team.badge.attached? + + url = "https://www.uefa.com/imgml/flags/140x140/#{team.abbrev}.png?imwidth=2048%202048w" + puts "#{team.name}: #{url}" + file = URI.open(url) + team.badge.attach(io: file, filename: 'badge.png', content_type: 'image/png') + puts team.badge.attached? ? 'Success' : 'Failed' +end + +puts 'Creating a test leaderboard w/ James as creator' +leaderboard = Leaderboard.find_or_create_by!( + name: 'Admin Leaderboard', + competition: euros, + user: trouni +) + +puts 'Creating test users...' +5.times do + Leaderboard.create!( + # fake emails for testing purposes + name: Faker::Sports::Football.team, + competition: euros, + user: trouni + ) +end +puts "... #{User.count} Total Users" + +puts 'Adding Users to the leaderboard' +Leaderboard.find_each do |ldbrd| + ([doug] + User.last(20)).each do |user| + Membership.find_or_create_by!(leaderboard: ldbrd, user: user) + end +end + +ScrapeMatchesService.new.call + +# puts 'Assigning random scores to matches before June 22nd' +# Match.where('kickoff_time < ?', Date.new(2021, 6, 22)).each do |match| +# match.update(team_away_score: rand(4), team_home_score: rand(4), status: :finished) +# end +# # Needed when migrating status enum from integer to string: +# Match.where('kickoff_time >= ?', Date.new(2021, 6, 22)).update(status: :upcoming) +# puts "...#{Match.finished.count} Finished Matches and #{Match.upcoming.count} Upcoming Matches" + +DatabaseViews.activate_callback(then_refresh: true) \ No newline at end of file diff --git a/db/seeds/1_leaderboards.rb b/db/seeds/1_leaderboards.rb new file mode 100644 index 0000000..138b84f --- /dev/null +++ b/db/seeds/1_leaderboards.rb @@ -0,0 +1,32 @@ +LEADERBOARDS = [ + { + name: 'Global Top 10', + description: 'The Top 10 players on Octacle', + rankings_top_n: 10, + leave_disabled: true, + auto_join: true, + } +] + +admin = User.find_by(email: 'trouni@gmail.com') + +DatabaseViews.deactivate_callback + +puts '-----> Seeding auto-join leaderboards...' +LEADERBOARDS.each do |leaderboard_hash| + Competition.find_each do |competition| + leaderboard = competition.leaderboards.find_or_initialize_by(leaderboard_hash.slice(:name)) + leaderboard.assign_attributes(leaderboard_hash) + leaderboard.user ||= admin + leaderboard.save! + + puts "-----> Creating memberships for #{leaderboard.name} (#{leaderboard.competition.name})" + bar = ProgressBar.new(User.count) + User.find_each do |user| + leaderboard.memberships.find_or_create_by!(user: user) + bar.increment! + end + end +end + +DatabaseViews.activate_callback(then_refresh: true) diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake new file mode 100644 index 0000000..d6f385e --- /dev/null +++ b/lib/tasks/db.rake @@ -0,0 +1,17 @@ +namespace :db do + namespace :seed do + Dir[File.join(Rails.root, 'db', 'seeds', '*.rb')].each do |filename| + task_name = File.basename(filename, '.rb').intern + task task_name => :environment do + load(filename) + end + end + + # This is for if you want to run all seeds inside db/seeds directory + task :all => :environment do + Dir[File.join(Rails.root, 'db', 'seeds', '*.rb')].sort.each do |filename| + load(filename) + end + end + end +end From f659a82bc9f396aff689410d0e55114b17828bdc Mon Sep 17 00:00:00 2001 From: Trouni <34345789+trouni@users.noreply.github.com> Date: Wed, 7 Dec 2022 02:11:14 +0900 Subject: [PATCH 255/286] Remove callback definition in Prediction (#112) --- app/controllers/v1/predictions_controller.rb | 1 - app/models/prediction.rb | 7 ------- 2 files changed, 8 deletions(-) diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index f51c480..71a9797 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -1,5 +1,4 @@ class V1::PredictionsController < ApplicationController - def create @match = Match.find(params[:match_id]) @prediction = Prediction.new(prediction_params) diff --git a/app/models/prediction.rb b/app/models/prediction.rb index 441157a..f5c4f6e 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -9,11 +9,4 @@ class Prediction < ApplicationRecord scope :locked, -> { joins(:match).where.not(matches: { status: :upcoming }) } after_commit :refresh_materialized_views - - private - - def refresh_materialized_views - UserScore.refresh - LeaderboardRanking.refresh - end end From 2a1de8a5630cd428c9b721d292644ecd6e7a584a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 8 Dec 2022 19:38:31 +0900 Subject: [PATCH 256/286] prevents duplicate games on the same day --- app/models/match.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/match.rb b/app/models/match.rb index ed41be6..eac1e5d 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -11,6 +11,7 @@ class Match < ApplicationRecord validates :status, presence: true validates :api_id, uniqueness: { allow_nil: true } validates_uniqueness_of :kickoff_time, scope: %i[team_home team_away] + validate :check_team_and_day_uniqueness enum status: { upcoming: 'upcoming', started: 'started', finished: 'finished' }, _default: :upcoming # Scenic views @@ -19,6 +20,12 @@ class Match < ApplicationRecord before_validation :set_round_and_competition, on: :create after_commit :refresh_materialized_views + def check_team_and_day_uniqueness + if Match.where(team_away: team_away, team_home: team_home).find_by("kickoff_time::date = ?", kickoff_time.to_date) + errors.add(:kickoff_time, "isn't available on this date") + end + end + def update_with_api(match_info) finished! if match_info['status'] == 'FINISHED' started! if match_info['status'] == 'IN PLAY' From e923dfdbb7f510ce67823e55ea5fc05eef18cac8 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 9 Dec 2022 15:30:27 +0900 Subject: [PATCH 257/286] fixed the api match creation so that there shouldn't be any duplicates for the same match --- app/jobs/match_update_future_job.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index 8fa2fe0..8162304 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -17,9 +17,15 @@ def update_matches_future(url) matches.each do |match_info| kickoff_time = DateTime.parse("#{match_info['date']} #{match_info['time']}") puts "Finding the match between : #{match_info['home_name']} v #{match_info['away_name']} (#{kickoff_time})" - match = @competition.matches.find_by(api_id: match_info['id']) || Match.new - match.team_home ||= Team.find_by(api_id: match_info['home_id']) - match.team_away ||= Team.find_by(api_id: match_info['away_id']) + # The API gives duplicate matches with different IDs, so need to find by day and teams + # match = @competition.matches.find_by(api_id: match_info['id']) || Match.new + team_home = Team.find_by(api_id: match_info['home_id']) + team_away = Team.find_by(api_id: match_info['away_id']) + match = + @competition.matches.where(team_home: team_home, team_away: team_away) + .find_by('kickoff_time::date = ?', match_info['date']) || Match.new + match.team_home ||= team_home + match.team_away ||= team_away next unless match.team_home && match.team_away # knock-out rounds with no teams yet # Only adding a round for knockout stages, group isn't provided by API :/ @@ -27,6 +33,7 @@ def update_matches_future(url) match.group = @competition.groups.find_by(api_id: match_info["group_id"]) else match.round = @competition.rounds.find_by(api_name: match_info['round']) + match.round = @competition.rounds.find_by(api_name: match_info['round']) end match.api_id = match_info['id'] match.location = match_info['location'] From 3183abc273176a6aced4c7ce12c915269ba57907 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 9 Dec 2022 15:31:22 +0900 Subject: [PATCH 258/286] removed duplicate line --- app/jobs/match_update_future_job.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index 8162304..b344a35 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -33,7 +33,6 @@ def update_matches_future(url) match.group = @competition.groups.find_by(api_id: match_info["group_id"]) else match.round = @competition.rounds.find_by(api_name: match_info['round']) - match.round = @competition.rounds.find_by(api_name: match_info['round']) end match.api_id = match_info['id'] match.location = match_info['location'] From 355b8bcad28cd2ec997c638345396c62773e5ed5 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Fri, 9 Dec 2022 15:40:40 +0900 Subject: [PATCH 259/286] filtering out own match on the validation --- app/models/match.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/match.rb b/app/models/match.rb index eac1e5d..ba5b522 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -21,7 +21,7 @@ class Match < ApplicationRecord after_commit :refresh_materialized_views def check_team_and_day_uniqueness - if Match.where(team_away: team_away, team_home: team_home).find_by("kickoff_time::date = ?", kickoff_time.to_date) + if Match.where(team_away: team_away, team_home: team_home).where.not(id: self).find_by("kickoff_time::date = ?", kickoff_time.to_date) errors.add(:kickoff_time, "isn't available on this date") end end From 69ec1dadf4fecbec2e491bd2ff812e0987277a01 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 11 Dec 2022 16:15:43 +0900 Subject: [PATCH 260/286] api match creator fixed for finding round --- app/jobs/match_update_future_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/match_update_future_job.rb b/app/jobs/match_update_future_job.rb index b344a35..38f9981 100644 --- a/app/jobs/match_update_future_job.rb +++ b/app/jobs/match_update_future_job.rb @@ -32,7 +32,7 @@ def update_matches_future(url) if %w[1 2 3].include?(match_info['round']) match.group = @competition.groups.find_by(api_id: match_info["group_id"]) else - match.round = @competition.rounds.find_by(api_name: match_info['round']) + match.round = Round.find_by(competition: @competition, api_name: match_info['round']) end match.api_id = match_info['id'] match.location = match_info['location'] From 531563bd25bd57187ab50a89d1b3b40b8afea20c Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 14:18:19 +0900 Subject: [PATCH 261/286] created rake task to create euros and added api_code to competitions --- ...0606050948_add_api_code_to_competitions.rb | 5 + db/schema.rb | 4 +- lib/tasks/euros.rake | 109 ++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240606050948_add_api_code_to_competitions.rb create mode 100644 lib/tasks/euros.rake diff --git a/db/migrate/20240606050948_add_api_code_to_competitions.rb b/db/migrate/20240606050948_add_api_code_to_competitions.rb new file mode 100644 index 0000000..a5b8aea --- /dev/null +++ b/db/migrate/20240606050948_add_api_code_to_competitions.rb @@ -0,0 +1,5 @@ +class AddApiCodeToCompetitions < ActiveRecord::Migration[6.1] + def change + add_column :competitions, :api_code, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index b2ef9e4..6b9ba4a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_12_05_114427) do +ActiveRecord::Schema.define(version: 2024_06_06_050948) do # These are extensions that must be enabled in order to support this database - enable_extension "pg_stat_statements" enable_extension "plpgsql" create_table "active_storage_attachments", force: :cascade do |t| @@ -61,6 +60,7 @@ t.datetime "updated_at", precision: 6, null: false t.bigint "current_round_id" t.integer "api_id" + t.string "api_code" t.index ["current_round_id"], name: "index_competitions_on_current_round_id" end diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake new file mode 100644 index 0000000..f6873ad --- /dev/null +++ b/lib/tasks/euros.rake @@ -0,0 +1,109 @@ +namespace :euros do + desc "Create the UEFA Euros competition" + task create: :environment do + groups = { + 'Group A' => { + api_id: nil, + teams: [ + { name: 'Germany', abbrev: 'GER' }, + { name: 'Scotland', abbrev: 'SCO' }, + { name: 'Switzerland', abbrev: 'SUI' }, + { name: 'Hungary', abbrev: 'HUN' } + ] + }, + 'Group B' => { + api_id: nil, + teams: [ + { name: 'Albania', abbrev: 'ALB' }, + { name: 'Italy', abbrev: 'ITA' }, + { name: 'Croatia', abbrev: 'CRO' }, + { name: 'Spain', abbrev: 'ESP' } + ] + }, + 'Group C' => { + api_id: nil, + teams: [ + { name: 'Denmark', abbrev: 'DEN' }, + { name: 'England', abbrev: 'ENG' }, + { name: 'Serbia', abbrev: 'SRB' }, + { name: 'Slovenia', abbrev: 'SVN' } + ] + }, + 'Group D' => { + api_id: nil, + teams: [ + { name: 'France', abbrev: 'FRA' }, + { name: 'Netherlands', abbrev: 'NED' }, + { name: 'Austria', abbrev: 'AUT' }, + { name: 'Poland', abbrev: 'POL' } + ] + }, + 'Group E' => { + api_id: nil, + teams: [ + { name: 'Belgium', abbrev: 'BEL' }, + { name: 'Romania', abbrev: 'ROU' }, + { name: 'Slovakia', abbrev: 'SVK' }, + { name: 'Ukraine', abbrev: 'UKR' } + ] + }, + 'Group F' => { + api_id: nil, + teams: [ + { name: 'Georgia', abbrev: 'GEO' }, + { name: 'Portugal', abbrev: 'POR' }, + { name: 'Czechia', abbrev: 'CZE' }, + { name: 'Turkey', abbrev: 'TUR' } + ] + }, + } + puts 'Creating the Euros...' + euros = Competition.find_or_create_by(name: 'Euros 2024', start_date: Date.new(2024, 06, 14), end_date: Date.new(2024, 07, 14), api_id: 2018, api_code: 'EC') + puts '.. created the Euros' + + puts 'Creating or finding first round...' + first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: euros, api_name: 'GROUP_STAGE') + puts "...#{euros.rounds.count} Total Rounds" + + puts 'Creating or finding groups...' + groups.each_key do |group_name| + puts "...#{group_name}..." + group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id]) + groups[group_name][:teams].each do |team_hash| + puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" + team = Team.find_or_create_by(team_hash) + Affiliation.find_or_create_by(team: team, group: group) + end + end + puts "...#{euros.teams.count} Total Teams" + puts "...#{euros.groups.count} Total Groups" + + doug = User.find_by(email: 'douglasmberkley@gmail.com') || User.create(email: 'douglasmberkley@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) + trouni = User.find_by(email: 'trouni@gmail.com') || User.create(email: 'trouni@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) + james = User.find_by(email: 'devereuxjj@gmail.com') || User.create(email: 'devereuxjj@gmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) + renato = User.find_by(email: 'renatonato_jr@hotmail.com') || User.create(email: 'renatonato_jr@hotmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) + caio = User.find_by(email: 'caio.santos@msn.com') || User.create(email: 'caio.santos@msn.com', password: ENV['ADMIN_PASSWORD'], admin: true) + + puts 'Creating a test leaderboards' + leaderboard = Leaderboard.find_or_create_by( + name: 'Admin Leaderboard 1', + competition: euros, + user: trouni + ) + Membership.find_or_create_by(leaderboard: leaderboard, user: doug) + Membership.find_or_create_by(leaderboard: leaderboard, user: james) + Membership.find_or_create_by(leaderboard: leaderboard, user: renato) + Membership.find_or_create_by(leaderboard: leaderboard, user: caio) + + leaderboard = Leaderboard.find_or_create_by( + name: 'Admin Leaderboard 2', + competition: euros, + user: doug + ) + Membership.find_or_create_by(leaderboard: leaderboard, user: trouni) + Membership.find_or_create_by(leaderboard: leaderboard, user: james) + Membership.find_or_create_by(leaderboard: leaderboard, user: renato) + Membership.find_or_create_by(leaderboard: leaderboard, user: caio) + end + +end From 1176cc7742eb89f61566b595deb88ee382f9b389 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 14:24:52 +0900 Subject: [PATCH 262/286] removed debugging --- lib/tasks/euros.rake | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index f6873ad..bd9d269 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -58,11 +58,13 @@ namespace :euros do }, } puts 'Creating the Euros...' - euros = Competition.find_or_create_by(name: 'Euros 2024', start_date: Date.new(2024, 06, 14), end_date: Date.new(2024, 07, 14), api_id: 2018, api_code: 'EC') + euros = Competition.find_or_create_by!(name: 'Euros 2024', start_date: Date.new(2024, 06, 14), end_date: Date.new(2024, 07, 14), api_id: 2018, api_code: 'EC') puts '.. created the Euros' puts 'Creating or finding first round...' - first_round = Round.find_or_create_by(name: 'Group Stage', number: 1, competition: euros, api_name: 'GROUP_STAGE') + first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: 'GROUP_STAGE') + euros.update!(current_round: first_round) + # TODO: this only works when there are matches puts "...#{euros.rounds.count} Total Rounds" puts 'Creating or finding groups...' @@ -71,10 +73,11 @@ namespace :euros do group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id]) groups[group_name][:teams].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" - team = Team.find_or_create_by(team_hash) - Affiliation.find_or_create_by(team: team, group: group) + team = Team.find_or_create_by!(team_hash) + Affiliation.find_or_create_by!(team: team, group: group) end end + # TODO: this only works when there are matches puts "...#{euros.teams.count} Total Teams" puts "...#{euros.groups.count} Total Groups" @@ -84,26 +87,26 @@ namespace :euros do renato = User.find_by(email: 'renatonato_jr@hotmail.com') || User.create(email: 'renatonato_jr@hotmail.com', password: ENV['ADMIN_PASSWORD'], admin: true) caio = User.find_by(email: 'caio.santos@msn.com') || User.create(email: 'caio.santos@msn.com', password: ENV['ADMIN_PASSWORD'], admin: true) - puts 'Creating a test leaderboards' - leaderboard = Leaderboard.find_or_create_by( + puts 'Creating test leaderboards' + leaderboard = Leaderboard.find_or_create_by!( name: 'Admin Leaderboard 1', competition: euros, user: trouni ) - Membership.find_or_create_by(leaderboard: leaderboard, user: doug) - Membership.find_or_create_by(leaderboard: leaderboard, user: james) - Membership.find_or_create_by(leaderboard: leaderboard, user: renato) - Membership.find_or_create_by(leaderboard: leaderboard, user: caio) + Membership.find_or_create_by!(leaderboard: leaderboard, user: doug) + Membership.find_or_create_by!(leaderboard: leaderboard, user: james) + Membership.find_or_create_by!(leaderboard: leaderboard, user: renato) + Membership.find_or_create_by!(leaderboard: leaderboard, user: caio) - leaderboard = Leaderboard.find_or_create_by( + leaderboard = Leaderboard.find_or_create_by!( name: 'Admin Leaderboard 2', competition: euros, user: doug ) - Membership.find_or_create_by(leaderboard: leaderboard, user: trouni) - Membership.find_or_create_by(leaderboard: leaderboard, user: james) - Membership.find_or_create_by(leaderboard: leaderboard, user: renato) - Membership.find_or_create_by(leaderboard: leaderboard, user: caio) + Membership.find_or_create_by!(leaderboard: leaderboard, user: trouni) + Membership.find_or_create_by!(leaderboard: leaderboard, user: james) + Membership.find_or_create_by!(leaderboard: leaderboard, user: renato) + Membership.find_or_create_by!(leaderboard: leaderboard, user: caio) end end From 7a7dc6129a93f8f9f0f6935e894de83c6c2c23d2 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 15:15:31 +0900 Subject: [PATCH 263/286] created a job to handle all the matches from data-football --- app/jobs/match_update_job.rb | 45 +++++++++++++++++++ app/services/data_football_api.rb | 5 +++ .../20240606055739_add_api_code_to_groups.rb | 5 +++ db/schema.rb | 3 +- lib/tasks/euros.rake | 15 ++++++- 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 app/jobs/match_update_job.rb create mode 100644 app/services/data_football_api.rb create mode 100644 db/migrate/20240606055739_add_api_code_to_groups.rb diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb new file mode 100644 index 0000000..650ee84 --- /dev/null +++ b/app/jobs/match_update_job.rb @@ -0,0 +1,45 @@ +class MatchUpdateJob < ApplicationJob + queue_as :default + + def perform(competition_id) + @competition = Competition.find(competition_id) + url_to_update = DataFootballApi.matches_url(@competition.api_code) + update_matches_future(url_to_update) + end + + def update_matches_future(url) + response = HTTParty.get( + url, + headers: { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => ENV['FOOTBALL_DATA_TOKEN'] + } + ).body + parsed_response = JSON.parse(response) + matches = parsed_response['matches'] + DatabaseViews.run_without_callback(then_refresh: true) do + matches.each do |match_info| + kickoff_time = DateTime.parse(match_info['utcDate']) + puts "Finding the match between : #{match_info['homeTeam']['name']} v #{match_info['awayTeam']['name']} (#{kickoff_time})" + team_home = Team.find_by(abbrev: match_info['homeTeam']['tla']) + team_away = Team.find_by(abbrev: match_info['awayTeam']['tla']) + match = + @competition.matches.where(team_home: team_home, team_away: team_away) + .find_by(kickoff_time: kickoff_time) || Match.new + match.team_home ||= team_home + match.team_away ||= team_away + next unless match.team_home && match.team_away # knock-out rounds with no teams yet + + match.round = Round.find_by(competition: @competition, api_name: match_info['stage']) + match.group = Group.find_by(round: match.round, api_code: match_info["group"]) if match_info["group"] + match.api_id = match_info['id'] + # TODO: Don't think we're getting the location from the API + # match.location = match_info['location'] + match.kickoff_time = kickoff_time + match.save + p match.errors.full_messages if match.errors.any? + puts 'Match Update' + end + end + end +end diff --git a/app/services/data_football_api.rb b/app/services/data_football_api.rb new file mode 100644 index 0000000..ae31c9d --- /dev/null +++ b/app/services/data_football_api.rb @@ -0,0 +1,5 @@ +class DataFootballApi + def self.matches_url(competition_api_code) + "https://api.football-data.org/v4/competitions/#{competition_api_code}/matches" + end +end diff --git a/db/migrate/20240606055739_add_api_code_to_groups.rb b/db/migrate/20240606055739_add_api_code_to_groups.rb new file mode 100644 index 0000000..a9c01df --- /dev/null +++ b/db/migrate/20240606055739_add_api_code_to_groups.rb @@ -0,0 +1,5 @@ +class AddApiCodeToGroups < ActiveRecord::Migration[6.1] + def change + add_column :groups, :api_code, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 6b9ba4a..b86f751 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: 2024_06_06_050948) do +ActiveRecord::Schema.define(version: 2024_06_06_055739) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -70,6 +70,7 @@ t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.integer "api_id" + t.string "api_code" t.index ["round_id"], name: "index_groups_on_round_id" end diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index bd9d269..8525be7 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -4,6 +4,7 @@ namespace :euros do groups = { 'Group A' => { api_id: nil, + api_code: 'GROUP_A', teams: [ { name: 'Germany', abbrev: 'GER' }, { name: 'Scotland', abbrev: 'SCO' }, @@ -13,6 +14,7 @@ namespace :euros do }, 'Group B' => { api_id: nil, + api_code: 'GROUP_B', teams: [ { name: 'Albania', abbrev: 'ALB' }, { name: 'Italy', abbrev: 'ITA' }, @@ -22,6 +24,7 @@ namespace :euros do }, 'Group C' => { api_id: nil, + api_code: 'GROUP_C', teams: [ { name: 'Denmark', abbrev: 'DEN' }, { name: 'England', abbrev: 'ENG' }, @@ -31,6 +34,7 @@ namespace :euros do }, 'Group D' => { api_id: nil, + api_code: 'GROUP_D', teams: [ { name: 'France', abbrev: 'FRA' }, { name: 'Netherlands', abbrev: 'NED' }, @@ -40,6 +44,7 @@ namespace :euros do }, 'Group E' => { api_id: nil, + api_code: 'GROUP_E', teams: [ { name: 'Belgium', abbrev: 'BEL' }, { name: 'Romania', abbrev: 'ROU' }, @@ -49,6 +54,7 @@ namespace :euros do }, 'Group F' => { api_id: nil, + api_code: 'GROUP_F', teams: [ { name: 'Georgia', abbrev: 'GEO' }, { name: 'Portugal', abbrev: 'POR' }, @@ -64,13 +70,20 @@ namespace :euros do puts 'Creating or finding first round...' first_round = Round.find_or_create_by!(name: 'Group Stage', number: 1, competition: euros, api_name: 'GROUP_STAGE') euros.update!(current_round: first_round) + Round.find_or_create_by!(name: 'Round of 16', number: 2, competition: euros, api_name: 'LAST_16') + Round.find_or_create_by!(name: 'Quarter-finals', number: 3, competition: euros, api_name: 'QUARTER_FINALS') + Round.find_or_create_by!(name: 'Semi-finals', number: 4, competition: euros, api_name: 'SEMI_FINALS') + # TODO: Doesn't look like the API has the 3rd place playoff + # Round.find_or_create_by!(name: 'Third Place', number: 5, competition: euros, api_name: '3PPO') + Round.find_or_create_by!(name: 'Final', number: 6, competition: euros, api_name: 'FINAL') + # TODO: this only works when there are matches puts "...#{euros.rounds.count} Total Rounds" puts 'Creating or finding groups...' groups.each_key do |group_name| puts "...#{group_name}..." - group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id]) + group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id], api_code: groups[group_name][:api_code]) groups[group_name][:teams].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" team = Team.find_or_create_by!(team_hash) From 5a79b6cbc40ab92c80d6a6227d6705aa7e860a50 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 15:39:08 +0900 Subject: [PATCH 264/286] fixed match update --- app/models/match.rb | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/models/match.rb b/app/models/match.rb index ba5b522..8683d8d 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -28,15 +28,18 @@ def check_team_and_day_uniqueness def update_with_api(match_info) finished! if match_info['status'] == 'FINISHED' - started! if match_info['status'] == 'IN PLAY' - self.team_home_score, self.team_away_score = match_info['score']&.split(' - ') - self.team_home_et_score, self.team_away_et_score = match_info['et_score']&.split(' - ') - self.team_home_ps_score, self.team_away_ps_score = match_info['ps_score']&.split(' - ') + started! if match_info['status'] == 'IN PLAY' || match_info['status'] == 'LIVE' + self.team_home_score = match_info['score']['fullTime']['home'] + self.team_away_score = match_info['score']['fullTime']['away'] + self.team_home_et_score = match_info['score']['extraTime']['home'] if match_info['score']['extraTime'] + self.team_away_et_score = match_info['score']['extraTime']['away'] if match_info['score']['extraTime'] + self.team_home_ps_score = match_info['score']['penalties']['home'] if match_info['score']['penalties'] + self.team_away_ps_score = match_info['score']['penalties']['away'] if match_info['score']['penalties'] save - scores = ["FT Score > #{match_info['score']}"] - scores << "Extra-time > #{match_info['et_score']}" unless match_info['et_score']&.blank? - scores << "Penalties > #{match_info['ps_score']}" unless match_info['ps_score']&.blank? + scores = ["FT Score > #{build_regular_time_score}"] + scores << "Extra-time > #{team_home_et_score} - #{team_away_et_score}" unless match_info['score']['extraTime']&.blank? + scores << "Penalties > #{team_home_ps_score} - #{team_away_ps_score}" unless match_info['score']['penalties']&.blank? puts "Match Update:\n#{scores.join("\n")}" end @@ -46,4 +49,12 @@ def set_round_and_competition self.round ||= group&.round self.competition ||= round&.competition end + + def build_regular_time_score + if team_home_ps_score || team_away_ps_score + "#{team_home_score - team_home_ps_score} - #{team_away_score - team_away_ps_score}" + else + "#{team_home_score} - #{team_away_score}" + end + end end From 772c9656fbbedada7622dd6e7108a9f96afdf20b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 15:40:51 +0900 Subject: [PATCH 265/286] updating scores in API matches call --- app/jobs/match_update_job.rb | 3 +++ app/models/match.rb | 1 + 2 files changed, 4 insertions(+) diff --git a/app/jobs/match_update_job.rb b/app/jobs/match_update_job.rb index 650ee84..551f68d 100644 --- a/app/jobs/match_update_job.rb +++ b/app/jobs/match_update_job.rb @@ -38,6 +38,9 @@ def update_matches_future(url) match.kickoff_time = kickoff_time match.save p match.errors.full_messages if match.errors.any? + + # Update scores + match.update_with_api(match_info) puts 'Match Update' end end diff --git a/app/models/match.rb b/app/models/match.rb index 8683d8d..07b6f6e 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -35,6 +35,7 @@ def update_with_api(match_info) self.team_away_et_score = match_info['score']['extraTime']['away'] if match_info['score']['extraTime'] self.team_home_ps_score = match_info['score']['penalties']['home'] if match_info['score']['penalties'] self.team_away_ps_score = match_info['score']['penalties']['away'] if match_info['score']['penalties'] + # TODO: I'm not sure how we're displaying the scores in the view save scores = ["FT Score > #{build_regular_time_score}"] From 70d88ac7b296960f60a7b7abbabcdbf73130ce15 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 15:46:02 +0900 Subject: [PATCH 266/286] updated rake tasks that we're updating matches --- lib/tasks/competition.rake | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 3a53137..6a804cb 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -118,22 +118,25 @@ namespace :competition do task update_ongoing_matches: :environment do competitions = Competition.on_going competitions.each do |competition| - MatchUpdateFutureJob.perform_later(competition.id) - MatchUpdateHistoryJob.perform_later(competition.id) - MatchUpdateLiveJob.perform_later(competition.id) + # MatchUpdateFutureJob.perform_later(competition.id) + # MatchUpdateHistoryJob.perform_later(competition.id) + # MatchUpdateLiveJob.perform_later(competition.id) + MatchUpdateJob.perform_later(competition.id) end end desc "Update upcoming fixtures for a competition" task :update_matches_future, [:competition_id] => :environment do |t, args| competition = Competition.find(args[:competition_id]) - MatchUpdateFutureJob.perform_later(competition.id) + # MatchUpdateFutureJob.perform_later(competition.id) + MatchUpdateJob.perform_later(competition.id) end desc "Update upcoming fixtures for a competition" task :update_matches_history, [:competition_id] => :environment do |t, args| competition = Competition.find(args[:competition_id]) - MatchUpdateHistoryJob.perform_later(competition.id) + # MatchUpdateHistoryJob.perform_later(competition.id) + MatchUpdateJob.perform_later(competition.id) end desc "Copy the first competition and start it today" From 3536c2c531a47f1ac30c40ac185b34249ae17232 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 16:05:54 +0900 Subject: [PATCH 267/286] created a job to fetch flags --- app/jobs/attach_flags_job.rb | 23 +++++++++++++++++++++++ app/services/data_football_api.rb | 4 ++++ lib/tasks/team.rake | 24 +++--------------------- test/jobs/attach_flags_job_test.rb | 7 +++++++ 4 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 app/jobs/attach_flags_job.rb create mode 100644 test/jobs/attach_flags_job_test.rb diff --git a/app/jobs/attach_flags_job.rb b/app/jobs/attach_flags_job.rb new file mode 100644 index 0000000..078b458 --- /dev/null +++ b/app/jobs/attach_flags_job.rb @@ -0,0 +1,23 @@ +class AttachFlagsJob < ApplicationJob + queue_as :default + + def perform(competition_id) + competition = Competition.find(competition_id) + url = DataFootballApi.teams_url(competition.api_code) + response = HTTParty.get( + url, + headers: { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => ENV['FOOTBALL_DATA_TOKEN'] + } + ).body + teams = JSON.parse(response)['teams'] + teams.each do |team_hash| + team = Team.find_by(abbrev: team_hash['tla']) + puts "#{team.name}: #{team_hash['crest']}" + team.flag.attach(io: URI.open(team_hash['crest']), filename: 'flag.png', content_type: 'image/png') unless team.flag.attached? + team.badge.attach(io: URI.open(team_hash['crest']), filename: 'badge.png', content_type: 'image/png') unless team.badge.attached? + puts team.flag.attached? ? 'Success' : 'Failed' + end + end +end diff --git a/app/services/data_football_api.rb b/app/services/data_football_api.rb index ae31c9d..69cb842 100644 --- a/app/services/data_football_api.rb +++ b/app/services/data_football_api.rb @@ -2,4 +2,8 @@ class DataFootballApi def self.matches_url(competition_api_code) "https://api.football-data.org/v4/competitions/#{competition_api_code}/matches" end + + def self.teams_url(competition_api_code) + "https://api.football-data.org/v4/competitions/#{competition_api_code}/teams" + end end diff --git a/lib/tasks/team.rake b/lib/tasks/team.rake index 253c5b5..7d99a5a 100644 --- a/lib/tasks/team.rake +++ b/lib/tasks/team.rake @@ -1,27 +1,9 @@ namespace :team do - desc "Calls Live-Score API, saves API id and gets the flag" + desc "Calls Data-Football API and gets the flags" task add_flag: :environment do - competition_id = 362 - season = 2022 - response = HTTParty.get("https://livescore-api.com/api-client/competitions/participants.json?key=#{ENV['LIVE_SCORE_KEY']}&secret=#{ENV['LIVE_SCORE_SECRET']}&competition_id=#{competition_id}&season=#{season}").body - countries = JSON.parse(response)['data'] - - not_found = [] - competition = Competition.find_by(api_id: competition_id) - competition.teams.find_each do |team| - country = countries.find { |country| country['name'] == team.name } - if country - team.api_id = country['id'] - team.save - next if team.flag.attached? && team.badge.attached? - - fetch_flag(team) - else - not_found << team.name - end + Competition.find_each do |competition| + AttachFlagsJob.perform_now(competition.id) end - - puts not_found.any? ? "Teams not found: #{not_found.join(', ')}" : 'Found all teams' end def fetch_flag(team) diff --git a/test/jobs/attach_flags_job_test.rb b/test/jobs/attach_flags_job_test.rb new file mode 100644 index 0000000..d9c190a --- /dev/null +++ b/test/jobs/attach_flags_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class AttachFlagsJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From a54a1a5f5633961a7c5e8633676c8f2191daa056 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 16:07:59 +0900 Subject: [PATCH 268/286] attaching flags when creating the euros --- lib/tasks/euros.rake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index 8525be7..3ccd13f 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -120,6 +120,8 @@ namespace :euros do Membership.find_or_create_by!(leaderboard: leaderboard, user: james) Membership.find_or_create_by!(leaderboard: leaderboard, user: renato) Membership.find_or_create_by!(leaderboard: leaderboard, user: caio) + + AttachFlagsJob.perform_now(euros.id) end end From f2aec343fc4cca88427e94c45dd04dd4deb3930b Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 16:18:24 +0900 Subject: [PATCH 269/286] added the match creation in the rake task --- lib/tasks/euros.rake | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index 3ccd13f..f7f66c0 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -77,9 +77,6 @@ namespace :euros do # Round.find_or_create_by!(name: 'Third Place', number: 5, competition: euros, api_name: '3PPO') Round.find_or_create_by!(name: 'Final', number: 6, competition: euros, api_name: 'FINAL') - # TODO: this only works when there are matches - puts "...#{euros.rounds.count} Total Rounds" - puts 'Creating or finding groups...' groups.each_key do |group_name| puts "...#{group_name}..." @@ -90,7 +87,11 @@ namespace :euros do Affiliation.find_or_create_by!(team: team, group: group) end end + # Calling the API to create the matches + MatchUpdateJob.perform_now(euros.id) + # TODO: this only works when there are matches + puts "...#{euros.rounds.count} Total Teams" puts "...#{euros.teams.count} Total Teams" puts "...#{euros.groups.count} Total Groups" From 8ceace9d1f2b20d53538baf177e0d85640f06a40 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 6 Jun 2024 16:33:51 +0900 Subject: [PATCH 270/286] changed wording in rake file --- lib/tasks/euros.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index f7f66c0..d344055 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -90,8 +90,8 @@ namespace :euros do # Calling the API to create the matches MatchUpdateJob.perform_now(euros.id) - # TODO: this only works when there are matches - puts "...#{euros.rounds.count} Total Teams" + # TODO: this only works when there are matches so you'll see 1 for now + puts "...#{euros.rounds.count} Total Round" puts "...#{euros.teams.count} Total Teams" puts "...#{euros.groups.count} Total Groups" From 967f3097063733b90cdf731e15413a7b4f088e45 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 8 Jun 2024 17:00:31 +0900 Subject: [PATCH 271/286] updated the team find just in case names were changed --- lib/tasks/euros.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index d344055..d2dbea2 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -83,7 +83,7 @@ namespace :euros do group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id], api_code: groups[group_name][:api_code]) groups[group_name][:teams].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" - team = Team.find_or_create_by!(team_hash) + team = Team.find_by!(abbrev: team_hash[:abbrev]) || Team.create!(team_hash) Affiliation.find_or_create_by!(team: team, group: group) end end From 62f44c517b6e5fff6a022fec67eaeccac975eef7 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 8 Jun 2024 18:00:35 +0900 Subject: [PATCH 272/286] removed exclamation mark --- lib/tasks/euros.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/euros.rake b/lib/tasks/euros.rake index d2dbea2..09a0a1c 100644 --- a/lib/tasks/euros.rake +++ b/lib/tasks/euros.rake @@ -83,7 +83,7 @@ namespace :euros do group = Group.find_or_create_by!(name: group_name, round: first_round, api_id: groups[group_name][:api_id], api_code: groups[group_name][:api_code]) groups[group_name][:teams].each do |team_hash| puts "Name: #{team_hash[:name]}, Abbrev: #{team_hash[:abbrev]}" - team = Team.find_by!(abbrev: team_hash[:abbrev]) || Team.create!(team_hash) + team = Team.find_by(abbrev: team_hash[:abbrev]) || Team.create!(team_hash) Affiliation.find_or_create_by!(team: team, group: group) end end From 11261cb4b478cb6b79cb0c8b2f3d21f42e70d1eb Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 9 Jun 2024 11:59:29 +0900 Subject: [PATCH 273/286] added a job to generate random results and then one to reset them --- lib/tasks/match.rake | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/tasks/match.rake b/lib/tasks/match.rake index 04b0b83..8e81907 100644 --- a/lib/tasks/match.rake +++ b/lib/tasks/match.rake @@ -1,15 +1,28 @@ namespace :match do - desc "Checks for matches in the past and randomly assigns a score" + desc "Randomly assigns results for the first 5 matches" task add_fake_results: :environment do - # TODO: Turn off before Friday - completed_matches = Match.where('kickoff_time < :date', date: DateTime.now) + @competition = Competition.last + completed_matches = @competition.matches.order(kickoff_time: :asc).first(5) completed_matches.each do |match| - next if match.finished? - match.finished! match.team_home_score = rand(0..3) match.team_away_score = rand(0..3) match.save + puts "#{match.team_home.name} vs. #{match.team_away.name}" + puts "#{match.team_home_score} vs. #{match.team_away_score}" + end + end + + desc "Restarts all the matches" + task restart_all: :environment do + @competition = Competition.last + completed_matches = @competition.matches.order(kickoff_time: :asc) + completed_matches.each do |match| + match.upcoming! + match.team_home_score = nil + match.team_away_score = nil + match.save + puts "#{match.team_home.name} vs. #{match.team_away.name} restarted" end end end From 62fbe3dea7153bf1ec9f8753a39bc417ed0f4230 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sun, 9 Jun 2024 12:13:50 +0900 Subject: [PATCH 274/286] added random predictions to the random match results --- lib/tasks/match.rake | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/tasks/match.rake b/lib/tasks/match.rake index 8e81907..e5b4acd 100644 --- a/lib/tasks/match.rake +++ b/lib/tasks/match.rake @@ -1,15 +1,24 @@ namespace :match do desc "Randomly assigns results for the first 5 matches" task add_fake_results: :environment do + return 'Not allowed in production' if Rails.env.production? + @competition = Competition.last completed_matches = @competition.matches.order(kickoff_time: :asc).first(5) completed_matches.each do |match| + puts "#{match.team_home.name} (H) vs. #{match.team_away.name} (A)" + User.find_each do |user| + prediction = Prediction.find_or_initialize_by(user: user, match: match) + prediction.choice = Prediction.choices.keys.sample + prediction.save + puts "- #{prediction.user.name} choose #{prediction.choice}" + end match.finished! match.team_home_score = rand(0..3) match.team_away_score = rand(0..3) match.save - puts "#{match.team_home.name} vs. #{match.team_away.name}" - puts "#{match.team_home_score} vs. #{match.team_away_score}" + puts "Result: #{match.team_home_score} vs. #{match.team_away_score}" + puts end end From e38feabb2fea671ea74d1d7e38a803cfef98b92d Mon Sep 17 00:00:00 2001 From: Trouni Tiet Date: Fri, 14 Jun 2024 11:23:51 +0900 Subject: [PATCH 275/286] Update Predictions create and update actions to only allow upcoming matches --- app/controllers/v1/predictions_controller.rb | 4 ++-- app/models/prediction.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/v1/predictions_controller.rb b/app/controllers/v1/predictions_controller.rb index 71a9797..52141df 100644 --- a/app/controllers/v1/predictions_controller.rb +++ b/app/controllers/v1/predictions_controller.rb @@ -1,6 +1,6 @@ class V1::PredictionsController < ApplicationController def create - @match = Match.find(params[:match_id]) + @match = Match.upcoming.find(params[:match_id]) @prediction = Prediction.new(prediction_params) @prediction.match = @match @prediction.user = current_user @@ -13,7 +13,7 @@ def create end def update - @prediction = Prediction.find_by(user: current_user, match: params[:match_id]) + @prediction = Prediction.editable.find_by(user: current_user, match: params[:match_id]) authorize @prediction if @prediction.update(prediction_params) render :show diff --git a/app/models/prediction.rb b/app/models/prediction.rb index f5c4f6e..ed4f488 100644 --- a/app/models/prediction.rb +++ b/app/models/prediction.rb @@ -6,6 +6,7 @@ class Prediction < ApplicationRecord validates :choice, presence: true enum choice: { home: 'home', away: 'away', draw: 'draw' } + scope :editable, -> { joins(:match).where(matches: { status: :upcoming }) } scope :locked, -> { joins(:match).where.not(matches: { status: :upcoming }) } after_commit :refresh_materialized_views From 5801d09e1d2373aa55350ff3ff4543412fc23258 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Sat, 15 Jun 2024 13:15:45 +0900 Subject: [PATCH 276/286] removed the filtering for global leaderboard --- app/views/v1/leaderboards/index.json.jbuilder | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/v1/leaderboards/index.json.jbuilder b/app/views/v1/leaderboards/index.json.jbuilder index 455fb77..797b040 100644 --- a/app/views/v1/leaderboards/index.json.jbuilder +++ b/app/views/v1/leaderboards/index.json.jbuilder @@ -1,7 +1,6 @@ json.array! @leaderboards do |leaderboard| json.partial! leaderboard rankings = leaderboard.rankings.order(:user_rank) - rankings = rankings.first(leaderboard.rankings_top_n) if leaderboard.rankings_top_n json.users rankings do |ranking| json.partial! 'v1/leaderboards/ranking', ranking: ranking end From 97f82c197051a9b25da9e96cc56b37783812532a Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 18 Jun 2024 12:26:20 +0900 Subject: [PATCH 277/286] added copa america --- app/jobs/competition_create_job.rb | 92 ++++++++++++++++++++++++ test/jobs/competition_create_job_test.rb | 7 ++ 2 files changed, 99 insertions(+) create mode 100644 app/jobs/competition_create_job.rb create mode 100644 test/jobs/competition_create_job_test.rb diff --git a/app/jobs/competition_create_job.rb b/app/jobs/competition_create_job.rb new file mode 100644 index 0000000..073767d --- /dev/null +++ b/app/jobs/competition_create_job.rb @@ -0,0 +1,92 @@ +class CompetitionCreateJob < ApplicationJob + queue_as :default + + def perform(competition_code) + url = DataFootballApi.teams_url(competition_code) + response = HTTParty.get( + url, + headers: { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => ENV['FOOTBALL_DATA_TOKEN'] + } + ).body + parsed_response = JSON.parse(response) + competition_parsed = parsed_response['competition'] + season_parsed = parsed_response['season'] + puts 'Creating the competition...' + competition = Competition.find_or_create_by!(name: competition_parsed['name'], start_date: Date.parse(season_parsed["startDate"]), end_date: Date.parse(season_parsed["endDate"]), api_id: competition_parsed['id'], api_code: competition_parsed['code']) + puts '.. created the competition' + + puts 'Creating or finding the rounds...' + season_parsed['stages'].each_with_index do |stage_key, index| + round = Round.find_or_create_by!(name: stage_key.titleize, number: index + 1, competition: competition, api_name: stage_key) + competition.update!(current_round: round) if index.zero? + end + + url = DataFootballApi.matches_url(competition_code) + response = HTTParty.get( + url, + headers: { + 'Content-Type' => 'application/json', + 'X-Auth-Token' => ENV['FOOTBALL_DATA_TOKEN'] + } + ).body + parsed_response = JSON.parse(response) + matches = parsed_response['matches'] + DatabaseViews.run_without_callback(then_refresh: true) do + matches.each do |match_info| + kickoff_time = DateTime.parse(match_info['utcDate']) + puts "Finding the match between : #{match_info['homeTeam']['name']} v #{match_info['awayTeam']['name']} (#{kickoff_time})" + next unless match_info['homeTeam']['tla'] && match_info['awayTeam']['tla'] # knock-out rounds with no teams yet + + team_home = Team.find_by!(abbrev: match_info['homeTeam']['tla']) || Team.create!(name: match_info['homeTeam']['shortName'], abbrev: match_info['homeTeam']['tla']) + team_away = Team.find_by!(abbrev: match_info['awayTeam']['tla']) || Team.create!(name: match_info['awayTeam']['shortName'], abbrev: match_info['awayTeam']['tla']) + + puts 'Getting/creating round and group...' + round = Round.find_by!(competition: competition, api_name: match_info['stage']) + group = Group.find_or_create_by!(name: match_info['group'].titleize, round: round, api_code: match_info['group']) + + puts "Adding #{team_home.abbrev} and #{team_away.abbrev} to #{group.name}" + Affiliation.find_or_create_by!(team: team_home, group: group) + Affiliation.find_or_create_by!(team: team_away, group: group) + + match = + competition.matches.where(team_home: team_home, team_away: team_away) + .find_by(kickoff_time: kickoff_time) || Match.new + match.team_home ||= team_home + match.team_away ||= team_away + match.round = round + match.group = Group.find_by(round: match.round, api_code: match_info["group"]) if match_info["group"] + match.api_id = match_info['id'] + # TODO: Don't think we're getting the location from the API + # match.location = match_info['location'] + match.kickoff_time = kickoff_time + match.save + p match.errors.full_messages if match.errors.any? + + # Update scores + match.update_with_api(match_info) + puts 'Match Update' + end + + leaderboard_hash = { + name: 'Global Top Players', + description: 'The top players on Octacle', + rankings_top_n: 10, + leave_disabled: true, + auto_join: true, + } + admin = User.find_by(admin: true) + leaderboard = competition.leaderboards.find_or_initialize_by(leaderboard_hash.slice(:name)) + leaderboard.assign_attributes(leaderboard_hash) + leaderboard.user ||= admin + leaderboard.save! + + puts "-----> Creating memberships for #{leaderboard.name} (#{leaderboard.competition.name})" + User.find_each { |user| leaderboard.memberships.find_or_create_by!(user: user) } + AttachFlagsJob.perform_later(competition.id) + end + + + end +end diff --git a/test/jobs/competition_create_job_test.rb b/test/jobs/competition_create_job_test.rb new file mode 100644 index 0000000..3d22709 --- /dev/null +++ b/test/jobs/competition_create_job_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class CompetitionCreateJobTest < ActiveJob::TestCase + # test "the truth" do + # assert true + # end +end From d7fc61bc8b0d486c85580768a524eede32752cf4 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Tue, 18 Jun 2024 13:44:26 +0900 Subject: [PATCH 278/286] Added the year to the competition name --- app/jobs/competition_create_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/competition_create_job.rb b/app/jobs/competition_create_job.rb index 073767d..4e956dc 100644 --- a/app/jobs/competition_create_job.rb +++ b/app/jobs/competition_create_job.rb @@ -14,7 +14,7 @@ def perform(competition_code) competition_parsed = parsed_response['competition'] season_parsed = parsed_response['season'] puts 'Creating the competition...' - competition = Competition.find_or_create_by!(name: competition_parsed['name'], start_date: Date.parse(season_parsed["startDate"]), end_date: Date.parse(season_parsed["endDate"]), api_id: competition_parsed['id'], api_code: competition_parsed['code']) + competition = Competition.find_or_create_by!(name: "#{competition_parsed['name']} #{Date.today.year}", start_date: Date.parse(season_parsed["startDate"]), end_date: Date.parse(season_parsed["endDate"]), api_id: competition_parsed['id'], api_code: competition_parsed['code']) puts '.. created the competition' puts 'Creating or finding the rounds...' From 86715aaec913aa6012fff5354f58612057c39a62 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 20 Jun 2024 11:17:46 +0900 Subject: [PATCH 279/286] removed exclamation mark --- app/jobs/competition_create_job.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/jobs/competition_create_job.rb b/app/jobs/competition_create_job.rb index 4e956dc..cb7bfb4 100644 --- a/app/jobs/competition_create_job.rb +++ b/app/jobs/competition_create_job.rb @@ -39,8 +39,8 @@ def perform(competition_code) puts "Finding the match between : #{match_info['homeTeam']['name']} v #{match_info['awayTeam']['name']} (#{kickoff_time})" next unless match_info['homeTeam']['tla'] && match_info['awayTeam']['tla'] # knock-out rounds with no teams yet - team_home = Team.find_by!(abbrev: match_info['homeTeam']['tla']) || Team.create!(name: match_info['homeTeam']['shortName'], abbrev: match_info['homeTeam']['tla']) - team_away = Team.find_by!(abbrev: match_info['awayTeam']['tla']) || Team.create!(name: match_info['awayTeam']['shortName'], abbrev: match_info['awayTeam']['tla']) + team_home = Team.find_by(abbrev: match_info['homeTeam']['tla']) || Team.create!(name: match_info['homeTeam']['shortName'], abbrev: match_info['homeTeam']['tla']) + team_away = Team.find_by(abbrev: match_info['awayTeam']['tla']) || Team.create!(name: match_info['awayTeam']['shortName'], abbrev: match_info['awayTeam']['tla']) puts 'Getting/creating round and group...' round = Round.find_by!(competition: competition, api_name: match_info['stage']) From 8ffafec5c3d6240c6f4fb0422227e343f62a1e46 Mon Sep 17 00:00:00 2001 From: Douglas Berkley Date: Thu, 20 Jun 2024 13:27:47 +0900 Subject: [PATCH 280/286] added photos for all the competitions --- app/jobs/competition_create_job.rb | 4 ++++ app/models/competition.rb | 8 +++++++- .../competitions/_competition.json.jbuilder | 1 + lib/tasks/competition.rake | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/jobs/competition_create_job.rb b/app/jobs/competition_create_job.rb index cb7bfb4..9ecde56 100644 --- a/app/jobs/competition_create_job.rb +++ b/app/jobs/competition_create_job.rb @@ -15,6 +15,10 @@ def perform(competition_code) season_parsed = parsed_response['season'] puts 'Creating the competition...' competition = Competition.find_or_create_by!(name: "#{competition_parsed['name']} #{Date.today.year}", start_date: Date.parse(season_parsed["startDate"]), end_date: Date.parse(season_parsed["endDate"]), api_id: competition_parsed['id'], api_code: competition_parsed['code']) + if competition["emblem"] && !competition.photo.attached? + file = URI.open(competition["emblem"]) + competition.photo.attach(io: file, filename: 'logo.png', content_type: 'image/png') + end puts '.. created the competition' puts 'Creating or finding the rounds...' diff --git a/app/models/competition.rb b/app/models/competition.rb index 8ff8cb3..6032651 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1,6 +1,6 @@ class Competition < ApplicationRecord belongs_to :current_round, class_name: 'Round', optional: true - has_many :matches + has_many :matches, dependent: :destroy has_many :rounds, -> { distinct }, through: :matches has_many :groups, through: :rounds has_many :affiliations, through: :groups @@ -8,6 +8,7 @@ class Competition < ApplicationRecord has_many :leaderboards, dependent: :destroy has_many :predictions, through: :matches, dependent: :destroy has_many :users, through: :leaderboards, source: :users + has_one_attached :photo validates :name, presence: true, uniqueness: { scope: :start_date} validates :start_date, presence: true @@ -16,6 +17,11 @@ class Competition < ApplicationRecord scope :on_going, -> { where('start_date < :start AND end_date > :end', start: Date.today + 1, end: Date.today - 1) } after_commit :refresh_materialized_views + before_destroy :destroy_rounds + + def destroy_rounds + Round.where(competition: self).destroy_all + end def max_possible_score matches.finished.joins(:round).sum('rounds.points') diff --git a/app/views/v1/competitions/_competition.json.jbuilder b/app/views/v1/competitions/_competition.json.jbuilder index 52cdebf..7162034 100644 --- a/app/views/v1/competitions/_competition.json.jbuilder +++ b/app/views/v1/competitions/_competition.json.jbuilder @@ -1 +1,2 @@ json.extract! competition, :id, :name, :start_date, :end_date, :current_round_id +json.photo_url cl_image_path(competition.photo.key) if competition.photo.attached? diff --git a/lib/tasks/competition.rake b/lib/tasks/competition.rake index 6a804cb..c2b69df 100644 --- a/lib/tasks/competition.rake +++ b/lib/tasks/competition.rake @@ -212,4 +212,23 @@ namespace :competition do end end + desc "Adds the photos to old competiton" + task add_photos: :environment do + euro_2020 = Competition.find_by(name: 'Euro 2020') + file = URI.open('https://upload.wikimedia.org/wikipedia/en/9/96/UEFA_Euro_2020_Logo.svg') + euro_2020.photo.attach(io: file, filename: 'logo.png', content_type: 'image/png') unless euro_2020.photo.attached? + + wc_2022 = Competition.find_by(name: 'World Cup 2022') + file = URI.open('https://crests.football-data.org/qatar.png') + wc_2022.photo.attach(io: file, filename: 'logo.png', content_type: 'image/png') unless wc_2022.photo.attached? + + euro_2024 = Competition.find_by(name: 'Euros 2024') + file = URI.open('https://www.football-data.org/assets/logo-euro_2020.svg') + euro_2024.photo.attach(io: file, filename: 'logo.png', content_type: 'image/png') unless euro_2024.photo.attached? + + copa_2024 = Competition.find_by(name: 'Copa America 2024') + file = URI.open('https://static.wikia.nocookie.net/internationalbroadcasts/images/8/80/2024_Copa_Am%C3%A9rica_logo.png/revision/latest/thumbnail/width/360/height/360?cb=20240402104618') + copa_2024.photo.attach(io: file, filename: 'logo.png', content_type: 'image/png') unless copa_2024.photo.attached? + end + end From 9517af90809ec7e00184d2b3ff65118d6f0248ad Mon Sep 17 00:00:00 2001 From: Yann Klein Date: Sat, 22 Jun 2024 10:58:19 +0900 Subject: [PATCH 281/286] fix deprecated faker safe_email method --- db/seeds/0_default.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds/0_default.rb b/db/seeds/0_default.rb index 5c34437..35c401d 100644 --- a/db/seeds/0_default.rb +++ b/db/seeds/0_default.rb @@ -13,7 +13,7 @@ 20.times do User.create( # fake emails for testing purposes - email: Faker::Internet.safe_email, + email: Faker::Internet.email, password: '123123' ) end From 9aff9a368ccbb00a7d64dc42a7af46b6dd8f3471 Mon Sep 17 00:00:00 2001 From: Yann Klein Date: Sat, 22 Jun 2024 10:58:56 +0900 Subject: [PATCH 282/286] fix deprecated watir browser option syntax --- app/services/scrape_matches_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/scrape_matches_service.rb b/app/services/scrape_matches_service.rb index 97aabf0..79267a3 100644 --- a/app/services/scrape_matches_service.rb +++ b/app/services/scrape_matches_service.rb @@ -18,8 +18,9 @@ def initialize end def call + browser_options = %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] DatabaseViews.run_without_callback(then_refresh: true) do - @browser = Watir::Browser.new :chrome, args: %w[--headless --no-sandbox --disable-dev-shm-usage --disable-gpu --remote-debugging-port=9222] + @browser = Watir::Browser.new :chrome, options: {args: browser_options} urls.each do |url| scrape(url) end From 5861785305c87b6213c06822e47466b128bf4a7c Mon Sep 17 00:00:00 2001 From: Yann Klein Date: Sat, 22 Jun 2024 10:59:21 +0900 Subject: [PATCH 283/286] fix deprecated skip_callback syntax --- app/services/database_views.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/database_views.rb b/app/services/database_views.rb index 4cc60cf..f26b740 100644 --- a/app/services/database_views.rb +++ b/app/services/database_views.rb @@ -7,7 +7,7 @@ def self.refresh(async: true) def self.deactivate_callback MODELS.each do |model| - model.skip_callback(:commit, :after, :refresh_materialized_views) + model.skip_callback(:commit, :after, :refresh_materialized_views, raise: false) end end From 48bc53178fd2eb5604dfd4791cbd39c1564e388f Mon Sep 17 00:00:00 2001 From: Yann Klein Date: Sat, 22 Jun 2024 10:59:54 +0900 Subject: [PATCH 284/286] add project installation details in readme --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 7ad5044..7108eea 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,41 @@ # predictor-app ## Project setup +### Create and populate .env file as follows (do the necessary changes): +``` +CLOUDINARY_URL=CLOUDINARY_URL=cloudinary://:@yanninthesky +FOOTBALL_DATA_TOKEN= +ADMIN_PASSWORD= +``` + +### Install and run sidekiq +``` +# On macOS +brew update +brew install redis +brew services start redis +sidekiq +``` + +``` +# On Ubuntu +curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg + +echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list + +sudo apt-get update +sudo apt-get install redis +sudo apt-get install redis-server +sidekiq +``` + ### Create DB / Migrate / Seed ``` rails db:create rails db:migrate rails db:seed ``` + ### Installs dependencies ``` bundle install From afaf01a4f6fa02e58d486450513ceb55d8e53150 Mon Sep 17 00:00:00 2001 From: Yann Klein Date: Sat, 22 Jun 2024 11:00:59 +0900 Subject: [PATCH 285/286] remove fix cloudinary version to accomodate to new v2 cloudinary lib --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 7f17724..bb98cc1 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,7 @@ gem 'bootsnap', '>= 1.4.4', require: false gem 'rack-cors', require: 'rack/cors' # Added -gem 'cloudinary', '~> 1.16.0' +gem 'cloudinary' gem 'devise' gem 'devise_token_auth', github: 'lynndylanhurley/devise_token_auth' gem 'faker' From c726461449cbd544d026a0b92435b1dffb9e4e8e Mon Sep 17 00:00:00 2001 From: Yann Klein Date: Sat, 22 Jun 2024 15:26:18 +0900 Subject: [PATCH 286/286] revert cloudinary, readme and skip_callback related changes --- Gemfile | 2 +- README.md | 29 ----------------------------- app/services/database_views.rb | 2 +- 3 files changed, 2 insertions(+), 31 deletions(-) diff --git a/Gemfile b/Gemfile index bb98cc1..7f17724 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,7 @@ gem 'bootsnap', '>= 1.4.4', require: false gem 'rack-cors', require: 'rack/cors' # Added -gem 'cloudinary' +gem 'cloudinary', '~> 1.16.0' gem 'devise' gem 'devise_token_auth', github: 'lynndylanhurley/devise_token_auth' gem 'faker' diff --git a/README.md b/README.md index 7108eea..7ad5044 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,12 @@ # predictor-app ## Project setup -### Create and populate .env file as follows (do the necessary changes): -``` -CLOUDINARY_URL=CLOUDINARY_URL=cloudinary://:@yanninthesky -FOOTBALL_DATA_TOKEN= -ADMIN_PASSWORD= -``` - -### Install and run sidekiq -``` -# On macOS -brew update -brew install redis -brew services start redis -sidekiq -``` - -``` -# On Ubuntu -curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg - -echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list - -sudo apt-get update -sudo apt-get install redis -sudo apt-get install redis-server -sidekiq -``` - ### Create DB / Migrate / Seed ``` rails db:create rails db:migrate rails db:seed ``` - ### Installs dependencies ``` bundle install diff --git a/app/services/database_views.rb b/app/services/database_views.rb index f26b740..4cc60cf 100644 --- a/app/services/database_views.rb +++ b/app/services/database_views.rb @@ -7,7 +7,7 @@ def self.refresh(async: true) def self.deactivate_callback MODELS.each do |model| - model.skip_callback(:commit, :after, :refresh_materialized_views, raise: false) + model.skip_callback(:commit, :after, :refresh_materialized_views) end end