dev1/backend/utils/opsEngine.js
2025-06-26 15:43:49 +00:00

171 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* applyOps execute a fenced ```ops``` block returned by Jess.
* Supports milestones, tasks, impacts, scenario utilities, and college profile.
*
* @param {object} opsObj parsed JSON inside ```ops```
* @param {object} req Express request (for auth header)
* @param {string} scenarioId current career_profile_id (optional but lets us
* auto-fill when the bot forgets)
* @return {string[]} human-readable confirmations
*/
export async function applyOps(opsObj = {}, req, scenarioId = null) {
if (!Array.isArray(opsObj?.milestones) && !Array.isArray(opsObj?.tasks)
&& !Array.isArray(opsObj?.impacts) && !Array.isArray(opsObj?.scenarios)
&& !opsObj.collegeProfile) return [];
const apiBase = process.env.APTIVA_INTERNAL_API || 'http://localhost:5002/api';
const auth = (p, o = {}) =>
internalFetch(req, `${apiBase}${p}`, {
headers: { 'Content-Type': 'application/json', ...(o.headers || {}) },
...o
});
const confirmations = [];
/* ────────────────────────────────────────────────────
1. MILESTONE-LEVEL OPS (unchanged behaviour)
──────────────────────────────────────────────────── */
for (const m of opsObj.milestones || []) {
const op = (m?.op || '').toUpperCase();
if (op === 'DELETE' && m.id) { // single-scenario delete
const r = await auth(`/premium/milestones/${m.id.trim()}`, { method: 'DELETE' });
if (r.ok) confirmations.push(`Deleted milestone ${m.id}`);
continue;
}
if (op === 'UPDATE' && m.id && m.patch) {
const r = await auth(`/premium/milestones/${m.id}`, {
method: 'PUT', body: JSON.stringify(m.patch)
});
if (r.ok) confirmations.push(`Updated milestone ${m.id}`);
continue;
}
if (op === 'CREATE' && m.data) {
m.data.career_profile_id = m.data.career_profile_id || scenarioId;
const r = await auth('/premium/milestone', { method: 'POST', body: JSON.stringify(m.data) });
if (r.ok) {
const j = await r.json();
const newId = Array.isArray(j) ? j[0]?.id : j.id;
confirmations.push(`Created milestone ${newId || '(new)'}`);
}
continue;
}
if (op === 'DELETEALL' && m.id) { // delete across every scenario
const r = await auth(`/premium/milestones/${m.id}/all`, { method: 'DELETE' });
if (r.ok) confirmations.push(`Deleted milestone ${m.id} from all scenarios`);
continue;
}
if (op === 'COPY' && m.id && Array.isArray(m.targetScenarioIds)) {
const r = await auth('/premium/milestone/copy', {
method: 'POST',
body : JSON.stringify({ milestoneId: m.id, scenarioIds: m.targetScenarioIds })
});
if (r.ok) confirmations.push(`Copied milestone ${m.id}${m.targetScenarioIds.length} scenario(s)`);
continue;
}
}
/* ────────────────────────────────────────────────────
2. TASK-LEVEL OPS
──────────────────────────────────────────────────── */
for (const t of opsObj.tasks || []) {
const op = (t?.op || '').toUpperCase();
if (op === 'CREATE' && t.data && t.data.milestone_id) {
await auth('/premium/tasks', { method: 'POST', body: JSON.stringify(t.data) });
confirmations.push(`Added task to milestone ${t.data.milestone_id}`);
continue;
}
if (op === 'UPDATE' && t.taskId && t.patch) {
await auth(`/premium/tasks/${t.taskId}`, { method: 'PUT', body: JSON.stringify(t.patch) });
confirmations.push(`Updated task ${t.taskId}`);
continue;
}
if (op === 'DELETE' && t.taskId) {
await auth(`/premium/tasks/${t.taskId}`, { method: 'DELETE' });
confirmations.push(`Deleted task ${t.taskId}`);
continue;
}
}
/* ────────────────────────────────────────────────────
3. IMPACT-LEVEL OPS
──────────────────────────────────────────────────── */
for (const imp of opsObj.impacts || []) {
const op = (imp?.op || '').toUpperCase();
if (op === 'CREATE' && imp.data && imp.data.milestone_id) {
await auth('/premium/milestone-impacts', { method: 'POST', body: JSON.stringify(imp.data) });
confirmations.push(`Added impact to milestone ${imp.data.milestone_id}`);
continue;
}
if (op === 'UPDATE' && imp.impactId && imp.patch) {
await auth(`/premium/milestone-impacts/${imp.impactId}`, { method: 'PUT', body: JSON.stringify(imp.patch) });
confirmations.push(`Updated impact ${imp.impactId}`);
continue;
}
if (op === 'DELETE' && imp.impactId) {
await auth(`/premium/milestone-impacts/${imp.impactId}`, { method: 'DELETE' });
confirmations.push(`Deleted impact ${imp.impactId}`);
continue;
}
}
/* ────────────────────────────────────────────────────
4. SCENARIO (career_profile) OPS
──────────────────────────────────────────────────── */
for (const s of opsObj.scenarios || []) {
const op = (s?.op || '').toUpperCase();
if (op === 'CREATE' && s.data?.career_name) {
await auth('/premium/career-profile', { method: 'POST', body: JSON.stringify(s.data) });
confirmations.push(`Created scenario “${s.data.career_name}`);
continue;
}
if (op === 'UPDATE' && s.scenarioId && s.patch) {
/* if only goals are patched, hit the goals route; otherwise create a PUT route */
const hasOnlyGoals = Object.keys(s.patch).length === 1 && s.patch.career_goals !== undefined;
const url = hasOnlyGoals
? `/premium/career-profile/${s.scenarioId}/goals`
: `/premium/career-profile`; // <-- add generic PATCH if you implemented one
await auth(url.replace(/\/$/, `/${s.scenarioId}`), { method: 'PUT', body: JSON.stringify(s.patch) });
confirmations.push(`Updated scenario ${s.scenarioId}`);
continue;
}
if (op === 'DELETE' && s.scenarioId) {
await auth(`/premium/career-profile/${s.scenarioId}`, { method: 'DELETE' });
confirmations.push(`Deleted scenario ${s.scenarioId}`);
continue;
}
if (op === 'CLONE' && s.sourceId) {
await auth('/premium/career-profile/clone', { method: 'POST', body: JSON.stringify({
sourceId : s.sourceId,
overrides : s.overrides || {}
})});
confirmations.push(`Cloned scenario ${s.sourceId}`);
continue;
}
}
/* ────────────────────────────────────────────────────
5. COLLEGE PROFILE (single op per block)
──────────────────────────────────────────────────── */
if (opsObj.collegeProfile?.op?.toUpperCase() === 'UPSERT' && opsObj.collegeProfile.data) {
await auth('/premium/college-profile', { method: 'POST', body: JSON.stringify(opsObj.collegeProfile.data) });
confirmations.push('Saved college profile');
}
return confirmations;
}