mirror of
https://github.com/SrIzan10/hc-harbor.git
synced 2026-05-01 10:45:21 +00:00
First version of bulk uploads
This commit is contained in:
137
app/controllers/api/hackatime/v1/hackatime_controller.rb
Normal file
137
app/controllers/api/hackatime/v1/hackatime_controller.rb
Normal file
@@ -0,0 +1,137 @@
|
||||
class Api::Hackatime::V1::HackatimeController < ApplicationController
|
||||
before_action :set_user, except: [ :index ]
|
||||
# before_action :set_json_format
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
def index
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def push_heartbeats
|
||||
puts "Raw params: #{params.inspect}"
|
||||
puts "Creating heartbeat with params: #{heartbeat_params}"
|
||||
attrs = heartbeat_params.merge({ user: @user })
|
||||
puts "Merged attrs: #{attrs}"
|
||||
new_heartbeat = Heartbeat.new(attrs)
|
||||
|
||||
if new_heartbeat.save!
|
||||
render json: { responses: [ { heartbeat: new_heartbeat.attributes, status: 201 } ] }, status: :created
|
||||
else
|
||||
render json: { error: "Failed to create heartbeat: #{new_heartbeat.errors.full_messages}" }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def push_heartbeats_bulk
|
||||
new_heartbeats = []
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
new_heartbeats = heartbeat_bulk_params.map do |heartbeat|
|
||||
attrs = heartbeat.merge({ user_id: @user.id })
|
||||
Heartbeat.create(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
responses = []
|
||||
new_heartbeats.each do |heartbeat|
|
||||
responses << [ heartbeat.attributes, heartbeat.persisted? ? 201 : 422 ]
|
||||
end
|
||||
|
||||
render json: { responses: responses }, status: :success
|
||||
end
|
||||
|
||||
def status_bar_today
|
||||
hbt = @user.heartbeats.today
|
||||
|
||||
render json: {
|
||||
"data": {
|
||||
"grand_total": {
|
||||
"decimal": "yolo",
|
||||
"digital": "wahoo",
|
||||
"hours": hbt.duration_seconds / 3600,
|
||||
"minutes": (hbt.duration_seconds % 3600) / 60,
|
||||
"text": @user.format_extension_text(hbt.duration_seconds),
|
||||
"total_seconds": hbt.duration_seconds
|
||||
},
|
||||
"categories": hbt.distinct.pluck(:category),
|
||||
"dependencies": hbt.distinct.pluck(:dependencies),
|
||||
"editors": hbt.distinct.pluck(:editor),
|
||||
"languages": hbt.distinct.pluck(:language),
|
||||
"machines": hbt.distinct.pluck(:machine),
|
||||
"operating_systems": hbt.distinct.pluck(:operating_system),
|
||||
"projects": hbt.distinct.pluck(:project),
|
||||
"range": {
|
||||
"text": "Today",
|
||||
"timezone": "UTC"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# def set_json_format
|
||||
# request.format = :json
|
||||
# end
|
||||
|
||||
def set_user
|
||||
api_header = request.headers["Authorization"]
|
||||
raw_token = api_header&.split(" ")&.last
|
||||
header_type = api_header&.split(" ")&.first
|
||||
if header_type == "Bearer"
|
||||
api_token = raw_token
|
||||
elsif header_type == "Basic"
|
||||
api_token = Base64.decode64(raw_token)
|
||||
end
|
||||
return render json: { error: "Unauthorized" }, status: :unauthorized unless api_token.present?
|
||||
valid_key = ApiKey.find_by(token: api_token)
|
||||
return render json: { error: "Unauthorized" }, status: :unauthorized unless valid_key.present?
|
||||
|
||||
@user = valid_key.user
|
||||
render json: { error: "Unauthorized" }, status: :unauthorized unless @user
|
||||
end
|
||||
|
||||
def heartbeat_keys
|
||||
[
|
||||
:branch,
|
||||
:category,
|
||||
:created_at,
|
||||
:cursorpos,
|
||||
:dependencies,
|
||||
:editor,
|
||||
:entity,
|
||||
:is_write,
|
||||
:language,
|
||||
:line_additions,
|
||||
:line_deletions,
|
||||
:lineno,
|
||||
:lines,
|
||||
:machine,
|
||||
:operating_system,
|
||||
:project,
|
||||
:project_root_count,
|
||||
:time,
|
||||
:type,
|
||||
:user_agent
|
||||
]
|
||||
end
|
||||
|
||||
# allow either heartbeat or heartbeats
|
||||
def heartbeat_bulk_params
|
||||
params.require(:hackatime).permit(
|
||||
heartbeats: [
|
||||
*heartbeat_keys
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def heartbeat_params
|
||||
# Handle both direct params and _json format from WakaTime
|
||||
if params[:_json].present?
|
||||
params[:_json].first.permit(*heartbeat_keys)
|
||||
elsif params[:hackatime].present?
|
||||
params.require(:hackatime).permit(*heartbeat_keys)
|
||||
else
|
||||
params.permit(*heartbeat_keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
class HackatimeController < ApplicationController
|
||||
before_action :set_user
|
||||
|
||||
def push_heartbeats
|
||||
@user.heartbeats.create(heartbeat_params)
|
||||
end
|
||||
|
||||
def push_heartbeats_bulk
|
||||
@user.heartbeats.create(heartbeat_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
# each user has a Hackatime::User with an api_key
|
||||
# the api_key is sent in the Authorization header as a Bearer token
|
||||
api_key = request.headers["Authorization"].split(" ")[1]
|
||||
@user = Hackatime::User.find_by(api_key: api_key)
|
||||
render json: { error: "Unauthorized" }, status: :unauthorized unless @user
|
||||
end
|
||||
|
||||
def heartbeat_params
|
||||
params.require(:heartbeat).permit(
|
||||
:branch,
|
||||
:category,
|
||||
:created_at,
|
||||
:cursorpos,
|
||||
:dependencies,
|
||||
:editor,
|
||||
:entity,
|
||||
:is_write,
|
||||
:language,
|
||||
:line_additions,
|
||||
:line_deletions,
|
||||
:lineno,
|
||||
:lines,
|
||||
:machine,
|
||||
:operating_system,
|
||||
:project,
|
||||
:project_root_count,
|
||||
:time,
|
||||
:type,
|
||||
:user_agent
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -21,6 +21,9 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
def short_time_detailed(time)
|
||||
# ie. 5h 10m 10s
|
||||
# ie. 10m 10s
|
||||
# ie. 10m
|
||||
hours = time.to_i / 3600
|
||||
minutes = (time.to_i % 3600) / 60
|
||||
seconds = time.to_i % 60
|
||||
@@ -31,4 +34,24 @@ module ApplicationHelper
|
||||
time_parts << "#{seconds}s" if seconds.positive?
|
||||
time_parts.join(" ")
|
||||
end
|
||||
|
||||
def time_in_emoji(duration)
|
||||
# ie. 15.hours => "🕒"
|
||||
half_hours = duration.to_i / 1800
|
||||
clocks = [
|
||||
"🕛", "🕧",
|
||||
"🕐", "🕜",
|
||||
"🕑", "🕝",
|
||||
"🕒", "🕞",
|
||||
"🕓", "🕟",
|
||||
"🕔", "🕠",
|
||||
"🕕", "🕡",
|
||||
"🕖", "🕢",
|
||||
"🕗", "🕣",
|
||||
"🕘", "🕤",
|
||||
"🕙", "🕥",
|
||||
"🕚", "🕦"
|
||||
]
|
||||
clocks[half_hours % clocks.length]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
class Heartbeat < ApplicationRecord
|
||||
# This is to prevent Rails from trying to use STI even though we have a "type" column
|
||||
self.inheritance_column = nil
|
||||
|
||||
belongs_to :user
|
||||
|
||||
validates :time, presence: true
|
||||
validates :time, uniqueness: { scope: [ :user_id, :project, :branch, :language, :dependencies, :lineno, :cursorpos, :is_write, :entity, :type, :category, :project_root_count ] }
|
||||
end
|
||||
|
||||
@@ -18,6 +18,24 @@ class User < ApplicationRecord
|
||||
|
||||
has_many :api_keys
|
||||
|
||||
enum :hackatime_extension_text_type, {
|
||||
simple_text: 0,
|
||||
clock_emoji: 1,
|
||||
compliment_text: 2
|
||||
}
|
||||
|
||||
def format_extension_text(duration)
|
||||
case hackatime_extension_text_type
|
||||
when :simple_text
|
||||
return "Start coding to track your time" if duration.zero?
|
||||
::ApplicationController.helpers.short_time_simple(duration)
|
||||
when :clock_emoji
|
||||
::ApplicationController.helpers.time_in_emoji(duration)
|
||||
when :compliment_text
|
||||
"You're doing great!"
|
||||
end
|
||||
end
|
||||
|
||||
def admin?
|
||||
is_admin
|
||||
end
|
||||
|
||||
@@ -48,15 +48,17 @@ Rails.application.routes.draw do
|
||||
get "my/settings", to: "users#edit", as: :my_settings
|
||||
patch "my/settings", to: "users#update"
|
||||
|
||||
|
||||
post "/sailors_log/slack/commands", to: "slack#create"
|
||||
post "/timedump/slack/commands", to: "slack#create"
|
||||
|
||||
# API routes
|
||||
namespace :api do
|
||||
namespace :wakatime do
|
||||
namespace :hackatime do
|
||||
namespace :v1 do
|
||||
post "/heartbeats", to: "hackatime#push_heartbeats"
|
||||
post "/heartbeats/bulk", to: "hackatime#push_heartbeats_bulk"
|
||||
get "/", to: "hackatime#index"
|
||||
get "/users/:id/statusbar/today", to: "hackatime#status_bar_today"
|
||||
post "/users/:id/heartbeats", to: "hackatime#push_heartbeats"
|
||||
post "/users/:id/heartbeats.bulk", to: "hackatime#push_heartbeats_bulk"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ class CreateHeartbeats < ActiveRecord::Migration[8.0]
|
||||
t.integer :cursorpos
|
||||
t.integer :project_root_count
|
||||
|
||||
t.datetime :time
|
||||
t.float :time, null: false
|
||||
|
||||
t.boolean :is_write
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddExtensionTextTypeToUsers < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :users, :hackatime_extension_text_type, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
||||
5
db/schema.rb
generated
5
db/schema.rb
generated
@@ -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_03_180842) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_03_04_032720) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
||||
@@ -134,7 +134,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_03_180842) do
|
||||
t.integer "lines"
|
||||
t.integer "cursorpos"
|
||||
t.integer "project_root_count"
|
||||
t.datetime "time"
|
||||
t.float "time", null: false
|
||||
t.boolean "is_write"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
@@ -250,6 +250,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_03_180842) do
|
||||
t.boolean "uses_slack_status", default: false, null: false
|
||||
t.string "slack_scopes", default: [], array: true
|
||||
t.text "slack_access_token"
|
||||
t.integer "hackatime_extension_text_type", default: 0, null: false
|
||||
t.index ["slack_uid"], name: "index_users_on_slack_uid", unique: true
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user