env variables for server3
This commit is contained in:
parent
24e5b4d0f9
commit
7ddbbe4227
20
backend/config/env.js
Normal file
20
backend/config/env.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// repo root = two levels up from /backend/config
|
||||||
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||||
|
const env = (process.env.NODE_ENV || 'development').trim();
|
||||||
|
|
||||||
|
// Prefer .env.development / .env.production — fall back to plain .env
|
||||||
|
const fileA = path.join(repoRoot, `.env.${env}`);
|
||||||
|
const fileB = path.join(repoRoot, '.env');
|
||||||
|
const chosen = fs.existsSync(fileA) ? fileA : fileB;
|
||||||
|
|
||||||
|
dotenv.config({ path: chosen });
|
||||||
|
|
||||||
|
console.log(`[env] loaded ${path.basename(chosen)} → DB_HOST=${process.env.DB_HOST}`);
|
@ -1,4 +1,5 @@
|
|||||||
// backend/config/mysqlPool.js
|
// backend/config/mysqlPool.js
|
||||||
|
import './env.js';
|
||||||
import mysql from 'mysql2/promise';
|
import mysql from 'mysql2/promise';
|
||||||
|
|
||||||
const pool = mysql.createPool({
|
const pool = mysql.createPool({
|
||||||
@ -12,4 +13,7 @@ const pool = mysql.createPool({
|
|||||||
...(process.env.DB_SOCKET ? { socketPath: process.env.DB_SOCKET } : {})
|
...(process.env.DB_SOCKET ? { socketPath: process.env.DB_SOCKET } : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('[mysqlPool] Using config →',
|
||||||
|
{ host: process.env.DB_HOST, port: process.env.DB_PORT, socket: process.env.DB_SOCKET });
|
||||||
|
|
||||||
export default pool;
|
export default pool;
|
@ -1,15 +1,11 @@
|
|||||||
// ─── server3.js ────────────────────────────────────────────────────────────
|
// ─── server3.js ────────────────────────────────────────────────────────────
|
||||||
|
import './config/env.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import dotenv from 'dotenv';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const env = (process.env.NODE_ENV || 'development').trim();
|
|
||||||
const envPath = path.resolve(__dirname, '..', `.env.${env}`);
|
|
||||||
dotenv.config({ path: envPath }); // ✅ envs are now ready
|
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
@ -810,6 +806,8 @@ ${econText}
|
|||||||
const { status: userStatus } = scenarioRow;
|
const { status: userStatus } = scenarioRow;
|
||||||
const { career_situation: userSituation } = userProfile;
|
const { career_situation: userSituation } = userProfile;
|
||||||
const careerName = scenarioRow?.career_name || "this career";
|
const careerName = scenarioRow?.career_name || "this career";
|
||||||
|
/* How many past exchanges to keep */
|
||||||
|
const MAX_CHAT_TURNS = 6;
|
||||||
|
|
||||||
const combinedStatusSituation = buildStatusSituationMessage(
|
const combinedStatusSituation = buildStatusSituationMessage(
|
||||||
userStatus,
|
userStatus,
|
||||||
@ -949,6 +947,17 @@ RESPOND ONLY with valid JSON in this shape:
|
|||||||
Otherwise, answer normally.
|
Otherwise, answer normally.
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
|
/* ─── date guard ─────────────────────────────────────────────── */
|
||||||
|
const todayISO = new Date().toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const systemPromptDateGuard = `
|
||||||
|
────────────────────────────────────────────────────────
|
||||||
|
📅 DATE GUARD
|
||||||
|
────────────────────────────────────────────────────────
|
||||||
|
Every milestone “date” must be **on or after** ${todayISO}.
|
||||||
|
If you’re asked for short-term dates, they still must be ≥ ${todayISO}.
|
||||||
|
Reject or re-ask if the user insists on a past date.
|
||||||
|
`.trim();
|
||||||
|
|
||||||
const avoidBlock = existingTitles.length
|
const avoidBlock = existingTitles.length
|
||||||
? "\nAVOID repeating any of these title|date combinations:\n" +
|
? "\nAVOID repeating any of these title|date combinations:\n" +
|
||||||
@ -969,8 +978,7 @@ ${systemPromptMilestoneFormat}
|
|||||||
${systemPromptDateGuard}
|
${systemPromptDateGuard}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
/* How many past exchanges to keep */
|
|
||||||
const MAX_CHAT_TURNS = 6;
|
|
||||||
|
|
||||||
// Build up the final messages array
|
// Build up the final messages array
|
||||||
const messagesToSend = [
|
const messagesToSend = [
|
||||||
|
@ -34,9 +34,9 @@ module.exports = {
|
|||||||
DB_PASSWORD : 'ps<g+2DO-eTb2mb5',
|
DB_PASSWORD : 'ps<g+2DO-eTb2mb5',
|
||||||
DB_NAME : 'user_profile_db',
|
DB_NAME : 'user_profile_db',
|
||||||
|
|
||||||
TWILIO_ACCOUNT_SID : 'AC…',
|
TWILIO_ACCOUNT_SID : 'ACd700c6fb9f691ccd9ccab73f2dd4173d',
|
||||||
TWILIO_AUTH_TOKEN : 'fb89…',
|
TWILIO_AUTH_TOKEN : 'fb8979ccb172032a249014c9c30eba80',
|
||||||
TWILIO_MESSAGING_SERVICE_SID : 'MG…'
|
TWILIO_MESSAGING_SERVICE_SID : 'MGMGaa07992a9231c841b1bfb879649026d6'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +360,8 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
|||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const [focusMid , setFocusMid ] = useState(null);
|
const [focusMid , setFocusMid ] = useState(null);
|
||||||
const [drawerMilestone, setDrawerMilestone] = useState(null);
|
const [drawerMilestone, setDrawerMilestone] = useState(null);
|
||||||
|
const [impactsById, setImpactsById] = useState({}); // id → [impacts]
|
||||||
|
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
const [simulationYearsInput, setSimulationYearsInput] = useState('20');
|
const [simulationYearsInput, setSimulationYearsInput] = useState('20');
|
||||||
@ -928,6 +930,14 @@ useEffect(() => {
|
|||||||
.map((m, i) => ({ ...m, impacts: impactsForEach[i] || [] }))
|
.map((m, i) => ({ ...m, impacts: impactsForEach[i] || [] }))
|
||||||
.flatMap((m) => m.impacts);
|
.flatMap((m) => m.impacts);
|
||||||
|
|
||||||
|
/* NEW – build a quick lookup table and expose it */
|
||||||
|
const map = {};
|
||||||
|
allImpacts.forEach((imp) => {
|
||||||
|
(map[imp.milestone_id] = map[imp.milestone_id] || []).push(imp);
|
||||||
|
});
|
||||||
|
setImpactsById(map); // <-- saves for the modal
|
||||||
|
|
||||||
|
|
||||||
const f = financialProfile;
|
const f = financialProfile;
|
||||||
const financialBase = {
|
const financialBase = {
|
||||||
currentSalary: parseFloatOrZero(f.current_salary, 0),
|
currentSalary: parseFloatOrZero(f.current_salary, 0),
|
||||||
@ -1196,10 +1206,12 @@ const DAILY_CLICK_LIMIT = 10; // example limit per day
|
|||||||
const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?';
|
const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?';
|
||||||
const chartRef = useRef(null);
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
|
|
||||||
const onEditMilestone = useCallback((m) => {
|
const onEditMilestone = useCallback((m) => {
|
||||||
setMilestoneForModal(m); // open modal
|
setMilestoneForModal({
|
||||||
}, []);
|
...m,
|
||||||
|
impacts: impactsById[m.id] || [] // give the modal what it needs
|
||||||
|
});
|
||||||
|
}, [impactsById]);
|
||||||
|
|
||||||
const currentIdRef = useRef(null);
|
const currentIdRef = useRef(null);
|
||||||
|
|
||||||
@ -1525,10 +1537,11 @@ const fetchMilestones = useCallback(async () => {
|
|||||||
{milestoneForModal && (
|
{milestoneForModal && (
|
||||||
<MilestoneEditModal
|
<MilestoneEditModal
|
||||||
careerProfileId={careerProfileId} // number
|
careerProfileId={careerProfileId} // number
|
||||||
milestones={scenarioMilestones} // array
|
milestones={scenarioMilestones}
|
||||||
|
milestone={milestoneForModal}
|
||||||
fetchMilestones={fetchMilestones} // helper to refresh list
|
fetchMilestones={fetchMilestones} // helper to refresh list
|
||||||
onClose={(didSave) => {
|
onClose={(didSave) => {
|
||||||
setMilestoneForModal(false); // or setShowMilestoneModal(false)
|
setMilestoneForModal(null); // or setShowMilestoneModal(false)
|
||||||
if (didSave) {
|
if (didSave) {
|
||||||
fetchMilestones();
|
fetchMilestones();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { Button } from "./ui/button.js";
|
import { Button } from "./ui/button.js";
|
||||||
import authFetch from "../utils/authFetch.js";
|
import authFetch from "../utils/authFetch.js";
|
||||||
import parseFloatOrZero from "../utils/ParseFloatorZero.js";
|
|
||||||
import MilestoneCopyWizard from "./MilestoneCopyWizard.js";
|
import MilestoneCopyWizard from "./MilestoneCopyWizard.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,6 +17,7 @@ import MilestoneCopyWizard from "./MilestoneCopyWizard.js";
|
|||||||
export default function MilestoneEditModal({
|
export default function MilestoneEditModal({
|
||||||
careerProfileId,
|
careerProfileId,
|
||||||
milestones: incomingMils = [],
|
milestones: incomingMils = [],
|
||||||
|
milestone: selectedMilestone,
|
||||||
fetchMilestones,
|
fetchMilestones,
|
||||||
onClose
|
onClose
|
||||||
}) {
|
}) {
|
||||||
@ -39,25 +39,27 @@ export default function MilestoneEditModal({
|
|||||||
});
|
});
|
||||||
const [copyWizardMilestone, setCopyWizardMilestone] = useState(null);
|
const [copyWizardMilestone, setCopyWizardMilestone] = useState(null);
|
||||||
|
|
||||||
function toSqlDate(str = '') {
|
function toSqlDate(val) {
|
||||||
// Handles '', null, undefined gracefully
|
if (!val) return ''; // null | undefined | '' | 0
|
||||||
return str.slice(0, 10); // "YYYY-MM-DD"
|
return String(val).slice(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* keep milestones in sync with prop */
|
/* keep milestones in sync with prop */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMilestones(incomingMils);
|
setMilestones(incomingMils);
|
||||||
}, [incomingMils]);
|
}, [incomingMils]);
|
||||||
|
|
||||||
/* ────────────────────────────────
|
/* ────────────────────────────────
|
||||||
Inline-edit helpers
|
Inline-edit helpers
|
||||||
──────────────────────────────────*/
|
──────────────────────────────────*/
|
||||||
const [originalImpactIdsMap, setOriginalImpactIdsMap] = useState({}); // snapshot per milestone
|
|
||||||
|
|
||||||
/* 1️⃣ fetch impacts + open editor */
|
// 1️⃣ fetch impacts + open editor ── moved **up** so the next effect
|
||||||
const loadMilestoneImpacts = useCallback(async (m) => {
|
// can safely reference it in its dependency array
|
||||||
|
const loadMilestoneImpacts = useCallback(async (m) => {
|
||||||
try {
|
try {
|
||||||
const res = await authFetch(`/api/premium/milestone-impacts?milestone_id=${m.id}`);
|
const res = await authFetch(
|
||||||
|
`/api/premium/milestone-impacts?milestone_id=${m.id}`
|
||||||
|
);
|
||||||
if (!res.ok) throw new Error('impact fetch failed');
|
if (!res.ok) throw new Error('impact fetch failed');
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
|
|
||||||
@ -70,7 +72,7 @@ const loadMilestoneImpacts = useCallback(async (m) => {
|
|||||||
end_date : toSqlDate(imp.end_date) || ''
|
end_date : toSqlDate(imp.end_date) || ''
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* editable copy for the form */
|
// editable copy for the form
|
||||||
setNewMilestoneMap(prev => ({
|
setNewMilestoneMap(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[m.id]: {
|
[m.id]: {
|
||||||
@ -84,17 +86,27 @@ const loadMilestoneImpacts = useCallback(async (m) => {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* snapshot the IDs that existed when editing started */
|
// snapshot of original impact IDs
|
||||||
setOriginalImpactIdsMap(prev => ({
|
setOriginalImpactIdsMap(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[m.id]: impacts.map(i => i.id) // array of strings
|
[m.id]: impacts.map(i => i.id)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setEditingMilestoneId(m.id); // open the accordion
|
setEditingMilestoneId(m.id); // open accordion
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('loadImpacts', err);
|
console.error('loadImpacts', err);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []); // ← useCallback deps (none)
|
||||||
|
|
||||||
|
// NOW the effect that calls it; declared **after** the callback
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedMilestone) {
|
||||||
|
loadMilestoneImpacts(selectedMilestone);
|
||||||
|
}
|
||||||
|
}, [selectedMilestone, loadMilestoneImpacts]);
|
||||||
|
|
||||||
|
const [originalImpactIdsMap, setOriginalImpactIdsMap] = useState({});
|
||||||
|
|
||||||
|
|
||||||
/* 2️⃣ toggle open / close */
|
/* 2️⃣ toggle open / close */
|
||||||
const handleEditMilestoneInline = (milestone) => {
|
const handleEditMilestoneInline = (milestone) => {
|
||||||
@ -360,7 +372,7 @@ const saveNewMilestone = async () => {
|
|||||||
</div>
|
</div>
|
||||||
<p>{m.description}</p>
|
<p>{m.description}</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Date:</strong> {m.date}
|
<strong>Date:</strong> {toSqlDate(m.date)}
|
||||||
</p>
|
</p>
|
||||||
<p>Progress: {m.progress}%</p>
|
<p>Progress: {m.progress}%</p>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user