diff --git a/src/routes/goals.ts b/src/routes/goals.ts index 10ff848..58b0b5b 100644 --- a/src/routes/goals.ts +++ b/src/routes/goals.ts @@ -3,29 +3,6 @@ import supabase from '../supabaseClient'; const router = express.Router(); -// Get all goals for a user -router.get('/user/:userId', async (req, res) => { - try { - const { data, error } = await supabase - .from('user_goals') - .select(` - id, - user_id, - goal_id, - assigned_at, - due_date, - status, - completed_at, - goals (*) - `) - .eq('user_id', req.params.userId); - if (error) throw new Error(error.message); - res.json(data); - } catch (error: unknown) { - res.status(500).json({ error: error instanceof Error ? error.message : 'An unknown error occurred' }); - } -}); - // Get goal by goal_id router.get('/:goalId', async (req, res) => { try { @@ -148,7 +125,7 @@ router.delete('/:id', async (req, res) => { } }); -// Update quiz_selected for multiple user goals +// Update status for multiple user goals. Used by the quiz router.post('/update-quiz-selected', async (req, res) => { const { userId, goalIds } = req.body; @@ -174,8 +151,7 @@ router.post('/update-quiz-selected', async (req, res) => { const newUserGoals = goalsToCreate.map(goalId => ({ user_id: userId, goal_id: goalId, - quiz_selected: true, - status: 'not_done' + status: 'focused' })); const { error: insertError } = await supabase @@ -188,7 +164,7 @@ router.post('/update-quiz-selected', async (req, res) => { // Update existing user_goals entries const { data: updatedGoals, error: updateError } = await supabase .from('user_goals') - .update({ quiz_selected: true }) + .update({ status: 'focused' }) .eq('user_id', userId) .in('goal_id', goalIds) .select(); @@ -208,90 +184,107 @@ router.post('/update-quiz-selected', async (req, res) => { } }); -router.get('/user-quiz-goals/:userId', async (req, res) => { +// Get all goals sorted by status: completed > focused > not_done +router.get('/user/:userId', async (req, res) => { const userId = parseInt(req.params.userId); if (isNaN(userId)) { - return res.status(400).json({ error: 'Invalid userId provided' }); + return res.status(400).json({ error: 'Invalid userId provided' }); } try { - // 1. Fetch user_goals with quiz_selected = true - const { data: selectedUserGoals, error: selectedUserGoalsError } = await supabase - .from('user_goals') - .select(` - goal_id, - quiz_selected, - goals (*) - `) - .eq('user_id', userId) - .eq('quiz_selected', true) - .order('sort_order', { referencedTable: 'goals' }); - - if (selectedUserGoalsError) throw new Error(selectedUserGoalsError.message); - - // 2. Fetch remaining user_goals (quiz_selected = false) - const { data: remainingUserGoals, error: remainingUserGoalsError } = await supabase - .from('user_goals') - .select(` - goal_id, - quiz_selected, - goals (*) - `) - .eq('user_id', userId) - .eq('quiz_selected', false) - .order('sort_order', { referencedTable: 'goals' }); - - if (remainingUserGoalsError) throw new Error(remainingUserGoalsError.message); - - // 3. Fetch all goals - const { data: allGoals, error: allGoalsError } = await supabase - .from('goals') - .select('*') - .order('sort_order'); - - if (allGoalsError) throw new Error(allGoalsError.message); - - // Create a set of goal IDs that are in user_goals - const userGoalIds = new Set([ - ...selectedUserGoals.map(ug => ug.goal_id), - ...remainingUserGoals.map(ug => ug.goal_id) - ]); - - // Combine the results in the specified order - const combinedGoals = [ - ...selectedUserGoals.map(ug => ({...ug.goals, quiz_selected: true})), - ...remainingUserGoals.map(ug => ({...ug.goals, quiz_selected: false})), - ...allGoals.filter(goal => !userGoalIds.has(goal.id)).map(goal => ({...goal, quiz_selected: null})) - ]; - - // Group goals by category - const goalsByCategory = combinedGoals.reduce((acc, goal) => { - if (!acc[goal.category]) { - acc[goal.category] = []; - } - acc[goal.category].push(goal); - return acc; - }, {}); - - // Create the final structure - const categorizedGoals = Object.entries(goalsByCategory).map(([category, goals]) => ({ - category, - goals - })); - - res.json({ - message: 'Goals fetched successfully', - categorizedGoals - }); + // 1. Fetch user_goals with status 'completed' + const { data: completedUserGoals, error: completedUserGoalsError } = await supabase + .from('user_goals') + .select(` + goal_id, + status, + goals (*) + `) + .eq('user_id', userId) + .eq('status', 'completed') + .order('sort_order', { referencedTable: 'goals' }); + + if (completedUserGoalsError) throw new Error(completedUserGoalsError.message); + + // 2. Fetch user_goals with status 'focused' + const { data: focusedUserGoals, error: focusedUserGoalsError } = await supabase + .from('user_goals') + .select(` + goal_id, + status, + goals (*) + `) + .eq('user_id', userId) + .eq('status', 'focused') + .order('sort_order', { referencedTable: 'goals' }); + + if (focusedUserGoalsError) throw new Error(focusedUserGoalsError.message); + + // 3. Fetch user_goals with status 'not_done' + const { data: notDoneUserGoals, error: notDoneUserGoalsError } = await supabase + .from('user_goals') + .select(` + goal_id, + status, + goals (*) + `) + .eq('user_id', userId) + .eq('status', 'not_done') + .order('sort_order', { referencedTable: 'goals' }); + + if (notDoneUserGoalsError) throw new Error(notDoneUserGoalsError.message); + + // 4. Fetch all goals + const { data: allGoals, error: allGoalsError } = await supabase + .from('goals') + .select('*') + .order('sort_order'); + + if (allGoalsError) throw new Error(allGoalsError.message); + + // Create a set of goal IDs that are in user_goals + const userGoalIds = new Set([ + ...completedUserGoals.map(ug => ug.goal_id), + ...focusedUserGoals.map(ug => ug.goal_id), + ...notDoneUserGoals.map(ug => ug.goal_id) + ]); + + // Combine the results in the specified order + const combinedGoals = [ + ...completedUserGoals.map(ug => ({ ...ug.goals, status: 'completed' })), + ...focusedUserGoals.map(ug => ({ ...ug.goals, status: 'focused' })), + ...notDoneUserGoals.map(ug => ({ ...ug.goals, status: 'not_done' })), + ...allGoals.filter(goal => !userGoalIds.has(goal.id)).map(goal => ({ ...goal, status: null })) + ]; + + // Group goals by category + const goalsByCategory = combinedGoals.reduce((acc, goal) => { + if (!acc[goal.category]) { + acc[goal.category] = []; + } + acc[goal.category].push(goal); + return acc; + }, {}); + + // Create the final structure + const categorizedGoals = Object.entries(goalsByCategory).map(([category, goals]) => ({ + category, + goals + })); + + res.json({ + message: 'Goals fetched successfully', + categorizedGoals + }); } catch (error: unknown) { - res.status(500).json({ - error: - error instanceof Error ? error.message : 'An unknown error occurred', - }); + res.status(500).json({ + error: error instanceof Error ? error.message : 'An unknown error occurred', + }); } }); + export default router; \ No newline at end of file diff --git a/supabase/migrations/20240729115609_initial_schema.sql b/supabase/migrations/20240729115609_initial_schema.sql index 2e6dc09..1cccbf8 100644 --- a/supabase/migrations/20240729115609_initial_schema.sql +++ b/supabase/migrations/20240729115609_initial_schema.sql @@ -64,8 +64,9 @@ CREATE TABLE IF NOT EXISTS goal_id bigint null, assigned_at timestamp with time zone not null default now(), due_date timestamp with time zone null, - status text null default 'not done', - quiz_selected boolean null default false, + status text null default 'not_done', + focus_origin text null default null, + -- quiz_selected boolean null default false, completed_at timestamp with time zone null, constraint user_goals_pkey primary key (id), constraint user_goals_user_id_goal_id_key unique (user_id, goal_id), @@ -75,9 +76,9 @@ CREATE TABLE IF NOT EXISTS ( status = any ( array[ - 'not_done'::text, - 'in_progress'::text, - 'completed'::text + 'completed'::text, + 'focused'::text, + 'not_done'::text ] ) ) diff --git a/supabase/seed.sql b/supabase/seed.sql index 34c7a2e..68a5f31 100644 --- a/supabase/seed.sql +++ b/supabase/seed.sql @@ -55,20 +55,20 @@ INSERT INTO users (created_at, email, first_name, last_name, password) VALUES -- Insert user_goals INSERT INTO user_goals (user_id, goal_id, assigned_at, due_date, status, completed_at) VALUES -(1, 1, '2023-07-01T10:00:00Z', '2023-12-31T23:59:59Z', 'in_progress', NULL), -(1, 2, '2023-07-02T11:30:00Z', '2023-11-30T23:59:59Z', 'completed', '2023-10-15T14:45:00Z'), +(1, 1, '2023-07-01T10:00:00Z', '2023-12-31T23:59:59Z', 'completed', NULL), +(1, 2, '2023-07-02T11:30:00Z', '2023-11-30T23:59:59Z', 'focused', '2023-10-15T14:45:00Z'), (1, 3, '2023-07-03T09:15:00Z', '2024-01-31T23:59:59Z', 'not_done', NULL), -(1, 4, '2023-07-04T14:00:00Z', '2023-12-15T23:59:59Z', 'in_progress', NULL), +(1, 4, '2023-07-04T14:00:00Z', '2023-12-15T23:59:59Z', 'completed', NULL), (1, 5, '2023-07-05T16:45:00Z', '2024-02-29T23:59:59Z', 'not_done', NULL), -(2, 1, '2023-07-06T08:30:00Z', '2023-10-31T23:59:59Z', 'completed', '2023-09-30T18:20:00Z'), -(2, 2, '2023-07-07T13:00:00Z', '2024-03-31T23:59:59Z', 'in_progress', NULL), +(2, 1, '2023-07-06T08:30:00Z', '2023-10-31T23:59:59Z', 'focused', '2023-09-30T18:20:00Z'), +(2, 2, '2023-07-07T13:00:00Z', '2024-03-31T23:59:59Z', 'completed', NULL), (2, 3, '2023-07-08T11:45:00Z', '2023-12-31T23:59:59Z', 'not_done', NULL), -(2, 4, '2023-07-09T10:30:00Z', '2024-01-15T23:59:59Z', 'in_progress', NULL), +(2, 4, '2023-07-09T10:30:00Z', '2024-01-15T23:59:59Z', 'not_done', NULL), (3, 1, '2023-07-10T15:15:00Z', '2023-11-30T23:59:59Z', 'completed', '2023-11-15T09:30:00Z'), -(3, 2, '2023-07-11T09:45:00Z', '2024-02-28T23:59:59Z', 'in_progress', NULL), +(3, 2, '2023-07-11T09:45:00Z', '2024-02-28T23:59:59Z', 'focused', NULL), (3, 3, '2023-07-12T14:30:00Z', '2023-12-31T23:59:59Z', 'not_done', NULL), (4, 1, '2023-07-13T11:00:00Z', '2023-10-31T23:59:59Z', 'completed', '2023-10-20T16:45:00Z'), -(4, 2, '2023-07-14T16:30:00Z', '2024-01-31T23:59:59Z', 'in_progress', NULL), +(4, 2, '2023-07-14T16:30:00Z', '2024-01-31T23:59:59Z', 'not_done', NULL), (5, 1, '2023-07-15T10:45:00Z', '2023-12-15T23:59:59Z', 'not_done', NULL); -- Insert questions diff --git a/tests/goals.http b/tests/goals.http index e4c2806..b668e76 100644 --- a/tests/goals.http +++ b/tests/goals.http @@ -1,7 +1,20 @@ ### Goals Endpoints ### -# Get all goals for a user REPURPOSE -GET http://localhost:3000/goals/user/2 +# Get all goals for a user +GET http://localhost:3000/goals/user/1 +# How to use it: +# fetch(`http://localhost:3000/user-quiz-goals/${userId}`) +# .then(response => response.json()) +# .then(data => { +# console.log(data.categorizedGoals); +# // data.categorizedGoals will be an array of objects like: +# // [ +# // { category: "Category1", goals: [...] }, +# // { category: "Category2", goals: [...] }, +# // ... +# // ] +# }) +# .catch(error => console.error('Error:', error)); ### Get goal by goal_id GET http://localhost:3000/goals/4 @@ -68,20 +81,4 @@ Content-Type: application/json # }) # .then(response => response.json()) # .then(result => console.log(result)) -# .catch(error => console.error('Error:', error)); - -### Given a user get all the goals in the right order -GET http://localhost:3000/goals/user-quiz-goals/1 -# How to use it: -# fetch(`http://localhost:3000/user-quiz-goals/${userId}`) -# .then(response => response.json()) -# .then(data => { -# console.log(data.categorizedGoals); -# // data.categorizedGoals will be an array of objects like: -# // [ -# // { category: "Category1", goals: [...] }, -# // { category: "Category2", goals: [...] }, -# // ... -# // ] -# }) # .catch(error => console.error('Error:', error)); \ No newline at end of file