no hackatiem id sent

This commit is contained in:
NotARoomba 2026-02-21 01:14:19 -05:00
parent 42ce53ea15
commit 2348592b58
5 changed files with 61 additions and 38 deletions

View file

@ -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,

View file

@ -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<string | null> {
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)

View file

@ -55,7 +55,6 @@
let selectedHackatimeProjects = $state<HackatimeProject[]>([]);
let hackatimeProjects = $state<HackatimeProject[]>([]);
let userSlackId = $state<string | null>(null);
let hackatimeUserId = $state<number | null>(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;

View file

@ -37,7 +37,6 @@
let uploadingImage = $state(false);
let hackatimeProjects = $state<HackatimeProject[]>([]);
let userSlackId = $state<string | null>(null);
let hackatimeUserId = $state<number | null>(null);
let selectedHackatimeName = $state<string | null>(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}`, {

View file

@ -51,7 +51,6 @@
let uploadingImage = $state(false);
let hackatimeProjects = $state<HackatimeProject[]>([]);
let userSlackId = $state<string | null>(null);
let hackatimeUserId = $state<number | null>(null);
let selectedHackatimeNames = $state<string[]>([]);
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 {