Fixed the ChatGPT screw up during rewrite from user_profile.id and .user_id

This commit is contained in:
Josh 2025-05-20 13:03:59 +00:00
parent d8027d6d17
commit fc4e9da50b
3 changed files with 146 additions and 164 deletions

View File

@ -109,9 +109,10 @@ app.use((req, res, next) => {
});
// =============== USER REGISTRATION (MySQL) ===============
// /api/register
app.post('/api/register', async (req, res) => {
const {
userId,
userId, // random ID from the front end
username,
password,
firstname,
@ -123,62 +124,54 @@ app.post('/api/register', async (req, res) => {
career_situation
} = req.body;
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);
// 1) Insert into user_profile first
const profileQuery = `
INSERT INTO user_profile (user_id, firstname, lastname, email, zipcode, state, area, career_situation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
// Insert row in user_profile, storing both user_id (random) and letting id auto-increment
const profileQuery = `
INSERT INTO user_profile
(user_id, firstname, lastname, email, zipcode, state, area, career_situation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
pool.query(
profileQuery,
[userId, firstname, lastname, email, zipcode, state, area, career_situation],
(errProfile, resultProfile) => {
if (errProfile) {
console.error('Error inserting user_profile:', errProfile.message);
return res.status(500).json({ error: 'Failed to create user profile' });
}
const newProfileAutoId = resultProfile.insertId; // auto-increment PK
// Insert into user_auth, referencing the auto-increment PK
const authQuery = `
INSERT INTO user_auth (user_id, username, hashed_password)
VALUES (?, ?, ?)
`;
pool.query(
profileQuery,
[userId, firstname, lastname, email, zipcode, state, area, career_situation],
(err2, results2) => {
if (err2) {
console.error('Error inserting into user_profile:', err2.message);
return res.status(500).json({ error: 'Failed to create user profile' });
pool.query(authQuery, [newProfileAutoId, username, hashedPassword], (errAuth) => {
if (errAuth) {
console.error('Error inserting user_auth:', errAuth.message);
if (errAuth.code === 'ER_DUP_ENTRY') {
return res.status(400).json({ error: 'Username already exists' });
}
// 2) Insert into user_auth
const authQuery = `
INSERT INTO user_auth (user_id, username, hashed_password)
VALUES (?, ?, ?)
`;
pool.query(authQuery, [userId, username, hashedPassword], (err, results) => {
if (err) {
console.error('Error inserting into user_auth:', err.message);
if (err.code === 'ER_DUP_ENTRY') {
return res.status(400).json({ error: 'Username already exists' });
}
return res.status(500).json({ error: 'Failed to register user' });
}
return res.status(201).json({ message: 'User registered successfully', userId });
});
return res.status(500).json({ error: 'Failed to register user' });
}
);
} catch (error) {
console.error('Error during registration:', error.message);
return res.status(201).json({
message: 'User registered successfully',
dbId: newProfileAutoId, // the auto-increment PK
customId: userId, // the random ID
});
});
}
);
} catch (err) {
console.error('Error during registration:', err.message);
return res.status(500).json({ error: 'Internal server error' });
}
});
// =============== SIGN-IN (MySQL) ===============
app.post('/api/signin', async (req, res) => {
const { username, password } = req.body;
@ -190,6 +183,7 @@ app.post('/api/signin', async (req, res) => {
SELECT
user_auth.user_id,
user_auth.hashed_password,
user_profile.id,
user_profile.zipcode,
user_profile.is_premium,
user_profile.is_pro_premium,
@ -200,7 +194,7 @@ app.post('/api/signin', async (req, res) => {
user_profile.career_priorities,
user_profile.career_list
FROM user_auth
LEFT JOIN user_profile ON user_auth.user_id = user_profile.user_id
LEFT JOIN user_profile ON user_auth.user_id = user_profile.id
WHERE user_auth.username = ?
`;
pool.query(query, [username], async (err, results) => {
@ -219,11 +213,11 @@ app.post('/api/signin', async (req, res) => {
return res.status(401).json({ error: 'Invalid username or password' });
}
const token = jwt.sign({ userId: row.user_id }, SECRET_KEY, { expiresIn: '2h' });
const token = jwt.sign({ userId: row.id }, SECRET_KEY, { expiresIn: '2h' });
res.status(200).json({
message: 'Login successful',
token,
userId: row.user_id,
userId: row.id,
user: {
user_id: row.user_id,
firstname: row.firstname,

View File

@ -1,16 +1,20 @@
/**************************************************
* server2.js - MySQL version
**************************************************/
import express from 'express';
import axios from 'axios';
import cors from 'cors';
import helmet from 'helmet'; // Import helmet for HTTP security headers
import helmet from 'helmet'; // For HTTP security headers
import dotenv from 'dotenv';
import xlsx from 'xlsx'; // Keep for CIP->SOC mapping only
import xlsx from 'xlsx'; // For CIP->SOC mapping only
import path from 'path';
import { fileURLToPath } from 'url';
import { open } from 'sqlite';
import sqlite3 from 'sqlite3';
import fs from 'fs';
import readline from 'readline';
// ********** NEW: use mysql2/promise for async/await queries **********
import mysql from 'mysql2/promise';
// --- Basic file init ---
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -33,41 +37,31 @@ const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.x
// Institution data
const institutionFilePath = path.resolve(rootPath, 'public/Institution_data.json');
// Create Express app
// ********** CREATE TWO POOLS FOR TWO DATABASES **********
// salary_data_db => poolSalary
// user_profile_db => poolProfile
const poolSalary = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: 'salary_data_db',
connectionLimit: 10
});
const poolProfile = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: 'user_profile_db',
connectionLimit: 10
});
const app = express();
const PORT = process.env.PORT || 5001;
/**************************************************
* DB connections
**************************************************/
let db;
const initDB = async () => {
try {
db = await open({
filename: '/home/jcoakley/aptiva-dev1-app/salary_info.db',
driver: sqlite3.Database,
});
console.log('Connected to SQLite database.');
} catch (error) {
console.error('Error connecting to database:', error);
}
};
initDB();
let userProfileDb;
const initUserProfileDb = async () => {
try {
userProfileDb = await open({
filename: '/home/jcoakley/aptiva-dev1-app/user_profile.db',
driver: sqlite3.Database
});
console.log('Connected to user_profile.db.');
} catch (error) {
console.error('Error connecting to user_profile.db:', error);
}
};
initUserProfileDb();
/**************************************************
* Security, CORS, JSON Body
**************************************************/
@ -141,13 +135,7 @@ if (socToCipMapping.length === 0) {
/**************************************************
* Load single JSON with all states + US
* Replaces old GA-only approach
**************************************************/
// const projectionsFilePath = path.resolve(__dirname, '..', 'public', 'occprj.xlsx');
// const workbook = xlsx.readFile(projectionsFilePath);
// const sheet = workbook.Sheets['GAOccProj 2022-2032'];
// const projectionsData = xlsx.utils.sheet_to_json(sheet, { header: 1 });
const singleProjFile = path.resolve(__dirname, '..', 'public', 'economicproj.json');
let allProjections = [];
try {
@ -205,7 +193,7 @@ app.get('/api/onet/questions', async (req, res) => {
}
});
// geocode
// Geocode
async function geocodeZipCode(zipCode) {
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
if (!apiKey) {
@ -394,7 +382,7 @@ app.get('/api/schools', (req, res) => {
// 1) Read `cipCodes` from query (comma-separated string)
const { cipCodes } = req.query;
if (!cipCodes ) {
if (!cipCodes) {
return res.status(400).json({ error: 'cipCodes (comma-separated) and state are required.' });
}
@ -422,8 +410,7 @@ app.get('/api/schools', (req, res) => {
return cipArray.some((cip) => scip.startsWith(cip));
});
// 5) (Optional) Deduplicate if you suspect overlaps among CIP codes.
// E.g. by a “UNITID” or unique property:
// 5) (Optional) Deduplicate if you suspect overlaps among CIP codes.
const uniqueMap = new Map();
for (const school of filtered) {
const key = school.UNITID || school.INSTNM; // pick your unique field
@ -441,7 +428,6 @@ app.get('/api/schools', (req, res) => {
}
});
// tuition
app.get('/api/tuition', (req, res) => {
const { cipCodes, state } = req.query;
@ -489,11 +475,9 @@ app.get('/api/tuition', (req, res) => {
}
});
/**************************************************
* SINGLE route for projections from economicproj.json
**************************************************/
// Remove old GA Excel approach; unify with single JSON approach
app.get('/api/projections/:socCode', (req, res) => {
const { socCode } = req.params;
const { state } = req.query;
@ -548,7 +532,7 @@ app.get('/api/projections/:socCode', (req, res) => {
});
/**************************************************
* Salary route
* Salary route (uses poolSalary)
**************************************************/
app.get('/api/salary', async (req, res) => {
const { socCode, area } = req.query;
@ -576,10 +560,17 @@ app.get('/api/salary', async (req, res) => {
try {
let regionalRow = null;
let nationalRow = null;
// If area is provided, fetch regional
if (area) {
regionalRow = await db.get(regionalQuery, [socCode, area]);
const [regRows] = await poolSalary.query(regionalQuery, [socCode, area]);
regionalRow = regRows.length ? regRows[0] : null;
}
nationalRow = await db.get(nationalQuery, [socCode]);
// Always fetch national
const [natRows] = await poolSalary.query(nationalQuery, [socCode]);
nationalRow = natRows.length ? natRows[0] : null;
if (!regionalRow && !nationalRow) {
console.log('No salary data found for:', { socCode, area });
return res.status(404).json({ error: 'No salary data found' });
@ -597,12 +588,12 @@ app.get('/api/salary', async (req, res) => {
});
/**************************************************
* job-zones route
* job-zones route (uses poolSalary)
**************************************************/
app.post('/api/job-zones', async (req, res) => {
const { socCodes } = req.body;
if (!socCodes || !Array.isArray(socCodes) || socCodes.length === 0) {
return res.status(400).json({ error: "SOC Codes are required." });
return res.status(400).json({ error: 'SOC Codes are required.' });
}
try {
// Format them
@ -614,28 +605,34 @@ app.post('/api/job-zones', async (req, res) => {
return cleaned.slice(0, 7);
});
const placeholders = formattedSocCodes.map(() => '?').join(',');
const q = `
SELECT OCC_CODE, JOB_ZONE,
A_MEDIAN, A_PCT10, A_PCT25, A_PCT75
FROM salary_data
WHERE OCC_CODE IN (${placeholders})
`;
const rows = await db.all(q, formattedSocCodes);
console.log("Salary Data Query Results:", rows);
// Use spread operator for the array
const [rows] = await poolSalary.query(q, [...formattedSocCodes]);
console.log('Salary Data Query Results:', rows);
const jobZoneMapping = rows.reduce((acc, row) => {
const isMissing = [row.A_MEDIAN, row.A_PCT10, row.A_PCT25, row.A_PCT75]
.some((v) => !v || v === '#' || v === '*');
const isMissing = [row.A_MEDIAN, row.A_PCT10, row.A_PCT25, row.A_PCT75].some(
(v) => !v || v === '#' || v === '*'
);
acc[row.OCC_CODE] = {
job_zone: row.JOB_ZONE,
limited_data: isMissing ? 1 : 0
};
return acc;
}, {});
console.log("Job Zone & Limited Data:", jobZoneMapping);
console.log('Job Zone & Limited Data:', jobZoneMapping);
res.json(jobZoneMapping);
} catch (error) {
console.error("Error fetching job zones:", error);
res.status(500).json({ error: "Failed to fetch job zones." });
console.error('Error fetching job zones:', error);
res.status(500).json({ error: 'Failed to fetch job zones.' });
}
});
@ -665,26 +662,10 @@ app.get('/api/skills/:socCode', async (req, res) => {
const data = response.data || {};
// 3) O*NET returns:
// {
// "code": "17-1011.00",
// "group": [
// {
// "title": { "id": "2.A", "name": "Basic Skills" },
// "element": [
// { "id": "2.A.1.a", "name": "reading work related information" },
// ...
// ]
// },
// ...
// ]
// }
// Instead of data.characteristic, parse data.group
const groups = data.group || [];
// 4) Flatten out the group[].element[] into a single skills array
const skills = [];
// Flatten out the group[].element[] into a single skills array
groups.forEach((groupItem) => {
const groupName = groupItem?.title?.name || 'Unknown Group';
@ -702,43 +683,42 @@ app.get('/api/skills/:socCode', async (req, res) => {
res.json({ skills });
} catch (error) {
console.error('Error fetching O*NET skills:', error.message);
console.error('Error fetching O*NET skills:', error.message);
if (error.response) {
console.error('O*NET error status:', error.response.status);
console.error('O*NET error data:', error.response.data);
} else if (error.request) {
// The request was made but no response was received
console.error('No response received from O*NET. Possibly a network or credentials error.');
console.error('Axios error.request:', error.request);
} else {
// Something else happened in setting up the request
console.error('Request setup error:', error.message);
if (error.response) {
console.error('O*NET error status:', error.response.status);
console.error('O*NET error data:', error.response.data);
} else if (error.request) {
console.error('No response received from O*NET.');
console.error('Axios error.request:', error.request);
} else {
console.error('Request setup error:', error.message);
}
return res.status(500).json({ error: 'Failed to fetch O*NET skills' });
}
return res.status(500).json({ error: 'Failed to fetch O*NET skills' });
}
});
/**************************************************
* user-profile by ID route
* user-profile by ID route (uses poolProfile)
**************************************************/
app.get('/api/user-profile/:id', (req, res) => {
app.get('/api/user-profile/:id', async (req, res) => {
const { id } = req.params;
if (!id) return res.status(400).json({ error: 'Profile ID is required' });
const query = `SELECT area, zipcode FROM user_profile WHERE id = ?`;
db.get(query, [id], (err, row) => {
if (err) {
console.error('Error fetching user profile:', err.message);
return res.status(500).json({ error: 'Failed to fetch user profile' });
}
if (!row) {
try {
const [rows] = await poolProfile.query(query, [id]);
if (!rows.length) {
return res.status(404).json({ error: 'Profile not found' });
}
const row = rows[0];
res.json({ area: row.area, zipcode: row.zipcode });
});
} catch (err) {
console.error('Error fetching user profile:', err.message);
return res.status(500).json({ error: 'Failed to fetch user profile' });
}
});
/**************************************************

View File

@ -48,6 +48,7 @@ function SignUp() {
const [areas, setAreas] = useState([]);
const [error, setError] = useState('');
const [loadingAreas, setLoadingAreas] = useState(false);
const [frontendUserId] = useState(() => Math.floor(Math.random() * 1000000000));
// new states
const [showCareerSituations, setShowCareerSituations] = useState(false);
@ -168,23 +169,30 @@ function SignUp() {
const handleSituationConfirm = async () => {
try {
// Verify payload clearly:
console.log("Payload sent to backend:", {
userId: Math.floor(Math.random() * 1000000000),
userId: frontendUserId,
username, password, firstname, lastname, email, zipcode, state, area,
career_situation: selectedSituation.id
});
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: Math.floor(Math.random() * 1000000000),
username, password, firstname, lastname, email, zipcode, state, area,
career_situation: selectedSituation.id
}),
});
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: frontendUserId,
username,
password,
firstname,
lastname,
email,
zipcode,
state,
area,
career_situation: selectedSituation.id
}),
});
const data = await response.json();