From 79291a4196f9bb209df7c737bac68aa9b8400ba1 Mon Sep 17 00:00:00 2001 From: Magnus-SmariSma <20734986-Magnus-SmariSma@users.noreply.replit.com> Date: Thu, 20 Mar 2025 22:57:19 +0000 Subject: [PATCH] Add IUCN API v4 support and token validation. Enhance API token management to include IUCN tokens and improve error handling. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e931b5ab-041b-42e7-baf1-50017869cef6 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/e19c6a51-7e4c-4bb8-a6a6-46dc00f0ec99/c394ef36-207a-4d21-9177-2073a33ff7ed.jpg --- server/routes.ts | 72 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/server/routes.ts b/server/routes.ts index 4e3b83a..7777006 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -7,7 +7,10 @@ import { ZodError } from "zod"; import { fromZodError } from "zod-validation-error"; const CITES_BASE_URL = "https://api.speciesplus.net/api/v1"; -const IUCN_BASE_URL = "https://apiv3.iucnredlist.org/api/v3"; + +// IUCN API versions +const IUCN_V3_BASE_URL = "https://apiv3.iucnredlist.org/api/v3"; +const IUCN_V4_BASE_URL = "https://apiv4.iucnredlist.org/api/v4"; export async function registerRoutes(app: Express): Promise { // API Token routes @@ -16,7 +19,7 @@ export async function registerRoutes(app: Express): Promise { const tokenData = insertApiTokenSchema.parse(req.body); const savedToken = await storage.saveApiToken(tokenData); - // Validate the token by making a test request to CITES API + // Validate the CITES token by making a test request to CITES API try { await axios.get(`${CITES_BASE_URL}/taxon_concepts`, { headers: { @@ -24,12 +27,28 @@ export async function registerRoutes(app: Express): Promise { } }); + // If IUCN token is provided, validate it as well + if (tokenData.iucnToken) { + try { + await axios.get(`${IUCN_V4_BASE_URL}/version`, { + headers: { + "Authorization": `Bearer ${tokenData.iucnToken}` + } + }); + } catch (iucnError) { + return res.status(400).json({ + success: false, + message: "Invalid IUCN API token. Please check and try again." + }); + } + } + res.json({ success: true, token: savedToken }); } catch (error) { if (axios.isAxiosError(error) && error.response) { return res.status(400).json({ success: false, - message: "Invalid API token. Please check and try again." + message: "Invalid CITES API token. Please check and try again." }); } throw error; @@ -52,7 +71,10 @@ export async function registerRoutes(app: Express): Promise { app.get("/api/token", async (req: Request, res: Response) => { try { const token = await storage.getActiveToken(); - res.json({ token: token?.token || null }); + res.json({ + token: token?.token || null, + iucnToken: token?.iucnToken || null + }); } catch (error) { res.status(500).json({ success: false, @@ -266,6 +288,32 @@ export async function registerRoutes(app: Express): Promise { // IUCN API Status check endpoint app.get("/api/iucn/status", async (req: Request, res: Response) => { try { + // Try V4 API first (with bearer token) + const activeToken = await storage.getActiveToken(); + if (activeToken?.iucnToken) { + // Use the version endpoint which is the simplest endpoint + const versionUrl = `${IUCN_V4_BASE_URL}/version`; + + try { + const response = await axios.get(versionUrl, { + headers: { + "Authorization": `Bearer ${activeToken.iucnToken}` + } + }); + + return res.json({ + success: true, + connected: true, + apiVersion: "v4", + message: "IUCN API v4 is connected and responding" + }); + } catch (error) { + // If V4 fails, fall back to checking V3 with environment API key + console.log("IUCN V4 API check failed, falling back to V3:", error.message); + } + } + + // Fallback to V3 API (with query parameter token) const apiKey = process.env.IUCN_API_KEY; if (!apiKey) { return res.status(401).json({ @@ -275,9 +323,8 @@ export async function registerRoutes(app: Express): Promise { }); } - // Use the version endpoint which is the simplest endpoint - // This is the most reliable endpoint as it doesn't need a species name - const versionUrl = `${IUCN_BASE_URL}/version`; + // Use the version endpoint for V3 + const versionUrl = `${IUCN_V3_BASE_URL}/version`; try { // Make sure we're not including any parameters in the URL itself - only in params object @@ -288,7 +335,8 @@ export async function registerRoutes(app: Express): Promise { res.json({ success: true, connected: true, - message: "IUCN API is connected and responding" + apiVersion: "v3", + message: "IUCN API v3 is connected and responding" }); } catch (error) { if (axios.isAxiosError(error) && error.response) { @@ -338,7 +386,7 @@ export async function registerRoutes(app: Express): Promise { const simplifiedName = nameParts.slice(0, 2).join(' '); try { - const response = await axios.get(`${IUCN_BASE_URL}/species/name/${encodeURIComponent(simplifiedName)}`, { + const response = await axios.get(`${IUCN_V3_BASE_URL}/species/name/${encodeURIComponent(simplifiedName)}`, { params: { token: apiKey } }); @@ -389,7 +437,7 @@ export async function registerRoutes(app: Express): Promise { const simplifiedName = nameParts.slice(0, 2).join(' '); try { - const response = await axios.get(`${IUCN_BASE_URL}/threats/species/name/${encodeURIComponent(simplifiedName)}`, { + const response = await axios.get(`${IUCN_V3_BASE_URL}/threats/species/name/${encodeURIComponent(simplifiedName)}`, { params: { token: apiKey } }); @@ -440,7 +488,7 @@ export async function registerRoutes(app: Express): Promise { const simplifiedName = nameParts.slice(0, 2).join(' '); try { - const response = await axios.get(`${IUCN_BASE_URL}/habitats/species/name/${encodeURIComponent(simplifiedName)}`, { + const response = await axios.get(`${IUCN_V3_BASE_URL}/habitats/species/name/${encodeURIComponent(simplifiedName)}`, { params: { token: apiKey } }); @@ -491,7 +539,7 @@ export async function registerRoutes(app: Express): Promise { const simplifiedName = nameParts.slice(0, 2).join(' '); try { - const response = await axios.get(`${IUCN_BASE_URL}/measures/species/name/${encodeURIComponent(simplifiedName)}`, { + const response = await axios.get(`${IUCN_V3_BASE_URL}/measures/species/name/${encodeURIComponent(simplifiedName)}`, { params: { token: apiKey } });