Skip to content

Commit a08cac4

Browse files
committed
Fix card publish repair flow and redesign claims admin dashboard
1 parent 33dbe33 commit a08cac4

5 files changed

Lines changed: 174 additions & 190 deletions

File tree

api/admin/claim.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,17 @@ module.exports = async function handler(req, res) {
3838
const transitionRows = db.normalizeRows(
3939
await db.query('select * from claim_status_transitions where claim_id = $1 order by created_at asc', [claimId])
4040
);
41+
const cardRows = db.normalizeRows(
42+
await db.query('select * from agent_cards where claim_id = $1 order by ens asc', [claimId])
43+
);
4144

4245
return res.status(200).json({
4346
ok: true,
4447
claim: claimRows[0],
4548
agents: agentRows,
4649
events: eventRows,
47-
transitions: transitionRows
50+
transitions: transitionRows,
51+
cards: cardRows
4852
});
4953
} catch (error) {
5054
console.error('ADMIN_CLAIM_QUERY_FAILED', { message: error.message, code: error.code });

api/admin/publish-agent-cards.js

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,29 @@ module.exports = async function handler(req, res) {
1919
const claimId = typeof req.body?.claimId === 'string' ? req.body.claimId.trim() : '';
2020
if (!claimId) return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' });
2121

22+
let transactionOpen = false;
2223
try {
2324
const claimRows = db.normalizeRows(await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId]));
2425
if (!claimRows.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' });
25-
if (claimRows[0].status !== 'approved' && claimRows[0].status !== 'cards_published') {
26-
return res.status(409).json({ ok: false, status: 'INVALID_STATUS', error: 'Claim must be approved before publishing cards.' });
26+
27+
const claim = claimRows[0];
28+
if (claim.status !== 'approved' && claim.status !== 'cards_published') {
29+
return res.status(409).json({ ok: false, status: 'CLAIM_NOT_APPROVED', error: 'Claim must be approved before publishing cards.' });
2730
}
2831

2932
const agents = db.normalizeRows(await db.query('select * from claim_agents where claim_id = $1 order by capability asc', [claimId]));
30-
if (!agents.length) return res.status(409).json({ ok: false, status: 'NO_AGENTS_FOR_CLAIM' });
33+
if (!agents.length) return res.status(409).json({ ok: false, status: 'AGENTS_NOT_FOUND' });
3134

3235
const existing = db.normalizeRows(await db.query('select * from agent_cards where claim_id = $1 order by ens asc', [claimId]));
33-
if (existing.length) {
34-
return res.status(200).json({ ok: true, claimId, status: 'CARDS_ALREADY_PUBLISHED', cards: existing.map((r) => ({ ens: r.ens, cardUrl: r.card_url })) });
36+
const existingByEns = new Map(existing.map((row) => [String(row.ens || '').trim(), row]));
37+
38+
if (claim.status === 'cards_published' && isComplete(agents, existingByEns)) {
39+
return res.status(200).json({ ok: true, claimId, status: 'CARDS_ALREADY_PUBLISHED', cards: formatCards(existing) });
3540
}
3641

42+
await db.query('begin');
43+
transactionOpen = true;
44+
3745
const cards = [];
3846
for (const agent of agents) {
3947
const ens = String(agent.ens || '').trim();
@@ -60,34 +68,84 @@ module.exports = async function handler(req, res) {
6068
`update claim_agents
6169
set card_url = $3,
6270
card_status = 'published',
63-
card_published_at = now()
71+
card_published_at = coalesce(card_published_at, now())
6472
where claim_id = $1 and id = $2`,
6573
[claimId, agent.id, cardUrl]
6674
);
6775
cards.push({ ens, cardUrl });
6876
}
6977

70-
await db.query("update claim_requests set status = 'cards_published' where claim_id = $1", [claimId]);
71-
await db.query(
72-
`insert into claim_events (claim_id, event_type, actor, message, event_json)
73-
values ($1, 'agent_cards.published', 'admin', 'Agent cards published', $2::jsonb)`,
74-
[claimId, JSON.stringify({ count: cards.length, cards })]
78+
const alreadyPublishedEvent = db.normalizeRows(
79+
await db.query("select id from claim_events where claim_id = $1 and event_type = 'agent_cards.published' limit 1", [claimId])
7580
);
76-
await db.query(
77-
`insert into claim_status_transitions (claim_id, from_status, to_status, action, actor, reason, metadata_json)
78-
values ($1, 'approved', 'cards_published', 'publish_agent_cards', 'admin', null, $2::jsonb)`,
79-
[claimId, JSON.stringify({ cardCount: cards.length })]
81+
if (!alreadyPublishedEvent.length) {
82+
await db.query(
83+
`insert into claim_events (claim_id, event_type, actor, message, event_json)
84+
values ($1, 'agent_cards.published', 'admin', 'Agent cards published', $2::jsonb)`,
85+
[claimId, JSON.stringify({ count: cards.length, cards })]
86+
);
87+
}
88+
89+
const publishedTransition = db.normalizeRows(
90+
await db.query(
91+
`select id from claim_status_transitions
92+
where claim_id = $1 and from_status = 'approved' and to_status = 'cards_published' and action = 'publish_agent_cards'
93+
limit 1`,
94+
[claimId]
95+
)
8096
);
97+
if (!publishedTransition.length) {
98+
await db.query(
99+
`insert into claim_status_transitions (claim_id, from_status, to_status, action, actor, reason, metadata_json)
100+
values ($1, 'approved', 'cards_published', 'publish_agent_cards', 'admin', null, $2::jsonb)`,
101+
[claimId, JSON.stringify({ cardCount: cards.length })]
102+
);
103+
}
81104

82-
return res.status(200).json({ ok: true, claimId, status: 'CARDS_PUBLISHED', cards });
105+
if (claim.status === 'approved') {
106+
await db.query("update claim_requests set status = 'cards_published' where claim_id = $1", [claimId]);
107+
}
108+
109+
await db.query('commit');
110+
transactionOpen = false;
111+
112+
return res.status(200).json({
113+
ok: true,
114+
claimId,
115+
status: claim.status === 'approved' ? 'CARDS_PUBLISHED' : 'AGENT_CARDS_REPAIRED',
116+
cards
117+
});
83118
} catch (error) {
119+
if (transactionOpen) {
120+
try { await db.query('rollback'); } catch (_) {}
121+
}
122+
123+
const statusCode = error && error.statusCode ? error.statusCode : 500;
124+
const status = error && error.status ? error.status : (statusCode === 500 ? 'ADMIN_PUBLISH_AGENT_CARDS_FAILED' : 'AGENT_CARD_PUBLISH_FAILED');
125+
const message = error && error.error ? error.error : 'Failed to publish agent cards.';
126+
84127
console.error('ADMIN_PUBLISH_AGENT_CARDS_FAILED', { message: error.message, code: error.code, claimId });
85-
const payload = { ok: false, status: 'ADMIN_PUBLISH_AGENT_CARDS_FAILED', error: 'Failed to publish agent cards.' };
128+
129+
const payload = { ok: false, status, error: message };
86130
if (process.env.NODE_ENV !== 'production') payload.debug = { message: error.message, code: error.code };
87-
return res.status(500).json(payload);
131+
return res.status(statusCode).json(payload);
88132
}
89133
};
90134

135+
function isComplete(agents, existingByEns) {
136+
for (const agent of agents) {
137+
const ens = String(agent.ens || '').trim();
138+
const row = existingByEns.get(ens);
139+
if (!row) return false;
140+
if (!agent.card_url || !agent.card_status) return false;
141+
}
142+
return true;
143+
}
144+
145+
function formatCards(rows) {
146+
return rows.map((row) => ({ ens: row.ens, cardUrl: row.card_url }));
147+
}
148+
91149
function buildCardJson(agent, ens, capability) {
92150
return {
93151
type: 'erc8004/registration/v1',

0 commit comments

Comments
 (0)