Fixed issues with API URL formatting and added proxy configuration

This commit is contained in:
Josh 2024-12-24 17:28:15 +00:00
parent 4b95b92261
commit 85dfd14aa8
10 changed files with 7919 additions and 9864 deletions

View File

@ -4,3 +4,5 @@ ONET_PASSWORD=2296ahq
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
COLLEGE_SCORECARD_KEY = BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
REACT_APP_API_URL=http://localhost:5001

View File

@ -53,6 +53,14 @@ app.use(
})
);
// 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');

View File

@ -6,18 +6,18 @@ import dotenv from 'dotenv';
import xlsx from 'xlsx'; // Import xlsx to read the Excel file
import path from 'path';
import { fileURLToPath } from 'url'; // Import fileURLToPath to handle the current file's URL
import { open } from 'sqlite'; // Use the open method directly from sqlite package
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
dotenv.config({ path: '/home/jcoakley/backend/.env' }); // Load environment variables
sqlite3.verbose();
dotenv.config({ path: '/home/jcoakley/aptiva-dev1-app/backend/.env' }); // Load environment variables
console.log('ONET_USERNAME:', process.env.ONET_USERNAME);
console.log('ONET_PASSWORD:', process.env.ONET_PASSWORD);
// Get the current directory path (workaround for __dirname in ES modules)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const allowedOrigins = ['http://localhost:3000', 'http://34.16.120.118:3000', 'https://dev.aptivaai.com'];
const mappingFilePath = '/home/jcoakley/public/CIP_to_ONET_SOC.xlsx'
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx'
const app = express();
const PORT = process.env.PORT || 5001;
@ -25,9 +25,10 @@ const PORT = process.env.PORT || 5001;
let db;
const initDB = async () => {
try {
// Opening SQLite connection using sqlite's open function and sqlite3 as the driver
db = await open({
filename: '/home/jcoakley/salary_info.db', // Correct file path
driver: sqlite3.Database,
filename: '/home/jcoakley/aptiva-dev1-app/salary_info.db', // Path to SQLite DB file
driver: sqlite3.Database, // Use sqlite3's Database driver
});
console.log('Connected to SQLite database.');
} catch (error) {
@ -131,6 +132,7 @@ app.get('/api/onet/questions', async (req, res) => {
},
}
);
console.log('O*Net Response:', response.data);
// Add questions to the result set
if (response.data.question && Array.isArray(response.data.question)) {
@ -156,56 +158,6 @@ app.get('/api/onet/questions', async (req, res) => {
}
});
// Route to fetch O*Net Careers list
app.get('/api/onet/questions', async (req, res) => {
const { start, end } = req.query;
if (!start || !end) {
return res.status(400).json({ error: 'Start and end parameters are required' });
}
try {
const questions = [];
let currentStart = parseInt(start, 10);
let currentEnd = parseInt(end, 10);
while (currentStart <= currentEnd) {
// Fetch questions from O*Net API for the current range
const response = await axios.get(
`https://services.onetcenter.org/ws/mnm/interestprofiler/questions?start=${currentStart}&end=${Math.min(currentEnd, currentStart + 11)}`,
{
auth: {
username: process.env.ONET_USERNAME,
password: process.env.ONET_PASSWORD,
},
}
);
// Add questions to the result set
if (response.data.question && Array.isArray(response.data.question)) {
questions.push(...response.data.question);
}
// Check if there's a next page
const nextLink = response.data.link?.find((link) => link.rel === 'next');
if (nextLink) {
// Update start and end based on the "next" link
const nextParams = new URLSearchParams(nextLink.href.split('?')[1]);
currentStart = parseInt(nextParams.get('start'), 10);
currentEnd = parseInt(nextParams.get('end'), 10);
} else {
break; // Stop if there are no more pages
}
}
res.status(200).json({ questions });
} catch (error) {
console.error('Error fetching O*Net questions:', error.message);
res.status(500).json({ error: 'Failed to fetch O*Net questions' });
}
});
// New route to handle Google Maps geocoding
app.get('/api/maps/distance', async (req, res) => {
const { origins, destinations } = req.query;

17647
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,14 +16,16 @@
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^7.0.2",
"react-scripts": "^3.0.1",
"react-router-dom": "^7.1.1",
"react-scripts": "^5.0.1",
"react-spinners": "^0.15.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"web-vitals": "^4.2.4"
"web-vitals": "^4.2.4",
"xlsx": "^0.18.5"
},
"scripts": {
"start": "react-scripts start",
"start": "react-scripts start --host 0.0.0.0",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
@ -50,5 +52,8 @@
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC"
"license": "ISC",
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
}
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState} from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import GettingStarted from './components/GettingStarted.js';
import SignIn from './components/SignIn.js';

View File

@ -4,7 +4,6 @@ import { useNavigate, useLocation } from 'react-router-dom';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
import { CareerSuggestions } from './CareerSuggestions.js';
import PopoutPanel from './PopoutPanel.js';
import LoanRepayment from './LoanRepayment.js';
import { Bar } from 'react-chartjs-2';
import './Dashboard.css';
@ -24,7 +23,7 @@ function Dashboard() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [cipCode, setCipCode] = useState(null);
const apiUrl = process.env.REACT_APP_API_URL;
useEffect(() => {
// Redirect if no data is available
@ -38,20 +37,8 @@ function Dashboard() {
}
}, [location.state, navigate]);
const fetchCipCode = async (socCode) => {
try {
const response = await fetch(`/api/cip/${socCode}`);
const data = await response.json();
if (!data.cipCode) throw new Error('No CIP Code returned');
setCipCode(data.cipCode.replace('.', '').slice(0, 4));
} catch (err) {
console.error('Error fetching CIP Code:', err.message);
setError('Failed to fetch CIP Code');
}
};
const fetchSchools = async (cipCode, userState) => {
const response = await axios.get(`/api/CIP_institution_mapping_fixed.json`);
const response = await axios.get(`${apiUrl}/api/CIP_institution_mapping_fixed.json`);
const schoolsData = response.data;
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
@ -82,13 +69,13 @@ function Dashboard() {
// Step 1: Fetch user profile to get state/area
const token = localStorage.getItem('token');
const profileResponse = await fetch('/api/user-profile', {
const profileResponse = await fetch(`${apiUrl}/api/user-profile`, {
headers: { Authorization: `Bearer ${token}` },
});
const { state: userState, area: userArea } = await profileResponse.json();
// Step 2: Fetch CIP Code for SOC Code
const cipResponse = await fetch(`/api/cip/${socCode}`);
const cipResponse = await fetch(`${apiUrl}/api/cip/${socCode}`);
const { cipCode } = await cipResponse.json();
if (!cipCode) throw new Error('Failed to fetch CIP Code');
@ -113,7 +100,7 @@ function Dashboard() {
});
setTuitionData(tuitionResponse.data);
const salaryResponse = await axios.get('/api/salary', {
const salaryResponse = await axios.get(`/api/salary`, {
params: { socCode: cleanedSocCode, area: userArea },
});
setSalaryData(salaryResponse.data);

View File

@ -5,13 +5,6 @@ import axios from 'axios';
function EconomicProjections({ socCode }) {
const [projections, setProjections] = useState(null);
const [error, setError] = useState(null);
const projections = {
"2022 Employment": projectionsData?.['2022 Employment'] || 'N/A',
"2032 Employment": projectionsData?.['2032 Employment'] || 'N/A',
"Total Change": projectionsData?.['Total Change'] || 'N/A',
"Annual Openings": projectionsData?.['Annual Openings'] || 'N/A',
"Projected Growth": projectionsData?.['Projected Growth'] || 'N/A',
};
useEffect(() => {
if (socCode) {

View File

@ -12,8 +12,11 @@ const InterestInventory = () => {
const [careerSuggestions, setCareerSuggestions] = useState([]);
const fetchQuestions = async () => {
const url = `/api/onet/questions?start=1&end=60`;
const baseUrl = process.env.NODE_ENV === 'production'
? '/api/onet/questions' // Production, assuming it's routed by Nginx
: 'http://localhost:5001/api/onet/questions'; // Development, backend runs on localhost:5000
const url = `${baseUrl}?start=1&end=60`;
try {
const response = await fetch(url, {
method: 'GET',
@ -93,7 +96,11 @@ const InterestInventory = () => {
try {
setIsSubmitting(true);
const response = await fetch('/api/onet/submit_answers', {
const baseUrl = process.env.NODE_ENV === 'production'
? '/api/onet/submit_answers' // In production, this is proxied by Nginx to server2 (port 5001)
: 'http://localhost:5001/api/onet/submit_answers'; // In development, server2 runs on port 5001
const response = await fetch(baseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ answers }),

View File

@ -1,6 +1,4 @@
// components/SignIn.js
import axios from 'axios';
import jwt_decode from 'jwt-decode'; // Optional for decoding the JWT
import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import './SignIn.css';