From c2bfd89c2ca8c6be083333e57fa241f1c6bbc0e0 Mon Sep 17 00:00:00 2001 From: Cameron Custer Date: Sat, 5 Apr 2025 12:34:28 -0700 Subject: [PATCH 1/4] created the db for user solve management --- database_schema_definition.sql | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/database_schema_definition.sql b/database_schema_definition.sql index 9681f51..d5a9c4a 100644 --- a/database_schema_definition.sql +++ b/database_schema_definition.sql @@ -214,4 +214,34 @@ WHERE id = p_problem_id; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Ensure the function is accessible to authenticated users -GRANT EXECUTE ON FUNCTION update_problem_feedback TO authenticated; \ No newline at end of file +GRANT EXECUTE ON FUNCTION update_problem_feedback TO authenticated; +-- Create user_solved_problems table to track which problems users have solved +CREATE TABLE IF NOT EXISTS user_solved_problems ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + problem_id UUID NOT NULL REFERENCES problems(id) ON DELETE CASCADE, + solved_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(user_id, problem_id) +); + +-- Create RLS policies for user_solved_problems table +ALTER TABLE user_solved_problems ENABLE ROW LEVEL SECURITY; + +-- Users can read all solved problems (needed for displaying statistics) +CREATE POLICY "Anyone can read solved problems" ON user_solved_problems FOR +SELECT USING (true); + +-- Users can only mark their own problems as solved +CREATE POLICY "Users can mark their own solved problems" ON user_solved_problems FOR +INSERT WITH CHECK (auth.uid() = user_id); + +-- Users can only update their own solved problems +CREATE POLICY "Users can update their own solved problems" ON user_solved_problems FOR +UPDATE USING (auth.uid() = user_id); + +-- Users can only delete their own solved problems +CREATE POLICY "Users can delete their own solved problems" ON user_solved_problems FOR +DELETE USING (auth.uid() = user_id); + +-- Grant access to authenticated users +GRANT ALL ON user_solved_problems TO authenticated; From ff1c201fdd9c266eb8952c98f66a2b689efc5a91 Mon Sep 17 00:00:00 2001 From: Cameron Custer Date: Sat, 5 Apr 2025 12:45:18 -0700 Subject: [PATCH 2/4] manage solves --- src/lib/components/ProblemTable.svelte | 66 +++++++++++++++---- src/lib/services/problem.ts | 91 ++++++++++++++++++++++++++ src/routes/+page.svelte | 54 ++++++++++++++- 3 files changed, 195 insertions(+), 16 deletions(-) diff --git a/src/lib/components/ProblemTable.svelte b/src/lib/components/ProblemTable.svelte index 1910b9d..348049a 100644 --- a/src/lib/components/ProblemTable.svelte +++ b/src/lib/components/ProblemTable.svelte @@ -8,7 +8,9 @@ const kattisLogo = '/images/kattis.png'; // Props export let problems: Problem[] = []; export let userFeedback: Record = {}; +export let userSolvedProblems: Set = new Set(); export let onLike: (problemId: string, isLike: boolean) => Promise; +export let onToggleSolved: (problemId: string, isSolved: boolean) => Promise; // State let isAuthenticated = false; @@ -82,7 +84,10 @@ function getDifficultyTooltip(problem: Problem): string { {#each problems as problem} - + + + + + {/if} diff --git a/src/lib/services/problem.ts b/src/lib/services/problem.ts index ec6d4b3..77e1579 100644 --- a/src/lib/services/problem.ts +++ b/src/lib/services/problem.ts @@ -42,6 +42,16 @@ export type UserFeedback = { feedback_type: 'like' | 'dislike'; }; +/** + * User solved problem type + */ +export type UserSolvedProblem = { + id: string; + user_id: string; + problem_id: string; + solved_at: string; +}; + /** * Determine the problem source based on URL */ @@ -221,6 +231,87 @@ export async function fetchUserFeedback(): Promise> { + const currentUser = get(user); + + if (!currentUser) { + return new Set(); + } + + try { + const { data, error } = await supabase + .from('user_solved_problems') + .select('problem_id') + .eq('user_id', currentUser.id); + + if (error) { + console.error('Error fetching user solved problems:', error); + return new Set(); + } + + return new Set(data.map((item) => item.problem_id)); + } catch (err) { + console.error('Failed to fetch user solved problems:', err); + return new Set(); + } +} + +/** + * Marks a problem as solved or unsolved by the current user + * @param problemId - Problem ID + * @param isSolved - Whether to mark as solved (true) or unsolved (false) + * @returns Promise with success flag + */ +export async function toggleProblemSolved(problemId: string, isSolved: boolean): Promise { + const currentUser = get(user); + + if (!currentUser) { + console.error('Cannot update solved status: User not authenticated'); + return false; + } + + try { + if (isSolved) { + // Mark problem as solved + const { error } = await supabase.from('user_solved_problems').insert({ + user_id: currentUser.id, + problem_id: problemId + }); + + if (error) { + // If the error is a duplicate key error, it means the problem is already marked as solved + if (error.code === '23505') { + // Postgres unique violation code + return true; // Already solved, so consider it a success + } + console.error(`Error marking problem ${problemId} as solved:`, error); + return false; + } + } else { + // Mark problem as unsolved (delete the record) + const { error } = await supabase + .from('user_solved_problems') + .delete() + .eq('user_id', currentUser.id) + .eq('problem_id', problemId); + + if (error) { + console.error(`Error marking problem ${problemId} as unsolved:`, error); + return false; + } + } + + return true; + } catch (err) { + console.error(`Failed to update solved status for problem ${problemId}:`, err); + return false; + } +} + /** * Updates a problem's likes or dislikes in the database * @param problemId - Problem ID diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8c88fb4..5134567 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,13 @@