From e44d6d2510024fb0f3db386ae44c07cca1616459 Mon Sep 17 00:00:00 2001 From: End Nightshade Date: Mon, 16 Feb 2026 19:25:29 -0700 Subject: [PATCH 1/4] feat: auto-submit projects to ysws thingie on review submission fires and forgets so it won't block the user if ysws is down. --- backend/src/config.ts | 5 +++- backend/src/lib/ysws.ts | 54 ++++++++++++++++++++++++++++++++++ backend/src/routes/projects.ts | 10 +++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 backend/src/lib/ysws.ts diff --git a/backend/src/config.ts b/backend/src/config.ts index 66cc4ba..64c39e5 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -36,5 +36,8 @@ export const config = { airtableToken: process.env.AIRTABLE_TOKEN, airtableBaseId: process.env.AIRTABLE_BASE_ID, airtableProjectsTableId: process.env.AIRTABLE_PROJECTS_TABLE_ID!, - airtableUsersTableId: process.env.AIRTABLE_USERS_TABLE_ID! + airtableUsersTableId: process.env.AIRTABLE_USERS_TABLE_ID!, + + // YSWS + fraudToken: process.env.FRAUD_TOKEN } diff --git a/backend/src/lib/ysws.ts b/backend/src/lib/ysws.ts new file mode 100644 index 0000000..f3255f2 --- /dev/null +++ b/backend/src/lib/ysws.ts @@ -0,0 +1,54 @@ +import { config } from '../config' + +const YSWS_API_URL = 'https://joe.fraud.hackclub.com/api/v1/ysws/events/new-ui-api' + +export async function submitProjectToYSWS(project: { + name: string + githubUrl: string | null + playableUrl: string | null + hackatimeProject: string | null + slackId: string | null +}) { + if (!config.fraudToken) { + console.log('[YSWS] Missing FRAUD_TOKEN, skipping submission') + return null + } + + const hackatimeProjects = project.hackatimeProject + ? project.hackatimeProject.split(',').map(n => n.trim()).filter(n => n.length > 0) + : [] + + const payload = { + name: project.name, + codeLink: project.githubUrl || '', + demoLink: project.playableUrl || '', + submitter: { + slackId: project.slackId || '' + }, + hackatimeProjects + } + + try { + const res = await fetch(`${YSWS_API_URL}/projects`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${config.fraudToken}` + }, + body: JSON.stringify(payload) + }) + + if (!res.ok) { + const text = await res.text() + console.error('[YSWS] submission failed:', res.status, text) + return null + } + + const data = await res.json() + console.log('[YSWS] submitted project:', project.name) + return data + } catch (e) { + console.error('[YSWS] submission error:', e) + return null + } +} diff --git a/backend/src/routes/projects.ts b/backend/src/routes/projects.ts index fb51600..f794068 100644 --- a/backend/src/routes/projects.ts +++ b/backend/src/routes/projects.ts @@ -8,6 +8,7 @@ import { projectActivityTable } from '../schemas/activity' import { getUserFromSession, fetchUserIdentity } from '../lib/auth' import { syncSingleProject } from '../lib/hackatime-sync' import { notifyProjectSubmitted } from '../lib/slack' +import { submitProjectToYSWS } from '../lib/ysws' import { config } from '../config' import { computeEffectiveHoursForProject } from '../lib/effective-hours' @@ -643,6 +644,15 @@ projects.post("/:id/submit", async ({ params, headers, body }) => { action: 'project_submitted' }) + // Submit to YSWS + submitProjectToYSWS({ + name: updated[0].name, + githubUrl: updated[0].githubUrl, + playableUrl: updated[0].playableUrl, + hackatimeProject: updated[0].hackatimeProject, + slackId: user.slackId + }).catch(err => console.error('[YSWS] failed:', err)) + // Send Slack DM notification that the project is waiting for review if (config.slackBotToken && user.slackId) { try { From aea9bd81de57c4d2c578242766bee4f3d7f917b4 Mon Sep 17 00:00:00 2001 From: End Nightshade Date: Mon, 16 Feb 2026 21:57:06 -0700 Subject: [PATCH 2/4] fix(shop): make favorites sort prioritize wishlist count first --- frontend/src/routes/shop/+page.svelte | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/shop/+page.svelte b/frontend/src/routes/shop/+page.svelte index e326b46..a90376e 100644 --- a/frontend/src/routes/shop/+page.svelte +++ b/frontend/src/routes/shop/+page.svelte @@ -59,7 +59,15 @@ let sortedItems = $derived.by(() => { let items = [...filteredItems]; if (sortBy === 'favorites') { - return items.sort((a, b) => b.heartCount - a.heartCount); + return items.sort((a, b) => { + if (b.heartCount !== a.heartCount) { + return b.heartCount - a.heartCount; + } + if (a.userHearted !== b.userHearted) { + return a.userHearted ? -1 : 1; + } + return a.id - b.id; + }); } else if (sortBy === 'probability') { return items.sort((a, b) => b.effectiveProbability - a.effectiveProbability); } else if (sortBy === 'cost') { @@ -224,7 +232,7 @@ ? 'bg-black text-white' : 'hover:border-dashed'}" > - {$t.shop.favorites} + {$t.shop.favorites} (most wished, then yours)