|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +const db = require('../../lib/db'); |
| 4 | +const { stableStringify, sha256Hex, pinJsonToPinata } = require('../../lib/ipfsPinning'); |
| 5 | + |
| 6 | +module.exports = async function handler(req, res) { |
| 7 | + res.setHeader('Content-Type', 'application/json; charset=utf-8'); |
| 8 | + res.setHeader('Cache-Control', 'no-store'); |
| 9 | + |
| 10 | + if (req.method !== 'POST') { |
| 11 | + res.setHeader('Allow', 'POST'); |
| 12 | + return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); |
| 13 | + } |
| 14 | + |
| 15 | + if (!process.env.ADMIN_API_KEY || req.headers['x-admin-api-key'] !== process.env.ADMIN_API_KEY) { |
| 16 | + return res.status(401).json({ ok: false, status: 'UNAUTHORIZED' }); |
| 17 | + } |
| 18 | + |
| 19 | + const provider = process.env.IPFS_PINNING_PROVIDER || 'pinata'; |
| 20 | + if (provider !== 'pinata') return res.status(400).json({ ok: false, status: 'UNSUPPORTED_PINNING_PROVIDER' }); |
| 21 | + |
| 22 | + const claimId = req.body && req.body.claimId; |
| 23 | + if (!claimId) return res.status(400).json({ ok: false, status: 'CLAIM_ID_REQUIRED' }); |
| 24 | + |
| 25 | + const claimResult = await db.query('select claim_id, status from claim_requests where claim_id = $1 limit 1', [claimId]); |
| 26 | + const claim = claimResult.rows[0]; |
| 27 | + if (!claim) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); |
| 28 | + if (claim.status !== 'paid' && claim.status !== 'cards_pinned') return res.status(400).json({ ok: false, status: 'CLAIM_NOT_PAID' }); |
| 29 | + |
| 30 | + const cardsResult = await db.query( |
| 31 | + `select id, claim_id, ens, card_json, card_cid, card_ipfs_uri, card_gateway_url, card_sha256, pin_status |
| 32 | + from agent_cards |
| 33 | + where claim_id = $1 and status = 'published' |
| 34 | + order by created_at asc`, |
| 35 | + [claimId] |
| 36 | + ); |
| 37 | + if (!cardsResult.rows.length) return res.status(400).json({ ok: false, status: 'NO_PUBLISHED_CARDS' }); |
| 38 | + |
| 39 | + const allPinned = cardsResult.rows.every((r) => r.card_cid && r.card_ipfs_uri && r.card_sha256); |
| 40 | + if (allPinned) return res.status(200).json({ ok: true, status: 'ALREADY_PINNED', claimId, cards: cardsResult.rows }); |
| 41 | + |
| 42 | + const gatewayBase = (process.env.IPFS_GATEWAY_BASE_URL || 'https://gateway.pinata.cloud/ipfs').replace(/\/$/, ''); |
| 43 | + |
| 44 | + try { |
| 45 | + const pinned = []; |
| 46 | + for (const row of cardsResult.rows) { |
| 47 | + const canonical = stableStringify(row.card_json); |
| 48 | + const hash = sha256Hex(canonical); |
| 49 | + |
| 50 | + if (row.card_cid && row.card_ipfs_uri && row.card_sha256) { |
| 51 | + pinned.push({ ens: row.ens, card_cid: row.card_cid, card_sha256: row.card_sha256, card_gateway_url: row.card_gateway_url }); |
| 52 | + continue; |
| 53 | + } |
| 54 | + |
| 55 | + const cid = await pinJsonToPinata(row.card_json); |
| 56 | + const ipfsUri = `ipfs://${cid}`; |
| 57 | + const gatewayUrl = `${gatewayBase}/${cid}`; |
| 58 | + await db.query( |
| 59 | + `update agent_cards |
| 60 | + set card_cid = $2, card_ipfs_uri = $3, card_gateway_url = $4, card_sha256 = $5, |
| 61 | + card_pinned_at = now(), pinning_provider = $6, pin_status = 'pinned', pin_error = null |
| 62 | + where id = $1`, |
| 63 | + [row.id, cid, ipfsUri, gatewayUrl, hash, provider] |
| 64 | + ); |
| 65 | + pinned.push({ ens: row.ens, card_cid: cid, card_sha256: hash, card_gateway_url: gatewayUrl }); |
| 66 | + } |
| 67 | + |
| 68 | + await db.query('update claim_requests set status = $2, updated_at = now() where claim_id = $1', [claimId, 'cards_pinned']); |
| 69 | + await db.query( |
| 70 | + `insert into claim_events (claim_id, event_type, message, metadata_json) |
| 71 | + values ($1, 'agent_cards.pinned', 'Agent cards pinned to IPFS.', $2::jsonb)`, |
| 72 | + [claimId, JSON.stringify({ provider, count: pinned.length })] |
| 73 | + ); |
| 74 | + await db.query( |
| 75 | + `insert into claim_status_transitions (claim_id, from_status, to_status) |
| 76 | + values ($1, 'paid', 'cards_pinned')`, |
| 77 | + [claimId] |
| 78 | + ); |
| 79 | + |
| 80 | + return res.status(200).json({ ok: true, status: 'CARDS_PINNED', claimId, provider, cards: pinned }); |
| 81 | + } catch (error) { |
| 82 | + await db.query( |
| 83 | + `update agent_cards set pin_status = 'error', pin_error = $2 where claim_id = $1 and status = 'published'`, |
| 84 | + [claimId, String(error && error.message ? error.message : 'pinning_failed')] |
| 85 | + ); |
| 86 | + return res.status(502).json({ ok: false, status: 'PINNING_FAILED', claimId }); |
| 87 | + } |
| 88 | +}; |
0 commit comments