Add initial ysws program claiming (#178)

This commit is contained in:
Max Wofford
2025-04-26 12:15:15 -04:00
committed by GitHub
parent 7e46519d05
commit c851b4c82e
5 changed files with 149 additions and 2 deletions

View File

@@ -0,0 +1,73 @@
module Api
module V1
class YswsProgramsController < ApplicationController
before_action :ensure_authenticated!, only: [ :claim ]
def index
render json: Heartbeat.ysws_programs.keys
end
def claim
validate_params
heartbeats = find_heartbeats
conflicting_heartbeats = heartbeats.where.not(ysws_program: [ nil, :nothing ])
if conflicting_heartbeats.any?
render json: {
error: "Some heartbeats are already claimed",
conflicts: conflicting_heartbeats.pluck(:id, :ysws_program)
}, status: :conflict
return
end
heartbeats.update_all(ysws_program: params[:program_id])
render json: {
message: "Successfully claimed #{heartbeats.count} heartbeats",
claimed_count: heartbeats.count
}
end
private
def ensure_authenticated!
token = request.headers["Authorization"]&.split(" ")&.last
token ||= params[:api_key]
render json: { error: "Unauthorized" }, status: :unauthorized unless token == ENV["STATS_API_KEY"]
end
def validate_params
required_params = [ :start_time, :end_time, :user_id, :program_id ]
missing_params = required_params.select { |param| params[param].blank? }
if missing_params.any?
render json: { error: "Missing required parameters: #{missing_params.join(', ')}" }, status: :bad_request
end
unless Heartbeat.ysws_programs.value?(params[:program_id].to_i)
render json: { error: "Invalid program_id value" }, status: :bad_request
end
end
def find_heartbeats
user = User.where(id: params[:user_id]).first
user ||= User.where(slack_uid: params[:user_id]).first
return Heartbeat.none unless user.present?
scope = Heartbeat.where(
user_id: user.id,
time: params[:start_time]..params[:end_time]
)
if params[:project].present?
scope = scope.where(project: params[:project])
end
scope
end
end
end
end

View File

@@ -12,6 +12,68 @@ class Heartbeat < ApplicationRecord
test_entry: 2
}
enum :ysws_program, {
nothing: 0,
high_seas: 1,
arcade: 2,
juice: 3,
onboard: 4,
sprig: 5,
cider: 6,
hackpad: 7,
boba_drops: 8,
the_bin: 9,
blot: 10,
infill: 11,
scrapyard: 12,
hackcraft_mod_edition: 13,
browser_buddy: 14,
hackaccino: 15,
cafe: 16,
low_skies: 17,
rasp_api: 18,
terminal_craft: 19,
neon: 20,
jungle: 21,
counterspell: 22,
riceathon: 23,
power_hour: 24,
scrapyard_flagship: 25,
ten_days_in_public: 26,
build_your_own_llm: 27,
cargo_cult_v2: 28,
sockathon: 29,
bakebuild: 30,
minus_twelve: 31,
easel: 32,
retrospect: 33,
cascade: 34,
ten_hours_in_public: 35,
swirl: 36,
tarot: 37,
asylum: 38,
cargo_cult: 39,
rpg: 40,
ham_club: 41,
anchor: 42,
dessert: 43,
wizard_orpheus: 44,
onboard_live: 45,
say_cheese: 46,
hackapet: 47,
clubs_competitions: 48,
printboard: 49,
black_box: 50,
shipwrecked: 51,
pizza_grant_ysws: 52,
pixeldust: 53,
hacklet: 54,
reflow: 55,
the_journey: 56,
visioneer: 57,
neighborhood: 58
}, prefix: :claimed_by
# This is to prevent Rails from trying to use STI even though we have a "type" column
self.inheritance_column = nil
@@ -36,7 +98,7 @@ class Heartbeat < ApplicationRecord
end
def self.unindexed_attributes
%w[id created_at updated_at source_type fields_hash]
%w[id created_at updated_at source_type fields_hash ysws_program]
end
private

View File

@@ -77,16 +77,22 @@ Rails.application.routes.draw do
# API routes
namespace :api do
# This is our own API don't worry about compatibility.
namespace :v1 do
get "stats", to: "stats#show"
get "users/:username/stats", to: "stats#user_stats"
resources :ysws_programs, only: [ :index ] do
post :claim, on: :collection
end
namespace :my do
get "heartbeats/most_recent", to: "heartbeats#most_recent"
get "heartbeats", to: "heartbeats#index"
end
end
# Everything in this namespace conforms to wakatime.com's API.
namespace :hackatime do
namespace :v1 do
get "/", to: "hackatime#index" # many clients seem to link this as the user's dashboard

View File

@@ -0,0 +1,5 @@
class AddYswsProgramToHeartbeats < ActiveRecord::Migration[8.0]
def change
add_column :heartbeats, :ysws_program, :integer, default: 0, null: false
end
end

3
db/schema.rb generated
View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_03_24_203539) do
ActiveRecord::Schema[8.0].define(version: 2025_04_25_211619) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@@ -150,6 +150,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_24_203539) do
t.text "fields_hash"
t.integer "source_type", null: false
t.inet "ip_address"
t.integer "ysws_program", default: 0, null: false
t.index ["category", "time"], name: "index_heartbeats_on_category_and_time"
t.index ["fields_hash"], name: "index_heartbeats_on_fields_hash", unique: true
t.index ["user_id"], name: "index_heartbeats_on_user_id"