From b6f3aa94ab2772fb28fe6da457b370252db5b178 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:27:14 +0200 Subject: [PATCH 1/7] Initial heartbeat download and frontend impl --- app/controllers/users_controller.rb | 8 +- .../migrate_wakatimecom_heartbeats_job.rb | 79 +++++ app/views/users/edit.html.erb | 18 ++ config/routes.rb | 1 + .../20250429114602_wakatime_api_key_user.rb | 5 + db/primary_direct_schema.rb | 277 ++++++++++++++++++ db/schema.rb | 3 +- prnotes.md | 1 + 8 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 app/jobs/one_time/migrate_wakatimecom_heartbeats_job.rb create mode 100644 db/migrate/20250429114602_wakatime_api_key_user.rb create mode 100644 db/primary_direct_schema.rb create mode 100644 prnotes.md diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 280fcb6d..6752ec6b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -36,6 +36,12 @@ def migrate_heartbeats notice: "Heartbeats & api keys migration started" end + def migrate_wakatimecom_heartbeats + OneTime::MigrateWakatimecomHeartbeatsJob.perform_later(@user.id) + redirect_to is_own_settings? ? my_settings_path : user_settings_path(@user), + notice: "Wakatime.com heartbeats migration started" + end + def wakatime_setup api_key = current_user&.api_keys&.last api_key ||= current_user.api_keys.create!(name: "Wakatime API Key") @@ -86,6 +92,6 @@ def is_own_settings? end def user_params - params.require(:user).permit(:uses_slack_status, :hackatime_extension_text_type, :timezone) + params.require(:user).permit(:uses_slack_status, :hackatime_extension_text_type, :timezone, :wakatime_api_key) end end diff --git a/app/jobs/one_time/migrate_wakatimecom_heartbeats_job.rb b/app/jobs/one_time/migrate_wakatimecom_heartbeats_job.rb new file mode 100644 index 00000000..6c1dc208 --- /dev/null +++ b/app/jobs/one_time/migrate_wakatimecom_heartbeats_job.rb @@ -0,0 +1,79 @@ +require "fileutils" +require "open-uri" + +class OneTime::MigrateWakatimecomHeartbeatsJob < ApplicationJob + queue_as :default + + include GoodJob::ActiveJobExtensions::Concurrency + + # only allow one instance of this job to run at a time + good_job_control_concurrency_with( + key: -> { "migrate_wakatimecom_heartbeats_job_#{arguments.first}" }, + total_limit: 1, + ) + + def perform(user_id) + @user = User.find(user_id) + import_heartbeats + end + + private + + def import_heartbeats + puts "starting wakatime.com heartbeats import for user #{@user.id}" + + # get dump once to check if there's already one. + # in development i've already created one and don't want to keep spamming dumps + # (it's also really slow for me, my entire coding career is in there) + dump = get_dumps + + if dump.empty? + create_dump + while true + sleep 5 + dump = get_dumps + puts "wakatime.com import for #{@user.id} is at #{dump['percent_complete']}%" + break unless dump.empty? + end + end + + output_dir = Rails.root.join('storage', 'wakatime_dumps') + FileUtils.mkdir_p(output_dir) + output_path = output_dir.join("wakatime_heartbeats_#{@user.id}.json") + + puts "downloading wakatime.com heartbeats dump for user #{@user.id}" + auth_token = Base64.strict_encode64("#{@user.wakatime_api_key}:") + + File.open(output_path, 'wb') do |file| + # i don't get why with HTTP it doesn't work... + file.write(URI.open(dump['download_url']).read) + end + + puts "wakatime.com heartbeats saved to #{output_path} for user #{@user.id}" + end + + def get_dumps + auth_token = Base64.strict_encode64("#{@user.wakatime_api_key}:") + response = HTTP.auth("Basic #{auth_token}") + .get('https://api.wakatime.com/api/v1/users/current/data_dumps') + + if response.status.success? + dumps = JSON.parse(response.body)['data'].find { |dump| dump['type'] == 'heartbeats' && dump['status'] == 'Completed' } + return dumps || {} + else + puts "Failed to fetch Wakatime.com data dumps: #{response.status} - #{response.body}" + return {} + end + end + + def create_dump + auth_token = Base64.strict_encode64("#{@user.wakatime_api_key}:") + response = HTTP.auth("Basic #{auth_token}") + .post('https://api.wakatime.com/api/v1/users/current/data_dumps', + json: { + type: 'heartbeats', + email_when_finished: false, + } + ) + end +end \ No newline at end of file diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 140f0f3f..0a827149 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -147,10 +147,28 @@

+
+

Wakatime.com API key

+ <%= form_with model: @user, + url: @is_own_settings ? my_settings_path : settings_user_path(@user), + method: :patch do |f| %> +
+ <%= f.label :wakatime_api_key, "Your API key" %> + <%= f.text_field :wakatime_api_key, value: @user.wakatime_api_key, class: "form-control", type: "password" %> + This will be used for the migration of heartbeats. +
+ <%= f.submit "Save API Key" %> + <% end %> +
+

Migration assistant

This will migrate your heartbeats from waka.hackclub.com to this platform.

<%= button_to "Migrate heartbeats", my_settings_migrate_heartbeats_path, method: :post %> + + <% if @user.wakatime_api_key.present? %> + <%= button_to "Migrate wakatime.com", my_settings_migrate_wakatimecom_heartbeats_path, method: :post %> + <% end %> <% if @heartbeats_migration_jobs.any? %>