diff --git a/api/admin/_auth.js b/api/admin/_auth.js new file mode 100644 index 0000000..d279848 --- /dev/null +++ b/api/admin/_auth.js @@ -0,0 +1,25 @@ +'use strict'; + +function requireAdminAuth(req, res) { + const configured = process.env.ADMIN_API_KEY; + if (!configured) { + res.status(503).json({ ok: false, status: 'ADMIN_NOT_CONFIGURED' }); + return false; + } + + const authorization = req.headers && (req.headers.authorization || req.headers.Authorization); + const token = typeof authorization === 'string' && authorization.startsWith('Bearer ') + ? authorization.slice('Bearer '.length) + : ''; + + if (!token || token !== configured) { + res.status(401).json({ ok: false, status: 'UNAUTHORIZED' }); + return false; + } + + return true; +} + +module.exports = { + requireAdminAuth +}; diff --git a/api/admin/claim.js b/api/admin/claim.js new file mode 100644 index 0000000..54caef0 --- /dev/null +++ b/api/admin/claim.js @@ -0,0 +1,50 @@ +'use strict'; + +const db = require('../../lib/db'); +const { requireAdminAuth } = require('./_auth'); + +module.exports = async function handler(req, res) { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.setHeader('Cache-Control', 'no-store'); + + if (req.method !== 'GET') { + res.setHeader('Allow', 'GET'); + return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); + } + + if (!requireAdminAuth(req, res)) { + return; + } + + const claimId = req.query && req.query.claimId; + if (!claimId || typeof claimId !== 'string') { + return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' }); + } + + try { + const claimResult = await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId]); + if (!claimResult.rows.length) { + return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); + } + + const agentsResult = await db.query( + `select ens, capability, canonical_parent, skill, skill_family, created_at + from claim_agents where claim_id = $1 order by created_at asc`, + [claimId] + ); + const eventsResult = await db.query( + `select event_type, message, metadata_json, created_at + from claim_events where claim_id = $1 order by created_at asc`, + [claimId] + ); + + return res.status(200).json({ + ok: true, + claim: claimResult.rows[0], + agents: agentsResult.rows, + events: eventsResult.rows + }); + } catch (error) { + return res.status(500).json({ ok: false, status: 'ADMIN_CLAIM_QUERY_FAILED' }); + } +}; diff --git a/api/admin/claims.js b/api/admin/claims.js new file mode 100644 index 0000000..e6cccce --- /dev/null +++ b/api/admin/claims.js @@ -0,0 +1,56 @@ +'use strict'; + +const db = require('../../lib/db'); +const { requireAdminAuth } = require('./_auth'); + +module.exports = async function handler(req, res) { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.setHeader('Cache-Control', 'no-store'); + + if (req.method !== 'GET') { + res.setHeader('Allow', 'GET'); + return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); + } + + if (!requireAdminAuth(req, res)) { + return; + } + + const requestedLimit = Number.parseInt(req.query && req.query.limit, 10); + const limit = Number.isFinite(requestedLimit) && requestedLimit > 0 + ? Math.min(requestedLimit, 200) + : 50; + + try { + const result = await db.query( + `select claim_id, tenant, authenticated_address, activation_mode, pack_id, status, created_at + from claim_requests + order by created_at desc + limit $1`, + [limit] + ); + + const claims = []; + for (const row of result.rows) { + const countResult = await db.query( + 'select count(*)::int as agent_count from claim_agents where claim_id = $1', + [row.claim_id] + ); + const agentCount = countResult.rows && countResult.rows[0] ? Number(countResult.rows[0].agent_count || 0) : 0; + claims.push({ + claimId: row.claim_id, + tenant: row.tenant, + authenticatedAddress: row.authenticated_address, + activationMode: row.activation_mode, + packId: row.pack_id, + status: row.status, + agentCount, + createdAt: row.created_at + }); + } + + return res.status(200).json({ ok: true, claims }); + } catch (error) { + return res.status(500).json({ ok: false, status: 'ADMIN_CLAIMS_QUERY_FAILED' }); + } +}; diff --git a/public/admin/claims.html b/public/admin/claims.html new file mode 100644 index 0000000..652f73d --- /dev/null +++ b/public/admin/claims.html @@ -0,0 +1,128 @@ + + +
+ + +| Claim ID | Tenant | Wallet | Pack | Status | Agents | Created |
|---|