Update hackatime.ts

This commit is contained in:
Nathan 2026-02-18 10:38:03 -05:00
parent 12431325af
commit c46fd4d92e

View file

@ -1,29 +1,26 @@
import { Elysia } from 'elysia'
import { getUserFromSession } from '../lib/auth'
import { config } from '../config'
const HACKATIME_API = 'https://hackatime.hackclub.com/api/v1'
const HACKATIME_ADMIN_API = 'https://hackatime.hackclub.com/api/admin/v1'
const SCRAPS_START_DATE = '2026-02-03'
interface HackatimeStatsProject {
interface HackatimeAdminProject {
name: string
total_seconds: number
}
interface HackatimeDetailsProject {
name: string
total_seconds: number
total_heartbeats: number
total_duration: number
first_heartbeat: number
last_heartbeat: number
languages: string[]
repo_url: string | null
repo: string | null
repo_mapping_id: number | null
archived: boolean
}
interface HackatimeStatsResponse {
data: {
projects: HackatimeStatsProject[]
}
}
interface HackatimeDetailsResponse {
projects: HackatimeDetailsProject[]
interface HackatimeAdminProjectsResponse {
user_id: number
username: string
projects: HackatimeAdminProject[]
}
const hackatime = new Elysia({ prefix: '/hackatime' })
@ -32,56 +29,65 @@ hackatime.get('/projects', async ({ headers }) => {
const user = await getUserFromSession(headers as Record<string, string>)
if (!user) return { error: 'Unauthorized' }
if (!user.slackId) {
console.log('[HACKATIME] No slackId found for user:', user.id)
return { error: 'No Slack ID found for user', projects: [] }
if (!user.email) {
console.log('[HACKATIME] No email found for user:', user.id)
return { error: 'No email found for user', projects: [] }
}
const statsParams = new URLSearchParams({
features: 'projects',
start_date: SCRAPS_START_DATE
})
const statsUrl = `${HACKATIME_API}/users/${encodeURIComponent(user.slackId)}/stats?${statsParams}`
const detailsUrl = `${HACKATIME_API}/users/${encodeURIComponent(user.slackId)}/projects/details`
console.log('[HACKATIME] Fetching projects:', { userId: user.id, slackId: user.slackId, statsUrl })
try {
// Fetch both stats (for hours after start date) and details (for repo URLs and languages)
const [statsResponse, detailsResponse] = await Promise.all([
fetch(statsUrl, { headers: { 'Accept': 'application/json' } }),
fetch(detailsUrl, { headers: { 'Accept': 'application/json' } })
])
// Step 1: Get hackatime user_id by email
const emailResponse = await fetch(`${HACKATIME_ADMIN_API}/user/get_user_by_email`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.hackatimeAdminKey}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ email: user.email })
})
if (!statsResponse.ok) {
const errorText = await statsResponse.text()
console.log('[HACKATIME] Stats API error:', { status: statsResponse.status, body: errorText })
if (!emailResponse.ok) {
const errorText = await emailResponse.text()
console.log('[HACKATIME] Email lookup error:', { status: emailResponse.status, body: errorText })
return { projects: [] }
}
const statsData: HackatimeStatsResponse = await statsResponse.json()
const detailsData: HackatimeDetailsResponse = detailsResponse.ok
? await detailsResponse.json()
: { projects: [] }
const { user_id } = await emailResponse.json() as { user_id: number }
console.log('[HACKATIME] Found hackatime user_id:', user_id, 'for email:', user.email)
const projects = statsData.data?.projects || []
// Step 2: Get projects via admin endpoint with start_date
const projectsParams = new URLSearchParams({
user_id: String(user_id),
start_date: SCRAPS_START_DATE
})
const projectsUrl = `${HACKATIME_ADMIN_API}/user/projects?${projectsParams}`
console.log('[HACKATIME] Fetching admin projects:', projectsUrl)
const projectsResponse = await fetch(projectsUrl, {
headers: {
'Authorization': `Bearer ${config.hackatimeAdminKey}`,
'Accept': 'application/json'
}
})
if (!projectsResponse.ok) {
const errorText = await projectsResponse.text()
console.log('[HACKATIME] Projects API error:', { status: projectsResponse.status, body: errorText })
return { projects: [] }
}
const data: HackatimeAdminProjectsResponse = await projectsResponse.json()
const projects = data.projects || []
console.log('[HACKATIME] Projects fetched:', projects.length)
// Create a map of project details for quick lookup
const detailsMap = new Map(
detailsData.projects?.map(p => [p.name, p]) || []
)
return {
slackId: user.slackId,
projects: projects.map((p) => {
const details = detailsMap.get(p.name)
return {
name: p.name,
hours: Math.round(p.total_seconds / 3600 * 10) / 10,
repoUrl: details?.repo_url || null,
languages: details?.languages || []
}
})
projects: projects.map((p) => ({
name: p.name,
hours: Math.round(p.total_duration / 3600 * 10) / 10,
repoUrl: p.repo || null,
languages: p.languages || []
}))
}
} catch (error) {
console.error('[HACKATIME] Error fetching projects:', error)