fixed server2 database and public file access

This commit is contained in:
Josh 2025-07-17 19:26:54 +00:00
parent a59ecd2b7a
commit c2cc63e2f7
22 changed files with 135 additions and 145 deletions

5
.env
View File

@ -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

View File

@ -4,8 +4,8 @@ ONET_PASSWORD=2296ahq
# ─── Publicfacing 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

View File

@ -3,9 +3,9 @@ ONET_USERNAME=aptivaai
ONET_PASSWORD=2296ahq
# ─── Publicfacing 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

View File

@ -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/*

View File

@ -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"]

View File

@ -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 \

View File

@ -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(',')

View File

@ -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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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]

View File

@ -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) => {

View File

@ -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,

View File

@ -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 }) {

View File

@ -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}
/>

View File

@ -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 =============

View File

@ -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)
})
});
}

View File

@ -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('');

View File

@ -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}),

View File

@ -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,
},

View File

@ -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 {

View File

@ -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]
);