Hackatime
repository·main·Indexed 41 days ago
https://github.com/hackclub/hackatimeAn open-source coding time tracker developed by Hack Club that is compatible with the WakaTime ecosystem. It allows developers to track programming activity using WakaTime-compatible tools and workflows, featuring a Rails and Inertia/Svelte architecture for managing heartbeats, project statistics, and leaderboards.
What's inside Hackatime
- Hackatime is a free, open-source coding time tracker developed by Hack Club. It is designed to be compatible with WakaTime, allowing users to track their programming activity using existing WakaTime-compatible tools and workflows.
Configure Hackatime project and privacy behavior
mainConfiguration for Hackatime is split between the Hackatime web platform and your local machine. Use the following guides to tune specific behaviors:
- Projects and GitHub: Configure project names, repository links, sharing, and archiving settings via the Projects and GitHub guide.
- Privacy and Tracked Data: Learn how to manage heartbeat metadata and how to omit or obfuscate sensitive file paths via the Privacy and tracked data guide.
- Plugin Settings: Manage local settings, including the shared
~/.wakatime.cfg, exclusions, and project-only tracking via the Plugin settings guide.
Note: Most editor plugins share the same WakaTime CLI configuration; changes made to the local configuration typically apply across all installed editors.
How the Rails and Inertia/Svelte UI flow works
mainHackatime uses a hybrid architecture where Rails manages routing, authorization, and data writes, while Svelte handles presentation and local UI state.
- Routing & Controllers: Routes dispatch to Rails controllers. Browser controllers inherit from
ApplicationController(handling session, lockout, time zones, etc.). Inertia controllers inherit fromInertiaController, which provides shared layout props (navigation, theme, CSRF, etc.). - Inertia Boundary: Controllers use
render inertia: "Directory/Page", props: .... Thepropsobject is the serialized boundary between the server and the client. - Frontend Resolution:
app/javascript/entrypoints/inertia.tsresolves page names to.sveltefiles and wraps them inAppLayout.svelte. - Client-side Navigation: Use Inertia
<Link>,<Form>, orroutercalls. Rails remains the owner of routing and authorization.
Important Rule for URLs: Do not hand-build URLs on the client. Use js_from_routes. Mark routes with
export: trueinconfig/routes.rb, regenerate, and import the controller module fromapp/javascript/api. Use.path()to generate paths/query parameters.# config/routes.rb # Mark routes to be exported to JS get '/some_path', to: 'some_controller#action', export: true// app/javascript/pages/SomePage.svelte import { SomeController } from 'app/javascript/api'; const path = SomeController.path({ param: 'value' });- Routing & Controllers: Routes dispatch to Rails controllers. Browser controllers inherit from
Dashboard and Profile Rollups
mainHackatime uses a read-facade pattern for statistics to maintain performance.
DashboardStats: The primary read facade. It usesdashboard_rollupsfor unfiltered all-time data. If data is missing or stale, it falls back to live calculation and schedules a refresh.DashboardRollupRefreshService: Rebuilds all user totals (dimensions, weekly projects, activity graphs, etc.) atomically in a transaction. It is triggered by heartbeat commits, soft-deletes, restores, timezone changes, or project archive changes.ProfileStatsService: A thin projection ofDashboardStatsused for things like OG-image totals. It does not have its own authoritative statistics; it shares logic with the dashboard.
Caching: Short Rails caches (1, 5, or 15 minutes) are used for fragments and are considered disposable.
Understand Hackatime OAuth scopes
mainScopes define the level of access your application has to user data. If no scopes are specified, only
profileis granted.Scope Description Granted by Default profileAccess basic profile info (user ID, email, Slack ID, GitHub username, trust factor) Yes readView basic info about the user's Hackatime account No adminAccess the Admin API on the user's behalf (subject to their role) No Admin Scope Restrictions
- Approval Required: Using the
adminscope requires explicit approval from Hack Club HQ. - User Roles: Only
admin+users (admin,superadmin,ultraadmin) can attach theadminscope. Viewers andadmin+users can authorize approved apps requesting this scope, but the token's power is limited to the user's actual role. - Security: Apps using the
adminscope must be configured as confidential (server-side).
- Approval Required: Using the
Understand what data is included in a heartbeat
mainHackatime receives activity events called heartbeats from WakaTime-compatible editor plugins. Heartbeats contain metadata about your coding session, but do not include the actual contents of your files, screenshots, keystroke streams, or password values.
Depending on your editor and plugin, a heartbeat may contain:
- Entity Info: File path, project name, branch, language, and dependencies.
- Environment Info: Editor, operating system, machine, and plugin information.
- Activity Metadata: Activity category, timestamp, and line-count.
- AI Metadata: AI coding metadata (supported by newer WakaTime clients).
- Request Metadata: IP address and user agent.
Understand the background job and messaging architecture
mainHackatime uses
ApplicationJobas the base class for all background jobs, providing shared error reporting and handling deserialization/concurrency failures.Background Jobs (GoodJob)
- Configuration:
config/initializers/good_job.rbis the source of truth for queue and cron settings. - Environments: Development uses async threads; non-development environments expect external workers; cron is enabled in production only.
- Best Practice: Do not perform slow remote work in request controllers. Use queues based on latency and ownership requirements.
Email (Action Mailer)
- Configuration: Production SMTP and the
latency_10sdeliver_laterqueue are configured inconfig/environments/production.rb. - Pattern: Some jobs call
deliver_nowinside an already-queued job. Maintain this boundary unless you are intentionally changing retry or queue semantics.
Slack Integration
- Ingress: Signed commands enter via
SlackController(app/controllers/slack_controller.rb). - Security: Outside of development, commands must have a valid Slack HMAC signature and a timestamp within five minutes.
- Logic: Remote API calls, token management, and rate-limiting are handled in the
SlackIntegrationconcern (app/models/concerns/slack_integration.rb). Controllers should focus on authenticating, validating, and enqueuing work.
- Configuration:
Understand Hackatime's Sources of Truth and Derived State
mainHackatime distinguishes between primary data (Sources of Truth) and data that is calculated or cached (Derived/Disposable State).
Key Principle: Never write a rollup, duration, profile statistic, or cache as if it were primary data. If data is incorrect, change the input or the derivation logic, invalidate the existing state, and let the system rebuild it.
Domain Source of Truth Derived / Disposable State Identity users,email_addresses, provider IDs, encrypted provider tokensSession cookie Coding activity Non-deleted heartbeatsDurations, dashboard/profile payloads, rollups, leaderboards, streaks, Rails caches Heartbeat project identity heartbeats.projectProject lists and project statistics Per-user project settings project_repo_mappings(keyed by project name)Discovery retry/coalescing cache keys Repository identity Shared repositoriesrow (url, host, owner, name)Stars, languages, homepage, commit count, sync timestamps, imported commits Async work Pending/scheduled good_jobsrowsFinished execution history, process records, cron history How Admin Authorization is enforced
mainAuthorization in Hackatime is handled via
AuthHelpersand explicit controller/model predicates.Warning: Hiding a navigation link in the UI is not authorization. You must enforce checks on the server.
Privilege Levels: Effective order of privilege (from lowest to highest):
defaultviewer(can access read-only admin surfaces and use admin API auth)admin(required for general browser admin writes)superadmin(required to change admin levels)ultraadmin(required to grantultraadminstatus)
Note: The numeric order stored in the database is historical and does not represent privilege order. Always use
User::ADMIN_LEVEL_RANKand associated helpers for checks.Project and Repository Management
mainHackatime manages the relationship between user activity and code repositories through two main models:
ProjectRepoMapping: Owned by the user. It maps a heartbeat project name to a repository and manages archive/sharing states. Ingestion asynchronously attempts to map new project names by searching the user's GitHub profile/organizations.Repository: A shared resource identified by its URL. It contains host/owner/name and synchronized metadata (stars, languages, etc.).
Key Distinction: A mapping is user-specific, but a repository is shared. Do not store user preferences on the
Repositorymodel.Extending Repository Hosts: Currently, only GitHub is supported via
GithubService. To add a new host:- Implement a service following
BaseService. - Update
RepoHost::ServiceFactoryto include the new host. - Update jobs that contain host-specific discovery or event logic.
Handle time zones and concurrency correctly
mainHackatime relies heavily on user-specific time zones for calendar-based logic (streaks, activity, etc.).
Time Zone Management
- Heartbeat Data: The
timefield in heartbeats is stored as Unix epoch time. - Calendar Logic: Concepts like 'today', 'week', and 'streak' use the validated
User#timezone. - Context: Browser requests are wrapped in
Time.use_zone(current_user.timezone). - Warning: Services or background jobs that do not run in a request context must use
Time.use_zoneexplicitly to ensure correct calendar math. - Side Effects: Changing a user's timezone invalidates both old/new activity cache keys and rollups.
Concurrency and Data Integrity
- Correctness: Use database constraints/upserts for cross-process correctness and transactions for multi-row replacement.
- Derived Work: Use
after_commithooks for work triggered by database changes. - Performance: Use GoodJob concurrency and cache coalescing for expensive, idempotent refreshes.
- Race Conditions: Ensure heartbeat deduplication is race-safe and rollup replacement is atomic. Note that the Rails cache is an optimization, not a source of truth or a lock.
- Heartbeat Data: The
Heartbeat Ingestion and Duration Semantics
mainAll coding activity is recorded as
Heartbeatrows. All writes should flow through theHeartbeatIngestservice to ensure normalization, deduplication, and proper scheduling of rollups.Heartbeat Ingestion (
HeartbeatIngest)- Handles input normalization, epoch validation, and language/user-agent inference.
- Performs bulk insertion with deduplication.
- Schedules rollup refreshes and project mapping after successful inserts.
- Soft Deletion: Uses
deleted_at. Usesoft_deleteorrestoremethods, which also trigger rollup invalidation.
Duration Calculation
Durations are not stored; they are derived from ordered heartbeat timestamps via the
Heartbeatableconcern.- Timeout: The default timeout is 120 seconds (2 minutes).
- Logic:
- The first heartbeat in a sequence contributes zero duration.
- Each subsequent heartbeat contributes
min(current_time - previous_time, 120s). - If a gap exceeds 120s, a new span is started.
- Ordering: Calculations rely on deterministic ordering by
time, id.