From 1db16233cb5f81262504d7a4fc186cbd35b7e533 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Tue, 13 May 2025 14:48:24 -0400 Subject: [PATCH] Add raw heartbeat request storing --- .../api/hackatime/v1/hackatime_controller.rb | 12 +++++++++++- app/models/heartbeat.rb | 1 + app/models/raw_heartbeat_upload.rb | 6 ++++++ .../20250513183739_create_raw_heartbeat_uploads.rb | 10 ++++++++++ ...3184040_add_raw_heartbeat_upload_to_heartbeats.rb | 5 +++++ db/schema.rb | 12 +++++++++++- 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 app/models/raw_heartbeat_upload.rb create mode 100644 db/migrate/20250513183739_create_raw_heartbeat_uploads.rb create mode 100644 db/migrate/20250513184040_add_raw_heartbeat_upload_to_heartbeats.rb diff --git a/app/controllers/api/hackatime/v1/hackatime_controller.rb b/app/controllers/api/hackatime/v1/hackatime_controller.rb index 6f2c3ca..5012610 100644 --- a/app/controllers/api/hackatime/v1/hackatime_controller.rb +++ b/app/controllers/api/hackatime/v1/hackatime_controller.rb @@ -1,6 +1,7 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController before_action :set_user, except: [ :index ] skip_before_action :verify_authenticity_token + before_action :set_raw_heartbeat_upload, only: [ :push_heartbeats ] def index redirect_to root_path @@ -28,7 +29,7 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController # { # ...heartbeat_data # } - heartbeat_array = [ heartbeat_params ] + heartbeat_array = Array(heartbeat_params) new_heartbeat = handle_heartbeat(heartbeat_array)&.first&.first render json: new_heartbeat, status: :accepted end @@ -51,6 +52,13 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController private + def set_raw_heartbeat_upload + @raw_heartbeat_upload = RawHeartbeatUpload.create!( + request_headers: request.headers, + request_body: params + ) + end + def handle_heartbeat(heartbeat_array) results = [] heartbeat_array.each do |heartbeat| @@ -72,6 +80,8 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController machine: request.headers["X-Machine"] }) new_heartbeat = Heartbeat.find_or_create_by(attrs) + new_heartbeat.raw_heartbeat_upload = @raw_heartbeat_upload + new_heartbeat.save! queue_project_mapping(heartbeat[:project]) results << [ new_heartbeat.attributes, 201 ] rescue => e diff --git a/app/models/heartbeat.rb b/app/models/heartbeat.rb index becc7da..30a6e75 100644 --- a/app/models/heartbeat.rb +++ b/app/models/heartbeat.rb @@ -87,6 +87,7 @@ class Heartbeat < ApplicationRecord self.inheritance_column = nil belongs_to :user + belongs_to :raw_heartbeat_upload, optional: true has_many :wakatime_mirrors, dependent: :destroy validates :time, presence: true diff --git a/app/models/raw_heartbeat_upload.rb b/app/models/raw_heartbeat_upload.rb new file mode 100644 index 0000000..08fd4f0 --- /dev/null +++ b/app/models/raw_heartbeat_upload.rb @@ -0,0 +1,6 @@ +class RawHeartbeatUpload < ApplicationRecord + has_many :heartbeats + + validates :request_headers, presence: true + validates :request_body, presence: true +end diff --git a/db/migrate/20250513183739_create_raw_heartbeat_uploads.rb b/db/migrate/20250513183739_create_raw_heartbeat_uploads.rb new file mode 100644 index 0000000..b2d7ad5 --- /dev/null +++ b/db/migrate/20250513183739_create_raw_heartbeat_uploads.rb @@ -0,0 +1,10 @@ +class CreateRawHeartbeatUploads < ActiveRecord::Migration[8.0] + def change + create_table :raw_heartbeat_uploads do |t| + t.jsonb :request_headers, null: false + t.jsonb :request_body, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20250513184040_add_raw_heartbeat_upload_to_heartbeats.rb b/db/migrate/20250513184040_add_raw_heartbeat_upload_to_heartbeats.rb new file mode 100644 index 0000000..0ff1721 --- /dev/null +++ b/db/migrate/20250513184040_add_raw_heartbeat_upload_to_heartbeats.rb @@ -0,0 +1,5 @@ +class AddRawHeartbeatUploadToHeartbeats < ActiveRecord::Migration[8.0] + def change + add_reference :heartbeats, :raw_heartbeat_upload, null: true, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 8151523..cc3a51f 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[8.0].define(version: 2025_05_12_205858) do +ActiveRecord::Schema[8.0].define(version: 2025_05_13_184040) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -209,8 +209,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_12_205858) do t.integer "ysws_program", default: 0, null: false t.datetime "deleted_at" t.jsonb "raw_data" + t.bigint "raw_heartbeat_upload_id" t.index ["category", "time"], name: "index_heartbeats_on_category_and_time" t.index ["fields_hash"], name: "index_heartbeats_on_fields_hash_when_not_deleted", unique: true, where: "(deleted_at IS NULL)" + t.index ["raw_heartbeat_upload_id"], name: "index_heartbeats_on_raw_heartbeat_upload_id" t.index ["user_id", "time"], name: "idx_heartbeats_user_time_active", where: "(deleted_at IS NULL)" t.index ["user_id"], name: "index_heartbeats_on_user_id" end @@ -261,6 +263,13 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_12_205858) do t.index ["user_id"], name: "index_project_repo_mappings_on_user_id" end + create_table "raw_heartbeat_uploads", force: :cascade do |t| + t.jsonb "request_headers", null: false + t.jsonb "request_body", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "sailors_log_leaderboards", force: :cascade do |t| t.string "slack_channel_id" t.string "slack_uid" @@ -358,6 +367,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_12_205858) do add_foreign_key "api_keys", "users" add_foreign_key "email_addresses", "users" add_foreign_key "email_verification_requests", "users" + add_foreign_key "heartbeats", "raw_heartbeat_uploads" add_foreign_key "heartbeats", "users" add_foreign_key "leaderboard_entries", "leaderboards" add_foreign_key "leaderboard_entries", "users"