About
- {#if $user && isUserAdmin}
+ {#if $user}
{
+ const currentUser = get(user);
+
+ if (!currentUser) {
+ return null;
+ }
+
+ try {
+ const { data, error } = await supabase
+ .from('user_platform_usernames')
+ .select('*')
+ .eq('user_id', currentUser.id)
+ .single();
+
+ if (error) {
+ if (error.code === 'PGRST116') {
+ // No record found, which is fine for new users
+ return null;
+ }
+ console.error('Error fetching user platform usernames:', error);
+ return null;
+ }
+
+ return data as UserPlatformUsernames;
+ } catch (err) {
+ console.error('Failed to fetch user platform usernames:', err);
+ return null;
+ }
+}
+
+/**
+ * Saves the user's platform usernames
+ * @param codeforcesUsername - Codeforces username
+ * @param kattisUsername - Kattis username
+ * @returns Success flag and message
+ */
+export async function saveUserPlatformUsernames(
+ codeforcesUsername?: string,
+ kattisUsername?: string
+): Promise<{ success: boolean; message?: string }> {
+ const currentUser = get(user);
+
+ if (!currentUser) {
+ return {
+ success: false,
+ message: 'You must be logged in to save platform usernames'
+ };
+ }
+
+ try {
+ // Check if the user already has platform usernames
+ const existingData = await fetchUserPlatformUsernames();
+
+ if (existingData) {
+ // Update existing record
+ const { error } = await supabase
+ .from('user_platform_usernames')
+ .update({
+ codeforces_username: codeforcesUsername || null,
+ kattis_username: kattisUsername || null,
+ updated_at: new Date().toISOString()
+ })
+ .eq('user_id', currentUser.id);
+
+ if (error) {
+ console.error('Error updating user platform usernames:', error);
+ return {
+ success: false,
+ message: `Error updating platform usernames: ${error.message}`
+ };
+ }
+ } else {
+ // Insert new record
+ const { error } = await supabase.from('user_platform_usernames').insert({
+ user_id: currentUser.id,
+ codeforces_username: codeforcesUsername || null,
+ kattis_username: kattisUsername || null
+ });
+
+ if (error) {
+ console.error('Error inserting user platform usernames:', error);
+ return {
+ success: false,
+ message: `Error saving platform usernames: ${error.message}`
+ };
+ }
+ }
+
+ return {
+ success: true
+ };
+ } catch (err) {
+ console.error('Failed to save user platform usernames:', err);
+ return {
+ success: false,
+ message: err instanceof Error ? err.message : 'Unknown error saving platform usernames'
+ };
+ }
+}
+
+/**
+ * Imports user's solved problems from Codeforces
+ * @param codeforcesUsername - Codeforces username
+ * @returns Success flag, message, and counts
+ */
+export async function importCodeforcesSolves(codeforcesUsername: string): Promise<{
+ success: boolean;
+ message?: string;
+ totalSolved?: number;
+ matchedCount?: number;
+ importedCount?: number;
+}> {
+ const currentUser = get(user);
+
+ if (!currentUser) {
+ return {
+ success: false,
+ message: 'You must be logged in to import solved problems'
+ };
+ }
+
+ if (!codeforcesUsername) {
+ return {
+ success: false,
+ message: 'Codeforces username is required'
+ };
+ }
+
+ try {
+ console.log(`Fetching solved problems for Codeforces user: ${codeforcesUsername}`);
+
+ // Fetch user's solved problems from Codeforces API
+ const response = await fetch(
+ `/api/codeforces/user-solves?username=${encodeURIComponent(codeforcesUsername)}`
+ );
+ const data = await response.json();
+
+ if (!response.ok) {
+ console.error('Error response from Codeforces API:', data);
+
+ // Check if the error is related to user not found
+ if (data.error && data.error.includes('not found')) {
+ return {
+ success: false,
+ message: `User "${codeforcesUsername}" not found on Codeforces. Please check that you've entered your Codeforces username correctly (case-sensitive). You can verify your username by checking if this link works:
https://codeforces.com/profile/${codeforcesUsername}`
+ };
+ }
+
+ return {
+ success: false,
+ message: data.error || 'Failed to fetch solved problems from Codeforces'
+ };
+ }
+
+ // Check if we got a valid response with solvedProblems array
+ if (!data.solvedProblems) {
+ console.error('Invalid response from Codeforces API:', data);
+ return {
+ success: false,
+ message: 'Invalid response from Codeforces API'
+ };
+ }
+
+ console.log(
+ `Found ${data.solvedProblems.length} solved problems for Codeforces user: ${codeforcesUsername}`
+ );
+
+ // Get all problems from our database
+ const { data: problems, error: problemsError } = await supabase.from('problems').select('*');
+
+ if (problemsError) {
+ console.error('Error fetching problems:', problemsError);
+ return {
+ success: false,
+ message: `Error fetching problems: ${problemsError.message}`
+ };
+ }
+
+ console.log(`Found ${problems.length} problems in our database`);
+
+ // Map problems by URL for easy lookup
+ const problemsByUrl = new Map
();
+ for (const problem of problems) {
+ problemsByUrl.set(problem.url, problem);
+ }
+
+ // Match solved problems with our database
+ const solvedProblems = data.solvedProblems || [];
+ const matchedProblems = [];
+
+ for (const solvedProblem of solvedProblems) {
+ const problem = problemsByUrl.get(solvedProblem.url);
+ if (problem) {
+ matchedProblems.push({
+ user_id: currentUser.id,
+ problem_id: problem.id,
+ solved_at: solvedProblem.solvedAt || new Date().toISOString()
+ });
+ }
+ }
+
+ console.log(`Matched ${matchedProblems.length} problems with our database`);
+
+ // Insert matched problems into user_solved_problems table
+ if (matchedProblems.length > 0) {
+ // First, get existing solved problems to avoid duplicates
+ const { data: existingSolved, error: existingError } = await supabase
+ .from('user_solved_problems')
+ .select('problem_id')
+ .eq('user_id', currentUser.id);
+
+ if (existingError) {
+ console.error('Error fetching existing solved problems:', existingError);
+ return {
+ success: false,
+ message: `Error fetching existing solved problems: ${existingError.message}`
+ };
+ }
+
+ // Filter out already solved problems
+ const existingSolvedIds = new Set(existingSolved.map((item) => item.problem_id));
+ const newSolvedProblems = matchedProblems.filter(
+ (item) => !existingSolvedIds.has(item.problem_id)
+ );
+
+ console.log(`Found ${newSolvedProblems.length} new problems to mark as solved`);
+
+ if (newSolvedProblems.length > 0) {
+ const { error: insertError } = await supabase
+ .from('user_solved_problems')
+ .insert(newSolvedProblems);
+
+ if (insertError) {
+ console.error('Error inserting solved problems:', insertError);
+ return {
+ success: false,
+ message: `Error inserting solved problems: ${insertError.message}`
+ };
+ }
+ }
+
+ return {
+ success: true,
+ totalSolved: solvedProblems.length,
+ matchedCount: matchedProblems.length,
+ importedCount: newSolvedProblems.length
+ };
+ }
+
+ return {
+ success: true,
+ totalSolved: solvedProblems.length,
+ matchedCount: matchedProblems.length,
+ importedCount: 0
+ };
+ } catch (err) {
+ console.error('Failed to import Codeforces solves:', err);
+ return {
+ success: false,
+ message: err instanceof Error ? err.message : 'Unknown error importing Codeforces solves'
+ };
+ }
+}
+
+/**
+ * Imports user's solved problems from Kattis
+ * @param kattisUsername - Kattis username
+ * @returns Success flag, message, and counts
+ */
+export async function importKattisSolves(kattisUsername: string): Promise<{
+ success: boolean;
+ message?: string;
+ totalSolved?: number;
+ importedCount?: number;
+ matchedCount?: number;
+}> {
+ const currentUser = get(user);
+
+ if (!currentUser) {
+ return {
+ success: false,
+ message: 'You must be logged in to import solved problems'
+ };
+ }
+
+ if (!kattisUsername) {
+ return {
+ success: false,
+ message: 'Kattis username is required'
+ };
+ }
+
+ try {
+ console.log(`Fetching solved problems for Kattis user: ${kattisUsername}`);
+
+ // Fetch user's solved problems from Kattis API
+ const response = await fetch(
+ `/api/kattis/user-solves?username=${encodeURIComponent(kattisUsername)}`
+ );
+ const data = await response.json();
+
+ if (!response.ok) {
+ console.error('Error response from Kattis API:', data);
+ return {
+ success: false,
+ message: data.error || 'Failed to fetch solved problems from Kattis'
+ };
+ }
+
+ // Check if we got a valid response
+ if (!data.success) {
+ console.error('Error response from Kattis API:', data);
+
+ // Provide a more helpful error message for user not found
+ if (data.error && data.error.includes('not found on Kattis')) {
+ return {
+ success: false,
+ message: `User "${kattisUsername}" not found on Kattis. Please check that you've entered your Kattis username exactly as it appears in your profile URL. You can verify your username by checking if this link works: https://open.kattis.com/users/${kattisUsername}`
+ };
+ }
+
+ return {
+ success: false,
+ message: data.error || 'Failed to fetch solved problems from Kattis'
+ };
+ }
+
+ // Check if solvedProblems array exists (it should, but just to be safe)
+ if (!data.solvedProblems) {
+ console.error('Invalid response from Kattis API:', data);
+ return {
+ success: false,
+ message: 'Invalid response from Kattis API'
+ };
+ }
+
+ // If the user has no solved problems, return early with success
+ if (data.solvedProblems.length === 0) {
+ console.log(`User ${kattisUsername} has no solved problems on Kattis`);
+ return {
+ success: true,
+ totalSolved: 0,
+ matchedCount: 0,
+ importedCount: 0
+ };
+ }
+
+ console.log(
+ `Found ${data.solvedProblems.length} solved problems for Kattis user: ${kattisUsername}`
+ );
+
+ // Get all problems from our database
+ const { data: problems, error: problemsError } = await supabase
+ .from('problems')
+ .select('*')
+ .like('url', 'https://open.kattis.com/problems/%');
+
+ if (problemsError) {
+ console.error('Error fetching problems:', problemsError);
+ return {
+ success: false,
+ message: `Error fetching problems: ${problemsError.message}`
+ };
+ }
+
+ console.log(`Found ${problems.length} Kattis problems in our database`);
+
+ // Map problems by URL for easy lookup
+ const problemsByUrl = new Map();
+ for (const problem of problems) {
+ problemsByUrl.set(problem.url, problem);
+ }
+
+ // Match solved problems with our database
+ const solvedProblems = data.solvedProblems || [];
+ const matchedProblems = [];
+
+ for (const solvedProblem of solvedProblems) {
+ const problem = problemsByUrl.get(solvedProblem.url);
+ if (problem) {
+ matchedProblems.push({
+ user_id: currentUser.id,
+ problem_id: problem.id,
+ solved_at: solvedProblem.solvedAt || new Date().toISOString()
+ });
+ }
+ }
+
+ console.log(`Matched ${matchedProblems.length} problems with our database`);
+
+ // Insert matched problems into user_solved_problems table
+ if (matchedProblems.length > 0) {
+ // First, get existing solved problems to avoid duplicates
+ const { data: existingSolved, error: existingError } = await supabase
+ .from('user_solved_problems')
+ .select('problem_id')
+ .eq('user_id', currentUser.id);
+
+ if (existingError) {
+ console.error('Error fetching existing solved problems:', existingError);
+ return {
+ success: false,
+ message: `Error fetching existing solved problems: ${existingError.message}`
+ };
+ }
+
+ // Filter out already solved problems
+ const existingSolvedIds = new Set(existingSolved.map((item) => item.problem_id));
+ const newSolvedProblems = matchedProblems.filter(
+ (item) => !existingSolvedIds.has(item.problem_id)
+ );
+
+ console.log(`Found ${newSolvedProblems.length} new problems to mark as solved`);
+
+ if (newSolvedProblems.length > 0) {
+ const { error: insertError } = await supabase
+ .from('user_solved_problems')
+ .insert(newSolvedProblems);
+
+ if (insertError) {
+ console.error('Error inserting solved problems:', insertError);
+ return {
+ success: false,
+ message: `Error inserting solved problems: ${insertError.message}`
+ };
+ }
+ }
+
+ return {
+ success: true,
+ totalSolved: solvedProblems.length,
+ matchedCount: matchedProblems.length,
+ importedCount: newSolvedProblems.length
+ };
+ }
+
+ return {
+ success: true,
+ totalSolved: solvedProblems.length,
+ matchedCount: matchedProblems.length,
+ importedCount: 0
+ };
+ } catch (err) {
+ console.error('Failed to import Kattis solves:', err);
+ return {
+ success: false,
+ message: err instanceof Error ? err.message : 'Unknown error importing Kattis solves'
+ };
+ }
+}
diff --git a/src/routes/api/codeforces/user-solves/+server.ts b/src/routes/api/codeforces/user-solves/+server.ts
new file mode 100644
index 0000000..ea2be7e
--- /dev/null
+++ b/src/routes/api/codeforces/user-solves/+server.ts
@@ -0,0 +1,78 @@
+import { json } from '@sveltejs/kit';
+import type { RequestHandler } from './$types';
+
+export const GET: RequestHandler = async ({ url }) => {
+ const username = url.searchParams.get('username');
+ if (!username) {
+ return json({ error: 'No username provided' }, { status: 400 });
+ }
+
+ try {
+ console.log(`Fetching submissions for Codeforces user: ${username}`);
+
+ // Fetch user's submissions from Codeforces API
+ const response = await fetch(`https://codeforces.com/api/user.status?handle=${username}`);
+ const data = await response.json();
+
+ console.log(`Codeforces API response status: ${data.status}`);
+ if (data.status !== 'OK') {
+ console.error('Codeforces API error:', data);
+ }
+
+ if (data.status !== 'OK') {
+ // Check for specific error messages
+ if (data.comment && data.comment.includes('not found')) {
+ return json({ error: `User ${username} not found on Codeforces` }, { status: 404 });
+ }
+
+ return json(
+ { error: data.comment || 'Failed to fetch submissions from Codeforces' },
+ { status: 500 }
+ );
+ }
+
+ // Filter for accepted submissions only
+ const acceptedSubmissions = data.result.filter(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (submission: any) => submission.verdict === 'OK'
+ );
+
+ // Extract unique problem URLs and solved timestamps
+ const solvedProblems = new Map();
+
+ for (const submission of acceptedSubmissions) {
+ const problem = submission.problem;
+ const contestId = problem.contestId;
+ const index = problem.index;
+
+ // Handle gym problems
+ const isGym = contestId >= 100000;
+
+ // Create normalized URL
+ const url = isGym
+ ? `https://codeforces.com/gym/${contestId}/problem/${index}`
+ : `https://codeforces.com/contest/${contestId}/problem/${index}`;
+
+ // Only keep the earliest solve for each problem
+ if (
+ !solvedProblems.has(url) ||
+ submission.creationTimeSeconds < solvedProblems.get(url).solvedAtTimestamp
+ ) {
+ solvedProblems.set(url, {
+ url,
+ name: problem.name,
+ solvedAt: new Date(submission.creationTimeSeconds * 1000).toISOString(),
+ solvedAtTimestamp: submission.creationTimeSeconds
+ });
+ }
+ }
+
+ return json({
+ success: true,
+ solvedProblems: Array.from(solvedProblems.values())
+ });
+ } catch (error) {
+ console.error('Error fetching Codeforces submissions:', error);
+ return json({ error: 'Failed to fetch submissions from Codeforces' }, { status: 500 });
+ }
+};
diff --git a/src/routes/api/kattis/user-solves/+server.ts b/src/routes/api/kattis/user-solves/+server.ts
new file mode 100644
index 0000000..574a158
--- /dev/null
+++ b/src/routes/api/kattis/user-solves/+server.ts
@@ -0,0 +1,173 @@
+import { json } from '@sveltejs/kit';
+import type { RequestHandler } from './$types';
+
+export const GET: RequestHandler = async ({ url }) => {
+ let username = url.searchParams.get('username');
+ if (!username) {
+ return json({ error: 'No username provided' }, { status: 400 });
+ }
+
+ // Normalize username - Kattis usernames are typically lowercase with hyphens
+ // If the username contains uppercase letters or spaces, convert them
+ const originalUsername = username;
+ username = username.trim().toLowerCase();
+
+ // Replace spaces with hyphens (common mistake)
+ if (username.includes(' ')) {
+ console.log(`Converting spaces to hyphens in username: "${username}"`);
+ username = username.replace(/ /g, '-');
+ }
+
+ if (username !== originalUsername) {
+ console.log(`Normalized username from "${originalUsername}" to "${username}"`);
+ }
+
+ try {
+ // Fetch user's profile page from Kattis
+ const response = await fetch(`https://open.kattis.com/users/${username}`);
+
+ if (!response.ok) {
+ return json(
+ { error: `Failed to fetch user profile from Kattis: ${response.statusText}` },
+ { status: response.status }
+ );
+ }
+
+ const html = await response.text();
+
+ // Parse the HTML to extract solved problems
+ // Kattis doesn't have an official API, so we need to scrape the user's profile page
+
+ // First, check if the user exists - log more details for debugging
+ console.log(`Checking if user exists in HTML. Username: "${username}"`);
+ console.log(`HTML contains >${username}<: ${html.includes(`>${username}<`)}`);
+ console.log(`HTML contains >${username}${username}`)}`);
+
+ // Try different variations of the username in the HTML
+ const usernameVariations = [
+ `>${username}<`,
+ `>${username}`,
+ `"${username}"`,
+ `/${username}"`,
+ `>${username.toLowerCase()}<`,
+ `>${username.toUpperCase()}<`
+ ];
+
+ let userFound = false;
+ for (const variation of usernameVariations) {
+ if (html.includes(variation)) {
+ console.log(`Found username variation: ${variation}`);
+ userFound = true;
+ break;
+ }
+ }
+
+ if (!userFound) {
+ // Let's check if the page is a 404 or contains error messages
+ if (html.includes('Page not found') || html.includes('does not exist')) {
+ console.log('Page indicates user not found');
+ return json({ error: `User ${username} not found on Kattis` }, { status: 404 });
+ }
+
+ // If we're here, the page exists but we couldn't find the username
+ // Let's try a more lenient approach - check if the URL path is in the HTML
+ if (html.includes(`/users/${username}`)) {
+ console.log(`Found username in URL path: /users/${username}`);
+ // User likely exists, continue processing
+ } else {
+ console.log('Username not found in any expected format');
+ return json({ error: `User ${username} not found on Kattis` }, { status: 404 });
+ }
+ }
+
+ // Check if the user has no solved problems
+ if (html.includes('has not solved any problems')) {
+ console.log(`User ${username} exists but has not solved any problems`);
+ return json({
+ success: true,
+ solvedProblems: []
+ });
+ }
+
+ // Log the HTML for debugging (truncated)
+ console.log(`HTML length: ${html.length}`);
+ console.log(`HTML snippet: ${html.substring(0, 200)}...`);
+
+ // Try different approaches to find problem links
+ console.log('Starting to extract problem links');
+ const solvedProblems = new Map();
+
+ // Try multiple approaches to find problem links
+ const approaches = [
+ // 1. Look for problems in the solved problems section with full HTML structure
+ {
+ regex: /]*>([^<]+)<\/a>/g,
+ name: 'specific HTML structure'
+ },
+
+ // 2. Look for any links to problems
+ { regex: /href="\/problems\/([a-z0-9]+)"/g, name: 'general problem links' },
+
+ // 3. Look for problem IDs in the URL path
+ { regex: /\/problems\/([a-z0-9]+)/g, name: 'URL paths' },
+
+ // 4. Look for problem IDs in a different format
+ { regex: /problem\/([a-z0-9]+)/g, name: 'alternative format' }
+ ];
+
+ // Try each approach until we find some problems
+ for (const approach of approaches) {
+ console.log(`Trying approach: ${approach.name}`);
+ const matches = html.matchAll(approach.regex);
+ let matchCount = 0;
+
+ for (const match of matches) {
+ matchCount++;
+ const problemId = match[1];
+ const url = `https://open.kattis.com/problems/${problemId}`;
+
+ // Get problem name if available (from first approach), otherwise use problem ID
+ const problemName = match.length > 2 ? match[2].trim() : problemId;
+
+ if (!solvedProblems.has(url)) {
+ solvedProblems.set(url, {
+ url,
+ problemId,
+ name: problemName,
+ solvedAt: new Date().toISOString()
+ });
+ }
+ }
+
+ console.log(`Found ${matchCount} matches with approach: ${approach.name}`);
+
+ // If we found some problems, we can stop trying other approaches
+ if (solvedProblems.size > 0) {
+ console.log(
+ `Successfully found ${solvedProblems.size} problems with approach: ${approach.name}`
+ );
+ break;
+ }
+ }
+
+ // 3. If we still didn't find any problems but the user exists, they might have no solved problems
+ if (solvedProblems.size === 0) {
+ console.log(`No problems found for user ${username}, but user exists`);
+ return json({
+ success: true,
+ solvedProblems: []
+ });
+ }
+
+ // Log the number of problems found for debugging
+ console.log(`Found ${solvedProblems.size} solved problems for Kattis user ${username}`);
+
+ return json({
+ success: true,
+ solvedProblems: Array.from(solvedProblems.values())
+ });
+ } catch (error) {
+ console.error('Error fetching Kattis submissions:', error);
+ return json({ error: 'Failed to fetch submissions from Kattis' }, { status: 500 });
+ }
+};
diff --git a/src/routes/import/+page.svelte b/src/routes/import/+page.svelte
new file mode 100644
index 0000000..8c61461
--- /dev/null
+++ b/src/routes/import/+page.svelte
@@ -0,0 +1,318 @@
+
+
+
+ AlgoHub | Import Solved Problems
+
+
+
+
Import Solved Problems
+
+ {#if loading}
+
+ {:else}
+ {#if error}
+
+ {/if}
+
+ {#if success}
+
+ {/if}
+
+
+
Platform Usernames
+
+ Enter your Codeforces and Kattis usernames to import your solved problems. We'll match your
+ solved problems with the problems in our database and mark them as solved for you.
+
+
+ Important: Make sure to enter your usernames exactly as they appear in your
+ profile URLs. Usernames are case-sensitive and must match exactly what's shown on the respective
+ platforms.
+
+
+
+
+
+ {#if codeforcesImportResult || kattisImportResult}
+
+
Import Results
+
+ {#if codeforcesImportResult}
+
+
Codeforces
+
+ Found {codeforcesImportResult.totalSolved} solved problems, matched {codeforcesImportResult.matchedCount}
+ with our database, imported {codeforcesImportResult.importedCount}
+ new solves.
+
+
+ {/if}
+
+ {#if kattisImportResult}
+
+
Kattis
+
+ {#if kattisImportResult.message}
+ {kattisImportResult.message}
+ {:else}
+ Found {kattisImportResult.totalSolved} solved problems, matched {kattisImportResult.matchedCount}
+ with our database, imported {kattisImportResult.importedCount}
+ new solves.
+ {/if}
+
+
+ {/if}
+
+
+ Note: Only problems that exist in our database can be imported. If you've solved problems
+ that aren't in our database, they won't be counted.
+
+
+ For Kattis problems, you'll need to manually mark them as solved on each problem page.
+
+
+ {/if}
+ {/if}
+