|
|
@@ -41,6 +41,17 @@ interface SyncResult {
|
|
|
errors: string[];
|
|
|
}
|
|
|
|
|
|
+interface ConvertResult {
|
|
|
+ success: boolean;
|
|
|
+ from: string;
|
|
|
+ to: string;
|
|
|
+ amount: number;
|
|
|
+ rate: number;
|
|
|
+ result: number;
|
|
|
+ timestamp: number;
|
|
|
+ error?: string;
|
|
|
+}
|
|
|
+
|
|
|
async function fetchExchangeRates(appId: string): Promise<OXRResponse> {
|
|
|
const url = `https://openexchangerates.org/api/latest.json?app_id=${appId}`;
|
|
|
const response = await fetch(url);
|
|
|
@@ -202,11 +213,68 @@ async function syncExchangeRates(env: Env): Promise<SyncResult> {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+async function convertCurrency(
|
|
|
+ env: Env,
|
|
|
+ from: string,
|
|
|
+ to: string,
|
|
|
+ amount: number
|
|
|
+): Promise<ConvertResult> {
|
|
|
+ const result: ConvertResult = {
|
|
|
+ success: false,
|
|
|
+ from: from.toUpperCase(),
|
|
|
+ to: to.toUpperCase(),
|
|
|
+ amount,
|
|
|
+ rate: 0,
|
|
|
+ result: 0,
|
|
|
+ timestamp: 0,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!env.OXR_APP_ID) {
|
|
|
+ result.error = 'Missing OXR_APP_ID';
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const oxrData = await fetchExchangeRates(env.OXR_APP_ID);
|
|
|
+ result.timestamp = oxrData.timestamp * 1000;
|
|
|
+
|
|
|
+ const fromRate = oxrData.rates[from.toUpperCase()];
|
|
|
+ const toRate = oxrData.rates[to.toUpperCase()];
|
|
|
+
|
|
|
+ if (!fromRate) {
|
|
|
+ result.error = `Unknown currency: ${from}`;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!toRate) {
|
|
|
+ result.error = `Unknown currency: ${to}`;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Convert: amount in USD = amount / fromRate
|
|
|
+ // Then convert to target: result = amountInUSD * toRate
|
|
|
+ const amountInUSD = amount / fromRate;
|
|
|
+ const convertedAmount = amountInUSD * toRate;
|
|
|
+
|
|
|
+ // Calculate the direct rate (1 from = X to)
|
|
|
+ const directRate = toRate / fromRate;
|
|
|
+
|
|
|
+ result.rate = directRate;
|
|
|
+ result.result = convertedAmount;
|
|
|
+ result.success = true;
|
|
|
+ } catch (error) {
|
|
|
+ const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
+ result.error = errorMessage;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
export default {
|
|
|
async fetch(req, env, ctx): Promise<Response> {
|
|
|
const url = new URL(req.url);
|
|
|
|
|
|
- // Manual trigger endpoint
|
|
|
+ // Manual sync endpoint
|
|
|
if (url.pathname === '/sync') {
|
|
|
const result = await syncExchangeRates(env);
|
|
|
return new Response(JSON.stringify(result, null, 2), {
|
|
|
@@ -214,9 +282,118 @@ export default {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- return new Response('Notion Exchange Rate Worker\n\nUse /sync to manually trigger sync.', {
|
|
|
- headers: { 'Content-Type': 'text/plain' },
|
|
|
- });
|
|
|
+ // Currency conversion endpoint
|
|
|
+ if (url.pathname === '/convert') {
|
|
|
+ const from = url.searchParams.get('from');
|
|
|
+ const to = url.searchParams.get('to');
|
|
|
+ const amountStr = url.searchParams.get('amount');
|
|
|
+
|
|
|
+ if (!from || !to || !amountStr) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({
|
|
|
+ error: 'Missing required parameters: from, to, amount',
|
|
|
+ usage: '/convert?from=USD&to=CNY&amount=100',
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const amount = parseFloat(amountStr);
|
|
|
+ if (isNaN(amount)) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Invalid amount. Must be a number.' }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = await convertCurrency(env, from, to, amount);
|
|
|
+ return new Response(JSON.stringify(result, null, 2), {
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Rates endpoint - get all rates for a base currency
|
|
|
+ if (url.pathname === '/rates') {
|
|
|
+ const base = url.searchParams.get('base') || 'USD';
|
|
|
+
|
|
|
+ if (!env.OXR_APP_ID) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Missing OXR_APP_ID' }),
|
|
|
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const oxrData = await fetchExchangeRates(env.OXR_APP_ID);
|
|
|
+
|
|
|
+ // If base is USD, return as-is
|
|
|
+ if (base.toUpperCase() === 'USD') {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify(
|
|
|
+ {
|
|
|
+ base: oxrData.base,
|
|
|
+ timestamp: oxrData.timestamp * 1000,
|
|
|
+ rates: oxrData.rates,
|
|
|
+ },
|
|
|
+ null,
|
|
|
+ 2
|
|
|
+ ),
|
|
|
+ { headers: { 'Content-Type': 'application/json' } }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Otherwise, recalculate rates for the new base
|
|
|
+ const baseRate = oxrData.rates[base.toUpperCase()];
|
|
|
+ if (!baseRate) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: `Unknown currency: ${base}` }),
|
|
|
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ const convertedRates: Record<string, number> = {};
|
|
|
+ for (const [currency, rate] of Object.entries(oxrData.rates)) {
|
|
|
+ convertedRates[currency] = rate / baseRate;
|
|
|
+ }
|
|
|
+ convertedRates['USD'] = 1 / baseRate;
|
|
|
+
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify(
|
|
|
+ {
|
|
|
+ base: base.toUpperCase(),
|
|
|
+ timestamp: oxrData.timestamp * 1000,
|
|
|
+ rates: convertedRates,
|
|
|
+ },
|
|
|
+ null,
|
|
|
+ 2
|
|
|
+ ),
|
|
|
+ { headers: { 'Content-Type': 'application/json' } }
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: errorMessage }),
|
|
|
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Home page
|
|
|
+ return new Response(
|
|
|
+ `Notion Exchange Rate Worker
|
|
|
+
|
|
|
+API Endpoints:
|
|
|
+ GET /convert?from=USD&to=CNY&amount=100 - Convert currency
|
|
|
+ GET /rates?base=USD - Get exchange rates
|
|
|
+ GET /sync - Sync rates to Notion`,
|
|
|
+ { headers: { 'Content-Type': 'text/plain' } }
|
|
|
+ );
|
|
|
},
|
|
|
|
|
|
async scheduled(event, env, ctx): Promise<void> {
|