Patch up oauth implementation (#560)

This commit is contained in:
Max Wofford 2025-10-03 18:22:37 -04:00 committed by GitHub
parent 15744b3442
commit 5ae07f5643
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 156 additions and 1 deletions

View file

@ -0,0 +1,30 @@
module Api
module V1
module Authenticated
class HeartbeatsController < ApplicationController
def latest
heartbeat = current_user.heartbeats
.where.not(source_type: :test_entry)
.order(time: :desc)
.first
if heartbeat
render json: {
id: heartbeat.id,
created_at: heartbeat.created_at,
project: heartbeat.project,
language: heartbeat.language,
editor: heartbeat.editor,
operating_system: heartbeat.operating_system,
machine: heartbeat.machine,
file: heartbeat.file,
duration: heartbeat.duration
}
else
render json: { heartbeat: nil }
end
end
end
end
end
end

View file

@ -0,0 +1,22 @@
module Api
module V1
module Authenticated
class HoursController < ApplicationController
def index
start_date = params[:start_date]&.to_date || 7.days.ago.to_date
end_date = params[:end_date]&.to_date || Date.current
total_seconds = current_user.heartbeats
.where(created_at: start_date.beginning_of_day..end_date.end_of_day)
.duration_seconds
render json: {
start_date: start_date,
end_date: end_date,
total_seconds: total_seconds
}
end
end
end
end
end

View file

@ -0,0 +1,47 @@
module Api
module V1
module Authenticated
class ProjectsController < ApplicationController
def index
projects = current_user.heartbeats
.where.not(project: [ nil, "" ])
.group(:project)
.map { |project|
{
name: project,
total_seconds: time_per_project[project] || 0,
most_recent_heartbeat: most_recent_heartbeat_per_project[project] ? Time.at(most_recent_heartbeat_per_project[project]).strftime("%Y-%m-%dT%H:%M:%SZ") : nil,
percentage: time_per_project.sum { |_, secs| secs }.zero? ? 0 : ((time_per_project[project] || 0) / time_per_project.sum { |_, secs| secs }.to_f * 100).round(2),
repo: project_repo_mappings[project]&.repo
}
}
render json: { projects: projects }
end
private
def project_repo_mappings
@project_repo_mappings ||= current_user.project_repo_mappings
.index_by(&:project)
end
def time_per_project
@time_per_project ||= current_user.heartbeats
.with_valid_timestamps
.where.not(project: [ nil, "" ])
.group(:project)
.duration_seconds
end
def most_recent_heartbeat_per_project
@most_recent_heartbeat_per_project ||= current_user.heartbeats
.with_valid_timestamps
.where.not(project: [ nil, "" ])
.group(:project)
.maximum(:time)
end
end
end
end
end

View file

@ -0,0 +1,13 @@
module Api
module V1
module Authenticated
class StreakController < ApplicationController
def show
render json: {
streak_days: current_user.streak_days
}
end
end
end
end
end

View file

@ -108,6 +108,16 @@ class User < ApplicationRecord
has_many :trust_level_audit_logs, dependent: :destroy
has_many :trust_level_changes_made, class_name: "TrustLevelAuditLog", foreign_key: "changed_by_id", dependent: :destroy
has_many :access_grants,
class_name: "Doorkeeper::AccessGrant",
foreign_key: :resource_owner_id,
dependent: :delete_all
has_many :access_tokens,
class_name: "Doorkeeper::AccessToken",
foreign_key: :resource_owner_id,
dependent: :delete_all
def streak_days
@streak_days ||= heartbeats.daily_streaks_for_users([ id ]).values.first
end

View file

@ -3,6 +3,10 @@
Doorkeeper.configure do
base_controller "ApplicationController"
default_scopes "profile"
optional_scopes "read"
enforce_configured_scopes
resource_owner_authenticator do
current_user || redirect_to(minimal_login_path(continue: request.fullpath))
end
@ -20,4 +24,11 @@ Doorkeeper.configure do
access_token_expires_in 16.years
reuse_access_token
# Allow public clients (desktop/mobile apps) without client secrets
allow_blank_redirect_uri
skip_client_authentication_for_password_grant
# Enable PKCE for public clients
force_ssl_in_redirect_uri false
end

View file

@ -154,8 +154,14 @@ Rails.application.routes.draw do
get "heartbeats", to: "heartbeats#index"
end
# oauth authenticated namespace
namespace :authenticated do
resources :me, only: [ :index ]
get "hours", to: "hours#index"
get "streak", to: "streak#show"
get "projects", to: "projects#index"
# get "projects/:name", to: "projects#show", constraints: { name: /.+/ }
get "heartbeats/latest", to: "heartbeats#latest"
end
end

View file

@ -0,0 +1,6 @@
class AddForeignKeysToOauthAccess < ActiveRecord::Migration[8.0]
def change
add_foreign_key :oauth_access_grants, :users, column: :resource_owner_id
add_foreign_key :oauth_access_tokens, :users, column: :resource_owner_id
end
end

4
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_08_21_021751) do
ActiveRecord::Schema[8.0].define(version: 2025_10_03_161836) do
create_schema "pganalyze"
# These are extensions that must be enabled in order to support this database
@ -584,7 +584,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_08_21_021751) do
add_foreign_key "leaderboard_entries", "users"
add_foreign_key "mailing_addresses", "users"
add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_grants", "users", column: "resource_owner_id"
add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
add_foreign_key "oauth_access_tokens", "users", column: "resource_owner_id"
add_foreign_key "physical_mails", "users"
add_foreign_key "project_repo_mappings", "repositories"
add_foreign_key "project_repo_mappings", "users"

View file

@ -2,6 +2,14 @@
# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
Doorkeeper::Application.find_or_create_by(
name: "Hackatime Desktop",
redirect_uri: "hackatime://auth/callback",
uid: "BPr5VekIV-xuQ2ZhmxbGaahJ3XVd7gM83pql-HYGYxQ",
scopes: [ "profile" ],
confidential: false,
)
# Only seed test data in development environment
if Rails.env.development?
# Creating test user