// server3.js import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import dotenv from 'dotenv'; import { open } from 'sqlite'; import sqlite3 from 'sqlite3'; import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; import path from 'path'; import { fileURLToPath } from 'url'; // If you still need the projection logic somewhere else import { simulateFinancialProjection } from '../src/utils/FinancialProjectionService.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); dotenv.config({ path: path.resolve(__dirname, '..', '.env') }); const app = express(); const PORT = process.env.PREMIUM_PORT || 5002; let db; const initDB = async () => { try { db = await open({ filename: '/home/jcoakley/aptiva-dev1-app/user_profile.db', driver: sqlite3.Database }); console.log('Connected to user_profile.db for Premium Services.'); } catch (error) { console.error('Error connecting to premium database:', error); } }; initDB(); app.use(helmet()); app.use(express.json()); const allowedOrigins = ['https://dev1.aptivaai.com']; app.use(cors({ origin: allowedOrigins, credentials: true })); const authenticatePremiumUser = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'Premium authorization required' }); try { const SECRET_KEY = process.env.SECRET_KEY || 'supersecurekey'; const { userId } = jwt.verify(token, SECRET_KEY); req.userId = userId; next(); } catch (error) { return res.status(403).json({ error: 'Invalid or expired token' }); } }; /* ------------------------------------------------------------------ CAREER PROFILE ENDPOINTS (Renamed from planned-path to career-profile) ------------------------------------------------------------------ */ // GET the latest selected career profile app.get('/api/premium/career-profile/latest', authenticatePremiumUser, async (req, res) => { try { const row = await db.get(` SELECT * FROM career_paths WHERE user_id = ? ORDER BY start_date DESC LIMIT 1 `, [req.userId]); res.json(row || {}); } catch (error) { console.error('Error fetching latest career profile:', error); res.status(500).json({ error: 'Failed to fetch latest career profile' }); } }); // GET all career profiles for the user app.get('/api/premium/career-profile/all', authenticatePremiumUser, async (req, res) => { try { const rows = await db.all(` SELECT * FROM career_paths WHERE user_id = ? ORDER BY start_date ASC `, [req.userId]); res.json({ careerPaths: rows }); } catch (error) { console.error('Error fetching career profiles:', error); res.status(500).json({ error: 'Failed to fetch career profiles' }); } }); // POST a new career profile app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res) => { const { career_name, status, start_date, projected_end_date, college_enrollment_status, currently_working } = req.body; // If you need to ensure the user gave us a career_name: if (!career_name) { return res.status(400).json({ error: 'career_name is required.' }); } try { const newCareerPathId = uuidv4(); const now = new Date().toISOString(); await db.run(` INSERT INTO career_paths ( id, user_id, career_name, status, start_date, projected_end_date, college_enrollment_status, currently_working, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(user_id, career_name) DO UPDATE SET status = excluded.status, start_date = excluded.start_date, projected_end_date = excluded.projected_end_date, college_enrollment_status = excluded.college_enrollment_status, currently_working = excluded.currently_working, updated_at = ? `, [ newCareerPathId, // id req.userId, // user_id career_name, // career_name status || 'planned', // status (if null, default to 'planned') start_date || now, projected_end_date || null, college_enrollment_status || null, currently_working || null, now, // created_at now, // updated_at on initial insert now // updated_at on conflict ]); // Optionally fetch the row's ID after upsert const result = await db.get(` SELECT id FROM career_paths WHERE user_id = ? AND career_name = ? `, [req.userId, career_name]); res.status(200).json({ message: 'Career profile upserted.', career_path_id: result?.id }); } catch (error) { console.error('Error upserting career profile:', error); res.status(500).json({ error: 'Failed to upsert career profile.' }); } }); app.post('/api/premium/milestone', authenticatePremiumUser, async (req, res) => { try { const body = req.body; // CASE 1: If client sent { milestones: [ ... ] }, do a bulk insert if (Array.isArray(body.milestones)) { const createdMilestones = []; for (const m of body.milestones) { const { milestone_type, title, description, date, career_path_id, progress, status, new_salary } = m; // Validate some required fields if (!milestone_type || !title || !date || !career_path_id) { // Optionally handle partial errors, but let's do a quick check return res.status(400).json({ error: 'One or more milestones missing required fields', details: m }); } const id = uuidv4(); const now = new Date().toISOString(); await db.run(` INSERT INTO milestones ( id, user_id, career_path_id, milestone_type, title, description, date, progress, status, new_salary, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ id, req.userId, career_path_id, milestone_type, title, description || '', date, progress || 0, status || 'planned', new_salary || null, now, now ]); createdMilestones.push({ id, user_id: req.userId, career_path_id, milestone_type, title, description: description || '', date, progress: progress || 0, status: status || 'planned', new_salary: new_salary || null, tasks: [] }); } // Return array of created milestones return res.status(201).json(createdMilestones); } // CASE 2: Handle single milestone (the old logic) const { milestone_type, title, description, date, career_path_id, progress, status, new_salary } = body; if (!milestone_type || !title || !date || !career_path_id) { return res.status(400).json({ error: 'Missing required fields', details: { milestone_type, title, date, career_path_id } }); } const id = uuidv4(); const now = new Date().toISOString(); await db.run(` INSERT INTO milestones ( id, user_id, career_path_id, milestone_type, title, description, date, progress, status, new_salary, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ id, req.userId, career_path_id, milestone_type, title, description || '', date, progress || 0, status || 'planned', new_salary || null, now, now ]); // Return the newly created single milestone object const newMilestone = { id, user_id: req.userId, career_path_id, milestone_type, title, description: description || '', date, progress: progress || 0, status: status || 'planned', new_salary: new_salary || null, tasks: [] }; res.status(201).json(newMilestone); } catch (err) { console.error('Error creating milestone(s):', err); res.status(500).json({ error: 'Failed to create milestone(s).' }); } }); app.put('/api/premium/milestones/:milestoneId', authenticatePremiumUser, async (req, res) => { try { const { milestoneId } = req.params; const { milestone_type, title, description, date, career_path_id, progress, status, new_salary } = req.body; // Check if milestone exists and belongs to user const existing = await db.get(` SELECT * FROM milestones WHERE id = ? AND user_id = ? `, [milestoneId, req.userId]); if (!existing) { return res.status(404).json({ error: 'Milestone not found or not yours.' }); } // Update const now = new Date().toISOString(); await db.run(` UPDATE milestones SET milestone_type = ?, title = ?, description = ?, date = ?, career_path_id = ?, progress = ?, status = ?, new_salary = ?, updated_at = ? WHERE id = ? `, [ milestone_type || existing.milestone_type, title || existing.title, description || existing.description, date || existing.date, career_path_id || existing.career_path_id, progress != null ? progress : existing.progress, status || existing.status, new_salary != null ? new_salary : existing.new_salary, now, milestoneId ]); // Return the updated record with tasks const updatedMilestoneRow = await db.get(` SELECT * FROM milestones WHERE id = ? `, [milestoneId]); // Fetch tasks for this milestone const tasks = await db.all(` SELECT * FROM tasks WHERE milestone_id = ? `, [milestoneId]); const updatedMilestone = { ...updatedMilestoneRow, tasks: tasks || [] }; res.json(updatedMilestone); } catch (err) { console.error('Error updating milestone:', err); res.status(500).json({ error: 'Failed to update milestone.' }); } }); app.get('/api/premium/milestones', authenticatePremiumUser, async (req, res) => { const { careerPathId } = req.query; try { // 1. Fetch the milestones for this user + path const milestones = await db.all(` SELECT * FROM milestones WHERE user_id = ? AND career_path_id = ? `, [req.userId, careerPathId]); // 2. For each milestone, fetch tasks const milestoneIds = milestones.map(m => m.id); let tasksByMilestone = {}; if (milestoneIds.length > 0) { const tasks = await db.all(` SELECT * FROM tasks WHERE milestone_id IN (${milestoneIds.map(() => '?').join(',')}) `, milestoneIds); tasksByMilestone = tasks.reduce((acc, t) => { if (!acc[t.milestone_id]) acc[t.milestone_id] = []; acc[t.milestone_id].push(t); return acc; }, {}); } // 3. Attach tasks to each milestone object const milestonesWithTasks = milestones.map(m => ({ ...m, tasks: tasksByMilestone[m.id] || [] })); res.json({ milestones: milestonesWithTasks }); } catch (err) { console.error('Error fetching milestones with tasks:', err); res.status(500).json({ error: 'Failed to fetch milestones.' }); } }); /* ------------------------------------------------------------------ FINANCIAL PROFILES (Renamed emergency_contribution) ------------------------------------------------------------------ */ app.get('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => { try { const row = await db.get(` SELECT * FROM financial_profiles WHERE user_id = ? `, [req.userId]); res.json(row || {}); } catch (error) { console.error('Error fetching financial profile:', error); res.status(500).json({ error: 'Failed to fetch financial profile' }); } }); app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => { const { current_salary, additional_income, monthly_expenses, monthly_debt_payments, retirement_savings, retirement_contribution, emergency_fund, emergency_contribution, extra_cash_emergency_pct, extra_cash_retirement_pct } = req.body; try { // Check if row exists const existing = await db.get(` SELECT user_id FROM financial_profiles WHERE user_id = ? `, [req.userId]); if (!existing) { // Insert new row await db.run(` INSERT INTO financial_profiles ( user_id, current_salary, additional_income, monthly_expenses, monthly_debt_payments, retirement_savings, emergency_fund, retirement_contribution, emergency_contribution, extra_cash_emergency_pct, extra_cash_retirement_pct, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) `, [ req.userId, current_salary || 0, additional_income || 0, monthly_expenses || 0, monthly_debt_payments || 0, retirement_savings || 0, emergency_fund || 0, retirement_contribution || 0, emergency_contribution || 0, // store new field extra_cash_emergency_pct || 0, extra_cash_retirement_pct || 0 ]); } else { // Update existing await db.run(` UPDATE financial_profiles SET current_salary = ?, additional_income = ?, monthly_expenses = ?, monthly_debt_payments = ?, retirement_savings = ?, emergency_fund = ?, retirement_contribution = ?, emergency_contribution = ?, extra_cash_emergency_pct = ?, extra_cash_retirement_pct = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? `, [ current_salary || 0, additional_income || 0, monthly_expenses || 0, monthly_debt_payments || 0, retirement_savings || 0, emergency_fund || 0, retirement_contribution || 0, emergency_contribution || 0, // updated field extra_cash_emergency_pct || 0, extra_cash_retirement_pct || 0, req.userId ]); } res.json({ message: 'Financial profile saved/updated.' }); } catch (error) { console.error('Error saving financial profile:', error); res.status(500).json({ error: 'Failed to save financial profile.' }); } }); /* ------------------------------------------------------------------ COLLEGE PROFILES ------------------------------------------------------------------ */ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, res) => { const { career_path_id, selected_school, selected_program, program_type, is_in_state, is_in_district, college_enrollment_status, is_online, credit_hours_per_year, credit_hours_required, hours_completed, program_length, expected_graduation, existing_college_debt, interest_rate, loan_term, loan_deferral_until_graduation, extra_payment, expected_salary, academic_calendar, annual_financial_aid, tuition, tuition_paid } = req.body; try { const user_id = req.userId; // For upsert, we either generate a new ID or (optionally) do a lookup for the old row's ID if you want to preserve it // For simplicity, let's generate a new ID each time. We'll handle the conflict resolution below. const newId = uuidv4(); // Now do an INSERT ... ON CONFLICT(...fields...). In SQLite, we reference 'excluded' for the new values. await db.run(` INSERT INTO college_profiles ( id, user_id, career_path_id, selected_school, selected_program, program_type, is_in_state, is_in_district, college_enrollment_status, annual_financial_aid, is_online, credit_hours_per_year, hours_completed, program_length, credit_hours_required, expected_graduation, existing_college_debt, interest_rate, loan_term, loan_deferral_until_graduation, extra_payment, expected_salary, academic_calendar, tuition, tuition_paid, created_at, updated_at ) VALUES ( :id, :user_id, :career_path_id, :selected_school, :selected_program, :program_type, :is_in_state, :is_in_district, :college_enrollment_status, :annual_financial_aid, :is_online, :credit_hours_per_year, :hours_completed, :program_length, :credit_hours_required, :expected_graduation, :existing_college_debt, :interest_rate, :loan_term, :loan_deferral_until_graduation, :extra_payment, :expected_salary, :academic_calendar, :tuition, :tuition_paid, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ) -- The magic: ON CONFLICT(user_id, career_path_id, selected_school, selected_program, program_type) DO UPDATE SET is_in_state = excluded.is_in_state, is_in_district = excluded.is_in_district, college_enrollment_status = excluded.college_enrollment_status, annual_financial_aid = excluded.annual_financial_aid, is_online = excluded.is_online, credit_hours_per_year = excluded.credit_hours_per_year, hours_completed = excluded.hours_completed, program_length = excluded.program_length, credit_hours_required = excluded.credit_hours_required, expected_graduation = excluded.expected_graduation, existing_college_debt = excluded.existing_college_debt, interest_rate = excluded.interest_rate, loan_term = excluded.loan_term, loan_deferral_until_graduation = excluded.loan_deferral_until_graduation, extra_payment = excluded.extra_payment, expected_salary = excluded.expected_salary, academic_calendar = excluded.academic_calendar, tuition = excluded.tuition, tuition_paid = excluded.tuition_paid, updated_at = CURRENT_TIMESTAMP ; `, { ':id': newId, ':user_id': user_id, ':career_path_id': career_path_id, ':selected_school': selected_school, ':selected_program': selected_program, ':program_type': program_type || null, ':is_in_state': is_in_state ? 1 : 0, ':is_in_district': is_in_district ? 1 : 0, ':college_enrollment_status': college_enrollment_status || null, ':annual_financial_aid': annual_financial_aid || 0, ':is_online': is_online ? 1 : 0, ':credit_hours_per_year': credit_hours_per_year || 0, ':hours_completed': hours_completed || 0, ':program_length': program_length || 0, ':credit_hours_required': credit_hours_required || 0, ':expected_graduation': expected_graduation || null, ':existing_college_debt': existing_college_debt || 0, ':interest_rate': interest_rate || 0, ':loan_term': loan_term || 10, ':loan_deferral_until_graduation': loan_deferral_until_graduation ? 1 : 0, ':extra_payment': extra_payment || 0, ':expected_salary': expected_salary || 0, ':academic_calendar': academic_calendar || 'semester', ':tuition': tuition || 0, ':tuition_paid': tuition_paid || 0 }); // If it was a conflict, the existing row is updated. // If not, a new row is inserted with ID = newId. res.status(201).json({ message: 'College profile upsert done.', // You might do an extra SELECT here to find which ID the final row uses if you need it }); } catch (error) { console.error('Error saving college profile:', error); res.status(500).json({ error: 'Failed to save college profile.' }); } }); app.get('/api/premium/college-profile', authenticatePremiumUser, async (req, res) => { const { careerPathId } = req.query; // find row const row = await db.get(` SELECT * FROM college_profiles WHERE user_id = ? AND career_path_id = ? ORDER BY created_at DESC LIMIT 1 `, [req.userId, careerPathId]); res.json(row || {}); }); /* ------------------------------------------------------------------ FINANCIAL PROJECTIONS ------------------------------------------------------------------ */ app.post('/api/premium/financial-projection/:careerPathId', authenticatePremiumUser, async (req, res) => { const { careerPathId } = req.params; const { projectionData, loanPaidOffMonth, finalEmergencySavings, finalRetirementSavings, finalLoanBalance } = req.body; try { const projectionId = uuidv4(); await db.run(` INSERT INTO financial_projections ( id, user_id, career_path_id, projection_data, loan_paid_off_month, final_emergency_savings, final_retirement_savings, final_loan_balance, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) `, [ projectionId, req.userId, careerPathId, JSON.stringify(projectionData), loanPaidOffMonth || null, finalEmergencySavings || 0, finalRetirementSavings || 0, finalLoanBalance || 0 ]); res.status(201).json({ message: 'Financial projection saved.', projectionId }); } catch (error) { console.error('Error saving financial projection:', error); res.status(500).json({ error: 'Failed to save financial projection.' }); } }); app.get('/api/premium/financial-projection/:careerPathId', authenticatePremiumUser, async (req, res) => { const { careerPathId } = req.params; try { const row = await db.get(` SELECT projection_data, loan_paid_off_month, final_emergency_savings, final_retirement_savings, final_loan_balance FROM financial_projections WHERE user_id = ? AND career_path_id = ? ORDER BY created_at DESC LIMIT 1 `, [req.userId, careerPathId]); if (!row) { return res.status(404).json({ error: 'Projection not found.' }); } const parsedProjectionData = JSON.parse(row.projection_data); res.status(200).json({ projectionData: parsedProjectionData, loanPaidOffMonth: row.loan_paid_off_month, finalEmergencySavings: row.final_emergency_savings, finalRetirementSavings: row.final_retirement_savings, finalLoanBalance: row.final_loan_balance }); } catch (error) { console.error('Error fetching financial projection:', error); res.status(500).json({ error: 'Failed to fetch financial projection.' }); } }); // POST create a new task app.post('/api/premium/tasks', authenticatePremiumUser, async (req, res) => { try { const { milestone_id, title, description, due_date } = req.body; // Ensure required fields if (!milestone_id || !title) { return res.status(400).json({ error: 'Missing required fields', details: { milestone_id, title } }); } // Confirm milestone is owned by this user const milestone = await db.get(` SELECT user_id FROM milestones WHERE id = ? `, [milestone_id]); if (!milestone || milestone.user_id !== req.userId) { return res.status(403).json({ error: 'Milestone not found or not yours.' }); } const taskId = uuidv4(); const now = new Date().toISOString(); // Insert the new task await db.run(` INSERT INTO tasks ( id, milestone_id, user_id, title, description, due_date, status, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, 'not_started', ?, ?) `, [ taskId, milestone_id, req.userId, title, description || '', due_date || null, now, now ]); // Return the newly created task as JSON const newTask = { id: taskId, milestone_id, user_id: req.userId, title, description: description || '', due_date: due_date || null, status: 'not_started' }; res.status(201).json(newTask); } catch (err) { console.error('Error creating task:', err); res.status(500).json({ error: 'Failed to create task.' }); } }); // GET tasks for a milestone app.get('/api/premium/milestones', authenticatePremiumUser, async (req, res) => { const { careerPathId } = req.query; try { // 1. Fetch the milestones for this user + path const milestones = await db.all(` SELECT * FROM milestones WHERE user_id = ? AND career_path_id = ? `, [req.userId, careerPathId]); // 2. For each milestone, fetch tasks (or do a single join—see note below) // We'll do it in Node code for clarity: const milestoneIds = milestones.map(m => m.id); let tasksByMilestone = {}; if (milestoneIds.length > 0) { const tasks = await db.all(` SELECT * FROM tasks WHERE milestone_id IN (${milestoneIds.map(() => '?').join(',')}) `, milestoneIds); // Group tasks by milestone_id tasksByMilestone = tasks.reduce((acc, t) => { if (!acc[t.milestone_id]) acc[t.milestone_id] = []; acc[t.milestone_id].push(t); return acc; }, {}); } // 3. Attach tasks to each milestone object const milestonesWithTasks = milestones.map(m => ({ ...m, tasks: tasksByMilestone[m.id] || [] })); res.json({ milestones: milestonesWithTasks }); } catch (err) { console.error('Error fetching milestones with tasks:', err); res.status(500).json({ error: 'Failed to fetch milestones.' }); } }); /************************************************************************ * MILESTONE IMPACTS ENDPOINTS ************************************************************************/ app.get('/api/premium/milestone-impacts', authenticatePremiumUser, async (req, res) => { try { // Example: GET /api/premium/milestone-impacts?milestone_id=12345 const { milestone_id } = req.query; if (!milestone_id) { return res.status(400).json({ error: 'milestone_id is required.' }); } // Verify the milestone belongs to this user const milestoneRow = await db.get(` SELECT user_id FROM milestones WHERE id = ? `, [milestone_id]); if (!milestoneRow || milestoneRow.user_id !== req.userId) { return res.status(404).json({ error: 'Milestone not found or not owned by this user.' }); } // Fetch all impacts for that milestone const impacts = await db.all(` SELECT id, milestone_id, impact_type, direction, amount, start_date, end_date, created_at, updated_at FROM milestone_impacts WHERE milestone_id = ? ORDER BY created_at ASC `, [milestone_id]); res.json({ impacts }); } catch (err) { console.error('Error fetching milestone impacts:', err); res.status(500).json({ error: 'Failed to fetch milestone impacts.' }); } }); app.post('/api/premium/milestone-impacts', authenticatePremiumUser, async (req, res) => { try { const { milestone_id, impact_type, direction = 'subtract', amount = 0, start_date = null, end_date = null, created_at, updated_at } = req.body; // Basic checks if (!milestone_id || !impact_type) { return res.status(400).json({ error: 'milestone_id and impact_type are required.' }); } // Confirm user owns the milestone const milestoneRow = await db.get(` SELECT user_id FROM milestones WHERE id = ? `, [milestone_id]); if (!milestoneRow || milestoneRow.user_id !== req.userId) { return res.status(403).json({ error: 'Milestone not found or not owned by this user.' }); } // Generate UUID for this new Impact const newUUID = uuidv4(); const now = new Date().toISOString(); const finalCreated = created_at || now; const finalUpdated = updated_at || now; // Insert row WITH that UUID into the "id" column await db.run(` INSERT INTO milestone_impacts ( id, milestone_id, impact_type, direction, amount, start_date, end_date, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ newUUID, milestone_id, impact_type, direction, amount, start_date, end_date, finalCreated, finalUpdated ]); // Fetch & return the inserted row const insertedRow = await db.get(` SELECT id, milestone_id, impact_type, direction, amount, start_date, end_date, created_at, updated_at FROM milestone_impacts WHERE id = ? `, [newUUID]); return res.status(201).json(insertedRow); } catch (err) { console.error('Error creating milestone impact:', err); return res.status(500).json({ error: 'Failed to create milestone impact.' }); } }); /************************************************************************ * UPDATE an existing milestone impact (PUT) ************************************************************************/ app.put('/api/premium/milestone-impacts/:impactId', authenticatePremiumUser, async (req, res) => { try { const { impactId } = req.params; const { milestone_id, impact_type, direction = 'subtract', amount = 0, start_date = null, end_date = null } = req.body; // 1) Check this impact belongs to user const existing = await db.get(` SELECT mi.id, m.user_id FROM milestone_impacts mi JOIN milestones m ON mi.milestone_id = m.id WHERE mi.id = ? `, [impactId]); if (!existing || existing.user_id !== req.userId) { return res.status(404).json({ error: 'Impact not found or not owned by user.' }); } const now = new Date().toISOString(); // 2) Update await db.run(` UPDATE milestone_impacts SET milestone_id = ?, impact_type = ?, direction = ?, amount = ?, start_date = ?, end_date = ?, updated_at = ? WHERE id = ? `, [ milestone_id, impact_type, direction, amount, start_date, end_date, now, impactId ]); // 3) Return updated const updatedRow = await db.get(` SELECT id, milestone_id, impact_type, direction, amount, start_date, end_date, created_at, updated_at FROM milestone_impacts WHERE id = ? `, [impactId]); res.json(updatedRow); } catch (err) { console.error('Error updating milestone impact:', err); res.status(500).json({ error: 'Failed to update milestone impact.' }); } }); /************************************************************************ * DELETE an existing milestone impact ************************************************************************/ app.delete('/api/premium/milestone-impacts/:impactId', authenticatePremiumUser, async (req, res) => { try { const { impactId } = req.params; // 1) check ownership const existing = await db.get(` SELECT mi.id, m.user_id FROM milestone_impacts mi JOIN milestones m ON mi.milestone_id = m.id WHERE mi.id = ? `, [impactId]); if (!existing || existing.user_id !== req.userId) { return res.status(404).json({ error: 'Impact not found or not owned by user.' }); } // 2) Delete await db.run(` DELETE FROM milestone_impacts WHERE id = ? `, [impactId]); res.json({ message: 'Impact deleted successfully.' }); } catch (err) { console.error('Error deleting milestone impact:', err); res.status(500).json({ error: 'Failed to delete milestone impact.' }); } }); app.use((req, res) => { console.warn(`No route matched for ${req.method} ${req.originalUrl}`); res.status(404).json({ error: 'Not found' }); }); app.listen(PORT, () => { console.log(`Premium server running on http://localhost:${PORT}`); });