/** * Mint Routes * * Core mint discovery and management endpoints. * * IMPORTANT: Route order matters! * Static routes (like /by-url/*) must come BEFORE parameterized routes (/:mint_id/*) */ import { Router } from 'express'; import { getMintById, getMintByUrl, getMints, getMintUrls, submitMint, countMintsByStatus, resolveMint } from '../services/MintService.js'; import { getMetadataSnapshot, getMetadataHistory, deriveFeatures } from '../services/MetadataService.js'; import { getUptime, getUptimeTimeseries, getUptimeSummary, getLatencyTimeseries } from '../services/UptimeService.js'; import { getIncidents, countIncidents, getLatestProbe } from '../services/ProbeService.js'; import { getTrustScore, getTrustScoreHistory, getTrustComparison, getTrustScoreHistoryDetailed } from '../services/TrustService.js'; import { getReviews, getReviewSummary } from '../services/ReviewService.js'; import { getPageviewStats, recordPageview, getTrendingMints, getViewsTimeseries } from '../services/PageviewService.js'; import { daysAgo } from '../utils/time.js'; import { getAllReviews, countReviews } from '../services/ReviewService.js'; import { getMintActivity, getRecentMints, getUpdatedMints, getPopularMints, getMintStats, getMintAvailability, getMintCard, getMintProfile, getMintCards, getMintMetrics, compareMints, getMintRankings, getSimilarMints, getRecommendedMints, getMintCompatibility, getWalletRecommendedMints, getMintRisk } from '../services/AnalyticsService.js'; const router = Router(); // ========================================== // HELPER: Resolve mint by URL query param // ========================================== function getMintFromUrl(req, res) { const { url } = req.query; if (!url) { res.status(400).json({ error: 'URL parameter required' }); return null; } const mint = getMintByUrl(url); if (!mint) { res.status(404).json({ error: 'Mint not found' }); return null; } return mint; } // ========================================== // HELPER: Resolve mint middleware for ID routes // ========================================== function resolveMintMiddleware(req, res, next) { const { mint_id } = req.params; if (!mint_id) { return res.status(400).json({ error: 'Mint ID required' }); } const mint = resolveMint(mint_id); if (!mint) { return res.status(404).json({ error: 'Mint not found' }); } req.mint = mint; next(); } // ========================================== // LIST & SUBMIT (no param conflicts) // ========================================== /** * GET /mints * List all mints with optional filters */ router.get('/', (req, res) => { try { const { status, limit = 100, offset = 0, sort_by, sort_order } = req.query; const mints = getMints({ status, limit: Math.min(parseInt(limit) || 100, 500), offset: parseInt(offset) || 0, sortBy: sort_by, sortOrder: sort_order }); // Enrich with uptime summary const enrichedMints = mints.map(mint => ({ ...mint, ...getUptimeSummary(mint.mint_id), incidents_7d: countIncidents(mint.mint_id, daysAgo(7)), incidents_30d: countIncidents(mint.mint_id, daysAgo(30)) })); res.json({ mints: enrichedMints, total: enrichedMints.length, limit: Math.min(parseInt(limit) || 100, 500), offset: parseInt(offset) || 0 }); } catch (error) { console.error('[API] Error listing mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * POST /mints/submit */ router.post('/submit', (req, res) => { try { const { mint_url } = req.body; if (!mint_url) { return res.status(400).json({ error: 'mint_url required' }); } const result = submitMint(mint_url); res.status(result.success ? 201 : 400).json(result); } catch (error) { console.error('[API] Error submitting mint:', error); res.status(500).json({ error: 'Internal server error' }); } }); // ========================================== // HOMEPAGE & DISCOVERY ENDPOINTS // ========================================== /** * GET /mints/activity * Get mint ecosystem activity overview */ router.get('/activity', (req, res) => { try { const activity = getMintActivity(); res.json(activity); } catch (error) { console.error('[API] Error getting mint activity:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/recent * Get recently added mints */ router.get('/recent', (req, res) => { try { const { window = '7d', limit = 20 } = req.query; const validWindows = ['24h', '7d', '30d']; const timeWindow = validWindows.includes(window) ? window : '7d'; const mints = getRecentMints(timeWindow, Math.min(parseInt(limit) || 20, 100)); res.json({ window: timeWindow, mints }); } catch (error) { console.error('[API] Error getting recent mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/updated * Get recently updated mints */ router.get('/updated', (req, res) => { try { const { limit = 20 } = req.query; const mints = getUpdatedMints(Math.min(parseInt(limit) || 20, 100)); res.json({ mints }); } catch (error) { console.error('[API] Error getting updated mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/popular * Get popular mints by views */ router.get('/popular', async(req, res) => { try { const { window = '7d', limit = 20 } = req.query; const validWindows = ['24h', '7d', '30d']; const timeWindow = validWindows.includes(window) ? window : '7d'; const mints = await getPopularMints(timeWindow, Math.min(parseInt(limit) || 20, 100)); res.json({ window: timeWindow, mints }); } catch (error) { console.error('[API] Error getting popular mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/trending * Get trending mints by view velocity */ router.get('/trending', async(req, res) => { try { const { window = '7d', limit = 10 } = req.query; // Validate window const validWindows = ['24h', '7d']; const timeWindow = validWindows.includes(window) ? window : '7d'; const trending = await getTrendingMints( Math.min(parseInt(limit) || 10, 50), timeWindow ); res.json({ window: timeWindow, mints: trending.map(m => ({ mint_id: m.mint_id, canonical_url: m.canonical_url, name: m.name, icon_url: m.icon_url, status: m.status, trust_score: m.trust_score, trust_level: m.trust_level, view_count: m.view_count, view_velocity: m.view_velocity, unique_sessions: m.unique_sessions })) }); } catch (error) { console.error('[API] Error getting trending mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/cards * Get lightweight mint cards for directory/list views */ router.get('/cards', (req, res) => { try { const { status, limit = 100, offset = 0, sort_by = 'trust_score', sort_order = 'desc' } = req.query; const cards = getMintCards({ status, limit: Math.min(parseInt(limit) || 100, 500), offset: parseInt(offset) || 0, sortBy: sort_by, sortOrder: sort_order }); res.json({ cards, total: cards.length, limit: Math.min(parseInt(limit) || 100, 500), offset: parseInt(offset) || 0 }); } catch (error) { console.error('[API] Error getting mint cards:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * POST /mints/compare * Compare multiple mints side by side */ router.post('/compare', (req, res) => { try { const { mint_ids } = req.body; if (!mint_ids || !Array.isArray(mint_ids) || mint_ids.length === 0) { return res.status(400).json({ error: 'mint_ids array required' }); } if (mint_ids.length > 10) { return res.status(400).json({ error: 'Maximum 10 mints can be compared' }); } const comparison = compareMints(mint_ids); res.json(comparison); } catch (error) { console.error('[API] Error comparing mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/rankings * Get ranked mint leaderboard */ router.get('/rankings', (req, res) => { try { const { by = 'trust', period = '30d', limit = 50 } = req.query; const validBy = ['trust', 'uptime', 'latency', 'reviews']; const validPeriods = ['7d', '30d', '90d']; const rankings = getMintRankings( validBy.includes(by) ? by : 'trust', validPeriods.includes(period) ? period : '30d', Math.min(parseInt(limit) || 50, 100) ); res.json(rankings); } catch (error) { console.error('[API] Error getting rankings:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/recommended * Get recommended mints by use case */ router.get('/recommended', (req, res) => { try { const { use_case = 'general', limit = 10 } = req.query; const validUseCases = ['general', 'mobile', 'high_volume', 'privacy']; const recommendations = getRecommendedMints( validUseCases.includes(use_case) ? use_case : 'general', Math.min(parseInt(limit) || 10, 50) ); res.json(recommendations); } catch (error) { console.error('[API] Error getting recommendations:', error); res.status(500).json({ error: 'Internal server error' }); } }); // ========================================== // BY-URL ROUTES (must come BEFORE :mint_id routes) // ========================================== /** * GET /mints/by-url */ router.get('/by-url', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const uptime = getUptimeSummary(mint.mint_id); res.json({ mint_id: mint.mint_id, canonical_url: mint.canonical_url, urls: getMintUrls(mint.mint_id).map(u => u.url), name: mint.name, icon_url: mint.icon_url, status: mint.status, offline_since: mint.offline_since, last_success_at: mint.last_success_at, last_failure_at: mint.last_failure_at, ...uptime, incidents_7d: countIncidents(mint.mint_id, daysAgo(7)), incidents_30d: countIncidents(mint.mint_id, daysAgo(30)), trust_score: mint.trust_score, trust_level: mint.trust_level }); } catch (error) { console.error('[API] Error getting mint by URL:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/urls */ router.get('/by-url/urls', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const urls = getMintUrls(mint.mint_id); res.json({ canonical_url: mint.canonical_url, urls }); } catch (error) { console.error('[API] Error getting mint URLs:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/metadata */ router.get('/by-url/metadata', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const metadata = getMetadataSnapshot(mint.mint_id); if (!metadata) { return res.json({ error: 'No metadata available', mint_id: mint.mint_id }); } res.json({ name: metadata.name, pubkey: metadata.pubkey, version: metadata.version, description: metadata.description, description_long: metadata.description_long, contact: metadata.contact, motd: metadata.motd, icon_url: metadata.icon_url, urls: metadata.urls, tos_url: metadata.tos_url, nuts: metadata.nuts, server_time: metadata.server_time, last_fetched_at: metadata.last_fetched_at }); } catch (error) { console.error('[API] Error getting metadata:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/metadata/history */ router.get('/by-url/metadata/history', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const { limit = 50 } = req.query; const history = getMetadataHistory(mint.mint_id, { limit: parseInt(limit) }); res.json({ history }); } catch (error) { console.error('[API] Error getting metadata history:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/status */ router.get('/by-url/status', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const latestProbe = getLatestProbe(mint.mint_id); res.json({ status: mint.status, offline_since: mint.offline_since, last_checked_at: latestProbe ? latestProbe.probed_at : null, current_rtt_ms: latestProbe ? latestProbe.rtt_ms : null }); } catch (error) { console.error('[API] Error getting status:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/uptime */ router.get('/by-url/uptime', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const { window = '24h' } = req.query; const uptime = getUptime(mint.mint_id, window); if (!uptime) { return res.json({ error: 'No uptime data available' }); } res.json(uptime); } catch (error) { console.error('[API] Error getting uptime:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/uptime/timeseries */ router.get('/by-url/uptime/timeseries', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const { window = '24h', bucket = '1h' } = req.query; const timeseries = getUptimeTimeseries(mint.mint_id, window, bucket); res.json({ data: timeseries }); } catch (error) { console.error('[API] Error getting uptime timeseries:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/incidents */ router.get('/by-url/incidents', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const { limit = 50 } = req.query; const incidents = getIncidents(mint.mint_id, { limit: parseInt(limit) }); res.json({ incidents }); } catch (error) { console.error('[API] Error getting incidents:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/trust */ router.get('/by-url/trust', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const trust = getTrustScore(mint.mint_id); if (!trust) { return res.json({ score_total: null, score_level: 'unknown', breakdown: null, computed_at: null }); } res.json(trust); } catch (error) { console.error('[API] Error getting trust score:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/reviews */ router.get('/by-url/reviews', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const { limit = 50, offset = 0 } = req.query; const reviews = getReviews(mint.mint_id, { limit: parseInt(limit), offset: parseInt(offset) }); res.json({ reviews }); } catch (error) { console.error('[API] Error getting reviews:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/views */ router.get('/by-url/views', async(req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const stats = await getPageviewStats(mint.mint_id); res.json(stats); } catch (error) { console.error('[API] Error getting views:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/by-url/features */ router.get('/by-url/features', (req, res) => { try { const mint = getMintFromUrl(req, res); if (!mint) return; const features = deriveFeatures(mint.mint_id); res.json(features); } catch (error) { console.error('[API] Error getting features:', error); res.status(500).json({ error: 'Internal server error' }); } }); // ========================================== // PARAMETERIZED ROUTES (:mint_id) // These must come AFTER /by-url/* routes // ========================================== /** * GET /mints/:mint_id */ router.get('/:mint_id', resolveMintMiddleware, (req, res) => { try { const { mint } = req; // Enrich with computed fields const uptime = getUptimeSummary(mint.mint_id); const response = { mint_id: mint.mint_id, canonical_url: mint.canonical_url, urls: getMintUrls(mint.mint_id).map(u => u.url), name: mint.name, icon_url: mint.icon_url, status: mint.status, offline_since: mint.offline_since, last_success_at: mint.last_success_at, last_failure_at: mint.last_failure_at, ...uptime, incidents_7d: countIncidents(mint.mint_id, daysAgo(7)), incidents_30d: countIncidents(mint.mint_id, daysAgo(30)), trust_score: mint.trust_score, trust_level: mint.trust_level }; // Record pageview recordPageview(mint.mint_id, { sessionId: req.headers['x-session-id'], userAgent: req.headers['user-agent'], referer: req.headers['referer'] }); res.json(response); } catch (error) { console.error('[API] Error getting mint:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/urls */ router.get('/:mint_id/urls', resolveMintMiddleware, (req, res) => { try { const urls = getMintUrls(req.mint.mint_id); res.json({ canonical_url: req.mint.canonical_url, urls }); } catch (error) { console.error('[API] Error getting mint URLs:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/metadata */ router.get('/:mint_id/metadata', resolveMintMiddleware, (req, res) => { try { const metadata = getMetadataSnapshot(req.mint.mint_id); if (!metadata) { return res.json({ error: 'No metadata available', mint_id: req.mint.mint_id }); } res.json({ name: metadata.name, pubkey: metadata.pubkey, version: metadata.version, description: metadata.description, description_long: metadata.description_long, contact: metadata.contact, motd: metadata.motd, icon_url: metadata.icon_url, urls: metadata.urls, tos_url: metadata.tos_url, nuts: metadata.nuts, server_time: metadata.server_time, last_fetched_at: metadata.last_fetched_at }); } catch (error) { console.error('[API] Error getting metadata:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/metadata/history */ router.get('/:mint_id/metadata/history', resolveMintMiddleware, (req, res) => { try { const { limit = 50 } = req.query; const history = getMetadataHistory(req.mint.mint_id, { limit: parseInt(limit) }); res.json({ history }); } catch (error) { console.error('[API] Error getting metadata history:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/status */ router.get('/:mint_id/status', resolveMintMiddleware, (req, res) => { try { const { mint } = req; const latestProbe = getLatestProbe(mint.mint_id); res.json({ status: mint.status, offline_since: mint.offline_since, last_checked_at: latestProbe ? latestProbe.probed_at : null, current_rtt_ms: latestProbe ? latestProbe.rtt_ms : null }); } catch (error) { console.error('[API] Error getting status:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/uptime */ router.get('/:mint_id/uptime', resolveMintMiddleware, (req, res) => { try { const { window = '24h' } = req.query; const uptime = getUptime(req.mint.mint_id, window); if (!uptime) { return res.json({ error: 'No uptime data available' }); } res.json(uptime); } catch (error) { console.error('[API] Error getting uptime:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/uptime/timeseries */ router.get('/:mint_id/uptime/timeseries', resolveMintMiddleware, (req, res) => { try { const { window = '24h', bucket = '1h' } = req.query; const timeseries = getUptimeTimeseries(req.mint.mint_id, window, bucket); res.json({ data: timeseries }); } catch (error) { console.error('[API] Error getting uptime timeseries:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/incidents */ router.get('/:mint_id/incidents', resolveMintMiddleware, (req, res) => { try { const { limit = 50 } = req.query; const incidents = getIncidents(req.mint.mint_id, { limit: parseInt(limit) }); res.json({ incidents }); } catch (error) { console.error('[API] Error getting incidents:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/trust */ router.get('/:mint_id/trust', resolveMintMiddleware, (req, res) => { try { const trust = getTrustScore(req.mint.mint_id); if (!trust) { return res.json({ score_total: null, score_level: 'unknown', breakdown: null, computed_at: null }); } res.json(trust); } catch (error) { console.error('[API] Error getting trust score:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/reviews */ router.get('/:mint_id/reviews', resolveMintMiddleware, (req, res) => { try { const { limit = 50, offset = 0 } = req.query; const reviews = getReviews(req.mint.mint_id, { limit: parseInt(limit), offset: parseInt(offset) }); res.json({ reviews }); } catch (error) { console.error('[API] Error getting reviews:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/views */ router.get('/:mint_id/views', resolveMintMiddleware, async(req, res) => { try { const stats = await getPageviewStats(req.mint.mint_id); res.json(stats); } catch (error) { console.error('[API] Error getting views:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/features */ router.get('/:mint_id/features', resolveMintMiddleware, (req, res) => { try { const features = deriveFeatures(req.mint.mint_id); res.json(features); } catch (error) { console.error('[API] Error getting features:', error); res.status(500).json({ error: 'Internal server error' }); } }); // ========================================== // MINT DETAIL AGGREGATED ENDPOINTS // ========================================== /** * GET /mints/:mint_id/stats * Aggregated mint KPIs (single endpoint for summary cards) */ router.get('/:mint_id/stats', resolveMintMiddleware, async(req, res) => { try { const stats = await getMintStats(req.mint.mint_id); res.json(stats); } catch (error) { console.error('[API] Error getting mint stats:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/latency/timeseries * Response time history for charting */ router.get('/:mint_id/latency/timeseries', resolveMintMiddleware, (req, res) => { try { const { window = '24h', bucket = '1h' } = req.query; const validWindows = ['24h', '7d', '30d']; const validBuckets = ['5m', '15m', '1h']; const timeWindow = validWindows.includes(window) ? window : '24h'; const timeBucket = validBuckets.includes(bucket) ? bucket : '1h'; const data = getLatencyTimeseries(req.mint.mint_id, timeWindow, timeBucket); res.json({ window: timeWindow, bucket: timeBucket, data }); } catch (error) { console.error('[API] Error getting latency timeseries:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/availability * Availability breakdown (online/degraded/offline percentages) */ router.get('/:mint_id/availability', resolveMintMiddleware, (req, res) => { try { const { window = '30d' } = req.query; const validWindows = ['24h', '7d', '30d']; const timeWindow = validWindows.includes(window) ? window : '30d'; const availability = getMintAvailability(req.mint.mint_id, timeWindow); res.json(availability); } catch (error) { console.error('[API] Error getting availability:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/trust/history * Trust score history with change reasons */ router.get('/:mint_id/trust/history', resolveMintMiddleware, (req, res) => { try { const { limit = 30 } = req.query; const history = getTrustScoreHistoryDetailed( req.mint.mint_id, Math.min(parseInt(limit) || 30, 100) ); res.json({ history }); } catch (error) { console.error('[API] Error getting trust history:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/trust/compare * Compare trust score against ecosystem benchmarks */ router.get('/:mint_id/trust/compare', resolveMintMiddleware, (req, res) => { try { const { against = 'ecosystem' } = req.query; const validBenchmarks = ['ecosystem', 'top10', 'median']; const benchmark = validBenchmarks.includes(against) ? against : 'ecosystem'; const comparison = getTrustComparison(req.mint.mint_id, benchmark); res.json(comparison); } catch (error) { console.error('[API] Error getting trust comparison:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/reviews/summary * Quick review overview (rating distribution, averages) */ router.get('/:mint_id/reviews/summary', resolveMintMiddleware, (req, res) => { try { const summary = getReviewSummary(req.mint.mint_id); res.json(summary); } catch (error) { console.error('[API] Error getting review summary:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/views/timeseries * Pageview history for adoption trends */ router.get('/:mint_id/views/timeseries', resolveMintMiddleware, async(req, res) => { try { const { window = '7d', bucket = '1d' } = req.query; const validWindows = ['7d', '30d']; const validBuckets = ['1h', '1d']; const timeWindow = validWindows.includes(window) ? window : '7d'; const timeBucket = validBuckets.includes(bucket) ? bucket : '1d'; const data = await getViewsTimeseries(req.mint.mint_id, timeWindow, timeBucket); res.json({ window: timeWindow, bucket: timeBucket, data }); } catch (error) { console.error('[API] Error getting views timeseries:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/card * Optimized endpoint for grid/list views */ router.get('/:mint_id/card', resolveMintMiddleware, (req, res) => { try { const card = getMintCard(req.mint.mint_id); if (!card) { return res.status(404).json({ error: 'Mint not found' }); } res.json(card); } catch (error) { console.error('[API] Error getting mint card:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/profile * Full mint profile - aggregated data in one request */ router.get('/:mint_id/profile', resolveMintMiddleware, async(req, res) => { try { const profile = await getMintProfile(req.mint.mint_id); if (!profile) { return res.status(404).json({ error: 'Mint not found' }); } // Record pageview recordPageview(req.mint.mint_id, { sessionId: req.headers['x-session-id'], userAgent: req.headers['user-agent'], referer: req.headers['referer'] }); res.json(profile); } catch (error) { console.error('[API] Error getting mint profile:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/metrics * Mint metrics timeseries for charts */ router.get('/:mint_id/metrics', resolveMintMiddleware, (req, res) => { try { const { range = '7d' } = req.query; const validRanges = ['7d', '30d', '90d']; const timeRange = validRanges.includes(range) ? range : '7d'; const metrics = getMintMetrics(req.mint.mint_id, timeRange); res.json(metrics); } catch (error) { console.error('[API] Error getting mint metrics:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/similar * Find similar mints */ router.get('/:mint_id/similar', resolveMintMiddleware, (req, res) => { try { const { limit = 5 } = req.query; const similar = getSimilarMints( req.mint.mint_id, Math.min(parseInt(limit) || 5, 20) ); res.json(similar); } catch (error) { console.error('[API] Error getting similar mints:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/compatibility * Wallet compatibility info */ router.get('/:mint_id/compatibility', resolveMintMiddleware, (req, res) => { try { const compatibility = getMintCompatibility(req.mint.mint_id); res.json(compatibility); } catch (error) { console.error('[API] Error getting compatibility:', error); res.status(500).json({ error: 'Internal server error' }); } }); /** * GET /mints/:mint_id/risk * Risk assessment and flags */ router.get('/:mint_id/risk', resolveMintMiddleware, (req, res) => { try { const risk = getMintRisk(req.mint.mint_id); res.json(risk); } catch (error) { console.error('[API] Error getting risk assessment:', error); res.status(500).json({ error: 'Internal server error' }); } }); export default router;