diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index ab9eaa0..450e17c 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -1660,4 +1660,101 @@ admin.post('/fix-negative-balances', async ({ headers, status }) => { } }) +// CSV export of shipped projects for YSWS +admin.get('/export/shipped-csv', async ({ headers, status }) => { + try { + const user = await requireAdmin(headers as Record) + if (!user) { + return status(401, { error: 'Unauthorized' }) + } + + const projects = await db + .select({ + name: projectsTable.name, + githubUrl: projectsTable.githubUrl, + playableUrl: projectsTable.playableUrl, + hackatimeProject: projectsTable.hackatimeProject, + slackId: usersTable.slackId + }) + .from(projectsTable) + .innerJoin(usersTable, eq(projectsTable.userId, usersTable.id)) + .where(and( + eq(projectsTable.status, 'shipped'), + or(eq(projectsTable.deleted, 0), isNull(projectsTable.deleted)) + )) + .orderBy(desc(projectsTable.updatedAt)) + + const escapeCSV = (val: string | null | undefined): string => { + if (!val) return '' + if (val.includes(',') || val.includes('"') || val.includes('\n')) { + return '"' + val.replace(/"/g, '""') + '"' + } + return val + } + + const rows = ['name,code_link,demo_link,slack_id,hackatime_projects'] + for (const p of projects) { + rows.push([ + escapeCSV(p.name), + escapeCSV(p.githubUrl), + escapeCSV(p.playableUrl), + escapeCSV(p.slackId), + escapeCSV(p.hackatimeProject) + ].join(',')) + } + + return new Response(rows.join('\n'), { + headers: { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': 'attachment; filename="scraps-shipped-projects.csv"' + } + }) + } catch (err) { + console.error(err) + return status(500, { error: 'Failed to export CSV' }) + } +}) + +admin.get('/export/ysws-json', async ({ headers, status }) => { + try { + const user = await requireAdmin(headers as Record) + if (!user) { + return status(401, { error: 'Unauthorized' }) + } + + const projects = await db + .select({ + name: projectsTable.name, + githubUrl: projectsTable.githubUrl, + playableUrl: projectsTable.playableUrl, + hackatimeProject: projectsTable.hackatimeProject, + slackId: usersTable.slackId + }) + .from(projectsTable) + .innerJoin(usersTable, eq(projectsTable.userId, usersTable.id)) + .where(and( + eq(projectsTable.status, 'shipped'), + or(eq(projectsTable.deleted, 0), isNull(projectsTable.deleted)) + )) + .orderBy(desc(projectsTable.updatedAt)) + + return projects.map(p => { + const hackatimeProjects = p.hackatimeProject + ? p.hackatimeProject.split(',').map((n: string) => n.trim()).filter((n: string) => n.length > 0) + : [] + + return { + name: p.name, + codeLink: p.githubUrl || '', + demoLink: p.playableUrl || '', + submitter: { slackId: p.slackId || '' }, + hackatimeProjects + } + }) + } catch (err) { + console.error(err) + return status(500, { error: 'Failed to export YSWS JSON' }) + } +}) + export default admin