Initial commit
This commit is contained in:
909
src/routes/mints.js
Normal file
909
src/routes/mints.js
Normal file
@@ -0,0 +1,909 @@
|
||||
/**
|
||||
* 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
|
||||
} 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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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' });
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// BY-URL ROUTES (must come BEFORE :mint_id routes)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* GET /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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 /v1/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' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user