mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 22:15:14 +00:00
Add daily leaderboard
This commit is contained in:
parent
8eea8efb98
commit
499036038e
11 changed files with 154 additions and 2 deletions
9
app/controllers/leaderboards_controller.rb
Normal file
9
app/controllers/leaderboards_controller.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
class LeaderboardsController < ApplicationController
|
||||
def index
|
||||
@leaderboard = Leaderboard.find_by(start_date: Date.current)
|
||||
|
||||
@entries = @leaderboard.entries
|
||||
.includes(:user)
|
||||
.order(total_seconds: :desc)
|
||||
end
|
||||
end
|
||||
36
app/jobs/leaderboard_update_job.rb
Normal file
36
app/jobs/leaderboard_update_job.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
class LeaderboardUpdateJob < ApplicationJob
|
||||
queue_as :default
|
||||
limits_concurrency to: 1, key: :date, duration: 5.minutes
|
||||
|
||||
def perform(date = nil, leaderboard = nil)
|
||||
if !leaderboard
|
||||
date ||= Date.current
|
||||
leaderboard = Leaderboard.find_or_initialize_by(start_date: date)
|
||||
end
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
# Reset the leaderboard to recalculate
|
||||
# leaderboard.calculating!
|
||||
leaderboard.entries.destroy_all
|
||||
|
||||
begin
|
||||
User.find_each do |user|
|
||||
seconds = user.heartbeats.where("DATE(time) = ?", date).duration_seconds
|
||||
next if seconds.zero?
|
||||
|
||||
leaderboard.entries.build(
|
||||
user_id: user.slack_uid,
|
||||
total_seconds: seconds
|
||||
)
|
||||
end
|
||||
|
||||
leaderboard.touch(:updated_at) unless leaderboard.new_record?
|
||||
leaderboard.save!
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to update current leaderboard: #{e.message}"
|
||||
# leaderboard.failed!
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
7
app/models/leaderboard.rb
Normal file
7
app/models/leaderboard.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Leaderboard < ApplicationRecord
|
||||
has_many :entries,
|
||||
class_name: "LeaderboardEntry",
|
||||
dependent: :destroy
|
||||
|
||||
validates :start_date, presence: true, uniqueness: true
|
||||
end
|
||||
8
app/models/leaderboard_entry.rb
Normal file
8
app/models/leaderboard_entry.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
class LeaderboardEntry < ApplicationRecord
|
||||
belongs_to :leaderboard
|
||||
belongs_to :user, primary_key: :slack_uid
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :total_seconds, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
||||
validates :user_id, uniqueness: { scope: :leaderboard_id }
|
||||
end
|
||||
37
app/views/leaderboards/index.html.erb
Normal file
37
app/views/leaderboards/index.html.erb
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<div class="leaderboard">
|
||||
<div class="header">
|
||||
<h1>Today's Leaderboard</h1>
|
||||
<p class="date">
|
||||
<%= Date.current.strftime("%B %d, %Y") %>
|
||||
<em>Updated <%= time_ago_in_words(@leaderboard.updated_at) %> ago</em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<% if @entries.any? %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>User</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @entries.each_with_index do |entry, index| %>
|
||||
<tr>
|
||||
<td><%= (index + 1).ordinalize %></td>
|
||||
<td><%= render "shared/user_mention", user: entry.user %></td>
|
||||
<td><%= Time.at(entry.total_seconds).utc.strftime("%H:%M:%S") %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<div class="empty-state">
|
||||
<h3>No data available</h3>
|
||||
<p>Check back later for today's results!</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -17,4 +17,6 @@ Rails.application.routes.draw do
|
|||
get "/auth/slack", to: "sessions#new", as: :slack_auth
|
||||
get "/auth/slack/callback", to: "sessions#create"
|
||||
delete "signout", to: "sessions#destroy", as: "signout"
|
||||
|
||||
resources :leaderboards, only: [ :index ]
|
||||
end
|
||||
|
|
|
|||
3
config/schedule.rb
Normal file
3
config/schedule.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
every 1.hour do
|
||||
runner "UpdateLeaderboardJob.perform_later"
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
class CreateDailyLeaderboardEntries < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :daily_leaderboard_entries do |t|
|
||||
t.references :daily_leaderboard, null: false, foreign_key: true
|
||||
t.string :user_id, null: false
|
||||
t.integer :total_seconds, null: false, default: 0
|
||||
t.integer :rank
|
||||
t.timestamps
|
||||
|
||||
t.index [ :daily_leaderboard_id, :user_id ], unique: true, name: 'idx_leaderboard_entries_on_leaderboard_and_user'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class RenameDailyLeaderboardTables < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
rename_table :daily_leaderboards, :leaderboards
|
||||
rename_table :daily_leaderboard_entries, :leaderboard_entries
|
||||
|
||||
# Update the foreign key
|
||||
rename_column :leaderboard_entries, :daily_leaderboard_id, :leaderboard_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class RemoveStatusFromLeaderboards < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
remove_column :leaderboards, :status, :integer
|
||||
end
|
||||
end
|
||||
27
db/schema.rb
generated
27
db/schema.rb
generated
|
|
@ -11,6 +11,27 @@
|
|||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_02_16_173459) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
||||
create_table "leaderboard_entries", force: :cascade do |t|
|
||||
t.bigint "leaderboard_id", null: false
|
||||
t.string "user_id", null: false
|
||||
t.integer "total_seconds", default: 0, null: false
|
||||
t.integer "rank"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["leaderboard_id", "user_id"], name: "idx_leaderboard_entries_on_leaderboard_and_user", unique: true
|
||||
t.index ["leaderboard_id"], name: "index_leaderboard_entries_on_leaderboard_id"
|
||||
end
|
||||
|
||||
create_table "leaderboards", force: :cascade do |t|
|
||||
t.date "start_date", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["start_date"], name: "index_leaderboards_on_start_date", unique: true
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "slack_uid", null: false
|
||||
t.string "email", null: false
|
||||
|
|
@ -28,8 +49,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_16_173459) do
|
|||
t.bigint "item_id", null: false
|
||||
t.string "item_type", null: false
|
||||
t.string "event", null: false
|
||||
t.text "object", limit: 1073741823
|
||||
t.text "object_changes", limit: 1073741823
|
||||
t.text "object"
|
||||
t.text "object_changes"
|
||||
t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
|
||||
end
|
||||
|
||||
add_foreign_key "leaderboard_entries", "leaderboards"
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue