Migrated server2 back to SQLite
This commit is contained in:
parent
ff8e3113bd
commit
b7c996ad9e
91417
Abilities.txt
Normal file
91417
Abilities.txt
Normal file
File diff suppressed because it is too large
Load Diff
58015
Knowledge.txt
Normal file
58015
Knowledge.txt
Normal file
File diff suppressed because it is too large
Load Diff
56653
Skills.txt
Normal file
56653
Skills.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,20 @@
|
|||||||
/**************************************************
|
/**************************************************
|
||||||
* server2.js - MySQL version
|
* server2.js - Reverted to SQLite version
|
||||||
**************************************************/
|
**************************************************/
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import helmet from 'helmet'; // For HTTP security headers
|
import helmet from 'helmet'; // For HTTP security headers
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import xlsx from 'xlsx'; // For CIP->SOC mapping only
|
import xlsx from 'xlsx'; // Keep for CIP->SOC mapping only
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { open } from 'sqlite';
|
||||||
|
import sqlite3 from 'sqlite3';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import readline from 'readline';
|
import readline from 'readline';
|
||||||
|
|
||||||
// ********** NEW: use mysql2/promise for async/await queries **********
|
|
||||||
import mysql from 'mysql2/promise';
|
|
||||||
|
|
||||||
// --- Basic file init ---
|
// --- Basic file init ---
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@ -28,40 +28,50 @@ dotenv.config({ path: envPath }); // Load .env
|
|||||||
const allowedOrigins = [
|
const allowedOrigins = [
|
||||||
'http://localhost:3000',
|
'http://localhost:3000',
|
||||||
'http://34.16.120.118:3000',
|
'http://34.16.120.118:3000',
|
||||||
'https://dev1.aptivaai.com'
|
'https://dev1.aptivaai.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
// CIP->SOC mapping file
|
// CIP->SOC mapping file
|
||||||
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx';
|
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx';
|
||||||
|
|
||||||
// Institution data
|
// Institution data
|
||||||
const institutionFilePath = path.resolve(rootPath, 'public/Institution_data.json');
|
const institutionFilePath = path.resolve(rootPath, 'public', 'Institution_data.json');
|
||||||
|
|
||||||
// ********** 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
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Create Express app
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 5001;
|
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 salary_info.db');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error connecting to salary_info.db:', 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
|
* Security, CORS, JSON Body
|
||||||
**************************************************/
|
**************************************************/
|
||||||
@ -130,11 +140,12 @@ function loadMapping() {
|
|||||||
}
|
}
|
||||||
const socToCipMapping = loadMapping();
|
const socToCipMapping = loadMapping();
|
||||||
if (socToCipMapping.length === 0) {
|
if (socToCipMapping.length === 0) {
|
||||||
console.error("SOC to CIP mapping data is empty.");
|
console.error('SOC to CIP mapping data is empty.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* Load single JSON with all states + US
|
* Load single JSON with all states + US
|
||||||
|
* Replaces old GA-only approach
|
||||||
**************************************************/
|
**************************************************/
|
||||||
const singleProjFile = path.resolve(__dirname, '..', 'public', 'economicproj.json');
|
const singleProjFile = path.resolve(__dirname, '..', 'public', 'economicproj.json');
|
||||||
let allProjections = [];
|
let allProjections = [];
|
||||||
@ -147,7 +158,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* ONet routes, CIP routes, distance routes, etc.
|
* O*Net routes, CIP routes, distance routes, etc.
|
||||||
**************************************************/
|
**************************************************/
|
||||||
|
|
||||||
// O*Net interest questions
|
// O*Net interest questions
|
||||||
@ -193,7 +204,7 @@ app.get('/api/onet/questions', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Geocode
|
// geocode
|
||||||
async function geocodeZipCode(zipCode) {
|
async function geocodeZipCode(zipCode) {
|
||||||
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
|
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
@ -287,7 +298,7 @@ app.post('/api/onet/submit_answers', async (req, res) => {
|
|||||||
console.error('Error in ONet API:', error.response?.data || error.message);
|
console.error('Error in ONet API:', error.response?.data || error.message);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Failed to fetch data from ONet',
|
error: 'Failed to fetch data from ONet',
|
||||||
details: error.response?.data || error.message
|
details: error.response?.data || error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -301,7 +312,7 @@ function filterHigherEducationCareers(careers) {
|
|||||||
fit: c.fit,
|
fit: c.fit,
|
||||||
code: c.code,
|
code: c.code,
|
||||||
title: c.title,
|
title: c.title,
|
||||||
tags: c.tags
|
tags: c.tags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -318,8 +329,8 @@ app.get('/api/onet/career-details/:socCode', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const response = await axios.get(`https://services.onetcenter.org/ws/mnm/careers/${socCode}`, {
|
const response = await axios.get(`https://services.onetcenter.org/ws/mnm/careers/${socCode}`, {
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.ONET_USERNAME,
|
username: process.env.ONet_USERNAME,
|
||||||
password: process.env.ONET_PASSWORD
|
password: process.env.ONET_PASSWORD,
|
||||||
},
|
},
|
||||||
headers: { Accept: 'application/json' },
|
headers: { Accept: 'application/json' },
|
||||||
});
|
});
|
||||||
@ -340,7 +351,7 @@ app.get('/api/onet/career-description/:socCode', async (req, res) => {
|
|||||||
const response = await axios.get(`https://services.onetcenter.org/ws/mnm/careers/${socCode}`, {
|
const response = await axios.get(`https://services.onetcenter.org/ws/mnm/careers/${socCode}`, {
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.ONET_USERNAME,
|
username: process.env.ONET_USERNAME,
|
||||||
password: process.env.ONET_PASSWORD
|
password: process.env.ONET_PASSWORD,
|
||||||
},
|
},
|
||||||
headers: { Accept: 'application/json' },
|
headers: { Accept: 'application/json' },
|
||||||
});
|
});
|
||||||
@ -349,7 +360,7 @@ app.get('/api/onet/career-description/:socCode', async (req, res) => {
|
|||||||
const tasks = on_the_job?.task || [];
|
const tasks = on_the_job?.task || [];
|
||||||
return res.json({
|
return res.json({
|
||||||
description: what_they_do || 'No description available',
|
description: what_they_do || 'No description available',
|
||||||
tasks: tasks.length ? tasks : ['No tasks available']
|
tasks: tasks.length ? tasks : ['No tasks available'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res.status(404).json({ error: 'Career not found for SOC code' });
|
return res.status(404).json({ error: 'Career not found for SOC code' });
|
||||||
@ -383,12 +394,17 @@ app.get('/api/schools', (req, res) => {
|
|||||||
const { cipCodes } = req.query;
|
const { cipCodes } = req.query;
|
||||||
|
|
||||||
if (!cipCodes) {
|
if (!cipCodes) {
|
||||||
return res.status(400).json({ error: 'cipCodes (comma-separated) and state are required.' });
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: 'cipCodes (comma-separated) and state are required.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 2) Convert `cipCodes` to array => e.g. "1101,1103,1104" => ["1101","1103","1104"]
|
// 2) Convert `cipCodes` to array => e.g. "1101,1103,1104" => ["1101","1103","1104"]
|
||||||
const cipArray = cipCodes.split(',').map((c) => c.trim()).filter(Boolean);
|
const cipArray = cipCodes
|
||||||
|
.split(',')
|
||||||
|
.map((c) => c.trim())
|
||||||
|
.filter(Boolean);
|
||||||
if (cipArray.length === 0) {
|
if (cipArray.length === 0) {
|
||||||
return res.status(400).json({ error: 'No valid CIP codes were provided.' });
|
return res.status(400).json({ error: 'No valid CIP codes were provided.' });
|
||||||
}
|
}
|
||||||
@ -404,13 +420,12 @@ app.get('/api/schools', (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4) Filter any school whose CIP code matches ANY of the CIP codes in the array
|
// 4) Filter any school whose CIP code matches ANY of the CIP codes in the array
|
||||||
// Convert the school's CIP code the same way you do in your old logic (remove dot, slice, etc.)
|
|
||||||
const filtered = schoolsData.filter((s) => {
|
const filtered = schoolsData.filter((s) => {
|
||||||
const scip = s['CIPCODE']?.toString().replace('.', '').slice(0, 4);
|
const scip = s['CIPCODE']?.toString().replace('.', '').slice(0, 4);
|
||||||
return cipArray.some((cip) => scip.startsWith(cip));
|
return cipArray.some((cip) => scip.startsWith(cip));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5) (Optional) Deduplicate if you suspect overlaps among CIP codes.
|
// 5) (Optional) Deduplicate
|
||||||
const uniqueMap = new Map();
|
const uniqueMap = new Map();
|
||||||
for (const school of filtered) {
|
for (const school of filtered) {
|
||||||
const key = school.UNITID || school.INSTNM; // pick your unique field
|
const key = school.UNITID || school.INSTNM; // pick your unique field
|
||||||
@ -439,7 +454,10 @@ app.get('/api/tuition', (req, res) => {
|
|||||||
const raw = fs.readFileSync(institutionFilePath, 'utf8');
|
const raw = fs.readFileSync(institutionFilePath, 'utf8');
|
||||||
const schoolsData = JSON.parse(raw);
|
const schoolsData = JSON.parse(raw);
|
||||||
|
|
||||||
const cipArray = cipCodes.split(',').map((c) => c.trim()).filter(Boolean);
|
const cipArray = cipCodes
|
||||||
|
.split(',')
|
||||||
|
.map((c) => c.trim())
|
||||||
|
.filter(Boolean);
|
||||||
if (!cipArray.length) {
|
if (!cipArray.length) {
|
||||||
return res.status(400).json({ error: 'No valid CIP codes.' });
|
return res.status(400).json({ error: 'No valid CIP codes.' });
|
||||||
}
|
}
|
||||||
@ -505,7 +523,9 @@ app.get('/api/projections/:socCode', (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!rowForState && !rowForUS) {
|
if (!rowForState && !rowForUS) {
|
||||||
return res.status(404).json({ error: 'No projections found for this SOC + area.' });
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ error: 'No projections found for this SOC + area.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRow(r) {
|
function formatRow(r) {
|
||||||
@ -519,20 +539,20 @@ app.get('/api/projections/:socCode', (req, res) => {
|
|||||||
change: r['Change'],
|
change: r['Change'],
|
||||||
percentChange: r['Percent Change'],
|
percentChange: r['Percent Change'],
|
||||||
annualOpenings: r['Average Annual Openings'],
|
annualOpenings: r['Average Annual Openings'],
|
||||||
occupationName: r['Occupation Name']
|
occupationName: r['Occupation Name'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
state: formatRow(rowForState),
|
state: formatRow(rowForState),
|
||||||
national: formatRow(rowForUS)
|
national: formatRow(rowForUS),
|
||||||
};
|
};
|
||||||
|
|
||||||
return res.json(result);
|
return res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* Salary route (uses poolSalary)
|
* Salary route
|
||||||
**************************************************/
|
**************************************************/
|
||||||
app.get('/api/salary', async (req, res) => {
|
app.get('/api/salary', async (req, res) => {
|
||||||
const { socCode, area } = req.query;
|
const { socCode, area } = req.query;
|
||||||
@ -541,35 +561,33 @@ app.get('/api/salary', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'SOC Code is required' });
|
return res.status(400).json({ error: 'SOC Code is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for regional
|
|
||||||
const regionalQuery = `
|
const regionalQuery = `
|
||||||
SELECT A_PCT10 AS regional_PCT10, A_PCT25 AS regional_PCT25,
|
SELECT A_PCT10 AS regional_PCT10,
|
||||||
A_MEDIAN AS regional_MEDIAN, A_PCT75 AS regional_PCT75,
|
A_PCT25 AS regional_PCT25,
|
||||||
|
A_MEDIAN AS regional_MEDIAN,
|
||||||
|
A_PCT75 AS regional_PCT75,
|
||||||
A_PCT90 AS regional_PCT90
|
A_PCT90 AS regional_PCT90
|
||||||
FROM salary_data
|
FROM salary_data
|
||||||
WHERE OCC_CODE = ? AND AREA_TITLE = ?
|
WHERE OCC_CODE = ? AND AREA_TITLE = ?
|
||||||
`;
|
`;
|
||||||
// Query for national
|
|
||||||
const nationalQuery = `
|
const nationalQuery = `
|
||||||
SELECT A_PCT10 AS national_PCT10, A_PCT25 AS national_PCT25,
|
SELECT A_PCT10 AS national_PCT10,
|
||||||
A_MEDIAN AS national_MEDIAN, A_PCT75 AS national_PCT75,
|
A_PCT25 AS national_PCT25,
|
||||||
|
A_MEDIAN AS national_MEDIAN,
|
||||||
|
A_PCT75 AS national_PCT75,
|
||||||
A_PCT90 AS national_PCT90
|
A_PCT90 AS national_PCT90
|
||||||
FROM salary_data
|
FROM salary_data
|
||||||
WHERE OCC_CODE = ? AND AREA_TITLE = 'U.S.'
|
WHERE OCC_CODE = ? AND AREA_TITLE = 'U.S.'
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let regionalRow = null;
|
let regionalRow = null;
|
||||||
let nationalRow = null;
|
let nationalRow = null;
|
||||||
|
|
||||||
// If area is provided, fetch regional
|
|
||||||
if (area) {
|
if (area) {
|
||||||
const [regRows] = await poolSalary.query(regionalQuery, [socCode, area]);
|
regionalRow = await db.get(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) {
|
if (!regionalRow && !nationalRow) {
|
||||||
console.log('No salary data found for:', { socCode, area });
|
console.log('No salary data found for:', { socCode, area });
|
||||||
@ -577,7 +595,7 @@ app.get('/api/salary', async (req, res) => {
|
|||||||
}
|
}
|
||||||
const salaryData = {
|
const salaryData = {
|
||||||
regional: regionalRow || {},
|
regional: regionalRow || {},
|
||||||
national: nationalRow || {}
|
national: nationalRow || {},
|
||||||
};
|
};
|
||||||
console.log('Salary data retrieved:', salaryData);
|
console.log('Salary data retrieved:', salaryData);
|
||||||
res.json(salaryData);
|
res.json(salaryData);
|
||||||
@ -588,7 +606,7 @@ app.get('/api/salary', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* job-zones route (uses poolSalary)
|
* job-zones route
|
||||||
**************************************************/
|
**************************************************/
|
||||||
app.post('/api/job-zones', async (req, res) => {
|
app.post('/api/job-zones', async (req, res) => {
|
||||||
const { socCodes } = req.body;
|
const { socCodes } = req.body;
|
||||||
@ -605,16 +623,17 @@ app.post('/api/job-zones', async (req, res) => {
|
|||||||
return cleaned.slice(0, 7);
|
return cleaned.slice(0, 7);
|
||||||
});
|
});
|
||||||
const placeholders = formattedSocCodes.map(() => '?').join(',');
|
const placeholders = formattedSocCodes.map(() => '?').join(',');
|
||||||
|
|
||||||
const q = `
|
const q = `
|
||||||
SELECT OCC_CODE, JOB_ZONE,
|
SELECT OCC_CODE,
|
||||||
A_MEDIAN, A_PCT10, A_PCT25, A_PCT75
|
JOB_ZONE,
|
||||||
|
A_MEDIAN,
|
||||||
|
A_PCT10,
|
||||||
|
A_PCT25,
|
||||||
|
A_PCT75
|
||||||
FROM salary_data
|
FROM salary_data
|
||||||
WHERE OCC_CODE IN (${placeholders})
|
WHERE OCC_CODE IN (${placeholders})
|
||||||
`;
|
`;
|
||||||
|
const rows = await db.all(q, formattedSocCodes);
|
||||||
// Use spread operator for the array
|
|
||||||
const [rows] = await poolSalary.query(q, [...formattedSocCodes]);
|
|
||||||
console.log('Salary Data Query Results:', rows);
|
console.log('Salary Data Query Results:', rows);
|
||||||
|
|
||||||
const jobZoneMapping = rows.reduce((acc, row) => {
|
const jobZoneMapping = rows.reduce((acc, row) => {
|
||||||
@ -623,11 +642,10 @@ app.post('/api/job-zones', async (req, res) => {
|
|||||||
);
|
);
|
||||||
acc[row.OCC_CODE] = {
|
acc[row.OCC_CODE] = {
|
||||||
job_zone: row.JOB_ZONE,
|
job_zone: row.JOB_ZONE,
|
||||||
limited_data: isMissing ? 1 : 0
|
limited_data: isMissing ? 1 : 0,
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
console.log('Job Zone & Limited Data:', jobZoneMapping);
|
console.log('Job Zone & Limited Data:', jobZoneMapping);
|
||||||
res.json(jobZoneMapping);
|
res.json(jobZoneMapping);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -655,9 +673,9 @@ app.get('/api/skills/:socCode', async (req, res) => {
|
|||||||
const response = await axios.get(onetUrl, {
|
const response = await axios.get(onetUrl, {
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.ONET_USERNAME,
|
username: process.env.ONET_USERNAME,
|
||||||
password: process.env.ONET_PASSWORD
|
password: process.env.ONET_PASSWORD,
|
||||||
},
|
},
|
||||||
headers: { Accept: 'application/json' }
|
headers: { Accept: 'application/json' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = response.data || {};
|
const data = response.data || {};
|
||||||
@ -668,14 +686,12 @@ app.get('/api/skills/:socCode', async (req, res) => {
|
|||||||
// Flatten out the group[].element[] into a single skills array
|
// Flatten out the group[].element[] into a single skills array
|
||||||
groups.forEach((groupItem) => {
|
groups.forEach((groupItem) => {
|
||||||
const groupName = groupItem?.title?.name || 'Unknown Group';
|
const groupName = groupItem?.title?.name || 'Unknown Group';
|
||||||
|
|
||||||
if (Array.isArray(groupItem.element)) {
|
if (Array.isArray(groupItem.element)) {
|
||||||
groupItem.element.forEach((elem) => {
|
groupItem.element.forEach((elem) => {
|
||||||
// Each "element" is a skill with an id and a name
|
|
||||||
skills.push({
|
skills.push({
|
||||||
groupName,
|
groupName,
|
||||||
skillId: elem.id,
|
skillId: elem.id,
|
||||||
skillName: elem.name
|
skillName: elem.name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -700,25 +716,23 @@ app.get('/api/skills/:socCode', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* user-profile by ID route (uses poolProfile)
|
* user-profile by ID route
|
||||||
**************************************************/
|
**************************************************/
|
||||||
app.get('/api/user-profile/:id', async (req, res) => {
|
app.get('/api/user-profile/:id', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
if (!id) return res.status(400).json({ error: 'Profile ID is required' });
|
if (!id) return res.status(400).json({ error: 'Profile ID is required' });
|
||||||
|
|
||||||
const query = `SELECT area, zipcode FROM user_profile WHERE id = ?`;
|
const query = `SELECT area, zipcode FROM user_profile WHERE id = ?`;
|
||||||
|
userProfileDb.get(query, [id], (err, row) => {
|
||||||
try {
|
if (err) {
|
||||||
const [rows] = await poolProfile.query(query, [id]);
|
console.error('Error fetching user profile:', err.message);
|
||||||
if (!rows.length) {
|
return res.status(500).json({ error: 'Failed to fetch user profile' });
|
||||||
|
}
|
||||||
|
if (!row) {
|
||||||
return res.status(404).json({ error: 'Profile not found' });
|
return res.status(404).json({ error: 'Profile not found' });
|
||||||
}
|
}
|
||||||
const row = rows[0];
|
|
||||||
res.json({ area: row.area, zipcode: row.zipcode });
|
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' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
|
|||||||
37
merge_ksa.js
Normal file
37
merge_ksa.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// merge_ksa.js
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import parseLine from './parseLine.js'; // your parseLine from above
|
||||||
|
|
||||||
|
function parseTextFile(filepath, ksaType) {
|
||||||
|
const raw = fs.readFileSync(filepath, 'utf8');
|
||||||
|
const lines = raw.split('\n').map(line => line.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
// convert each line to an object with 15 columns
|
||||||
|
// then attach ksa_type
|
||||||
|
return lines.map((line) => {
|
||||||
|
const parsed = parseLine(line);
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
ksa_type: ksaType
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
// Adjust to your real file paths
|
||||||
|
const knowledgeFile = path.resolve('Knowledge.txt');
|
||||||
|
const skillsFile = path.resolve('Skills.txt');
|
||||||
|
const abilitiesFile = path.resolve('Abilities.txt');
|
||||||
|
|
||||||
|
const knowledgeData = parseTextFile(knowledgeFile, 'Knowledge');
|
||||||
|
const skillsData = parseTextFile(skillsFile, 'Skill');
|
||||||
|
const abilitiesData = parseTextFile(abilitiesFile, 'Ability');
|
||||||
|
|
||||||
|
const merged = [...knowledgeData, ...skillsData, ...abilitiesData];
|
||||||
|
|
||||||
|
fs.writeFileSync('ksa_data.json', JSON.stringify(merged, null, 2), 'utf8');
|
||||||
|
console.log(`Wrote ${merged.length} lines (incl. headers) to ksa_data.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
44
parseLine.js
Normal file
44
parseLine.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// parseLine.js
|
||||||
|
function parseLine(line) {
|
||||||
|
// Split on tabs
|
||||||
|
const cols = line.split(/\t/).map((c) => c.trim());
|
||||||
|
|
||||||
|
// We expect 15 columns, but we won't skip lines if some are missing or extra.
|
||||||
|
// We'll fill them with "" if not present, to preserve all data
|
||||||
|
const col0 = cols[0] || ""; // O*NET-SOC Code
|
||||||
|
const col1 = cols[1] || ""; // Title
|
||||||
|
const col2 = cols[2] || ""; // Element ID
|
||||||
|
const col3 = cols[3] || ""; // Element Name
|
||||||
|
const col4 = cols[4] || ""; // Scale ID
|
||||||
|
const col5 = cols[5] || ""; // Scale Name
|
||||||
|
const col6 = cols[6] || ""; // Data Value
|
||||||
|
const col7 = cols[7] || ""; // N
|
||||||
|
const col8 = cols[8] || ""; // Standard Error
|
||||||
|
const col9 = cols[9] || ""; // Lower CI Bound
|
||||||
|
const col10 = cols[10] || ""; // Upper CI Bound
|
||||||
|
const col11 = cols[11] || ""; // Recommend Suppress
|
||||||
|
const col12 = cols[12] || ""; // Not Relevant
|
||||||
|
const col13 = cols[13] || ""; // Date
|
||||||
|
const col14 = cols[14] || ""; // Domain Source
|
||||||
|
|
||||||
|
// Return an object with keys matching your definitions
|
||||||
|
return {
|
||||||
|
onetSocCode: col0,
|
||||||
|
title: col1,
|
||||||
|
elementID: col2,
|
||||||
|
elementName: col3,
|
||||||
|
scaleID: col4,
|
||||||
|
scaleName: col5,
|
||||||
|
dataValue: col6,
|
||||||
|
n: col7,
|
||||||
|
standardError: col8,
|
||||||
|
lowerCI: col9,
|
||||||
|
upperCI: col10,
|
||||||
|
recommendSuppress: col11,
|
||||||
|
notRelevant: col12,
|
||||||
|
date: col13,
|
||||||
|
domainSource: col14
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseLine;
|
||||||
3709532
public/ksa_data.json
Normal file
3709532
public/ksa_data.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -629,7 +629,7 @@ function CareerExplorer() {
|
|||||||
className="bg-green-600 text-white"
|
className="bg-green-600 text-white"
|
||||||
onClick={() => handleSelectForEducation(career)}
|
onClick={() => handleSelectForEducation(career)}
|
||||||
>
|
>
|
||||||
Search for Education
|
Plan your Education/Skills
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user