// server3.js - Premium Services API 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'; 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' }); } }; // Get latest selected planned path app.get('/api/premium/planned-path/latest', authenticatePremiumUser, async (req, res) => { try { const row = await db.get( `SELECT * FROM career_path WHERE user_id = ? ORDER BY start_date DESC LIMIT 1`, [req.userId] ); res.json(row || {}); } catch (error) { console.error('Error fetching latest career path:', error); res.status(500).json({ error: 'Failed to fetch latest planned path' }); } }); // Get all planned paths for the user app.get('/api/premium/planned-path/all', authenticatePremiumUser, async (req, res) => { try { const rows = await db.all( `SELECT * FROM career_path WHERE user_id = ? ORDER BY start_date ASC`, [req.userId] ); res.json({ careerPath: rows }); } catch (error) { console.error('Error fetching career paths:', error); res.status(500).json({ error: 'Failed to fetch planned paths' }); } }); // Save a new planned path app.post('/api/premium/planned-path', authenticatePremiumUser, async (req, res) => { let { career_name } = req.body; if (!career_name) { return res.status(400).json({ error: 'Career name is required.' }); } try { // Ensure that career_name is always a string if (typeof career_name !== 'string') { console.warn('career_name was not a string. Converting to string.'); career_name = String(career_name); // Convert to string } // Check if the career path already exists for the user const existingCareerPath = await db.get( `SELECT id FROM career_path WHERE user_id = ? AND career_name = ?`, [req.userId, career_name] ); if (existingCareerPath) { return res.status(200).json({ message: 'Career path already exists. Would you like to reload it or create a new one?', career_path_id: existingCareerPath.id, action_required: 'reload_or_create' }); } // Define a new career path id and insert into the database const newCareerPathId = uuidv4(); await db.run( `INSERT INTO career_path (id, user_id, career_name) VALUES (?, ?, ?)`, [newCareerPathId, req.userId, career_name] ); res.status(201).json({ message: 'Career path saved.', career_path_id: newCareerPathId, action_required: 'new_created' }); } catch (error) { console.error('Error saving career path:', error); res.status(500).json({ error: 'Failed to save career path.' }); } }); // Save a new milestone app.post('/api/premium/milestone', authenticatePremiumUser, async (req, res) => { const rawMilestones = Array.isArray(req.body.milestones) ? req.body.milestones : [req.body]; const errors = []; const validMilestones = []; for (const [index, m] of rawMilestones.entries()) { const { milestone_type, title, description, date, career_path_id, salary_increase, status = 'planned', date_completed = null, context_snapshot = null, progress = 0, } = m; // Validate required fields if (!milestone_type || !title || !description || !date || !career_path_id) { errors.push({ index, error: 'Missing required fields', title, // <-- Add the title for identification date, details: { milestone_type: !milestone_type ? 'Required' : undefined, title: !title ? 'Required' : undefined, description: !description ? 'Required' : undefined, date: !date ? 'Required' : undefined, career_path_id: !career_path_id ? 'Required' : undefined, } }); continue; } validMilestones.push({ id: uuidv4(), // ✅ assign UUID for unique milestone ID user_id: req.userId, milestone_type, title, description, date, career_path_id, salary_increase: salary_increase || null, status, date_completed, context_snapshot, progress }); } if (errors.length) { console.warn('❗ Some milestones failed validation. Logging malformed records...'); console.warn(JSON.stringify(errors, null, 2)); return res.status(400).json({ error: 'Some milestones are invalid', errors }); } try { const insertPromises = validMilestones.map(m => db.run( `INSERT INTO milestones ( id, user_id, milestone_type, title, description, date, career_path_id, salary_increase, status, date_completed, context_snapshot, progress, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`, [ m.id, m.user_id, m.milestone_type, m.title, m.description, m.date, m.career_path_id, m.salary_increase, m.status, m.date_completed, m.context_snapshot, m.progress ] ) ); await Promise.all(insertPromises); res.status(201).json({ message: 'Milestones saved successfully', count: validMilestones.length }); } catch (error) { console.error('Error saving milestones:', error); res.status(500).json({ error: 'Failed to save milestones' }); } }); // Get all milestones app.get('/api/premium/milestones', authenticatePremiumUser, async (req, res) => { try { const { careerPathId } = req.query; if (!careerPathId) { return res.status(400).json({ error: 'careerPathId is required' }); } const milestones = await db.all( `SELECT * FROM milestones WHERE user_id = ? AND career_path_id = ? ORDER BY date ASC`, [req.userId, careerPathId] ); res.json({ milestones }); } catch (error) { console.error('Error fetching milestones:', error); res.status(500).json({ error: 'Failed to fetch milestones' }); } }); /// Update an existing milestone app.put('/api/premium/milestones/:id', authenticatePremiumUser, async (req, res) => { try { const { id } = req.params; const numericId = parseInt(id, 10); // 👈 Block-defined for SQLite safety if (isNaN(numericId)) { return res.status(400).json({ error: 'Invalid milestone ID' }); } const { milestone_type, title, description, date, progress, status, date_completed, salary_increase, context_snapshot, } = req.body; // Explicit required field validation if (!milestone_type || !title || !description || !date || progress === undefined) { return res.status(400).json({ error: 'Missing required fields', details: { milestone_type: !milestone_type ? 'Required' : undefined, title: !title ? 'Required' : undefined, description: !description ? 'Required' : undefined, date: !date ? 'Required' : undefined, progress: progress === undefined ? 'Required' : undefined, } }); } console.log('Updating milestone with:', { milestone_type, title, description, date, progress, status, date_completed, salary_increase, context_snapshot, id: numericId, userId: req.userId }); await db.run( `UPDATE milestones SET milestone_type = ?, title = ?, description = ?, date = ?, progress = ?, status = ?, date_completed = ?, salary_increase = ?, context_snapshot = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?`, [ milestone_type, title, description, date, progress || 0, status || 'planned', date_completed, salary_increase || null, context_snapshot || null, numericId, // 👈 used here in the query req.userId ] ); res.status(200).json({ message: 'Milestone updated successfully' }); } catch (error) { console.error('Error updating milestone:', error.message, error.stack); res.status(500).json({ error: 'Failed to update milestone' }); } }); app.delete('/api/premium/milestones/:id', authenticatePremiumUser, async (req, res) => { const { id } = req.params; try { await db.run(`DELETE FROM milestones WHERE id = ? AND user_id = ?`, [id, req.userId]); res.status(200).json({ message: 'Milestone deleted successfully' }); } catch (error) { console.error('Error deleting milestone:', error); res.status(500).json({ error: 'Failed to delete milestone' }); } }); // Archive current career to history app.post('/api/premium/career-history', authenticatePremiumUser, async (req, res) => { const { career_path_id, company } = req.body; if (!career_path_id || !company) { return res.status(400).json({ error: 'Career path ID and company are required' }); } try { const career = await db.get(`SELECT * FROM career_path WHERE id = ? AND user_id = ?`, [career_path_id, req.userId]); if (!career) { return res.status(404).json({ error: 'Career path not found' }); } await db.run( `INSERT INTO career_history (user_id, job_title, company, start_date) VALUES (?, ?, ?, DATE('now'))`, [req.userId, career.job_title, company] ); await db.run(`DELETE FROM career_path WHERE id = ?`, [career_path_id]); res.status(201).json({ message: 'Career moved to history successfully' }); } catch (error) { console.error('Error moving career to history:', error); res.status(500).json({ error: 'Failed to update career history' }); } }); app.get('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => { try { const row = await db.get(`SELECT * FROM financial_profile 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 { currentSalary, additionalIncome, monthlyExpenses, monthlyDebtPayments, retirementSavings, retirementContribution, emergencyFund, inCollege, expectedGraduation, partTimeIncome, tuitionPaid, collegeLoanTotal } = req.body; try { // Upsert-style logic: Check if exists const existing = await db.get(`SELECT id FROM financial_profile WHERE user_id = ?`, [req.userId]); if (existing) { await db.run(` UPDATE financial_profile SET current_salary = ?, additional_income = ?, monthly_expenses = ?, monthly_debt_payments = ?, retirement_savings = ?, retirement_contribution = ?, emergency_fund = ?, in_college = ?, expected_graduation = ?, part_time_income = ?, tuition_paid = ?, college_loan_total = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? `, [ currentSalary, additionalIncome, monthlyExpenses, monthlyDebtPayments, retirementSavings, retirementContribution, emergencyFund, inCollege ? 1 : 0, expectedGraduation, partTimeIncome, tuitionPaid, collegeLoanTotal, req.userId ]); } else { await db.run(` INSERT INTO financial_profile ( id, user_id, current_salary, additional_income, monthly_expenses, monthly_debt_payments, retirement_savings, retirement_contribution, emergency_fund, in_college, expected_graduation, part_time_income, tuition_paid, college_loan_total ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ uuidv4(), req.userId, currentSalary, additionalIncome, monthlyExpenses, monthlyDebtPayments, retirementSavings, retirementContribution, emergencyFund, inCollege ? 1 : 0, expectedGraduation, partTimeIncome, tuitionPaid, collegeLoanTotal ]); } res.status(200).json({ message: 'Financial profile saved.' }); } catch (error) { console.error('Error saving financial profile:', error); res.status(500).json({ error: 'Failed to save financial profile.' }); } }); // Retrieve career history app.get('/api/premium/career-history', authenticatePremiumUser, async (req, res) => { try { const history = await db.all( `SELECT * FROM career_history WHERE user_id = ? ORDER BY start_date DESC;`, [req.userId] ); res.json({ careerHistory: history }); } catch (error) { console.error('Error fetching career history:', error); res.status(500).json({ error: 'Failed to fetch career history' }); } }); // ROI Analysis (placeholder logic) app.get('/api/premium/roi-analysis', authenticatePremiumUser, async (req, res) => { try { const userCareer = await db.get( `SELECT * FROM career_path WHERE user_id = ? ORDER BY start_date DESC LIMIT 1`, [req.userId] ); if (!userCareer) return res.status(404).json({ error: 'No planned path found for user' }); const roi = { jobTitle: userCareer.job_title, salary: userCareer.salary, tuition: 50000, netGain: userCareer.salary - 50000 }; res.json(roi); } catch (error) { console.error('Error calculating ROI:', error); res.status(500).json({ error: 'Failed to calculate ROI' }); } }); app.listen(PORT, () => { console.log(`Premium server running on http://localhost:${PORT}`); });