520 lines
15 KiB
JavaScript
520 lines
15 KiB
JavaScript
// 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.' });
|
|
}
|
|
});
|
|
|
|
|
|
/* ------------------------------------------------------------------
|
|
MILESTONES (same as before)
|
|
------------------------------------------------------------------ */
|
|
app.post('/api/premium/milestone', authenticatePremiumUser, async (req, res) => {
|
|
// ... no changes, same logic ...
|
|
});
|
|
|
|
// GET, PUT, DELETE milestones
|
|
// ... no changes ...
|
|
|
|
/* ------------------------------------------------------------------
|
|
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 id = uuidv4();
|
|
const user_id = req.userId;
|
|
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
|
|
)
|
|
`, [
|
|
id,
|
|
user_id,
|
|
career_path_id,
|
|
selected_school,
|
|
selected_program,
|
|
program_type || null,
|
|
is_in_state ? 1 : 0,
|
|
is_in_district ? 1 : 0,
|
|
college_enrollment_status || null,
|
|
annual_financial_aid || 0,
|
|
is_online ? 1 : 0,
|
|
credit_hours_per_year || 0,
|
|
hours_completed || 0,
|
|
program_length || 0,
|
|
credit_hours_required || 0,
|
|
expected_graduation || null,
|
|
existing_college_debt || 0,
|
|
interest_rate || 0,
|
|
loan_term || 10,
|
|
loan_deferral_until_graduation ? 1 : 0,
|
|
extra_payment || 0,
|
|
expected_salary || 0,
|
|
academic_calendar || 'semester',
|
|
tuition || 0,
|
|
tuition_paid || 0
|
|
]);
|
|
|
|
res.status(201).json({
|
|
message: 'College profile saved.',
|
|
collegeProfileId: id
|
|
});
|
|
} catch (error) {
|
|
console.error('Error saving college profile:', error);
|
|
res.status(500).json({ error: 'Failed to save college profile.' });
|
|
}
|
|
});
|
|
|
|
/* ------------------------------------------------------------------
|
|
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.' });
|
|
}
|
|
});
|
|
|
|
/* ------------------------------------------------------------------
|
|
ROI ANALYSIS (placeholder)
|
|
------------------------------------------------------------------ */
|
|
app.get('/api/premium/roi-analysis', authenticatePremiumUser, async (req, res) => {
|
|
try {
|
|
const userCareer = await db.get(`
|
|
SELECT * FROM career_paths
|
|
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.career_name,
|
|
salary: 80000,
|
|
tuition: 50000,
|
|
netGain: 80000 - 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}`);
|
|
});
|