added exchange-rate endpoint, modified some other stuff

This commit is contained in:
Xargana 2025-04-07 21:52:43 +03:00
parent 8979cf12f0
commit 7cc5ea85e0
8 changed files with 539 additions and 2140 deletions

View file

@ -0,0 +1,351 @@
const express = require('express');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const router = express.Router();
// Get API key from environment variables
const API_KEY = process.env.EXCHANGE_RATE_API_KEY;
if (!API_KEY) {
console.error('WARNING: EXCHANGE_RATE_API_KEY environment variable is not set!');
console.error('Exchange rate functionality will not work correctly.');
}
const BASE_URL = 'https://v6.exchangerate-api.com/v6';
// We'll use USD as our single base currency
const BASE_CURRENCY = 'USD';
// Path to the cache file
const CACHE_FILE_PATH = path.join(__dirname, 'exchange-rates-cache.json');
// In-memory storage for cached exchange rates
let exchangeRatesCache = {
USD: {
lastUpdated: null,
rates: {},
nextUpdateTime: null
}
};
// one day in milliseconds
const UPDATE_INTERVAL = 1 * 24 * 60 * 60 * 1000;
// Load cached exchange rates from file
function loadCachedRates() {
try {
if (fs.existsSync(CACHE_FILE_PATH)) {
const data = fs.readFileSync(CACHE_FILE_PATH, 'utf8');
const parsedData = JSON.parse(data);
// Convert string dates back to Date objects
if (parsedData.USD) {
if (parsedData.USD.lastUpdated) {
parsedData.USD.lastUpdated = new Date(parsedData.USD.lastUpdated);
}
if (parsedData.USD.nextUpdateTime) {
parsedData.USD.nextUpdateTime = new Date(parsedData.USD.nextUpdateTime);
}
}
exchangeRatesCache = parsedData;
console.log('Loaded exchange rates from cache file');
} else {
console.log('No cache file found, will create one when rates are fetched');
}
} catch (error) {
console.error('Error loading cached exchange rates:', error.message);
// Continue with default cache if file can't be loaded
}
}
// Save exchange rates to cache file
function saveCachedRates() {
try {
fs.writeFileSync(CACHE_FILE_PATH, JSON.stringify(exchangeRatesCache, null, 2));
console.log('Exchange rates saved to cache file');
} catch (error) {
console.error('Error saving exchange rates to cache file:', error.message);
}
}
// Function to fetch and update exchange rates using USD as base
async function updateExchangeRates() {
if (!API_KEY) {
console.error('Cannot update exchange rates: API key is not set');
return false;
}
try {
console.log(`Fetching latest exchange rates using ${BASE_CURRENCY} as base...`);
const response = await axios.get(`${BASE_URL}/${API_KEY}/latest/${BASE_CURRENCY}`);
if (response.data && response.data.result === 'success') {
exchangeRatesCache.USD = {
lastUpdated: new Date(),
rates: response.data.conversion_rates,
nextUpdateTime: new Date(Date.now() + UPDATE_INTERVAL)
};
// Save to file after updating
saveCachedRates();
console.log('Exchange rates updated successfully');
return true;
}
return false;
} catch (error) {
console.error('Failed to update exchange rates:', error.message);
return false;
}
}
// Check if rates need updating and update if necessary
async function ensureRatesUpdated() {
if (!exchangeRatesCache.USD.lastUpdated ||
Date.now() > exchangeRatesCache.USD.nextUpdateTime.getTime()) {
return await updateExchangeRates();
}
console.log(`Using cached rates, next update: ${exchangeRatesCache.USD.nextUpdateTime}`);
return true;
}
// Calculate conversion rate between any two currencies using USD as base
function calculateRate(from, to) {
const rates = exchangeRatesCache.USD.rates;
// If either currency is USD, we can use the rate directly
if (from === 'USD') return rates[to];
if (to === 'USD') return 1 / rates[from];
// Otherwise, calculate cross rate: from -> USD -> to
return rates[to] / rates[from];
}
// Load cached rates when the module is loaded
loadCachedRates();
// Initialize rates if needed
ensureRatesUpdated();
// Root endpoint
router.get('/', (req, res) => {
const availableCurrencies = exchangeRatesCache.USD.rates ?
Object.keys(exchangeRatesCache.USD.rates) : [];
res.json({
message: 'Exchange Rate API is running',
baseCurrency: BASE_CURRENCY,
availableCurrencies,
lastUpdated: exchangeRatesCache.USD.lastUpdated,
nextUpdate: exchangeRatesCache.USD.nextUpdateTime,
updateInterval: '3 days',
endpoints: {
latest: '/latest',
convert: '/convert/:from/:to/:amount',
currencies: '/currencies'
}
});
});
// Get all cached exchange rates
router.get('/latest', async (req, res) => {
await ensureRatesUpdated();
if (!exchangeRatesCache.USD.rates) {
return res.status(503).json({ error: 'Exchange rate data not yet available' });
}
res.json({
result: 'success',
base: BASE_CURRENCY,
lastUpdated: exchangeRatesCache.USD.lastUpdated,
nextUpdate: exchangeRatesCache.USD.nextUpdateTime,
rates: exchangeRatesCache.USD.rates
});
});
// Get rates for a specific currency as base
router.get('/latest/:currency', async (req, res) => {
const { currency } = req.params;
const currencyCode = currency.toUpperCase();
await ensureRatesUpdated();
if (!exchangeRatesCache.USD.rates) {
return res.status(503).json({ error: 'Exchange rate data not yet available' });
}
// Check if the currency is supported
if (!exchangeRatesCache.USD.rates[currencyCode] && currencyCode !== 'USD') {
return res.status(400).json({ error: `Currency '${currencyCode}' not supported` });
}
// Calculate rates with the requested currency as base
const rates = {};
const usdRates = exchangeRatesCache.USD.rates;
// If the requested base is USD, return rates directly
if (currencyCode === 'USD') {
res.json({
result: 'success',
base: currencyCode,
lastUpdated: exchangeRatesCache.USD.lastUpdated,
nextUpdate: exchangeRatesCache.USD.nextUpdateTime,
rates: usdRates
});
return;
}
// Otherwise, calculate rates for all currencies with the requested currency as base
const baseRate = usdRates[currencyCode]; // Rate of 1 USD in the requested currency
// Add USD rate
rates['USD'] = 1 / baseRate;
// Add rates for all other currencies
for (const toCurrency in usdRates) {
if (toCurrency !== currencyCode) {
// Convert through USD: from -> USD -> to
rates[toCurrency] = usdRates[toCurrency] / baseRate;
}
}
// Add rate for the base currency itself
rates[currencyCode] = 1;
res.json({
result: 'success',
base: currencyCode,
lastUpdated: exchangeRatesCache.USD.lastUpdated,
nextUpdate: exchangeRatesCache.USD.nextUpdateTime,
rates: rates
});
});
// Get list of available currencies
router.get('/currencies', async (req, res) => {
await ensureRatesUpdated();
const availableCurrencies = exchangeRatesCache.USD.rates ?
Object.keys(exchangeRatesCache.USD.rates) : [];
res.json({
result: 'success',
baseCurrency: BASE_CURRENCY,
availableCurrencies,
lastUpdated: exchangeRatesCache.USD.lastUpdated,
nextUpdate: exchangeRatesCache.USD.nextUpdateTime
});
});
// Convert between currencies using cached rates
router.get('/convert/:from/:to/:amount', async (req, res) => {
const { from, to, amount } = req.params;
const fromCurrency = from.toUpperCase();
const toCurrency = to.toUpperCase();
await ensureRatesUpdated();
if (!exchangeRatesCache.USD.rates) {
return res.status(503).json({ error: 'Exchange rate data not yet available' });
}
// Check if currencies are supported
if (fromCurrency !== 'USD' && !exchangeRatesCache.USD.rates[fromCurrency]) {
return res.status(400).json({ error: `Currency '${fromCurrency}' not supported` });
}
if (toCurrency !== 'USD' && !exchangeRatesCache.USD.rates[toCurrency]) {
return res.status(400).json({ error: `Currency '${toCurrency}' not supported` });
}
try {
const numericAmount = parseFloat(amount);
if (isNaN(numericAmount)) {
return res.status(400).json({ error: 'Invalid amount' });
}
// Calculate conversion rate
const rate = calculateRate(fromCurrency, toCurrency);
const convertedAmount = numericAmount * rate;
res.json({
result: 'success',
from: fromCurrency,
to: toCurrency,
amount: numericAmount,
rate,
convertedAmount: parseFloat(convertedAmount.toFixed(4)),
lastUpdated: exchangeRatesCache.USD.lastUpdated,
nextUpdate: exchangeRatesCache.USD.nextUpdateTime
});
} catch (error) {
console.error('Conversion error:', error);
res.status(500).json({ error: 'Failed to convert currency' });
}
});
// Direct pair conversion (fallback to API if needed)
router.get('/pair/:from/:to/:amount', async (req, res) => {
const { from, to, amount } = req.params;
const fromCurrency = from.toUpperCase();
const toCurrency = to.toUpperCase();
// First try to use our cached rates
await ensureRatesUpdated();
if (exchangeRatesCache.USD.rates &&
(fromCurrency === 'USD' || exchangeRatesCache.USD.rates[fromCurrency]) &&
(toCurrency === 'USD' || exchangeRatesCache.USD.rates[toCurrency])) {
try {
const numericAmount = parseFloat(amount);
if (isNaN(numericAmount)) {
return res.status(400).json({ error: 'Invalid amount' });
}
// Calculate conversion rate
const rate = calculateRate(fromCurrency, toCurrency);
const convertedAmount = numericAmount * rate;
res.json({
result: 'success',
from: fromCurrency,
to: toCurrency,
amount: numericAmount,
rate,
convertedAmount: parseFloat(convertedAmount.toFixed(4)),
lastUpdated: exchangeRatesCache.USD.lastUpdated,
source: 'cache'
});
return;
} catch (error) {
console.error('Error using cached rates:', error);
// Fall through to API call
}
}
// If we can't use cached rates, call the API directly
if (!API_KEY) {
return res.status(503).json({ error: 'Exchange rate API key is not configured' });
}
try {
const response = await axios.get(`${BASE_URL}/${API_KEY}/pair/${fromCurrency}/${toCurrency}/${amount}`);
// Update our cache with the latest USD rates if it's time
ensureRatesUpdated();
res.json({
...response.data,
source: 'api'
});
} catch (error) {
res.status(500).json({ error: 'Failed to convert currency' });
}
});
module.exports = router;

View file

@ -0,0 +1,171 @@
{
"USD": {
"lastUpdated": "2025-04-07T18:49:41.384Z",
"rates": {
"USD": 1,
"AED": 3.6725,
"AFN": 71.3562,
"ALL": 90.1377,
"AMD": 391.1786,
"ANG": 1.79,
"AOA": 918.5748,
"ARS": 1075.88,
"AUD": 1.662,
"AWG": 1.79,
"AZN": 1.6999,
"BAM": 1.7805,
"BBD": 2,
"BDT": 121.3674,
"BGN": 1.7804,
"BHD": 0.376,
"BIF": 2960.3407,
"BMD": 1,
"BND": 1.3404,
"BOB": 6.8885,
"BRL": 5.7194,
"BSD": 1,
"BTN": 85.5708,
"BWP": 13.8733,
"BYN": 3.1354,
"BZD": 2,
"CAD": 1.423,
"CDF": 2899.0345,
"CHF": 0.8524,
"CLP": 960.6602,
"CNY": 7.2857,
"COP": 4203.747,
"CRC": 502.4294,
"CUP": 24,
"CVE": 100.3829,
"CZK": 22.9524,
"DJF": 177.721,
"DKK": 6.7905,
"DOP": 62.8304,
"DZD": 132.995,
"EGP": 50.8068,
"ERN": 15,
"ETB": 131.8684,
"EUR": 0.9102,
"FJD": 2.3154,
"FKP": 0.7757,
"FOK": 6.7935,
"GBP": 0.7751,
"GEL": 2.7559,
"GGP": 0.7757,
"GHS": 15.5067,
"GIP": 0.7757,
"GMD": 72.6441,
"GNF": 8570.968,
"GTQ": 7.6902,
"GYD": 209.8017,
"HKD": 7.7734,
"HNL": 25.5147,
"HRK": 6.8592,
"HTG": 130.7844,
"HUF": 369.6088,
"IDR": 16757.5574,
"ILS": 3.7448,
"IMP": 0.7757,
"INR": 85.5703,
"IQD": 1309.7144,
"IRR": 42008.9149,
"ISK": 131.125,
"JEP": 0.7757,
"JMD": 157.719,
"JOD": 0.709,
"JPY": 145.2473,
"KES": 129.2338,
"KGS": 86.8492,
"KHR": 3997.3556,
"KID": 1.6621,
"KMF": 447.8769,
"KRW": 1457.9608,
"KWD": 0.307,
"KYD": 0.8333,
"KZT": 510.3326,
"LAK": 21751.6772,
"LBP": 89500,
"LKR": 295.3817,
"LRD": 199.3433,
"LSL": 19.1915,
"LYD": 4.8358,
"MAD": 9.5166,
"MDL": 17.6443,
"MGA": 4654.3433,
"MKD": 55.9208,
"MMK": 2090.2388,
"MNT": 3479.6583,
"MOP": 8.0068,
"MRU": 39.8843,
"MUR": 44.5309,
"MVR": 15.4592,
"MWK": 1740.2553,
"MXN": 20.5515,
"MYR": 4.437,
"MZN": 63.6781,
"NAD": 19.1915,
"NGN": 1529.0612,
"NIO": 36.6793,
"NOK": 10.7699,
"NPR": 136.9132,
"NZD": 1.7956,
"OMR": 0.3845,
"PAB": 1,
"PEN": 3.6809,
"PGK": 4.0959,
"PHP": 57.3644,
"PKR": 280.7358,
"PLN": 3.8787,
"PYG": 8001.2022,
"QAR": 3.64,
"RON": 4.5185,
"RSD": 106.3911,
"RUB": 84.4536,
"RWF": 1422.8596,
"SAR": 3.75,
"SBD": 8.3385,
"SCR": 14.8196,
"SDG": 458.3047,
"SEK": 10.0072,
"SGD": 1.3405,
"SHP": 0.7757,
"SLE": 22.7181,
"SLL": 22718.051,
"SOS": 571.0444,
"SRD": 36.8241,
"SSP": 4519.748,
"STN": 22.3043,
"SYP": 12873.9497,
"SZL": 19.1915,
"THB": 34.3823,
"TJS": 10.9221,
"TMT": 3.4983,
"TND": 3.0571,
"TOP": 2.3833,
"TRY": 38.0295,
"TTD": 6.7342,
"TVD": 1.6621,
"TWD": 33.1309,
"TZS": 2647.2453,
"UAH": 41.1747,
"UGX": 3662.2001,
"UYU": 42.042,
"UZS": 12937.493,
"VES": 72.1856,
"VND": 25642.7185,
"VUV": 121.865,
"WST": 2.8015,
"XAF": 597.1692,
"XCD": 2.7,
"XCG": 1.79,
"XDR": 0.751,
"XOF": 597.1692,
"XPF": 108.6373,
"YER": 244.8828,
"ZAR": 19.2142,
"ZMW": 27.9801,
"ZWL": 6.7864
},
"nextUpdateTime": "2025-04-10T18:49:41.384Z"
}
}

View file

@ -3,18 +3,24 @@ const cors = require("cors");
const fs = require("fs");
const https = require("https");
const http = require("http");
const status = require("./status/server")
const path = require("path");
// load environment variables from .env file
require('dotenv').config({ path: path.join(__dirname, '../.env') });
const status = require("./status/status");
const exchangeRate = require("./exchange-rate/exchange-rate");
const app = express();
const PORT = 2589;
const PORT = process.env.PORT || 2589;
const key = "/etc/letsencrypt/live/blahaj.tr/privkey.pem"
const cert = "/etc/letsencrypt/live/blahaj.tr/fullchain.pem"
const key = process.env.SSL_KEY_PATH || "/etc/letsencrypt/live/blahaj.tr/privkey.pem";
const cert = process.env.SSL_CERT_PATH || "/etc/letsencrypt/live/blahaj.tr/fullchain.pem";
app.use(cors());
app.use("/status", status);
app.use("/exchange-rate", exchangeRate);
// Try to load certificates
// try to load certificates
try {
const sslOptions = {
key: fs.readFileSync(key),
@ -33,7 +39,7 @@ try {
console.log("Starting server without SSL...");
// Start http server as fallback
// start http server as fallback
http.createServer(app).listen(PORT, () => {
console.log(`API running at http://localhost:${PORT}`);
});