fixed server2 database and public file access
This commit is contained in:
parent
a59ecd2b7a
commit
c2cc63e2f7
5
.env
5
.env
@ -1,5 +1,6 @@
|
||||
IMG_TAG=20250716
|
||||
IMG_TAG=20250716
|
||||
SERVER1_PORT=5000
|
||||
SERVER2_PORT=5001
|
||||
SERVER3_PORT=5002
|
||||
SALARY_DB=/salary_info.db
|
||||
SALARY_DB=/salary_info.db
|
||||
NODE_ENV=production
|
@ -4,8 +4,8 @@ ONET_PASSWORD=2296ahq
|
||||
|
||||
# ─── Public‐facing React build ───────────
|
||||
NODE_ENV=development
|
||||
REACT_APP_ENV=production
|
||||
APTIVA_API_BASE=https://dev1.aptivaai.com/api
|
||||
REACT_APP_ENV=development
|
||||
APTIVA_API_BASE=https://dev1.aptivaai.com
|
||||
REACT_APP_API_URL=${APTIVA_API_BASE}
|
||||
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
|
||||
@ -15,7 +15,7 @@ REACT_APP_OPENAI_API_KEY=sk-proj-IyBOKc2T9RyViN_WBZwnjNCwUiRDBekmrghpHTKyf6OsqWx
|
||||
OPENAI_API_KEY=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
|
||||
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||
COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
|
||||
SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db
|
||||
SALARY_DB=/salary_info.db
|
||||
|
||||
# ─── Database (premium server) ───────────
|
||||
DB_HOST=34.67.180.54
|
||||
@ -31,10 +31,10 @@ TWILIO_AUTH_TOKEN=fb8979ccb172032a249014c9c30eba80
|
||||
TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6
|
||||
|
||||
# ─── Anything new goes here ──────────────
|
||||
JWT_SECRET=a35F0iFAkkdWvSjnaLzepAl/JIxPRUh4NpcGptJgry2Z3KVLX4ZcYY5KaTf7kJY0
|
||||
JWT_SECRET=gW4QsOu4AJA4MooIUC9ld2i71VbBovzV1INsaU6ftxYPrxLIeMq6/OY61j0X2RV7
|
||||
|
||||
# ------------ CORS ------------
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://34.16.120.118:3000,https://dev1.aptivaai.com
|
||||
CORS_ALLOWED_ORIGINS=https://dev1.aptivaai.com,http://34.16.120.118:3000,http://localhost:3000
|
||||
SERVER1_PORT=5000
|
||||
SERVER2_PORT=5001
|
||||
SERVER3_PORT=5002
|
||||
|
@ -3,9 +3,9 @@ ONET_USERNAME=aptivaai
|
||||
ONET_PASSWORD=2296ahq
|
||||
|
||||
# ─── Public‐facing React build ───────────
|
||||
NODE_ENV=staging
|
||||
REACT_APP_ENV=production
|
||||
APTIVA_API_BASE=https://staging.aptivaai.com/api
|
||||
NODE_ENV=production
|
||||
REACT_APP_ENV=staging
|
||||
APTIVA_API_BASE=https://staging.aptivaai.com
|
||||
REACT_APP_API_URL=${APTIVA_API_BASE}
|
||||
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
|
||||
@ -15,7 +15,7 @@ REACT_APP_OPENAI_API_KEY=sk-proj-IyBOKc2T9RyViN_WBZwnjNCwUiRDBekmrghpHTKyf6OsqWx
|
||||
OPENAI_API_KEY=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
|
||||
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||
COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
|
||||
SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db
|
||||
SALARY_DB=/salary_info.db
|
||||
|
||||
# ─── Database (premium server) ───────────
|
||||
DB_HOST=34.67.180.54
|
||||
|
@ -1,5 +1,5 @@
|
||||
ARG APPPORT=5000
|
||||
FROM --platform=$TARGETPLATFORM node:20-slim
|
||||
FROM node:20-slim
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN apt-get update -y && apt-get install -y --no-install-recommends build-essential python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||
|
@ -1,10 +1,16 @@
|
||||
ARG APPPORT=5001
|
||||
FROM --platform=$TARGETPLATFORM node:20-slim
|
||||
FROM node:20-bullseye AS base
|
||||
WORKDIR /app
|
||||
|
||||
# ---- native build deps ----
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential python3 pkg-config && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# ---------------------------
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev --ignore-scripts
|
||||
RUN apt-get update && apt-get install -y build-essential python3 make g++ sqlite3 && rm -rf /var/lib/apt/lists/*
|
||||
RUN npm ci --unsafe-perm
|
||||
COPY public/ /app/public/
|
||||
COPY . .
|
||||
ENV PORT=5001
|
||||
EXPOSE 5001
|
||||
CMD ["node","backend/server2.js"]
|
||||
|
||||
CMD ["node", "backend/server2.js"]
|
@ -1,5 +1,5 @@
|
||||
ARG APPPORT=5002
|
||||
FROM --platform=$TARGETPLATFORM node:20-slim
|
||||
FROM node:20-slim
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN apt-get update -y \
|
||||
|
@ -30,6 +30,21 @@ const env = process.env.NODE_ENV?.trim() || 'development';
|
||||
const envPath = path.resolve(rootPath, `.env.${env}`);
|
||||
dotenv.config({ path: envPath }); // Load .env
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '..'); // repo root
|
||||
const PUBLIC_DIR = path.join(ROOT_DIR, 'public'); // static json files
|
||||
const CIP_TO_SOC_PATH = path.join(PUBLIC_DIR, 'CIP_to_ONET_SOC.xlsx');
|
||||
const INSTITUTION_DATA_PATH = path.join(PUBLIC_DIR, 'Institution_data.json');
|
||||
const SALARY_DB_PATH = path.join(ROOT_DIR, 'salary_info.db');
|
||||
const USER_PROFILE_DB_PATH = path.join(ROOT_DIR, 'user_profile.db');
|
||||
|
||||
for (const p of [CIP_TO_SOC_PATH, INSTITUTION_DATA_PATH,
|
||||
SALARY_DB_PATH, USER_PROFILE_DB_PATH]) {
|
||||
if (!fs.existsSync(p)) {
|
||||
console.error(`FATAL Required data file not found → ${p}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
|
||||
const chatLimiter = rateLimit({
|
||||
@ -38,16 +53,8 @@ const chatLimiter = rateLimit({
|
||||
keyGenerator: req => req.user?.id || req.ip
|
||||
});
|
||||
|
||||
if (!process.env.APTIVA_API_BASE) {
|
||||
console.error('FATAL APTIVA_API_BASE is not set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// CIP->SOC mapping file
|
||||
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx';
|
||||
|
||||
// Institution data
|
||||
const institutionFilePath = 'home/jcoakley/aptiva-dev1-app/public/Institution_data.json';
|
||||
const institutionData = JSON.parse(fs.readFileSync(INSTITUTION_DATA_PATH, 'utf8'));
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
@ -66,13 +73,14 @@ let userProfileDb;
|
||||
async function initDatabases() {
|
||||
try {
|
||||
db = await open({
|
||||
filename: '/home/jcoakley/aptiva-dev1-app/salary_info.db',
|
||||
driver : sqlite3.Database
|
||||
filename: SALARY_DB_PATH,
|
||||
driver : sqlite3.Database,
|
||||
mode : sqlite3.OPEN_READONLY
|
||||
});
|
||||
console.log('✅ Connected to salary_info.db');
|
||||
|
||||
userProfileDb = await open({
|
||||
filename: '/home/jcoakley/aptiva-dev1-app/user_profile.db',
|
||||
filename: USER_PROFILE_DB_PATH,
|
||||
driver : sqlite3.Database
|
||||
});
|
||||
console.log('✅ Connected to user_profile.db');
|
||||
@ -164,14 +172,9 @@ app.use((req, res, next) => next());
|
||||
* Load CIP->SOC mapping
|
||||
**************************************************/
|
||||
function loadMapping() {
|
||||
try {
|
||||
const workbook = xlsx.readFile(mappingFilePath);
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
return xlsx.utils.sheet_to_json(sheet);
|
||||
} catch (error) {
|
||||
console.error('Error reading CIP_to_ONET_SOC:', error);
|
||||
return [];
|
||||
}
|
||||
const wb = xlsx.readFile(CIP_TO_SOC_PATH);
|
||||
const sheet= wb.Sheets[wb.SheetNames[0]];
|
||||
return xlsx.utils.sheet_to_json(sheet); // socToCipMapping array
|
||||
}
|
||||
const socToCipMapping = loadMapping();
|
||||
if (socToCipMapping.length === 0) {
|
||||
@ -404,7 +407,7 @@ app.post('/api/onet/submit_answers', async (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
if (token) {
|
||||
try {
|
||||
await axios.post(`${process.env.APTIVA_API_BASE}/api/user-profile`,
|
||||
await axios.post('/api/user-profile',
|
||||
{
|
||||
interest_inventory_answers: answers,
|
||||
riasec: riasecCode
|
||||
@ -579,8 +582,7 @@ app.get('/api/schools', (req, res) => {
|
||||
// 3) Load your raw schools data
|
||||
let schoolsData = [];
|
||||
try {
|
||||
const rawData = fs.readFileSync(institutionFilePath, 'utf8');
|
||||
schoolsData = JSON.parse(rawData);
|
||||
schoolsData = institutionData;
|
||||
} catch (err) {
|
||||
console.error('Error parsing institution data:', err.message);
|
||||
return res.status(500).json({ error: 'Failed to load schools data.' });
|
||||
@ -633,8 +635,7 @@ app.get('/api/tuition', (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = fs.readFileSync(institutionFilePath, 'utf8');
|
||||
const schoolsData = JSON.parse(raw);
|
||||
schoolsData = institutionData;
|
||||
|
||||
const cipArray = cipCodes
|
||||
.split(',')
|
||||
|
@ -1,5 +0,0 @@
|
||||
services:
|
||||
server1: { env_file: ./env/dev.env }
|
||||
server2: { env_file: ./env/dev.env }
|
||||
server3: { env_file: ./env/dev.env }
|
||||
nginx : { env_file: ./env/dev.env }
|
@ -1,5 +0,0 @@
|
||||
services:
|
||||
server1: { env_file: ./env/prod.env }
|
||||
server2: { env_file: ./env/prod.env }
|
||||
server3: { env_file: ./env/prod.env }
|
||||
nginx : { env_file: ./env/prod.env }
|
@ -1,5 +0,0 @@
|
||||
services:
|
||||
server1: { env_file: ./env/staging.env }
|
||||
server2: { env_file: ./env/staging.env }
|
||||
server3: { env_file: ./env/staging.env }
|
||||
nginx : { env_file: ./env/staging.env }
|
@ -1,8 +1,13 @@
|
||||
x-env: &with-env
|
||||
env_file:
|
||||
- ${RUNTIME_ENV_FILE:-.env.production} # default for local runs
|
||||
restart: unless-stopped
|
||||
|
||||
services:
|
||||
server1:
|
||||
<<: *with-env
|
||||
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:${IMG_TAG}
|
||||
expose: ["${SERVER1_PORT}"]
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"]
|
||||
interval: 30s
|
||||
@ -13,6 +18,12 @@ services:
|
||||
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:${IMG_TAG}
|
||||
expose: ["${SERVER2_PORT}"]
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- ${RUNTIME_ENV_FILE}
|
||||
volumes:
|
||||
- ./public:/app/public:ro
|
||||
- ./salary_info.db:/app/salary_info.db:ro
|
||||
- ./user_profile.db:/app/user_profile.db
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER2_PORT}/healthz || exit 1"]
|
||||
interval: 30s
|
||||
@ -20,9 +31,9 @@ services:
|
||||
retries: 3
|
||||
|
||||
server3:
|
||||
<<: *with-env
|
||||
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server3:${IMG_TAG}
|
||||
expose: ["${SERVER3_PORT}"]
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER3_PORT}/healthz || exit 1"]
|
||||
interval: 30s
|
||||
@ -30,8 +41,10 @@ services:
|
||||
retries: 3
|
||||
|
||||
nginx:
|
||||
<<: *with-env
|
||||
image: nginx:1.25-alpine
|
||||
command: ["nginx","-g","daemon off;"]
|
||||
command: ["nginx", "-g", "daemon off;"]
|
||||
depends_on: [server1, server2, server3]
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
@ -40,4 +53,3 @@ services:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
- ./empty:/etc/nginx/conf.d
|
||||
depends_on: [server1, server2, server3]
|
||||
|
@ -38,7 +38,6 @@ import ChatCtx from './contexts/ChatCtx.js';
|
||||
|
||||
|
||||
export const ProfileCtx = React.createContext();
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '';
|
||||
|
||||
|
||||
function App() {
|
||||
@ -131,7 +130,7 @@ const uiToolHandlers = useMemo(() => {
|
||||
}
|
||||
|
||||
// If we have a token, validate it by fetching user
|
||||
fetch(`${apiUrl}/user-profile`, {
|
||||
fetch('/api/user-profile', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.then((res) => {
|
||||
|
@ -58,7 +58,6 @@ function getFullStateName(code) {
|
||||
function CareerExplorer() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '';
|
||||
|
||||
// ---------- Component States ----------
|
||||
const [userProfile, setUserProfile] = useState(null);
|
||||
@ -170,7 +169,7 @@ function CareerExplorer() {
|
||||
setProgress(0);
|
||||
|
||||
// 1) O*NET answers -> initial career list
|
||||
const submitRes = await axios.post(`${apiUrl}/onet/submit_answers`, {
|
||||
const submitRes = await axios.post('/api/onet/submit_answers', {
|
||||
answers,
|
||||
state: profileData.state,
|
||||
area: profileData.area,
|
||||
@ -204,7 +203,7 @@ function CareerExplorer() {
|
||||
|
||||
// 2) job zones (one call for all SOC codes)
|
||||
const socCodes = flattened.map((c) => c.code);
|
||||
const zonesRes = await axios.post(`${apiUrl}/job-zones`, { socCodes }).catch(() => null);
|
||||
const zonesRes = await axios.post('/api/job-zones', { socCodes }).catch(() => null);
|
||||
// increment progress for this single request
|
||||
increment();
|
||||
|
||||
@ -215,9 +214,9 @@ function CareerExplorer() {
|
||||
const strippedSoc = career.code.split('.')[0];
|
||||
|
||||
// build URLs
|
||||
const cipUrl = `${apiUrl}/cip/${career.code}`;
|
||||
const jobDetailsUrl = `${apiUrl}/onet/career-description/${career.code}`;
|
||||
const economicUrl = `${apiUrl}/projections/${strippedSoc}`;
|
||||
const cipUrl = `/api/cip/${career.code}`;
|
||||
const jobDetailsUrl = `/api/onet/career-description/${career.code}`;
|
||||
const economicUrl = `/api/projections/${strippedSoc}`;
|
||||
const salaryParams = { socCode: strippedSoc, area: profileData.area };
|
||||
|
||||
// We'll fetch them in parallel with our custom fetchWithProgress:
|
||||
@ -225,7 +224,7 @@ function CareerExplorer() {
|
||||
fetchWithProgress(cipUrl),
|
||||
fetchWithProgress(jobDetailsUrl),
|
||||
fetchWithProgress(economicUrl),
|
||||
fetchWithProgress(`${apiUrl}/salary`, salaryParams),
|
||||
fetchWithProgress('/api/salary', salaryParams),
|
||||
]);
|
||||
|
||||
// parse data
|
||||
@ -292,7 +291,7 @@ function CareerExplorer() {
|
||||
const fetchUserProfile = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await axios.get(`${apiUrl}/user-profile`, {
|
||||
const res = await axios.get('/api/user-profile', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
@ -323,7 +322,7 @@ function CareerExplorer() {
|
||||
};
|
||||
|
||||
fetchUserProfile();
|
||||
}, [apiUrl]);
|
||||
}, []);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// If user came from Interest Inventory => auto-fetch
|
||||
@ -363,7 +362,7 @@ function CareerExplorer() {
|
||||
|
||||
try {
|
||||
// 1) CIP fetch
|
||||
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`);
|
||||
const cipResponse = await fetch(`/api/cip/${socCode}`);
|
||||
if (!cipResponse.ok) {
|
||||
setError(
|
||||
`We're sorry, but specific details for "${career.title}" are not available at this time.`
|
||||
@ -379,7 +378,7 @@ function CareerExplorer() {
|
||||
|
||||
// 2) Job details (description + tasks)
|
||||
const jobDetailsResponse = await fetch(
|
||||
`${apiUrl}/onet/career-description/${socCode}`
|
||||
`/api/onet/career-description/${socCode}`
|
||||
);
|
||||
if (!jobDetailsResponse.ok) {
|
||||
setCareerDetails({
|
||||
@ -393,7 +392,7 @@ function CareerExplorer() {
|
||||
// 3) Salary data
|
||||
let salaryResponse;
|
||||
try {
|
||||
salaryResponse = await axios.get(`${apiUrl}/salary`, {
|
||||
salaryResponse = await axios.get('/api/salary', {
|
||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||
});
|
||||
} catch (error) {
|
||||
@ -437,7 +436,7 @@ function CareerExplorer() {
|
||||
let economicResponse = { data: {} };
|
||||
try {
|
||||
economicResponse = await axios.get(
|
||||
`${apiUrl}/projections/${socCode.split('.')[0]}`,
|
||||
`/api/projections/${socCode.split('.')[0]}`,
|
||||
{
|
||||
params: { state: fullStateName },
|
||||
}
|
||||
@ -457,13 +456,13 @@ function CareerExplorer() {
|
||||
|
||||
try {
|
||||
// Check local DB first (SQLite -> server2)
|
||||
const localRiskRes = await axios.get(`${apiUrl}/ai-risk/${socCode}`);
|
||||
const localRiskRes = await axios.get(`/api/ai-risk/${socCode}`);
|
||||
aiRisk = localRiskRes.data;
|
||||
} catch (err) {
|
||||
// If 404, we call server3's ChatGPT route at the SAME base url
|
||||
if (err.response && err.response.status === 404) {
|
||||
try {
|
||||
const aiRes = await axios.post(`${apiUrl}/public/ai-risk-analysis`, {
|
||||
const aiRes = await axios.post('/api/public/ai-risk-analysis', {
|
||||
socCode,
|
||||
careerName: career.title,
|
||||
jobDescription: description,
|
||||
@ -473,7 +472,7 @@ function CareerExplorer() {
|
||||
const { riskLevel, reasoning } = aiRes.data;
|
||||
|
||||
// store it back in server2 to avoid repeated GPT calls
|
||||
await axios.post(`${apiUrl}/ai-risk`, {
|
||||
await axios.post('/api/ai-risk', {
|
||||
socCode,
|
||||
careerName: aiRes.data.careerName,
|
||||
jobDescription: aiRes.data.jobDescription,
|
||||
@ -521,7 +520,7 @@ function CareerExplorer() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[userState, apiUrl, areaTitle]
|
||||
[userState, areaTitle]
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
@ -644,7 +643,7 @@ useEffect(() => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.post(
|
||||
`${apiUrl}/user-profile`,
|
||||
'/api/user-profile',
|
||||
{
|
||||
firstName: userProfile?.firstname,
|
||||
lastName: userProfile?.lastname,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL;
|
||||
|
||||
|
||||
function CareerModal({ career, careerDetails, closeModal, addCareerToList }) {
|
||||
|
@ -38,10 +38,6 @@ import InfoTooltip from "./ui/infoTooltip.js";
|
||||
import differenceInMonths from 'date-fns/differenceInMonths';
|
||||
|
||||
import "../styles/legacy/MilestoneTimeline.legacy.css";
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '';
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------
|
||||
// Register ChartJS Plugins
|
||||
@ -339,7 +335,6 @@ function getYearsInCareer(startDateString) {
|
||||
export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
||||
const { careerId } = useParams();
|
||||
const location = useLocation();
|
||||
const apiURL = process.env.REACT_APP_API_URL;
|
||||
|
||||
const [interestStrategy, setInterestStrategy] = useState('FLAT'); // 'NONE' | 'FLAT' | 'MONTE_CARLO'
|
||||
const [flatAnnualRate, setFlatAnnualRate] = useState(0.06);
|
||||
@ -398,7 +393,7 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
||||
const reloadScenarioAndCollege = useCallback(async () => {
|
||||
if (!careerProfileId) return;
|
||||
const s = await authFetch(
|
||||
`${apiURL}/premium/career-profile/${careerProfileId}`
|
||||
`api/premium/career-profile/${careerProfileId}`
|
||||
);
|
||||
if (s.ok) {
|
||||
const row = await s.json();
|
||||
@ -408,10 +403,10 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
||||
}
|
||||
|
||||
const c = await authFetch(
|
||||
`${apiURL}/premium/college-profile?careerProfileId=${careerProfileId}`
|
||||
`api/premium/college-profile?careerProfileId=${careerProfileId}`
|
||||
);
|
||||
if (c.ok) setCollegeProfile(await c.json());
|
||||
}, [careerProfileId, apiURL]);
|
||||
}, [careerProfileId]);
|
||||
|
||||
const milestoneGroups = useMemo(() => {
|
||||
if (!scenarioMilestones.length) return [];
|
||||
@ -514,10 +509,10 @@ useEffect(() => {
|
||||
const up = await authFetch('/api/user-profile');
|
||||
if (up.ok) setUserProfile(await up.json());
|
||||
|
||||
const fp = await authFetch(`${apiURL}/premium/financial-profile`);
|
||||
const fp = await authFetch('api/premium/financial-profile');
|
||||
if (fp.ok) setFinancialProfile(await fp.json());
|
||||
})();
|
||||
}, [apiURL]);
|
||||
}, []);
|
||||
|
||||
/* quick derived helpers */
|
||||
const userSalary = parseFloatOrZero(financialProfile?.current_salary);
|
||||
@ -687,7 +682,7 @@ useEffect(() => {
|
||||
|
||||
(async function init () {
|
||||
/* 1 ▸ get every row the user owns */
|
||||
const r = await authFetch(`${apiURL}/premium/career-profile/all`);
|
||||
const r = await authFetch('api/premium/career-profile/all');
|
||||
if (!r?.ok || cancelled) return;
|
||||
const { careerProfiles=[] } = await r.json();
|
||||
setExistingCareerProfiles(careerProfiles);
|
||||
@ -771,9 +766,9 @@ useEffect(() => {
|
||||
|
||||
const refetchScenario = useCallback(async () => {
|
||||
if (!careerProfileId) return;
|
||||
const r = await authFetch(`${apiURL}/premium/career-profile/${careerProfileId}`);
|
||||
const r = await authFetch('api/premium/career-profile/${careerProfileId}');
|
||||
if (r.ok) setScenarioRow(await r.json());
|
||||
}, [careerProfileId, apiURL]);
|
||||
}, [careerProfileId]);
|
||||
|
||||
// 5) from scenarioRow => find the full SOC => strip
|
||||
useEffect(() => {
|
||||
@ -822,14 +817,14 @@ async function fetchAiRisk(socCode, careerName, description, tasks) {
|
||||
|
||||
try {
|
||||
// 1) Check server2 for existing entry
|
||||
const localRiskRes = await axios.get(`${apiUrl}/ai-risk/${socCode}`);
|
||||
const localRiskRes = await axios.get('api/ai-risk/${socCode}');
|
||||
aiRisk = localRiskRes.data; // { socCode, riskLevel, ... }
|
||||
} catch (err) {
|
||||
// 2) If 404 => call server3
|
||||
if (err.response && err.response.status === 404) {
|
||||
try {
|
||||
// Call GPT via server3
|
||||
const aiRes = await axios.post(`${apiUrl}/public/ai-risk-analysis`, {
|
||||
const aiRes = await axios.post('api/public/ai-risk-analysis', {
|
||||
socCode,
|
||||
careerName,
|
||||
jobDescription: description,
|
||||
@ -863,7 +858,7 @@ try {
|
||||
}
|
||||
|
||||
// 3) Store in server2
|
||||
await axios.post(`${apiUrl}/ai-risk`, storePayload);
|
||||
await axios.post('api/ai-risk', storePayload);
|
||||
|
||||
// Construct final object for usage here
|
||||
aiRisk = {
|
||||
@ -900,7 +895,7 @@ useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const qs = new URLSearchParams({ socCode: strippedSocCode, area: userArea });
|
||||
const res = await fetch(`${apiURL}/salary?${qs}`, { signal: ctrl.signal });
|
||||
const res = await fetch('api/salary?${qs}', { signal: ctrl.signal });
|
||||
|
||||
if (res.ok) {
|
||||
setSalaryData(await res.json());
|
||||
@ -916,7 +911,7 @@ useEffect(() => {
|
||||
|
||||
// cancel if strippedSocCode / userArea changes before the fetch ends
|
||||
return () => ctrl.abort();
|
||||
}, [strippedSocCode, userArea, apiURL]);
|
||||
}, [strippedSocCode, userArea]);
|
||||
|
||||
/* 7) Economic Projections ---------------------------------------- */
|
||||
useEffect(() => {
|
||||
@ -932,7 +927,7 @@ useEffect(() => {
|
||||
try {
|
||||
const qs = new URLSearchParams({ state: userState });
|
||||
const res = await authFetch(
|
||||
`${apiURL}/projections/${strippedSocCode}?${qs}`,
|
||||
`api/projections/${strippedSocCode}?${qs}`,
|
||||
{ signal: ctrl.signal }
|
||||
);
|
||||
|
||||
@ -949,7 +944,7 @@ useEffect(() => {
|
||||
})();
|
||||
|
||||
return () => ctrl.abort();
|
||||
}, [strippedSocCode, userState, apiURL]);
|
||||
}, [strippedSocCode, userState]);
|
||||
|
||||
// 8) Build financial projection
|
||||
async function buildProjection(milestones) {
|
||||
@ -960,7 +955,7 @@ useEffect(() => {
|
||||
|
||||
// fetch impacts
|
||||
const imPromises = allMilestones.map((m) =>
|
||||
authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`)
|
||||
authFetch(`api/premium/milestone-impacts?milestone_id=${m.id}`)
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((dd) => dd?.impacts || [])
|
||||
.catch((e) => {
|
||||
@ -1280,8 +1275,8 @@ const fetchMilestones = useCallback(async () => {
|
||||
if (!careerProfileId) return;
|
||||
|
||||
const [profRes, uniRes] = await Promise.all([
|
||||
authFetch(`${apiURL}/premium/milestones?careerProfileId=${careerProfileId}`),
|
||||
authFetch(`${apiURL}/premium/milestones?careerProfileId=universal`)
|
||||
authFetch(`api/premium/milestones?careerProfileId=${careerProfileId}`),
|
||||
authFetch(`api/premium/milestones?careerProfileId=universal`)
|
||||
]);
|
||||
if (!profRes.ok || !uniRes.ok) return;
|
||||
|
||||
@ -1293,7 +1288,7 @@ const fetchMilestones = useCallback(async () => {
|
||||
if (financialProfile && scenarioRow && collegeProfile) {
|
||||
buildProjection(merged);
|
||||
} // single rebuild
|
||||
}, [financialProfile, scenarioRow, careerProfileId, apiURL]); // ← NOTICE: no buildProjection here
|
||||
}, [financialProfile, scenarioRow, careerProfileId]); // ← NOTICE: no buildProjection here
|
||||
|
||||
|
||||
return (
|
||||
@ -1543,7 +1538,6 @@ const fetchMilestones = useCallback(async () => {
|
||||
setFinancialProfile={setFinancialProfile}
|
||||
collegeProfile={collegeProfile}
|
||||
setCollegeProfile={setCollegeProfile}
|
||||
apiURL={apiURL}
|
||||
authFetch={authFetch}
|
||||
/>
|
||||
|
||||
|
@ -159,7 +159,6 @@ function Dashboard() {
|
||||
const popoutVisible = !!selectedCareer;
|
||||
|
||||
// ============= Auth & URL Setup =============
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '';
|
||||
|
||||
// AUTH fetch
|
||||
const authFetch = async (url, options = {}, onUnauthorized) => {
|
||||
@ -194,7 +193,7 @@ function Dashboard() {
|
||||
|
||||
// ============= Fetch user profile =============
|
||||
const fetchUserProfile = async () => {
|
||||
const res = await authFetch(`${apiUrl}/user-profile`);
|
||||
const res = await authFetch('api/user-profile');
|
||||
if (!res) return;
|
||||
|
||||
if (res.ok) {
|
||||
@ -215,7 +214,7 @@ function Dashboard() {
|
||||
// ============= Lifecycle: Load Profile =============
|
||||
useEffect(() => {
|
||||
fetchUserProfile();
|
||||
}, [apiUrl]); // load once
|
||||
}, []); // load once
|
||||
|
||||
// ============= jobZone & Fit Setup =============
|
||||
const jobZoneLabels = {
|
||||
@ -237,7 +236,7 @@ function Dashboard() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setProgress(0);
|
||||
const response = await authFetch(`${apiUrl}/onet/submit_answers`, {
|
||||
const response = await authFetch('api/onet/submit_answers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ answers }),
|
||||
@ -294,7 +293,7 @@ function Dashboard() {
|
||||
if (careerSuggestions.length === 0) return;
|
||||
const socCodes = careerSuggestions.map((career) => career.code);
|
||||
try {
|
||||
const response = await axios.post(`${apiUrl}/job-zones`, { socCodes });
|
||||
const response = await axios.post('api/job-zones', { socCodes });
|
||||
const jobZoneData = response.data;
|
||||
const updatedCareers = careerSuggestions.map((career) => ({
|
||||
...career,
|
||||
@ -306,7 +305,7 @@ function Dashboard() {
|
||||
}
|
||||
};
|
||||
fetchJobZones();
|
||||
}, [careerSuggestions, apiUrl]);
|
||||
}, [careerSuggestions]);
|
||||
|
||||
// ============= Filter by job zone, fit =============
|
||||
const filteredCareers = useMemo(() => {
|
||||
@ -382,20 +381,20 @@ function Dashboard() {
|
||||
|
||||
try {
|
||||
// CIP fetch
|
||||
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`);
|
||||
const cipResponse = await fetch(`api/cip/${socCode}`);
|
||||
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
|
||||
const { cipCode } = await cipResponse.json();
|
||||
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
||||
|
||||
// Job details
|
||||
const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`);
|
||||
const jobDetailsResponse = await fetch(`api/onet/career-description/${socCode}`);
|
||||
if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description');
|
||||
const { description, tasks } = await jobDetailsResponse.json();
|
||||
|
||||
// Salary
|
||||
let salaryResponse;
|
||||
try {
|
||||
salaryResponse = await axios.get(`${apiUrl}/salary`, {
|
||||
salaryResponse = await axios.get('api/salary', {
|
||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||
});
|
||||
} catch (error) {
|
||||
@ -407,7 +406,7 @@ function Dashboard() {
|
||||
// Economic
|
||||
let economicResponse;
|
||||
try {
|
||||
economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, {
|
||||
economicResponse = await axios.get(`api/projections/${socCode.split('.')[0]}`, {
|
||||
params: { state: fullName }, // e.g. "Kentucky"
|
||||
});
|
||||
} catch (error) {
|
||||
@ -417,7 +416,7 @@ function Dashboard() {
|
||||
// Tuition
|
||||
let tuitionResponse;
|
||||
try {
|
||||
tuitionResponse = await axios.get(`${apiUrl}/tuition`, {
|
||||
tuitionResponse = await axios.get('api/tuition', {
|
||||
params: { cipCode: cleanedCipCode, state: userState },
|
||||
});
|
||||
} catch (error) {
|
||||
@ -515,7 +514,7 @@ function Dashboard() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[userState, apiUrl, areaTitle, userZipcode, updateChatbotContext]
|
||||
[userState, areaTitle, userZipcode, updateChatbotContext]
|
||||
);
|
||||
|
||||
// ============= Let typed careers open PopoutPanel =============
|
||||
|
@ -8,7 +8,6 @@ const MilestoneAddModal = ({
|
||||
defaultScenarioId,
|
||||
scenarioId, // which scenario this milestone applies to
|
||||
editMilestone, // if editing an existing milestone, pass its data
|
||||
apiURL
|
||||
}) => {
|
||||
// Basic milestone fields
|
||||
const [title, setTitle] = useState('');
|
||||
@ -74,7 +73,7 @@ const MilestoneAddModal = ({
|
||||
if (editMilestone) {
|
||||
// 1) Update existing milestone
|
||||
milestoneId = editMilestone.id;
|
||||
await authFetch(`${apiURL}/premium/milestones/${milestoneId}`, {
|
||||
await authFetch(`api/premium/milestones/${milestoneId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@ -87,7 +86,7 @@ const MilestoneAddModal = ({
|
||||
// Then handle impacts below...
|
||||
} else {
|
||||
// 1) Create new milestone
|
||||
const res = await authFetch(`${apiURL}/premium/milestones`, {
|
||||
const res = await authFetch('api/premium/milestones', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@ -105,7 +104,7 @@ const MilestoneAddModal = ({
|
||||
// For simplicity, let's do multiple POST calls
|
||||
for (const impact of impacts) {
|
||||
// If editing, you might do a PUT if the impact already has an id
|
||||
await authFetch(`${apiURL}/premium/milestone-impacts`, {
|
||||
await authFetch('api/premium/milestone-impacts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@ -117,8 +116,8 @@ const MilestoneAddModal = ({
|
||||
end_month: impact.end_month !== null
|
||||
? parseInt(impact.end_month, 10)
|
||||
: null,
|
||||
created_at: new Date()..toISOString().slice(0, 10),
|
||||
updated_at: new Date()..toISOString().slice(0, 10)
|
||||
created_at: new Date().toISOString().slice(0, 10),
|
||||
updated_at: new Date().toISOString().slice(0, 10)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ import authFetch from '../../utils/authFetch.js';
|
||||
// 1) Import your CareerSearch component
|
||||
import CareerSearch from '../CareerSearch.js'; // adjust path as necessary
|
||||
|
||||
const apiURL = process.env.REACT_APP_API_URL;
|
||||
|
||||
const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
|
||||
// We store local state for “are you working,” “selectedCareer,” etc.
|
||||
const [currentlyWorking, setCurrentlyWorking] = useState('');
|
||||
|
@ -10,7 +10,6 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
||||
const [error, setError] = useState('');
|
||||
const [showSessionExpiredMsg, setShowSessionExpiredMsg] = useState(false);
|
||||
const location = useLocation();
|
||||
const apiUrl = process.env.REACT_APP_API_URL;
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the URL query param has ?session=expired
|
||||
@ -43,7 +42,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${apiUrl}/signin`, {
|
||||
const resp = await fetch('api/signin', {
|
||||
method : 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body : JSON.stringify({username, password}),
|
||||
|
@ -49,7 +49,6 @@ export function haversineDistance(lat1, lon1, lat2, lon2) {
|
||||
|
||||
export async function fetchSchools(cipCodes) {
|
||||
try {
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '';
|
||||
|
||||
// 1) If `cipCodes` is a single string => wrap in array
|
||||
let codesArray = Array.isArray(cipCodes) ? cipCodes : [cipCodes];
|
||||
@ -59,7 +58,7 @@ export async function fetchSchools(cipCodes) {
|
||||
const cipParam = codesArray.join(',');
|
||||
|
||||
// 3) Call your endpoint with `?cipCodes=1101,1409&state=NY`
|
||||
const response = await axios.get(`${apiUrl}/schools`, {
|
||||
const response = await axios.get('api/schools', {
|
||||
params: {
|
||||
cipCodes: cipParam,
|
||||
},
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export async function fetchCareerEnrichment(apiUrl, socCode, area) {
|
||||
export async function fetchCareerEnrichment(socCode, area) {
|
||||
// strippedSoc = remove decimals from e.g. "15-1132.00" => "15-1132"
|
||||
const strippedSoc = socCode.includes('.') ? socCode.split('.')[0] : socCode;
|
||||
|
||||
const [cipData, jobDetailsData, economicData, salaryData] = await Promise.all([
|
||||
axios.get(`${apiUrl}/cip/${socCode}`).catch(() => null),
|
||||
axios.get(`${apiUrl}/onet/career-description/${socCode}`).catch(() => null),
|
||||
axios.get(`${apiUrl}/projections/${strippedSoc}`, { params: { area } }).catch(() => null),
|
||||
axios.get(`${apiUrl}/salary`, { params: { socCode: strippedSoc, area } }).catch(() => null),
|
||||
axios.get(`api/cip/${socCode}`).catch(() => null),
|
||||
axios.get(`api/onet/career-description/${socCode}`).catch(() => null),
|
||||
axios.get(`api/projections/${strippedSoc}`, { params: { area } }).catch(() => null),
|
||||
axios.get('api/salary', { params: { socCode: strippedSoc, area } }).catch(() => null),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -21,20 +21,20 @@
|
||||
|
||||
try {
|
||||
// CIP fetch
|
||||
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`);
|
||||
const cipResponse = await fetch(`api/cip/${socCode}`);
|
||||
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
|
||||
const { cipCode } = await cipResponse.json();
|
||||
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
||||
|
||||
// Job details
|
||||
const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`);
|
||||
const jobDetailsResponse = await fetch(`api/onet/career-description/${socCode}`);
|
||||
if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description');
|
||||
const { description, tasks } = await jobDetailsResponse.json();
|
||||
|
||||
// Salary
|
||||
let salaryResponse;
|
||||
try {
|
||||
salaryResponse = await axios.get(`${apiUrl}/salary`, {
|
||||
salaryResponse = await axios.get('api/salary', {
|
||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||
});
|
||||
} catch (error) {
|
||||
@ -46,7 +46,7 @@
|
||||
// Economic
|
||||
let economicResponse;
|
||||
try {
|
||||
economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, {
|
||||
economicResponse = await axios.get(`api/projections/${socCode.split('.')[0]}`, {
|
||||
params: { state: fullName }, // e.g. "Kentucky"
|
||||
});
|
||||
} catch (error) {
|
||||
@ -56,7 +56,7 @@
|
||||
// Tuition
|
||||
let tuitionResponse;
|
||||
try {
|
||||
tuitionResponse = await axios.get(`${apiUrl}/tuition`, {
|
||||
tuitionResponse = await axios.get('api/tuition', {
|
||||
params: { cipCode: cleanedCipCode, state: userState },
|
||||
});
|
||||
} catch (error) {
|
||||
@ -154,5 +154,5 @@
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[userState, apiUrl, areaTitle, userZipcode, updateChatbotContext]
|
||||
[userState, areaTitle, userZipcode, updateChatbotContext]
|
||||
);
|
Loading…
Reference in New Issue
Block a user