Fixed the ChatGPT screw up during rewrite from user_profile.id and .user_id
This commit is contained in:
parent
d8027d6d17
commit
fc4e9da50b
@ -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,
|
||||
|
@ -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.' });
|
||||
}
|
||||
|
||||
@ -423,7 +411,6 @@ app.get('/api/schools', (req, res) => {
|
||||
});
|
||||
|
||||
// 5) (Optional) Deduplicate if you suspect overlaps among CIP codes.
|
||||
// E.g. by a “UNITID” or unique property:
|
||||
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' });
|
||||
}
|
||||
});
|
||||
|
||||
/**************************************************
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user