import express from 'express'; import axios from 'axios'; import cors from 'cors'; import helmet from 'helmet'; import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import path from 'path'; import sqlite3 from 'sqlite3'; // Import SQLite import bodyParser from 'body-parser'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; // For token-based authentication // Derive __dirname for ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const dbPath = path.resolve('/home/jcoakley/aptiva-dev1-app/user_profile.db'); const db = new sqlite3.Database(dbPath, (err) => { if (err) { console.error('Error connecting to database:', err.message); } else { console.log('Connected to user_profile.db'); } }); dotenv.config({ path: path.resolve('/home/jcoakley/aptiva-dev1-app/.env') }); // Adjust the path based on your folder structure const SECRET_KEY = process.env.SECRET_KEY || 'supersecurekey'; // Use a secure key in production console.log('ONET_USERNAME:', process.env.ONET_USERNAME); console.log('ONET_PASSWORD:', process.env.ONET_PASSWORD); console.log('Current Working Directory:', process.cwd()); const app = express(); const PORT = 5000; const allowedOrigins = ['http://localhost:3000', 'http://34.16.120.118:3000', 'https://dev1.aptivaai.com']; app.disable('x-powered-by'); app.use(bodyParser.json()); app.use(express.json()); app.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, }) ); // Enable CORS with dynamic origin checking app.use( cors({ origin: (origin, callback) => { if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { console.error('Blocked by CORS:', origin); callback(new Error('Not allowed by CORS')); } }, methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Authorization', 'Content-Type', 'Accept', 'Origin', 'X-Requested-With'], credentials: true, }) ); // Handle preflight requests explicitly app.options('*', (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, Accept, Origin, X-Requested-With'); res.status(200).end(); }); // Add HTTP headers for security and caching app.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); res.setHeader('Content-Security-Policy', "default-src 'self';"); res.removeHeader('X-Powered-By'); next(); }); // Force Content-Type to application/json on all responses app.use((req, res, next) => { res.setHeader('Content-Type', 'application/json'); next(); }); // Route for user registration app.post('/api/register', async (req, res) => { const { userId, username, password, firstname, lastname, email, zipcode, state, area, career_situation } = req.body; // Validate all required fields, including career_situation if (!userId || !username || !password || !firstname || !lastname || !email || !zipcode || !state || !area || !career_situation) { return res.status(400).json({ error: 'All fields including career_situation are required' }); } try { const hashedPassword = await bcrypt.hash(password, 10); // Insert into user_auth const authQuery = ` INSERT INTO user_auth (user_id, username, hashed_password) VALUES (?, ?, ?) `; db.run(authQuery, [userId, username, hashedPassword], function (err) { if (err) { console.error('Error inserting into user_auth:', err.message); if (err.message.includes('UNIQUE constraint failed')) { return res.status(400).json({ error: 'Username already exists' }); } return res.status(500).json({ error: 'Failed to register user' }); } // Insert into user_profile including career_situation const profileQuery = ` INSERT INTO user_profile (user_id, firstname, lastname, email, zipcode, state, area, career_situation) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `; db.run(profileQuery, [userId, firstname, lastname, email, zipcode, state, area, career_situation], (err) => { if (err) { console.error('Error inserting into user_profile:', err.message); return res.status(500).json({ error: 'Failed to create user profile' }); } return res.status(201).json({ message: 'User registered successfully', userId }); }); }); } catch (error) { console.error('Error during registration:', error.message); return res.status(500).json({ error: 'Internal server error' }); } }); // Route to handle user sign-in (updated with career_priorities and career_list) app.post('/api/signin', async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Both username and password are required' }); } // Updated query includes career_priorities and career_list const query = ` SELECT user_auth.user_id, user_auth.hashed_password, user_profile.zipcode, user_profile.is_premium, user_profile.is_pro_premium, user_profile.career_situation, user_profile.email, user_profile.firstname, user_profile.lastname, user_profile.career_priorities, -- new field user_profile.career_list -- new field FROM user_auth LEFT JOIN user_profile ON user_auth.user_id = user_profile.user_id WHERE user_auth.username = ? `; db.get(query, [username], async (err, row) => { if (err) { console.error('Error querying user_auth:', err.message); return res.status(500).json({ error: 'Failed to query user authentication data' }); } // If no matching username if (!row) { return res.status(401).json({ error: 'Invalid username or password' }); } // Verify the password using bcrypt const isMatch = await bcrypt.compare(password, row.hashed_password); if (!isMatch) { return res.status(401).json({ error: 'Invalid username or password' }); } // Generate JWT const token = jwt.sign({ userId: row.user_id }, SECRET_KEY, { expiresIn: '2h' }); // Return fully updated user object res.status(200).json({ message: 'Login successful', token, userId: row.user_id, user: { user_id: row.user_id, firstname: row.firstname, lastname: row.lastname, email: row.email, zipcode: row.zipcode, is_premium: row.is_premium, is_pro_premium: row.is_pro_premium, career_situation: row.career_situation, career_priorities: row.career_priorities, // newly added career_list: row.career_list, // newly added } }); }); }); /// Check if username already exists app.get('/api/check-username/:username', (req, res) => { const { username } = req.params; const query = `SELECT username FROM user_auth WHERE username = ?`; db.get(query, [username], (err, row) => { if (err) { console.error('Error checking username:', err.message); return res.status(500).json({ error: 'Database error' }); } if (row) { res.status(200).json({ exists: true }); } else { res.status(200).json({ exists: false }); } }); }); // Upsert user profile (corrected and robust) app.post('/api/user-profile', (req, res) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Authorization token is required' }); } let userId; try { const decoded = jwt.verify(token, SECRET_KEY); userId = decoded.userId; } catch (error) { console.error('JWT verification failed:', error); return res.status(401).json({ error: 'Invalid or expired token' }); } const { firstName, lastName, email, zipCode, state, area, careerSituation, interest_inventory_answers, career_priorities, career_list, } = req.body; // Check existing profile explicitly first db.get(`SELECT * FROM user_profile WHERE user_id = ?;`, [userId], (err, existingRow) => { if (err) { console.error('Error checking profile:', err.message); return res.status(500).json({ error: 'Database error' }); } // Require fields ONLY if no existing profile found if (!existingRow && (!firstName || !lastName || !email || !zipCode || !state || !area)) { return res.status(400).json({ error: 'All fields are required for initial profile creation.' }); } const finalAnswers = interest_inventory_answers !== undefined ? interest_inventory_answers : existingRow?.interest_inventory_answers || null; const finalCareerPriorities = career_priorities !== undefined ? career_priorities : existingRow?.career_priorities || null; const finalCareerList = career_list !== undefined ? career_list : existingRow?.career_list || null; if (existingRow) { // Update existing profile clearly const updateQuery = ` UPDATE user_profile SET firstname = ?, lastname = ?, email = ?, zipcode = ?, state = ?, area = ?, career_situation = ?, interest_inventory_answers = ?, career_priorities = ?, career_list = ? WHERE user_id = ? `; const params = [ firstName || existingRow.firstname, lastName || existingRow.lastname, email || existingRow.email, zipCode || existingRow.zipcode, state || existingRow.state, area || existingRow.area, careerSituation || existingRow.career_situation, finalAnswers, finalCareerPriorities, finalCareerList, userId, ]; db.run(updateQuery, params, function (err) { if (err) { console.error('Update error:', err.message); return res.status(500).json({ error: 'Failed to update user profile' }); } res.status(200).json({ message: 'User profile updated successfully' }); }); } else { // Insert new profile clearly const insertQuery = ` INSERT INTO user_profile (user_id, firstname, lastname, email, zipcode, state, area, career_situation, interest_inventory_answers, career_priorities, career_list) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ userId, firstName, lastName, email, zipCode, state, area, careerSituation || null, finalAnswers, finalCareerPriorities, finalCareerList, ]; db.run(insertQuery, params, function (err) { if (err) { console.error('Insert error:', err.message); return res.status(500).json({ error: 'Failed to create user profile' }); } res.status(201).json({ message: 'User profile created successfully', id: this.lastID }); }); } }); }); // Route to fetch user profile app.get('/api/user-profile', (req, res) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Authorization token is required' }); } try { // Extract the userId (user_id) from the token const { userId } = jwt.verify(token, SECRET_KEY); // Query user_profile using user_id const query = 'SELECT * FROM user_profile WHERE user_id = ?'; db.get(query, [userId], (err, row) => { if (err) { console.error('Error fetching user profile:', err.message); return res.status(500).json({ error: 'Internal server error' }); } if (!row) { return res.status(404).json({ error: 'User profile not found' }); } res.status(200).json(row); // Return the profile row }); } catch (error) { console.error('Error verifying token:', error.message); res.status(401).json({ error: 'Invalid or expired token' }); } }); // Route to fetch areas by state app.get('/api/areas', (req, res) => { const { state } = req.query; if (!state) { return res.status(400).json({ error: 'State parameter is required' }); } const dbPath = path.resolve('/home/jcoakley/aptiva-dev1-app/salary_info.db'); // Path to salary_info.db const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => { if (err) { console.error('Error connecting to database:', err.message); return res.status(500).json({ error: 'Failed to connect to database' }); } }); const query = `SELECT DISTINCT AREA_TITLE FROM salary_data WHERE PRIM_STATE = ?`; db.all(query, [state], (err, rows) => { if (err) { console.error('Error executing query:', err.message); return res.status(500).json({ error: 'Failed to fetch areas' }); } const areas = rows.map((row) => row.AREA_TITLE); res.json({ areas }); }); db.close((err) => { if (err) { console.error('Error closing the database:', err.message); } }); }); // Start the server app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });