From 03ccfe7611795473117ab0b5922e15660f555d07 Mon Sep 17 00:00:00 2001 From: Antti Leinonen Date: Fri, 23 Jan 2026 16:11:12 +0200 Subject: [PATCH 1/2] Remove duplicates from downloaded completion lists --- backend/api/routes/completions.ts | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/backend/api/routes/completions.ts b/backend/api/routes/completions.ts index 2f8e05414..e203d2ddd 100644 --- a/backend/api/routes/completions.ts +++ b/backend/api/routes/completions.ts @@ -205,11 +205,7 @@ export class CompletionController extends Controller { .join("course as c", "com.course_id", "c.id") .join("user as u", "com.user_id", "u.id") .where("c.id", course.completions_handled_by_id ?? course.id) - .distinct("u.id", "com.course_id") .orderBy("com.completion_date", "asc") - .orderBy("u.last_name", "asc") - .orderBy("u.first_name", "asc") - .orderBy("u.id", "asc") if (fromDate && typeof fromDate === "string") { try { @@ -222,6 +218,27 @@ export class CompletionController extends Controller { const completions = await query + // Filter to keep only the oldest completion per user (already sorted by completion_date asc) + const seenUsers = new Set() + const uniqueCompletions = completions.filter((row) => { + if (seenUsers.has(row.id)) { + return false + } + seenUsers.add(row.id) + return true + }) + + // Sort by usuniqueCr name and ID + uniqueCompletions.sort((a, b) => { + if (a.last_name !== b.last_name) { + return (a.last_name || "").localeCompare(b.last_name || "") + } + if (a.first_name !== b.first_name) { + return (a.first_name || "").localeCompare(b.first_name || "") + } + return a.id.localeCompare(b.id) + }) + const headers = [ "User ID", "Email", @@ -237,7 +254,9 @@ export class CompletionController extends Controller { row.email, row.first_name, row.last_name, - row.completion_date, + row.completion_date + ? new Date(row.completion_date).toISOString() + : row.completion_date, row.completion_language, row.grade, ]) From 7d0793cc5b7a93bb2d953bf4baf6a528f07b65c8 Mon Sep 17 00:00:00 2001 From: Antti Leinonen Date: Fri, 23 Jan 2026 16:50:05 +0200 Subject: [PATCH 2/2] Obey the rabbit --- backend/api/routes/completions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/api/routes/completions.ts b/backend/api/routes/completions.ts index e203d2ddd..9bf93c62a 100644 --- a/backend/api/routes/completions.ts +++ b/backend/api/routes/completions.ts @@ -228,7 +228,7 @@ export class CompletionController extends Controller { return true }) - // Sort by usuniqueCr name and ID + // Sort by last name, first name, and ID uniqueCompletions.sort((a, b) => { if (a.last_name !== b.last_name) { return (a.last_name || "").localeCompare(b.last_name || "") @@ -249,7 +249,7 @@ export class CompletionController extends Controller { "Grade", ] - const rows = completions.map((row) => [ + const rows = uniqueCompletions.map((row) => [ row.id, row.email, row.first_name,