171 lines
7.7 KiB
JavaScript
171 lines
7.7 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|