From 2348592b5884913e4a76fce3233877f3295e0c71 Mon Sep 17 00:00:00 2001 From: NotARoomba Date: Sat, 21 Feb 2026 01:14:19 -0500 Subject: [PATCH] no hackatiem id sent --- backend/src/routes/hackatime.ts | 1 - backend/src/routes/projects.ts | 64 ++++++++++++++++--- .../lib/components/CreateProjectModal.svelte | 4 +- .../src/lib/components/ProjectModal.svelte | 17 +---- .../routes/projects/[id]/edit/+page.svelte | 13 +--- 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/backend/src/routes/hackatime.ts b/backend/src/routes/hackatime.ts index 4681efc..ae1e6b4 100644 --- a/backend/src/routes/hackatime.ts +++ b/backend/src/routes/hackatime.ts @@ -82,7 +82,6 @@ hackatime.get('/projects', async ({ headers }) => { return { slackId: user.slackId, - hackatimeUserId, projects: projects.map((p) => ({ name: p.name, hours: Math.round(p.total_duration / 3600 * 10) / 10, diff --git a/backend/src/routes/projects.ts b/backend/src/routes/projects.ts index e87656b..3e0c0d4 100644 --- a/backend/src/routes/projects.ts +++ b/backend/src/routes/projects.ts @@ -6,7 +6,7 @@ import { reviewsTable } from '../schemas/reviews' import { usersTable } from '../schemas/users' import { projectActivityTable } from '../schemas/activity' import { getUserFromSession, fetchUserIdentity } from '../lib/auth' -import { syncSingleProject } from '../lib/hackatime-sync' +import { syncSingleProject, getHackatimeUser } from '../lib/hackatime-sync' import { notifyProjectSubmitted } from '../lib/slack' import { submitProjectToYSWS } from '../lib/ysws' import { config } from '../config' @@ -19,6 +19,53 @@ function parseHackatimeProject(hackatimeProject: string | null): string | null { return hackatimeProject.trim() } +async function prefixHackatimeIds(hackatimeProject: string | null, email: string): Promise { + if (!hackatimeProject) return null + const names = hackatimeProject.split(',').map(p => p.trim()).filter(p => p.length > 0) + if (names.length === 0) return null + + // Check if already prefixed (has numeric id: prefix) + const alreadyPrefixed = names.every(n => { + const colonIdx = n.indexOf(':') + return colonIdx !== -1 && !n.startsWith('U') && !isNaN(parseInt(n.substring(0, colonIdx), 10)) + }) + if (alreadyPrefixed) return hackatimeProject + + const hackatimeUser = await getHackatimeUser(email) + if (!hackatimeUser || typeof hackatimeUser.user_id !== 'number') return hackatimeProject + + return names.map(name => { + const colonIdx = name.indexOf(':') + if (colonIdx !== -1 && !name.startsWith('U') && !isNaN(parseInt(name.substring(0, colonIdx), 10))) { + return name + } + const slashIdx = name.indexOf('/') + if (slashIdx !== -1 && name.startsWith('U')) { + name = name.substring(slashIdx + 1) + } + return `${hackatimeUser.user_id}:${name}` + }).join(',') +} + +function stripHackatimeIds(hackatimeProject: string | null): string | null { + if (!hackatimeProject) return null + return hackatimeProject + .split(',') + .map(entry => { + const trimmed = entry.trim() + const colonIdx = trimmed.indexOf(':') + if (colonIdx !== -1 && !trimmed.startsWith('U')) { + return trimmed.substring(colonIdx + 1) + } + const slashIdx = trimmed.indexOf('/') + if (slashIdx !== -1 && trimmed.startsWith('U')) { + return trimmed.substring(slashIdx + 1) + } + return trimmed + }) + .join(',') || null +} + function parseHackatimeProjects(hackatimeProject: string | null): string | null { if (!hackatimeProject) return null return hackatimeProject @@ -196,6 +243,7 @@ projects.get('/', async ({ headers, query }) => { return { data: projectsList.map(p => ({ ...p, + hackatimeProject: stripHackatimeIds(p.hackatimeProject), status: p.status === 'pending_admin_approval' ? 'waiting_for_review' : p.status })), pagination: { @@ -367,7 +415,7 @@ projects.get('/:id', async ({ params, headers }) => { image: project[0].image, githubUrl: project[0].githubUrl, playableUrl: project[0].playableUrl, - hackatimeProject: isOwner ? project[0].hackatimeProject : undefined, + hackatimeProject: isOwner ? stripHackatimeIds(project[0].hackatimeProject) : undefined, hours: projectHours, hoursOverride: isOwner ? project[0].hoursOverride : undefined, tier: project[0].tier, @@ -410,7 +458,7 @@ projects.post('/', async ({ body, headers }) => { return { error: 'Image must be from cdn.hackclub.com' } } - const projectName = parseHackatimeProjects(data.hackatimeProject || null) + const projectName = await prefixHackatimeIds(parseHackatimeProjects(data.hackatimeProject || null), user.email) const tier = data.tier !== undefined ? Math.max(1, Math.min(4, data.tier)) : 1 const newProject = await db @@ -441,7 +489,7 @@ projects.post('/', async ({ body, headers }) => { action: 'project_created' }) - return newProject[0] + return { ...newProject[0], hackatimeProject: stripHackatimeIds(newProject[0].hackatimeProject) } }) projects.put('/:id', async ({ params, body, headers }) => { @@ -484,7 +532,7 @@ projects.put('/:id', async ({ params, body, headers }) => { return { error: playableCheck.error } } - const projectName = parseHackatimeProjects(data.hackatimeProject || null) + const projectName = await prefixHackatimeIds(parseHackatimeProjects(data.hackatimeProject || null), user.email) const tier = data.tier !== undefined ? Math.max(1, Math.min(4, data.tier)) : undefined const updated = await db @@ -513,7 +561,7 @@ projects.put('/:id', async ({ params, body, headers }) => { updated[0].hours = syncResult.hours } - return updated[0] + return { ...updated[0], hackatimeProject: stripHackatimeIds(updated[0].hackatimeProject) } }) projects.delete("/:id", async ({ params, headers }) => { @@ -579,7 +627,7 @@ projects.post("/:id/unsubmit", async ({ params, headers }) => { action: 'project_unsubmitted' }) - return updated[0] + return { ...updated[0], hackatimeProject: stripHackatimeIds(updated[0].hackatimeProject) } }) // Submit project for review @@ -668,7 +716,7 @@ projects.post("/:id/submit", async ({ params, headers, body }) => { } } - return updated[0] + return { ...updated[0], hackatimeProject: stripHackatimeIds(updated[0].hackatimeProject) } }) // Get project reviews (public feedback) diff --git a/frontend/src/lib/components/CreateProjectModal.svelte b/frontend/src/lib/components/CreateProjectModal.svelte index 68f8b05..f536721 100644 --- a/frontend/src/lib/components/CreateProjectModal.svelte +++ b/frontend/src/lib/components/CreateProjectModal.svelte @@ -55,7 +55,6 @@ let selectedHackatimeProjects = $state([]); let hackatimeProjects = $state([]); let userSlackId = $state(null); - let hackatimeUserId = $state(null); let loadingProjects = $state(false); let showDropdown = $state(false); let loading = $state(false); @@ -97,7 +96,6 @@ const data = await response.json(); hackatimeProjects = data.projects || []; userSlackId = data.slackId || null; - hackatimeUserId = data.hackatimeUserId ?? null; } } catch (e) { console.error('Failed to fetch hackatime projects:', e); @@ -198,7 +196,7 @@ error = null; const hackatimeValue = selectedHackatimeProjects.length > 0 - ? selectedHackatimeProjects.map(p => hackatimeUserId != null ? `${hackatimeUserId}:${p.name}` : p.name).join(',') + ? selectedHackatimeProjects.map(p => p.name).join(',') : null; const finalGithubUrl = githubUrl.trim() || selectedHackatimeProjects[0]?.repoUrl || null; diff --git a/frontend/src/lib/components/ProjectModal.svelte b/frontend/src/lib/components/ProjectModal.svelte index d85111d..3b8c468 100644 --- a/frontend/src/lib/components/ProjectModal.svelte +++ b/frontend/src/lib/components/ProjectModal.svelte @@ -37,7 +37,6 @@ let uploadingImage = $state(false); let hackatimeProjects = $state([]); let userSlackId = $state(null); - let hackatimeUserId = $state(null); let selectedHackatimeName = $state(null); let loadingProjects = $state(false); let showDropdown = $state(false); @@ -73,7 +72,6 @@ const data = await response.json(); hackatimeProjects = data.projects || []; userSlackId = data.slackId || null; - hackatimeUserId = data.hackatimeUserId ?? null; } } catch (e) { console.error('Failed to fetch hackatime projects:', e); @@ -87,16 +85,7 @@ editedProject = { ...project }; imagePreview = project.image; error = null; - const raw = project.hackatimeProject || null; - if (raw) { - const colonIdx = raw.indexOf(':'); - const slashIdx = raw.indexOf('/'); - if (colonIdx !== -1 && !raw.startsWith('U')) selectedHackatimeName = raw.substring(colonIdx + 1); - else if (slashIdx !== -1 && raw.startsWith('U')) selectedHackatimeName = raw.substring(slashIdx + 1); - else selectedHackatimeName = raw; - } else { - selectedHackatimeName = null; - } + selectedHackatimeName = project.hackatimeProject || null; fetchHackatimeProjects(); } }); @@ -165,9 +154,7 @@ loading = true; error = null; - const hackatimeValue = selectedHackatimeName - ? (hackatimeUserId != null ? `${hackatimeUserId}:${selectedHackatimeName}` : selectedHackatimeName) - : null; + const hackatimeValue = selectedHackatimeName || null; try { const response = await fetch(`${API_URL}/projects/${editedProject.id}`, { diff --git a/frontend/src/routes/projects/[id]/edit/+page.svelte b/frontend/src/routes/projects/[id]/edit/+page.svelte index e97f112..2087075 100644 --- a/frontend/src/routes/projects/[id]/edit/+page.svelte +++ b/frontend/src/routes/projects/[id]/edit/+page.svelte @@ -51,7 +51,6 @@ let uploadingImage = $state(false); let hackatimeProjects = $state([]); let userSlackId = $state(null); - let hackatimeUserId = $state(null); let selectedHackatimeNames = $state([]); let loadingProjects = $state(false); let showDropdown = $state(false); @@ -106,14 +105,7 @@ aiDescription = project?.aiDescription || ''; reviewerNotes = project?.reviewerNotes || ''; if (project?.hackatimeProject) { - selectedHackatimeNames = project.hackatimeProject.split(',').map((p: string) => { - p = p.trim(); - const colonIdx = p.indexOf(':'); - if (colonIdx !== -1 && !p.startsWith('U')) return p.substring(colonIdx + 1); - const slashIdx = p.indexOf('/'); - if (slashIdx !== -1 && p.startsWith('U')) return p.substring(slashIdx + 1); - return p; - }).filter((p: string) => p.length > 0); + selectedHackatimeNames = project.hackatimeProject.split(',').map((p: string) => p.trim()).filter((p: string) => p.length > 0); } fetchHackatimeProjects(); } catch (e) { @@ -133,7 +125,6 @@ const apiData = await response.json(); hackatimeProjects = apiData.projects || []; userSlackId = apiData.slackId || null; - hackatimeUserId = apiData.hackatimeUserId ?? null; } } catch (e) { console.error('Failed to fetch hackatime projects:', e); @@ -225,7 +216,7 @@ error = null; const hackatimeValue = selectedHackatimeNames.length > 0 - ? selectedHackatimeNames.map(n => hackatimeUserId != null ? `${hackatimeUserId}:${n}` : n).join(',') + ? selectedHackatimeNames.join(',') : null; try {