Add frontend-optimized aggregation endpoints

- Add GET /mints/{mint_id}/profile - full mint profile in one request
- Add GET /mints/cards - lightweight cards for directory/grid views
- Add GET /mints/{mint_id}/metrics - timeseries metrics for charts
- Add GET /analytics/activity - ecosystem activity timeline
- Add POST /mints/compare - side-by-side mint comparison
- Add GET /mints/rankings - ranked leaderboards by trust/uptime/latency/reviews
- Add GET /mints/{mint_id}/similar - find similar mints
- Add GET /mints/recommended - use-case based recommendations
- Add GET /mints/{mint_id}/compatibility - wallet compatibility info
- Add GET /wallets/{wallet_name}/recommended-mints - wallet-specific recommendations
- Add GET /mints/{mint_id}/risk - risk assessment and flags
- Update OpenAPI documentation for all new endpoints
- Fix review rating parsing from content field
- Fix trust score calculation to include parsed ratings
This commit is contained in:
Michaël
2025-12-26 19:15:25 -03:00
parent 48965a6f18
commit 5383af4695
6 changed files with 2142 additions and 35 deletions

View File

@@ -32,7 +32,17 @@ import {
getPopularMints,
getMintStats,
getMintAvailability,
getMintCard
getMintCard,
getMintProfile,
getMintCards,
getMintMetrics,
compareMints,
getMintRankings,
getSimilarMints,
getRecommendedMints,
getMintCompatibility,
getWalletRecommendedMints,
getMintRisk
} from '../services/AnalyticsService.js';
const router = Router();
@@ -249,6 +259,103 @@ router.get('/trending', async(req, res) => {
}
});
/**
* 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)
// ==========================================
@@ -906,4 +1013,93 @@ router.get('/:mint_id/card', resolveMintMiddleware, (req, res) => {
}
});
/**
* 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;