From 7ddbbe42278f8365748e04ff6bf329faebd7f4db Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 23 Jun 2025 11:45:04 +0000 Subject: [PATCH] env variables for server3 --- backend/config/env.js | 20 +++++ backend/config/mysqlPool.js | 4 + backend/server3.js | 22 ++++-- ecosystem.config.cjs | 6 +- src/components/CareerRoadmap.js | 23 ++++-- src/components/MilestoneEditModal.js | 108 +++++++++++++++------------ 6 files changed, 120 insertions(+), 63 deletions(-) create mode 100644 backend/config/env.js diff --git a/backend/config/env.js b/backend/config/env.js new file mode 100644 index 0000000..16b71c6 --- /dev/null +++ b/backend/config/env.js @@ -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}`); \ No newline at end of file diff --git a/backend/config/mysqlPool.js b/backend/config/mysqlPool.js index df066c0..bd7ca97 100644 --- a/backend/config/mysqlPool.js +++ b/backend/config/mysqlPool.js @@ -1,4 +1,5 @@ // backend/config/mysqlPool.js +import './env.js'; import mysql from 'mysql2/promise'; const pool = mysql.createPool({ @@ -12,4 +13,7 @@ const pool = mysql.createPool({ ...(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; \ No newline at end of file diff --git a/backend/server3.js b/backend/server3.js index 9907d5e..ca98d67 100644 --- a/backend/server3.js +++ b/backend/server3.js @@ -1,15 +1,11 @@ // ─── server3.js ──────────────────────────────────────────────────────────── +import './config/env.js'; import path from 'path'; import { fileURLToPath } from 'url'; -import dotenv from 'dotenv'; const __filename = fileURLToPath(import.meta.url); 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 cors from 'cors'; import helmet from 'helmet'; @@ -810,6 +806,8 @@ ${econText} const { status: userStatus } = scenarioRow; const { career_situation: userSituation } = userProfile; const careerName = scenarioRow?.career_name || "this career"; + /* How many past exchanges to keep */ + const MAX_CHAT_TURNS = 6; const combinedStatusSituation = buildStatusSituationMessage( userStatus, @@ -949,6 +947,17 @@ RESPOND ONLY with valid JSON in this shape: Otherwise, answer normally. `.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 ? "\nAVOID repeating any of these title|date combinations:\n" + @@ -969,8 +978,7 @@ ${systemPromptMilestoneFormat} ${systemPromptDateGuard} `.trim(); -/* How many past exchanges to keep */ -const MAX_CHAT_TURNS = 6; + // Build up the final messages array const messagesToSend = [ diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index d13e35e..6eb6836 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -34,9 +34,9 @@ module.exports = { DB_PASSWORD : 'ps { .map((m, i) => ({ ...m, impacts: impactsForEach[i] || [] })) .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 financialBase = { 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 chartRef = useRef(null); - 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); @@ -1525,10 +1537,11 @@ const fetchMilestones = useCallback(async () => { {milestoneForModal && ( { - setMilestoneForModal(false); // or setShowMilestoneModal(false) + setMilestoneForModal(null); // or setShowMilestoneModal(false) if (didSave) { fetchMilestones(); } diff --git a/src/components/MilestoneEditModal.js b/src/components/MilestoneEditModal.js index 63c6a3e..13ab77a 100644 --- a/src/components/MilestoneEditModal.js +++ b/src/components/MilestoneEditModal.js @@ -1,7 +1,6 @@ import React, { useState, useEffect, useCallback } from "react"; import { Button } from "./ui/button.js"; import authFetch from "../utils/authFetch.js"; -import parseFloatOrZero from "../utils/ParseFloatorZero.js"; import MilestoneCopyWizard from "./MilestoneCopyWizard.js"; /** @@ -18,6 +17,7 @@ import MilestoneCopyWizard from "./MilestoneCopyWizard.js"; export default function MilestoneEditModal({ careerProfileId, milestones: incomingMils = [], + milestone: selectedMilestone, fetchMilestones, onClose }) { @@ -39,62 +39,74 @@ export default function MilestoneEditModal({ }); const [copyWizardMilestone, setCopyWizardMilestone] = useState(null); - function toSqlDate(str = '') { - // Handles '', null, undefined gracefully - return str.slice(0, 10); // "YYYY-MM-DD" -} + function toSqlDate(val) { + if (!val) return ''; // null | undefined | '' | 0 + return String(val).slice(0, 10); + } - /* keep milestones in sync with prop */ + /* keep milestones in sync with prop */ useEffect(() => { setMilestones(incomingMils); }, [incomingMils]); -/* ──────────────────────────────── - Inline-edit helpers -──────────────────────────────────*/ -const [originalImpactIdsMap, setOriginalImpactIdsMap] = useState({}); // snapshot per milestone + /* ──────────────────────────────── + Inline-edit helpers + ──────────────────────────────────*/ -/* 1️⃣ fetch impacts + open editor */ -const loadMilestoneImpacts = useCallback(async (m) => { - try { - const res = await authFetch(`/api/premium/milestone-impacts?milestone_id=${m.id}`); - if (!res.ok) throw new Error('impact fetch failed'); - const json = await res.json(); + // 1️⃣ fetch impacts + open editor ── moved **up** so the next effect + // can safely reference it in its dependency array + const loadMilestoneImpacts = useCallback(async (m) => { + try { + const res = await authFetch( + `/api/premium/milestone-impacts?milestone_id=${m.id}` + ); + if (!res.ok) throw new Error('impact fetch failed'); + const json = await res.json(); - const impacts = (json.impacts || []).map(imp => ({ - id : imp.id, - impact_type : imp.impact_type || 'ONE_TIME', - direction : imp.direction || 'subtract', - amount : imp.amount || 0, - start_date : toSqlDate(imp.start_date) || '', - end_date : toSqlDate(imp.end_date) || '' - })); + const impacts = (json.impacts || []).map(imp => ({ + id : imp.id, + impact_type : imp.impact_type || 'ONE_TIME', + direction : imp.direction || 'subtract', + amount : imp.amount || 0, + start_date : toSqlDate(imp.start_date) || '', + end_date : toSqlDate(imp.end_date) || '' + })); - /* editable copy for the form */ - setNewMilestoneMap(prev => ({ - ...prev, - [m.id]: { - title : m.title || '', - description : m.description || '', - date : toSqlDate(m.date) || '', - progress : m.progress || 0, - newSalary : m.new_salary || '', - impacts, - isUniversal : m.is_universal ? 1 : 0 - } - })); + // editable copy for the form + setNewMilestoneMap(prev => ({ + ...prev, + [m.id]: { + title : m.title || '', + description : m.description || '', + date : toSqlDate(m.date) || '', + progress : m.progress || 0, + newSalary : m.new_salary || '', + impacts, + isUniversal : m.is_universal ? 1 : 0 + } + })); - /* snapshot the IDs that existed when editing started */ - setOriginalImpactIdsMap(prev => ({ - ...prev, - [m.id]: impacts.map(i => i.id) // array of strings - })); + // snapshot of original impact IDs + setOriginalImpactIdsMap(prev => ({ + ...prev, + [m.id]: impacts.map(i => i.id) + })); + + setEditingMilestoneId(m.id); // open accordion + } catch (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({}); - setEditingMilestoneId(m.id); // open the accordion - } catch (err) { - console.error('loadImpacts', err); - } -}, []); /* 2️⃣ toggle open / close */ const handleEditMilestoneInline = (milestone) => { @@ -360,7 +372,7 @@ const saveNewMilestone = async () => {

{m.description}

- Date: {m.date} + Date: {toSqlDate(m.date)}

Progress: {m.progress}%