瀏覽代碼

refactor: use Notion database as data source for convert/rates API

- /convert and /rates endpoints now read from Notion database
- Reduces API calls to Open Exchange Rates
- timestamp reflects last sync time from Notion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kotoyuuko 3 天之前
父節點
當前提交
2329dd4fc4
共有 1 個文件被更改,包括 79 次插入58 次删除
  1. 79 58
      src/index.ts

+ 79 - 58
src/index.ts

@@ -15,11 +15,20 @@ interface OXRResponse {
   timestamp: number;
   timestamp: number;
 }
 }
 
 
+interface NotionPage {
+  id: string;
+  code: string;
+  rate: number | null;
+  updatedAt: string | null;
+}
+
 interface NotionQueryResponse {
 interface NotionQueryResponse {
   results: Array<{
   results: Array<{
     id: string;
     id: string;
     properties: {
     properties: {
       Code: { title: Array<{ plain_text: string }> };
       Code: { title: Array<{ plain_text: string }> };
+      Rate: { number: number | null };
+      'Updated At': { date: { start: string } | null };
     };
     };
   }>;
   }>;
   has_more: boolean;
   has_more: boolean;
@@ -48,10 +57,16 @@ interface ConvertResult {
   amount: number;
   amount: number;
   rate: number;
   rate: number;
   result: number;
   result: number;
-  timestamp: number;
+  timestamp: string | null;
   error?: string;
   error?: string;
 }
 }
 
 
+interface RatesResult {
+  base: string;
+  timestamp: string | null;
+  rates: Record<string, number>;
+}
+
 async function fetchExchangeRates(appId: string): Promise<OXRResponse> {
 async function fetchExchangeRates(appId: string): Promise<OXRResponse> {
   const url = `https://openexchangerates.org/api/latest.json?app_id=${appId}`;
   const url = `https://openexchangerates.org/api/latest.json?app_id=${appId}`;
   const response = await fetch(url);
   const response = await fetch(url);
@@ -63,11 +78,11 @@ async function fetchExchangeRates(appId: string): Promise<OXRResponse> {
   return response.json() as Promise<OXRResponse>;
   return response.json() as Promise<OXRResponse>;
 }
 }
 
 
-async function fetchAllNotionPages(
+async function fetchNotionRates(
   databaseId: string,
   databaseId: string,
   token: string
   token: string
-): Promise<Map<string, string>> {
-  const currencyMap = new Map<string, string>();
+): Promise<NotionPage[]> {
+  const pages: NotionPage[] = [];
   let hasMore = true;
   let hasMore = true;
   let nextCursor: string | null = null;
   let nextCursor: string | null = null;
 
 
@@ -97,7 +112,12 @@ async function fetchAllNotionPages(
     for (const page of data.results) {
     for (const page of data.results) {
       const code = page.properties.Code.title[0]?.plain_text;
       const code = page.properties.Code.title[0]?.plain_text;
       if (code) {
       if (code) {
-        currencyMap.set(code, page.id);
+        pages.push({
+          id: page.id,
+          code,
+          rate: page.properties.Rate.number,
+          updatedAt: page.properties['Updated At'].date?.start || null,
+        });
       }
       }
     }
     }
 
 
@@ -105,7 +125,7 @@ async function fetchAllNotionPages(
     nextCursor = data.next_cursor;
     nextCursor = data.next_cursor;
   }
   }
 
 
-  return currencyMap;
+  return pages;
 }
 }
 
 
 async function updateNotionPage(
 async function updateNotionPage(
@@ -152,54 +172,49 @@ async function syncExchangeRates(env: Env): Promise<SyncResult> {
     errors: [],
     errors: [],
   };
   };
 
 
-  // Validate environment variables
   if (!env.OXR_APP_ID || !env.NOTION_TOKEN || !env.NOTION_DATABASE_ID) {
   if (!env.OXR_APP_ID || !env.NOTION_TOKEN || !env.NOTION_DATABASE_ID) {
     result.errors.push('Missing required environment variables');
     result.errors.push('Missing required environment variables');
     return result;
     return result;
   }
   }
 
 
   try {
   try {
-    // Step 1: Fetch all existing pages from Notion database
     console.log('Fetching existing pages from Notion...');
     console.log('Fetching existing pages from Notion...');
-    const currencyMap = await fetchAllNotionPages(env.NOTION_DATABASE_ID, env.NOTION_TOKEN);
-    result.currenciesFound = currencyMap.size;
-    console.log(`Found ${currencyMap.size} currencies in Notion database`);
+    const notionPages = await fetchNotionRates(env.NOTION_DATABASE_ID, env.NOTION_TOKEN);
+    result.currenciesFound = notionPages.length;
+    console.log(`Found ${notionPages.length} currencies in Notion database`);
 
 
-    if (currencyMap.size === 0) {
+    if (notionPages.length === 0) {
       result.success = true;
       result.success = true;
       console.log('No currencies found in Notion database. Nothing to update.');
       console.log('No currencies found in Notion database. Nothing to update.');
       return result;
       return result;
     }
     }
 
 
-    // Step 2: Fetch exchange rates from OXR
     console.log('Fetching exchange rates from OXR...');
     console.log('Fetching exchange rates from OXR...');
     const oxrData = await fetchExchangeRates(env.OXR_APP_ID);
     const oxrData = await fetchExchangeRates(env.OXR_APP_ID);
     result.ratesFetched = Object.keys(oxrData.rates).length;
     result.ratesFetched = Object.keys(oxrData.rates).length;
     console.log(`Fetched ${result.ratesFetched} currency rates. Base: ${oxrData.base}`);
     console.log(`Fetched ${result.ratesFetched} currency rates. Base: ${oxrData.base}`);
 
 
-    // Step 3: Update only currencies that exist in Notion
     const results: UpdateResult[] = [];
     const results: UpdateResult[] = [];
 
 
-    for (const [currencyCode, pageId] of currencyMap) {
-      const rate = oxrData.rates[currencyCode];
+    for (const page of notionPages) {
+      const rate = oxrData.rates[page.code];
 
 
       if (rate === undefined) {
       if (rate === undefined) {
-        console.warn(`No rate found for ${currencyCode} in OXR data`);
+        console.warn(`No rate found for ${page.code} in OXR data`);
         continue;
         continue;
       }
       }
 
 
       try {
       try {
-        await updateNotionPage(pageId, env.NOTION_TOKEN, rate);
-        results.push({ currency: currencyCode, success: true });
+        await updateNotionPage(page.id, env.NOTION_TOKEN, rate);
+        results.push({ currency: page.code, success: true });
       } catch (error) {
       } catch (error) {
         const errorMessage = error instanceof Error ? error.message : String(error);
         const errorMessage = error instanceof Error ? error.message : String(error);
-        results.push({ currency: currencyCode, success: false, error: errorMessage });
-        result.errors.push(`${currencyCode}: ${errorMessage}`);
-        console.error(`Failed to update ${currencyCode}: ${errorMessage}`);
+        results.push({ currency: page.code, success: false, error: errorMessage });
+        result.errors.push(`${page.code}: ${errorMessage}`);
+        console.error(`Failed to update ${page.code}: ${errorMessage}`);
       }
       }
     }
     }
 
 
-    // Summary
     result.succeeded = results.filter((r) => r.success).length;
     result.succeeded = results.filter((r) => r.success).length;
     result.failed = results.filter((r) => !r.success).length;
     result.failed = results.filter((r) => !r.success).length;
     result.success = true;
     result.success = true;
@@ -213,6 +228,32 @@ async function syncExchangeRates(env: Env): Promise<SyncResult> {
   return result;
   return result;
 }
 }
 
 
+async function getRatesFromNotion(env: Env): Promise<RatesResult> {
+  if (!env.NOTION_TOKEN || !env.NOTION_DATABASE_ID) {
+    throw new Error('Missing NOTION_TOKEN or NOTION_DATABASE_ID');
+  }
+
+  const pages = await fetchNotionRates(env.NOTION_DATABASE_ID, env.NOTION_TOKEN);
+
+  const rates: Record<string, number> = {};
+  let latestTimestamp: string | null = null;
+
+  for (const page of pages) {
+    if (page.rate !== null) {
+      rates[page.code] = page.rate;
+      if (page.updatedAt && (!latestTimestamp || page.updatedAt > latestTimestamp)) {
+        latestTimestamp = page.updatedAt;
+      }
+    }
+  }
+
+  return {
+    base: 'USD',
+    timestamp: latestTimestamp,
+    rates,
+  };
+}
+
 async function convertCurrency(
 async function convertCurrency(
   env: Env,
   env: Env,
   from: string,
   from: string,
@@ -226,20 +267,15 @@ async function convertCurrency(
     amount,
     amount,
     rate: 0,
     rate: 0,
     result: 0,
     result: 0,
-    timestamp: 0,
+    timestamp: null,
   };
   };
 
 
-  if (!env.OXR_APP_ID) {
-    result.error = 'Missing OXR_APP_ID';
-    return result;
-  }
-
   try {
   try {
-    const oxrData = await fetchExchangeRates(env.OXR_APP_ID);
-    result.timestamp = oxrData.timestamp * 1000;
+    const ratesData = await getRatesFromNotion(env);
+    result.timestamp = ratesData.timestamp;
 
 
-    const fromRate = oxrData.rates[from.toUpperCase()];
-    const toRate = oxrData.rates[to.toUpperCase()];
+    const fromRate = ratesData.rates[from.toUpperCase()];
+    const toRate = ratesData.rates[to.toUpperCase()];
 
 
     if (!fromRate) {
     if (!fromRate) {
       result.error = `Unknown currency: ${from}`;
       result.error = `Unknown currency: ${from}`;
@@ -282,7 +318,7 @@ export default {
       });
       });
     }
     }
 
 
-    // Currency conversion endpoint
+    // Currency conversion endpoint (data from Notion)
     if (url.pathname === '/convert') {
     if (url.pathname === '/convert') {
       const from = url.searchParams.get('from');
       const from = url.searchParams.get('from');
       const to = url.searchParams.get('to');
       const to = url.searchParams.get('to');
@@ -318,38 +354,23 @@ export default {
       });
       });
     }
     }
 
 
-    // Rates endpoint - get all rates for a base currency
+    // Rates endpoint - get all rates from Notion database
     if (url.pathname === '/rates') {
     if (url.pathname === '/rates') {
       const base = url.searchParams.get('base') || 'USD';
       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 {
       try {
-        const oxrData = await fetchExchangeRates(env.OXR_APP_ID);
+        const ratesData = await getRatesFromNotion(env);
 
 
         // If base is USD, return as-is
         // If base is USD, return as-is
         if (base.toUpperCase() === 'USD') {
         if (base.toUpperCase() === 'USD') {
           return new Response(
           return new Response(
-            JSON.stringify(
-              {
-                base: oxrData.base,
-                timestamp: oxrData.timestamp * 1000,
-                rates: oxrData.rates,
-              },
-              null,
-              2
-            ),
+            JSON.stringify(ratesData, null, 2),
             { headers: { 'Content-Type': 'application/json' } }
             { headers: { 'Content-Type': 'application/json' } }
           );
           );
         }
         }
 
 
         // Otherwise, recalculate rates for the new base
         // Otherwise, recalculate rates for the new base
-        const baseRate = oxrData.rates[base.toUpperCase()];
+        const baseRate = ratesData.rates[base.toUpperCase()];
         if (!baseRate) {
         if (!baseRate) {
           return new Response(
           return new Response(
             JSON.stringify({ error: `Unknown currency: ${base}` }),
             JSON.stringify({ error: `Unknown currency: ${base}` }),
@@ -358,7 +379,7 @@ export default {
         }
         }
 
 
         const convertedRates: Record<string, number> = {};
         const convertedRates: Record<string, number> = {};
-        for (const [currency, rate] of Object.entries(oxrData.rates)) {
+        for (const [currency, rate] of Object.entries(ratesData.rates)) {
           convertedRates[currency] = rate / baseRate;
           convertedRates[currency] = rate / baseRate;
         }
         }
         convertedRates['USD'] = 1 / baseRate;
         convertedRates['USD'] = 1 / baseRate;
@@ -367,7 +388,7 @@ export default {
           JSON.stringify(
           JSON.stringify(
             {
             {
               base: base.toUpperCase(),
               base: base.toUpperCase(),
-              timestamp: oxrData.timestamp * 1000,
+              timestamp: ratesData.timestamp,
               rates: convertedRates,
               rates: convertedRates,
             },
             },
             null,
             null,
@@ -389,9 +410,9 @@ export default {
       `Notion Exchange Rate Worker
       `Notion Exchange Rate Worker
 
 
 API Endpoints:
 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`,
+  GET /convert?from=USD&to=CNY&amount=100  - Convert currency (data from Notion)
+  GET /rates?base=USD                      - Get exchange rates (data from Notion)
+  GET /sync                                - Sync rates from OXR to Notion`,
       { headers: { 'Content-Type': 'text/plain' } }
       { headers: { 'Content-Type': 'text/plain' } }
     );
     );
   },
   },