Copy // server/index.js
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const Database = require('better-sqlite3');
const cron = require('node-cron');
const app = express();
app.use(cors());
app.use(express.json());
// Initialize SQLite database
const db = new Database('prices.db');
db.exec(`
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gpu_type TEXT NOT NULL,
price_spot REAL,
price_ondemand REAL,
available_count INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_gpu_timestamp ON price_history(gpu_type, timestamp);
CREATE TABLE IF NOT EXISTS alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gpu_type TEXT NOT NULL,
target_price REAL NOT NULL,
email TEXT,
webhook_url TEXT,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
const CLORE_API_KEY = process.env.CLORE_API_KEY || 'YOUR_API_KEY';
const CLORE_API_URL = 'https://api.clore.ai';
// Fetch marketplace data
async function fetchMarketplace() {
try {
const response = await axios.get(`${CLORE_API_URL}/v1/marketplace`, {
headers: { 'auth': CLORE_API_KEY }
});
return response.data.servers || [];
} catch (error) {
console.error('Failed to fetch marketplace:', error.message);
return [];
}
}
// Aggregate prices by GPU type
function aggregatePrices(servers) {
const gpuPrices = {};
servers.forEach(server => {
const gpuArray = server.gpu_array || [];
gpuArray.forEach(gpu => {
// Normalize GPU name
const gpuType = normalizeGpuName(gpu);
if (!gpuPrices[gpuType]) {
gpuPrices[gpuType] = {
spotPrices: [],
ondemandPrices: [],
availableCount: 0,
totalCount: 0
};
}
gpuPrices[gpuType].totalCount++;
if (!server.rented) {
gpuPrices[gpuType].availableCount++;
const usdPrices = server.price?.usd || {};
if (usdPrices.spot) {
gpuPrices[gpuType].spotPrices.push(usdPrices.spot);
}
if (usdPrices.on_demand_clore) {
gpuPrices[gpuType].ondemandPrices.push(usdPrices.on_demand_clore);
}
}
});
});
// Calculate aggregates
const result = {};
Object.entries(gpuPrices).forEach(([gpuType, data]) => {
result[gpuType] = {
gpu_type: gpuType,
spot_min: data.spotPrices.length ? Math.min(...data.spotPrices) : null,
spot_avg: data.spotPrices.length ? average(data.spotPrices) : null,
spot_max: data.spotPrices.length ? Math.max(...data.spotPrices) : null,
ondemand_min: data.ondemandPrices.length ? Math.min(...data.ondemandPrices) : null,
ondemand_avg: data.ondemandPrices.length ? average(data.ondemandPrices) : null,
ondemand_max: data.ondemandPrices.length ? Math.max(...data.ondemandPrices) : null,
available_count: data.availableCount,
total_count: data.totalCount
};
});
return result;
}
function normalizeGpuName(gpu) {
const patterns = [
{ match: /RTX\s*4090/i, name: 'RTX 4090' },
{ match: /RTX\s*4080/i, name: 'RTX 4080' },
{ match: /RTX\s*4070/i, name: 'RTX 4070' },
{ match: /RTX\s*3090/i, name: 'RTX 3090' },
{ match: /RTX\s*3080/i, name: 'RTX 3080' },
{ match: /RTX\s*3070/i, name: 'RTX 3070' },
{ match: /A100.*80/i, name: 'A100 80GB' },
{ match: /A100/i, name: 'A100 40GB' },
{ match: /A6000/i, name: 'A6000' },
{ match: /A5000/i, name: 'A5000' },
{ match: /A4000/i, name: 'A4000' },
];
for (const pattern of patterns) {
if (pattern.match.test(gpu)) {
return pattern.name;
}
}
return gpu;
}
function average(arr) {
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
// Store prices in database
function storePrices(prices) {
const stmt = db.prepare(`
INSERT INTO price_history (gpu_type, price_spot, price_ondemand, available_count)
VALUES (?, ?, ?, ?)
`);
Object.values(prices).forEach(data => {
stmt.run(data.gpu_type, data.spot_min, data.ondemand_min, data.available_count);
});
}
// Check alerts and trigger notifications
async function checkAlerts(prices) {
const alerts = db.prepare('SELECT * FROM alerts WHERE is_active = 1').all();
for (const alert of alerts) {
const priceData = prices[alert.gpu_type];
if (!priceData) continue;
const currentPrice = priceData.spot_min || priceData.ondemand_min;
if (currentPrice && currentPrice <= alert.target_price) {
await sendAlert(alert, priceData);
}
}
}
async function sendAlert(alert, priceData) {
console.log(`🚨 Price alert triggered: ${alert.gpu_type} at $${priceData.spot_min}/hr`);
if (alert.webhook_url) {
try {
await axios.post(alert.webhook_url, {
text: `🚨 GPU Price Alert: ${alert.gpu_type} now at $${priceData.spot_min}/hr (target: $${alert.target_price})`
});
} catch (e) {
console.error('Webhook failed:', e.message);
}
}
}
// Scheduled price collection (every 5 minutes)
cron.schedule('*/5 * * * *', async () => {
console.log('Collecting prices...');
const servers = await fetchMarketplace();
const prices = aggregatePrices(servers);
storePrices(prices);
await checkAlerts(prices);
});
// API Routes
// Get current prices
app.get('/api/prices', async (req, res) => {
const servers = await fetchMarketplace();
const prices = aggregatePrices(servers);
res.json(Object.values(prices));
});
// Get price history
app.get('/api/prices/history', (req, res) => {
const { gpu_type, hours = 24 } = req.query;
let query = `
SELECT gpu_type, price_spot, price_ondemand, available_count, timestamp
FROM price_history
WHERE timestamp > datetime('now', '-${parseInt(hours)} hours')
`;
if (gpu_type) {
query += ` AND gpu_type = '${gpu_type}'`;
}
query += ' ORDER BY timestamp ASC';
const history = db.prepare(query).all();
res.json(history);
});
// Get detailed server list
app.get('/api/servers', async (req, res) => {
const { gpu_type, max_price, available_only } = req.query;
const servers = await fetchMarketplace();
let filtered = servers;
if (gpu_type) {
filtered = filtered.filter(s =>
(s.gpu_array || []).some(g => normalizeGpuName(g) === gpu_type)
);
}
if (available_only === 'true') {
filtered = filtered.filter(s => !s.rented);
}
if (max_price) {
filtered = filtered.filter(s => {
const price = s.price?.usd?.spot || s.price?.usd?.on_demand_clore;
return price && price <= parseFloat(max_price);
});
}
// Format response
const result = filtered.map(s => ({
id: s.id,
gpus: s.gpu_array || [],
gpu_count: (s.gpu_array || []).length,
rented: s.rented,
reliability: s.reliability,
rating: s.rating,
price_spot: s.price?.usd?.spot,
price_ondemand: s.price?.usd?.on_demand_clore,
specs: s.specs
}));
res.json(result);
});
// Alerts management
app.get('/api/alerts', (req, res) => {
const alerts = db.prepare('SELECT * FROM alerts ORDER BY created_at DESC').all();
res.json(alerts);
});
app.post('/api/alerts', (req, res) => {
const { gpu_type, target_price, email, webhook_url } = req.body;
const result = db.prepare(`
INSERT INTO alerts (gpu_type, target_price, email, webhook_url)
VALUES (?, ?, ?, ?)
`).run(gpu_type, target_price, email, webhook_url);
res.json({ id: result.lastInsertRowid, success: true });
});
app.delete('/api/alerts/:id', (req, res) => {
db.prepare('DELETE FROM alerts WHERE id = ?').run(req.params.id);
res.json({ success: true });
});
// Cost calculator
app.post('/api/calculate', async (req, res) => {
const { gpu_type, hours, use_spot } = req.body;
const servers = await fetchMarketplace();
const prices = aggregatePrices(servers);
const gpuData = prices[gpu_type];
if (!gpuData) {
return res.status(404).json({ error: 'GPU type not found' });
}
const pricePerHour = use_spot ? gpuData.spot_min : gpuData.ondemand_min;
const totalCost = pricePerHour * hours;
res.json({
gpu_type,
price_per_hour: pricePerHour,
hours,
total_cost: totalCost,
pricing_type: use_spot ? 'spot' : 'on-demand'
});
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});