import type { Express, Request, Response, NextFunction } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import axios from "axios"; import { insertSpeciesSchema, insertSearchSchema, insertApiTokenSchema } from "@shared/schema"; import { ZodError } from "zod"; import { fromZodError } from "zod-validation-error"; const CITES_BASE_URL = "https://api.speciesplus.net/api/v1"; export async function registerRoutes(app: Express): Promise { // API Token routes app.post("/api/token", async (req: Request, res: Response) => { try { const tokenData = insertApiTokenSchema.parse(req.body); let citesValid = false; let iucnValid = false; let warnings = []; // Validate the CITES token by making a test request to CITES API try { await axios.get(`${CITES_BASE_URL}/taxon_concepts`, { headers: { "X-Authentication-Token": tokenData.token } }); citesValid = true; } catch (error) { return res.status(400).json({ success: false, message: "Invalid CITES API token. Please check and try again." }); } // If IUCN token is provided, try to validate it if (tokenData.iucnToken) { try { await axios.get("https://apiv4.iucnredlist.org/api/v4/version", { headers: { "Authorization": `Bearer ${tokenData.iucnToken}` } }); iucnValid = true; } catch (iucnError) { warnings.push("The IUCN v4 token could not be validated. Please check your token and try again."); // Don't store the invalid token tokenData.iucnToken = null; } } // Save the validated token const savedToken = await storage.saveApiToken(tokenData); // Return appropriate response based on validation results if (warnings.length > 0) { return res.json({ success: true, token: savedToken, warnings, message: "CITES API token validated and saved. The IUCN v4 token validation failed." }); } res.json({ success: true, token: savedToken, message: tokenData.iucnToken ? "Both CITES and IUCN API tokens validated and saved successfully!" : "CITES API token validated and saved successfully!" }); } catch (error) { if (error instanceof ZodError) { return res.status(400).json({ success: false, message: fromZodError(error).message }); } console.error("Error saving API token:", error); res.status(500).json({ success: false, message: "Failed to save API token" }); } }); app.get("/api/token", async (req: Request, res: Response) => { try { const token = await storage.getActiveToken(); res.json({ token: token?.token || null, iucnToken: token?.iucnToken || null }); } catch (error) { res.status(500).json({ success: false, message: "Failed to retrieve API token" }); } }); // Species search routes app.get("/api/species/search", async (req: Request, res: Response) => { try { const { query, format = "json" } = req.query; if (!query) { return res.status(400).json({ success: false, message: "Search query is required" }); } // Get the API token const tokenData = await storage.getActiveToken(); if (!tokenData) { return res.status(401).json({ success: false, message: "API token is not configured. Please set your CITES+ API token." }); } // Record the search query await storage.addSearch({ query: query.toString() }); // Make request to CITES API try { const response = await axios.get(`${CITES_BASE_URL}/taxon_concepts.${format}`, { params: { name: query }, headers: { "X-Authentication-Token": tokenData.token } }); res.json({ success: true, data: response.data }); } catch (error) { if (axios.isAxiosError(error) && error.response) { return res.status(error.response.status).json({ success: false, message: error.response.data?.message || "Error from CITES+ API", status: error.response.status }); } throw error; } } catch (error) { res.status(500).json({ success: false, message: "Failed to search species" }); } }); app.get("/api/species/:id", async (req: Request, res: Response) => { try { const { id } = req.params; const { endpoint, format = "json" } = req.query; const tokenData = await storage.getActiveToken(); if (!tokenData) { return res.status(401).json({ success: false, message: "API token is not configured" }); } // Make request to CITES API for specific data about the taxon try { let apiUrl = `${CITES_BASE_URL}/taxon_concepts/${id}`; if (endpoint) { apiUrl += `/${endpoint}`; } apiUrl += `.${format}`; const response = await axios.get(apiUrl, { headers: { "X-Authentication-Token": tokenData.token } }); res.json({ success: true, data: response.data }); } catch (error) { if (axios.isAxiosError(error) && error.response) { return res.status(error.response.status).json({ success: false, message: error.response.data?.message || "Error from CITES+ API", status: error.response.status }); } throw error; } } catch (error) { res.status(500).json({ success: false, message: "Failed to get species details" }); } }); // Save species to database app.post("/api/species", async (req: Request, res: Response) => { try { const speciesData = insertSpeciesSchema.parse(req.body); const savedSpecies = await storage.saveSpecies(speciesData); res.json({ success: true, species: savedSpecies }); } catch (error) { if (error instanceof ZodError) { return res.status(400).json({ success: false, message: fromZodError(error).message }); } res.status(500).json({ success: false, message: "Failed to save species data" }); } }); // Get all saved species app.get("/api/species", async (req: Request, res: Response) => { try { const allSpecies = await storage.getAllSpecies(); res.json({ success: true, species: allSpecies }); } catch (error) { res.status(500).json({ success: false, message: "Failed to retrieve saved species" }); } }); // Get recent searches app.get("/api/searches", async (req: Request, res: Response) => { try { const { limit = 10 } = req.query; const recentSearches = await storage.getRecentSearches(Number(limit)); res.json({ success: true, searches: recentSearches }); } catch (error) { res.status(500).json({ success: false, message: "Failed to retrieve recent searches" }); } }); // CITES API Status check endpoint app.get("/api/cites/status", async (req: Request, res: Response) => { try { const activeToken = await storage.getActiveToken(); if (!activeToken || !activeToken.token) { return res.status(401).json({ success: false, connected: false, message: "CITES API token is not configured" }); } try { // Make a request to the CITES API to verify the token const response = await axios.get(`${CITES_BASE_URL}/taxon_concepts/search`, { params: { page: 1, per_page: 1 }, headers: { 'X-Authentication-Token': activeToken.token } }); res.json({ success: true, connected: true, message: "CITES API is connected and responding" }); } catch (error) { if (axios.isAxiosError(error) && error.response) { return res.status(error.response.status).json({ success: false, connected: false, message: "Failed to connect to CITES API or invalid token", status: error.response.status }); } throw error; } } catch (error) { res.status(500).json({ success: false, connected: false, message: "Error checking CITES API status" }); } }); // IUCN API Status check endpoint - v4 only app.get("/api/iucn/status", async (req: Request, res: Response) => { try { const activeToken = await storage.getActiveToken(); if (!activeToken?.iucnToken) { return res.status(401).json({ success: false, connected: false, message: "IUCN API v4 token is not configured. Please set your token in the API Token panel." }); } try { // Check if the API is working by hitting the version endpoint const response = await axios.get("https://apiv4.iucnredlist.org/api/v4/version", { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` } }); return res.json({ success: true, connected: true, apiVersion: "v4", message: "IUCN API v4 is connected and responding" }); } catch (error: any) { console.log("IUCN API check failed:", error.message); // Return useful error messages if (error.response?.status === 401) { return res.status(401).json({ success: false, connected: false, message: "IUCN API v4 token is invalid. Please check your token and try again." }); } // For network errors or other issues return res.status(error.response?.status || 500).json({ success: false, connected: false, message: "Failed to connect to IUCN API: " + (error.response?.data?.message || error.message) }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); res.status(500).json({ success: false, connected: false, message: "Error checking IUCN API status: " + errorMessage }); } }); // IUCN Red List API routes app.get("/api/iucn/species", async (req: Request, res: Response) => { try { const { name } = req.query; if (!name) { return res.status(400).json({ success: false, message: "Species name is required" }); } // Extract genus and species for v4 API const nameParts = String(name).split(' '); const [genusName, speciesName] = nameParts; // Get IUCN token const activeToken = await storage.getActiveToken(); if (!activeToken?.iucnToken) { return res.status(401).json({ success: false, message: "IUCN API v4 token is not configured. Please set your token in the API Token panel." }); } try { // Use the v4 API with scientific name endpoint const response = await axios.get("https://apiv4.iucnredlist.org/api/v4/taxa/scientific_name", { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` }, params: { genus_name: genusName, species_name: speciesName || "" } }); return res.json({ success: true, data: response.data, apiVersion: "v4" }); } catch (error: any) { console.log("IUCN API species lookup failed:", error.message); if (error.response?.status === 401) { return res.status(401).json({ success: false, message: "IUCN API v4 token is invalid. Please check your token and try again." }); } return res.status(error.response?.status || 500).json({ success: false, message: error.response?.data?.message || "Error from IUCN Red List API: " + error.message, status: error.response?.status }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); res.status(500).json({ success: false, message: "Failed to retrieve IUCN species data: " + errorMessage }); } }); app.get("/api/iucn/threats", async (req: Request, res: Response) => { try { const { name } = req.query; if (!name) { return res.status(400).json({ success: false, message: "Species name is required" }); } // Extract genus and species for v4 API query const nameParts = String(name).split(' '); const [genusName, speciesName] = nameParts; // Get IUCN token const activeToken = await storage.getActiveToken(); if (!activeToken?.iucnToken) { return res.status(401).json({ success: false, message: "IUCN API v4 token is not configured. Please set your token in the API Token panel." }); } try { // First, we need to find the species taxon ID using scientific name lookup const taxaResponse = await axios.get("https://apiv4.iucnredlist.org/api/v4/taxa/scientific_name", { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` }, params: { genus_name: genusName, species_name: speciesName || "" } }); // Check if we found the species if (!taxaResponse.data?.result || taxaResponse.data.result.length === 0) { return res.status(404).json({ success: false, message: `No species found with the name "${name}" in the IUCN Red List database.` }); } const taxonId = taxaResponse.data.result[0].taxonid; // Now retrieve the threats using the taxon ID const threatsResponse = await axios.get(`https://apiv4.iucnredlist.org/api/v4/threats/species/id/${taxonId}`, { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` } }); return res.json({ success: true, data: threatsResponse.data, apiVersion: "v4" }); } catch (error: any) { console.log("IUCN API threats lookup failed:", error.message); if (error.response?.status === 401) { return res.status(401).json({ success: false, message: "IUCN API v4 token is invalid. Please check your token and try again." }); } return res.status(error.response?.status || 500).json({ success: false, message: error.response?.data?.message || "Error from IUCN Red List API: " + error.message, status: error.response?.status }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); res.status(500).json({ success: false, message: "Failed to retrieve IUCN threats data: " + errorMessage }); } }); app.get("/api/iucn/habitats", async (req: Request, res: Response) => { try { const { name } = req.query; if (!name) { return res.status(400).json({ success: false, message: "Species name is required" }); } // Extract genus and species for v4 API query const nameParts = String(name).split(' '); const [genusName, speciesName] = nameParts; // Get IUCN token const activeToken = await storage.getActiveToken(); if (!activeToken?.iucnToken) { return res.status(401).json({ success: false, message: "IUCN API v4 token is not configured. Please set your token in the API Token panel." }); } try { // First, we need to find the species taxon ID using scientific name lookup const taxaResponse = await axios.get("https://apiv4.iucnredlist.org/api/v4/taxa/scientific_name", { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` }, params: { genus_name: genusName, species_name: speciesName || "" } }); // Check if we found the species if (!taxaResponse.data?.result || taxaResponse.data.result.length === 0) { return res.status(404).json({ success: false, message: `No species found with the name "${name}" in the IUCN Red List database.` }); } const taxonId = taxaResponse.data.result[0].taxonid; // Now retrieve the habitats using the taxon ID const habitatsResponse = await axios.get(`https://apiv4.iucnredlist.org/api/v4/habitats/species/id/${taxonId}`, { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` } }); return res.json({ success: true, data: habitatsResponse.data, apiVersion: "v4" }); } catch (error: any) { console.log("IUCN API habitats lookup failed:", error.message); if (error.response?.status === 401) { return res.status(401).json({ success: false, message: "IUCN API v4 token is invalid. Please check your token and try again." }); } return res.status(error.response?.status || 500).json({ success: false, message: error.response?.data?.message || "Error from IUCN Red List API: " + error.message, status: error.response?.status }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); res.status(500).json({ success: false, message: "Failed to retrieve IUCN habitats data: " + errorMessage }); } }); app.get("/api/iucn/measures", async (req: Request, res: Response) => { try { const { name } = req.query; if (!name) { return res.status(400).json({ success: false, message: "Species name is required" }); } // Extract genus and species for v4 API query const nameParts = String(name).split(' '); const [genusName, speciesName] = nameParts; // Get IUCN token const activeToken = await storage.getActiveToken(); if (!activeToken?.iucnToken) { return res.status(401).json({ success: false, message: "IUCN API v4 token is not configured. Please set your token in the API Token panel." }); } try { // First, we need to find the species taxon ID using scientific name lookup const taxaResponse = await axios.get("https://apiv4.iucnredlist.org/api/v4/taxa/scientific_name", { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` }, params: { genus_name: genusName, species_name: speciesName || "" } }); // Check if we found the species if (!taxaResponse.data?.result || taxaResponse.data.result.length === 0) { return res.status(404).json({ success: false, message: `No species found with the name "${name}" in the IUCN Red List database.` }); } const taxonId = taxaResponse.data.result[0].taxonid; // Now retrieve the conservation measures using the taxon ID const measuresResponse = await axios.get(`https://apiv4.iucnredlist.org/api/v4/measures/species/id/${taxonId}`, { headers: { "Authorization": `Bearer ${activeToken.iucnToken}` } }); return res.json({ success: true, data: measuresResponse.data, apiVersion: "v4" }); } catch (error: any) { console.log("IUCN API conservation measures lookup failed:", error.message); if (error.response?.status === 401) { return res.status(401).json({ success: false, message: "IUCN API v4 token is invalid. Please check your token and try again." }); } return res.status(error.response?.status || 500).json({ success: false, message: error.response?.data?.message || "Error from IUCN Red List API: " + error.message, status: error.response?.status }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); res.status(500).json({ success: false, message: "Failed to retrieve IUCN conservation measures data: " + errorMessage }); } }); const httpServer = createServer(app); return httpServer; }