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
|
||||
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;
|
@ -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 = [
|
||||
|
@ -34,9 +34,9 @@ module.exports = {
|
||||
DB_PASSWORD : 'ps<g+2DO-eTb2mb5',
|
||||
DB_NAME : 'user_profile_db',
|
||||
|
||||
TWILIO_ACCOUNT_SID : 'AC…',
|
||||
TWILIO_AUTH_TOKEN : 'fb89…',
|
||||
TWILIO_MESSAGING_SERVICE_SID : 'MG…'
|
||||
TWILIO_ACCOUNT_SID : 'ACd700c6fb9f691ccd9ccab73f2dd4173d',
|
||||
TWILIO_AUTH_TOKEN : 'fb8979ccb172032a249014c9c30eba80',
|
||||
TWILIO_MESSAGING_SERVICE_SID : 'MGMGaa07992a9231c841b1bfb879649026d6'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,6 +360,8 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [focusMid , setFocusMid ] = useState(null);
|
||||
const [drawerMilestone, setDrawerMilestone] = useState(null);
|
||||
const [impactsById, setImpactsById] = useState({}); // id → [impacts]
|
||||
|
||||
|
||||
// Config
|
||||
const [simulationYearsInput, setSimulationYearsInput] = useState('20');
|
||||
@ -928,6 +930,14 @@ useEffect(() => {
|
||||
.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 && (
|
||||
<MilestoneEditModal
|
||||
careerProfileId={careerProfileId} // number
|
||||
milestones={scenarioMilestones} // array
|
||||
milestones={scenarioMilestones}
|
||||
milestone={milestoneForModal}
|
||||
fetchMilestones={fetchMilestones} // helper to refresh list
|
||||
onClose={(didSave) => {
|
||||
setMilestoneForModal(false); // or setShowMilestoneModal(false)
|
||||
setMilestoneForModal(null); // or setShowMilestoneModal(false)
|
||||
if (didSave) {
|
||||
fetchMilestones();
|
||||
}
|
||||
|
@ -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,9 +39,9 @@ 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 */
|
||||
@ -52,12 +52,14 @@ export default function MilestoneEditModal({
|
||||
/* ────────────────────────────────
|
||||
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
|
||||
// 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}`);
|
||||
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();
|
||||
|
||||
@ -70,7 +72,7 @@ const loadMilestoneImpacts = useCallback(async (m) => {
|
||||
end_date : toSqlDate(imp.end_date) || ''
|
||||
}));
|
||||
|
||||
/* editable copy for the form */
|
||||
// editable copy for the form
|
||||
setNewMilestoneMap(prev => ({
|
||||
...prev,
|
||||
[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 => ({
|
||||
...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) {
|
||||
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 */
|
||||
const handleEditMilestoneInline = (milestone) => {
|
||||
@ -360,7 +372,7 @@ const saveNewMilestone = async () => {
|
||||
</div>
|
||||
<p>{m.description}</p>
|
||||
<p>
|
||||
<strong>Date:</strong> {m.date}
|
||||
<strong>Date:</strong> {toSqlDate(m.date)}
|
||||
</p>
|
||||
<p>Progress: {m.progress}%</p>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user