Hackatime

repository·main·Indexed 41 days ago

https://github.com/hackclub/hackatime

An 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.

Tokens
38.8K
Snippets
66
Records
234
Agent score
52%

What's inside Hackatime

  1. Overview of Hackatime

    main
    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.
  2. Configure Hackatime project and privacy behavior

    main

    Configuration 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.

  3. How the Rails and Inertia/Svelte UI flow works

    main

    Hackatime uses a hybrid architecture where Rails manages routing, authorization, and data writes, while Svelte handles presentation and local UI state.

    1. Routing & Controllers: Routes dispatch to Rails controllers. Browser controllers inherit from ApplicationController (handling session, lockout, time zones, etc.). Inertia controllers inherit from InertiaController, which provides shared layout props (navigation, theme, CSRF, etc.).
    2. Inertia Boundary: Controllers use render inertia: "Directory/Page", props: .... The props object is the serialized boundary between the server and the client.
    3. Frontend Resolution: app/javascript/entrypoints/inertia.ts resolves page names to .svelte files and wraps them in AppLayout.svelte.
    4. Client-side Navigation: Use Inertia <Link>, <Form>, or router calls. 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: true in config/routes.rb, regenerate, and import the controller module from app/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' });
  4. Dashboard and Profile Rollups

    main

    Hackatime uses a read-facade pattern for statistics to maintain performance.

    • DashboardStats: The primary read facade. It uses dashboard_rollups for 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 of DashboardStats used 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.

  5. Understand Hackatime OAuth scopes

    main

    Scopes define the level of access your application has to user data. If no scopes are specified, only profile is granted.

    ScopeDescriptionGranted by Default
    profileAccess basic profile info (user ID, email, Slack ID, GitHub username, trust factor)Yes
    readView basic info about the user's Hackatime accountNo
    adminAccess the Admin API on the user's behalf (subject to their role)No

    Admin Scope Restrictions

    • Approval Required: Using the admin scope requires explicit approval from Hack Club HQ.
    • User Roles: Only admin+ users (admin, superadmin, ultraadmin) can attach the admin scope. Viewers and admin+ 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 admin scope must be configured as confidential (server-side).
  6. Understand what data is included in a heartbeat

    main

    Hackatime 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.
  7. Understand the background job and messaging architecture

    main

    Hackatime uses ApplicationJob as the base class for all background jobs, providing shared error reporting and handling deserialization/concurrency failures.

    Background Jobs (GoodJob)

    • Configuration: config/initializers/good_job.rb is 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_10s deliver_later queue are configured in config/environments/production.rb.
    • Pattern: Some jobs call deliver_now inside 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 SlackIntegration concern (app/models/concerns/slack_integration.rb). Controllers should focus on authenticating, validating, and enqueuing work.
  8. Understand Hackatime's Sources of Truth and Derived State

    main

    Hackatime 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.

    DomainSource of TruthDerived / Disposable State
    Identityusers, email_addresses, provider IDs, encrypted provider tokensSession cookie
    Coding activityNon-deleted heartbeatsDurations, dashboard/profile payloads, rollups, leaderboards, streaks, Rails caches
    Heartbeat project identityheartbeats.projectProject lists and project statistics
    Per-user project settingsproject_repo_mappings (keyed by project name)Discovery retry/coalescing cache keys
    Repository identityShared repositories row (url, host, owner, name)Stars, languages, homepage, commit count, sync timestamps, imported commits
    Async workPending/scheduled good_jobs rowsFinished execution history, process records, cron history
  9. How Admin Authorization is enforced

    main

    Authorization in Hackatime is handled via AuthHelpers and 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):

    1. default
    2. viewer (can access read-only admin surfaces and use admin API auth)
    3. admin (required for general browser admin writes)
    4. superadmin (required to change admin levels)
    5. ultraadmin (required to grant ultraadmin status)

    Note: The numeric order stored in the database is historical and does not represent privilege order. Always use User::ADMIN_LEVEL_RANK and associated helpers for checks.

  10. Project and Repository Management

    main

    Hackatime 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 Repository model.

    Extending Repository Hosts: Currently, only GitHub is supported via GithubService. To add a new host:

    1. Implement a service following BaseService.
    2. Update RepoHost::ServiceFactory to include the new host.
    3. Update jobs that contain host-specific discovery or event logic.
  11. Handle time zones and concurrency correctly

    main

    Hackatime relies heavily on user-specific time zones for calendar-based logic (streaks, activity, etc.).

    Time Zone Management

    • Heartbeat Data: The time field 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_zone explicitly 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_commit hooks 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.
  12. Heartbeat Ingestion and Duration Semantics

    main

    All coding activity is recorded as Heartbeat rows. All writes should flow through the HeartbeatIngest service 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. Use soft_delete or restore methods, which also trigger rollup invalidation.

    Duration Calculation

    Durations are not stored; they are derived from ordered heartbeat timestamps via the Heartbeatable concern.

    • 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.