path-approve-hosted.mjs
116 lines 3.6 KB
Raw
sha256:fbe982a22c05c6fe2e93876f250deecdb43d883f648b4e1810c7546caa9f17db docs: activate KNOWTATION- board identity and preserve livi… Human minor ⚠ breaking 1 day ago
1 /**
2 * Gateway hook: after canister proposal approve, apply bridge learning-path indexes
3 * (KN-WORK-PATH-LIST-b — parity with task/capture/media hooks).
4 *
5 * Returns null unless the fetched proposal is a path proposal
6 * (review_queue === "learning-path" and proposal_kind in the closed allowlist).
7 * Must not steal task / capture / media apply.
8 */
9
10 import { proposalIdFromApprovePath } from '../../lib/muse-thin-bridge.mjs';
11 import {
12 fetchCanisterProposalForPath,
13 isPathProposalForHostedApply,
14 } from '../../lib/path/path-hosted-proposal.mjs';
15
16 const PROPOSAL_APPROVE_RE = /^\/api\/v1\/proposals\/[^/]+\/approve\/?$/;
17
18 /**
19 * @param {{
20 * method: string,
21 * pathOnly: string,
22 * upstreamStatus: number,
23 * canisterUrl: string,
24 * bridgeUrl: string,
25 * authorization: string|undefined,
26 * vaultId: string,
27 * effectiveUserId: string,
28 * actorUserId: string,
29 * canisterAuthHeaders: () => Record<string, string>,
30 * }} ctx
31 * @returns {Promise<{ applied: boolean, error?: string, code?: string, payload?: Record<string, unknown> }|null>}
32 */
33 export async function maybeApplyHostedPathAfterApprove(ctx) {
34 if (ctx.method !== 'POST' || !PROPOSAL_APPROVE_RE.test(ctx.pathOnly)) return null;
35 if (ctx.upstreamStatus < 200 || ctx.upstreamStatus >= 300) return null;
36
37 const proposalId = proposalIdFromApprovePath(ctx.pathOnly);
38 if (!proposalId || !ctx.bridgeUrl || !ctx.canisterUrl) return null;
39
40 const headers = {
41 ...ctx.canisterAuthHeaders(),
42 'X-User-Id': ctx.effectiveUserId,
43 'X-Actor-Id': ctx.actorUserId,
44 'X-Vault-Id': ctx.vaultId,
45 };
46
47 const fetched = await fetchCanisterProposalForPath({
48 canisterUrl: ctx.canisterUrl,
49 headers,
50 proposalId,
51 });
52 if (!fetched.ok) return null;
53 if (!isPathProposalForHostedApply(fetched.proposal)) return null;
54
55 const bridgeRes = await fetch(
56 `${ctx.bridgeUrl.replace(/\/$/, '')}/api/v1/learning-paths/proposals/${encodeURIComponent(proposalId)}/apply-approved`,
57 {
58 method: 'POST',
59 headers: {
60 Accept: 'application/json',
61 'Content-Type': 'application/json',
62 ...(ctx.authorization ? { Authorization: ctx.authorization } : {}),
63 'X-Vault-Id': ctx.vaultId,
64 },
65 body: JSON.stringify({}),
66 },
67 );
68
69 const text = await bridgeRes.text();
70 /** @type {Record<string, unknown>} */
71 let json = {};
72 try {
73 json = text ? JSON.parse(text) : {};
74 } catch {
75 json = {};
76 }
77
78 if (!bridgeRes.ok) {
79 return {
80 applied: false,
81 error: typeof json.error === 'string' ? json.error : text.slice(0, 200),
82 code: typeof json.code === 'string' ? json.code : 'PATH_APPLY_FAILED',
83 };
84 }
85
86 return {
87 applied: true,
88 payload: json && typeof json === 'object' ? /** @type {Record<string, unknown>} */ (json) : {},
89 };
90 }
91
92 /**
93 * Merge path apply outcome into canister approve JSON for the Hub client.
94 *
95 * @param {string} responseText
96 * @param {{ applied: boolean, error?: string, code?: string, payload?: Record<string, unknown> }|null} applyOutcome
97 * @returns {string}
98 */
99 export function mergePathApplyIntoApproveResponse(responseText, applyOutcome) {
100 if (!applyOutcome) return responseText;
101 try {
102 const body = JSON.parse(responseText);
103 if (!body || typeof body !== 'object') return responseText;
104 body.path_index_applied = applyOutcome.applied;
105 if (applyOutcome.applied && applyOutcome.payload) {
106 body.path_apply = applyOutcome.payload;
107 }
108 if (!applyOutcome.applied) {
109 body.path_apply_error = applyOutcome.error ?? 'Path index apply failed';
110 body.path_apply_code = applyOutcome.code ?? 'PATH_APPLY_FAILED';
111 }
112 return JSON.stringify(body);
113 } catch {
114 return responseText;
115 }
116 }
File History 1 commit
sha256:fbe982a22c05c6fe2e93876f250deecdb43d883f648b4e1810c7546caa9f17db docs: activate KNOWTATION- board identity and preserve livi… Human minor 1 day ago