feat: co-steward invite/list/remove endpoints, co_steward_emails schema
This commit is contained in:
parent
abefc012fa
commit
0bfdb2285e
1 changed files with 77 additions and 0 deletions
|
|
@ -61,6 +61,9 @@ export async function initStewardDash() {
|
||||||
`);
|
`);
|
||||||
// Also add to registry for forward-compat (new stewards via web form)
|
// Also add to registry for forward-compat (new stewards via web form)
|
||||||
await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS dashboard_token TEXT`).catch(() => {});
|
await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS dashboard_token TEXT`).catch(() => {});
|
||||||
|
// Co-steward collaboration: array of invited co-steward emails
|
||||||
|
await pool.query(`ALTER TABLE agentify_subject_registry ADD COLUMN IF NOT EXISTS co_steward_emails TEXT[] DEFAULT '{}'`).catch(() => {});
|
||||||
|
await pool.query(`ALTER TABLE personaforge_subjects ADD COLUMN IF NOT EXISTS co_steward_emails TEXT[] DEFAULT '{}'`).catch(() => {});
|
||||||
console.log("[StewardDash] Schema columns ready");
|
console.log("[StewardDash] Schema columns ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1248,6 +1251,80 @@ Then give your verdict on a new line: "Verdict: Approve", "Verdict: Flag", or "V
|
||||||
const dashUrl = `https://agentify.help/steward/${slug}?token=${token}`;
|
const dashUrl = `https://agentify.help/steward/${slug}?token=${token}`;
|
||||||
res.json({ slug, dashboard_token: token, dashboard_url: dashUrl });
|
res.json({ slug, dashboard_token: token, dashboard_url: dashUrl });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── POST /api/steward/:slug/invite-co-steward ────────────────────────────
|
||||||
|
// Invite a collaborator email to co-steward this agent.
|
||||||
|
// They gain access via the /my-agents portfolio magic-link flow.
|
||||||
|
app.post("/api/steward/:slug/invite-co-steward", express.json({ limit: "1mb" }), async (req: Request, res: Response) => {
|
||||||
|
if (!isAgentify(req)) return res.status(404).json({ error: "Not found" });
|
||||||
|
const { slug } = req.params;
|
||||||
|
const token = req.query.token as string | undefined;
|
||||||
|
const key = (req.headers["x-admin-key"] as string) || (req.query.admin_key as string);
|
||||||
|
if (key !== ADMIN_KEY && !(await validateStewardToken(slug, token))) {
|
||||||
|
return res.status(403).json({ error: "Steward token or admin key required" });
|
||||||
|
}
|
||||||
|
const { email } = req.body ?? {};
|
||||||
|
if (!email || !email.includes("@")) return res.status(400).json({ error: "Valid email required" });
|
||||||
|
const normalized = email.toLowerCase().trim();
|
||||||
|
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE agentify_subject_registry
|
||||||
|
SET co_steward_emails = array_append(COALESCE(co_steward_emails, '{}'), $2)
|
||||||
|
WHERE subject_slug = $1
|
||||||
|
AND NOT ($2 = ANY(COALESCE(co_steward_emails, '{}')))`,
|
||||||
|
[slug, normalized]
|
||||||
|
).catch(() => {});
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE personaforge_subjects
|
||||||
|
SET co_steward_emails = array_append(COALESCE(co_steward_emails, '{}'), $2)
|
||||||
|
WHERE slug = $1
|
||||||
|
AND NOT ($2 = ANY(COALESCE(co_steward_emails, '{}')))`,
|
||||||
|
[slug, normalized]
|
||||||
|
).catch(() => {});
|
||||||
|
|
||||||
|
res.json({ ok: true, slug, co_steward_email: normalized,
|
||||||
|
note: "Co-steward added. They can now use /my-agents with their email to receive a portal access link." });
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── GET /api/steward/:slug/co-stewards ───────────────────────────────────
|
||||||
|
app.get("/api/steward/:slug/co-stewards", async (req: Request, res: Response) => {
|
||||||
|
if (!isAgentify(req)) return res.status(404).json({ error: "Not found" });
|
||||||
|
const { slug } = req.params;
|
||||||
|
const token = req.query.token as string | undefined;
|
||||||
|
const key = (req.headers["x-admin-key"] as string) || (req.query.admin_key as string);
|
||||||
|
if (key !== ADMIN_KEY && !(await validateStewardToken(slug, token))) {
|
||||||
|
return res.status(403).json({ error: "Steward token or admin key required" });
|
||||||
|
}
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
`SELECT co_steward_emails FROM agentify_subject_registry WHERE subject_slug = $1`, [slug]
|
||||||
|
).catch(() => ({ rows: [] }));
|
||||||
|
res.json({ slug, co_steward_emails: rows[0]?.co_steward_emails || [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── DELETE /api/steward/:slug/co-stewards/:email ─────────────────────────
|
||||||
|
app.delete("/api/steward/:slug/co-stewards/:email", async (req: Request, res: Response) => {
|
||||||
|
if (!isAgentify(req)) return res.status(404).json({ error: "Not found" });
|
||||||
|
const { slug, email } = req.params;
|
||||||
|
const token = req.query.token as string | undefined;
|
||||||
|
const key = (req.headers["x-admin-key"] as string) || (req.query.admin_key as string);
|
||||||
|
if (key !== ADMIN_KEY && !(await validateStewardToken(slug, token))) {
|
||||||
|
return res.status(403).json({ error: "Steward token or admin key required" });
|
||||||
|
}
|
||||||
|
const normalized = decodeURIComponent(email).toLowerCase().trim();
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE agentify_subject_registry
|
||||||
|
SET co_steward_emails = array_remove(COALESCE(co_steward_emails, '{}'), $2)
|
||||||
|
WHERE subject_slug = $1`,
|
||||||
|
[slug, normalized]
|
||||||
|
).catch(() => {});
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE personaforge_subjects
|
||||||
|
SET co_steward_emails = array_remove(COALESCE(co_steward_emails, '{}'), $2)
|
||||||
|
WHERE slug = $1`,
|
||||||
|
[slug, normalized]
|
||||||
|
).catch(() => {});
|
||||||
|
res.json({ ok: true, slug, removed: normalized });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Approved-only corpus build (async) ───────────────────────────────────────
|
// ── Approved-only corpus build (async) ───────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue