From 404c8382a3ed8401eece1c76f168ff58f58248a9 Mon Sep 17 00:00:00 2001 From: Burak <8755484+kriptoburak@users.noreply.github.com> Date: Sun, 19 Jul 2026 08:43:10 +0300 Subject: [PATCH] Add Xquik API and repair catalogue search Signed-off-by: Burak <8755484+kriptoburak@users.noreply.github.com> --- backend/main.py | 49 ++++++++++++++++++++++++++++---------------- backend/test_main.py | 26 +++++++++++++++++++++++ catalog/apis.json | 16 +++++++++++++++ frontend/index.html | 27 +++++++++++++++--------- 4 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 backend/test_main.py diff --git a/backend/main.py b/backend/main.py index a235ee4..d9d18fc 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,16 +1,13 @@ -"""API Directory Backend — Serves the catalog of public APIs with search and filters.""" +"""API Directory Backend - Serves the catalog of public APIs with search and filters.""" import json -import httpx +import random +from datetime import datetime from pathlib import Path + from fastapi import FastAPI, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse -from pydantic import BaseModel -from typing import Optional -from datetime import datetime -import asyncio -import random app = FastAPI( title="Public API Directory", @@ -28,6 +25,7 @@ # Load catalog CATALOG_PATH = Path(__file__).parent.parent / "catalog" / "apis.json" + def load_catalog() -> list[dict]: with open(CATALOG_PATH) as f: return json.load(f)["apis"] @@ -41,7 +39,9 @@ async def get_apis( search: str = Query("", description="Search in name, description, tags"), cors: str = Query("", description="Filter: true/false for CORS support"), min_popularity: int = Query(0, ge=0, le=5, description="Minimum popularity (1-5)"), - sort: str = Query("name", regex="^(name|popularity|category|price)$", description="Sort field"), + sort: str = Query( + "name", pattern="^(name|popularity|category|price)$", description="Sort field" + ), page: int = Query(1, ge=1), per_page: int = Query(50, ge=1, le=200), ): @@ -69,7 +69,8 @@ async def get_apis( if search: s = search.lower() apis = [ - a for a in apis + a + for a in apis if s in a["name"].lower() or s in a["description"].lower() or s in a["category"].lower() @@ -156,7 +157,11 @@ async def get_stats(): auth_types[auth_simple] = auth_types.get(auth_simple, 0) + 1 # Price - if "free" in a["price"].lower() and "paid" not in a["price"].lower() and "+" not in a["price"]: + if ( + "free" in a["price"].lower() + and "paid" not in a["price"].lower() + and "+" not in a["price"] + ): price_tiers["free"] += 1 elif "free" in a["price"].lower(): price_tiers["freemium"] += 1 @@ -179,7 +184,8 @@ async def get_stats(): } -@app.get("/api/ap/{api_id}") +@app.get("/api/ap/{api_id}", include_in_schema=False) +@app.get("/api/apis/{api_id}") async def get_api_detail(api_id: str): """Get details for a specific API.""" apis = load_catalog() @@ -213,7 +219,9 @@ async def compare_apis(ids: str = Query(..., description="Comma-separated API ID found = [a for a in apis if a["id"] in id_list] if not found: - return JSONResponse(status_code=404, content={"error": "No matching APIs found"}) + return JSONResponse( + status_code=404, content={"error": "No matching APIs found"} + ) return { "apis": found, @@ -221,7 +229,9 @@ async def compare_apis(ids: str = Query(..., description="Comma-separated API ID "free_count": sum(1 for a in found if "free" in a["price"].lower()), "no_auth_count": sum(1 for a in found if a["auth"] == "None"), "cors_count": sum(1 for a in found if a["cors"]), - "avg_popularity": round(sum(a["popularity"] for a in found) / len(found), 1), + "avg_popularity": round( + sum(a["popularity"] for a in found) / len(found), 1 + ), }, } @@ -238,11 +248,13 @@ async def get_suggestions(q: str = Query("", description="Partial query")): for a in apis: if q_lower in a["name"].lower() or any(q_lower in t.lower() for t in a["tags"]): - suggestions.append({ - "id": a["id"], - "name": a["name"], - "category": a["category"], - }) + suggestions.append( + { + "id": a["id"], + "name": a["name"], + "category": a["category"], + } + ) if len(suggestions) >= 10: break @@ -262,4 +274,5 @@ async def health(): if __name__ == "__main__": import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/backend/test_main.py b/backend/test_main.py new file mode 100644 index 0000000..59a4ce0 --- /dev/null +++ b/backend/test_main.py @@ -0,0 +1,26 @@ +import unittest + +from fastapi.testclient import TestClient + +from backend.main import app + + +class ApiDetailTest(unittest.TestCase): + def setUp(self): + self.client = TestClient(app) + + def test_canonical_detail_route_returns_xquik(self): + response = self.client.get("/api/apis/xquik-api") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["api"]["id"], "xquik-api") + + def test_legacy_detail_route_remains_compatible(self): + response = self.client.get("/api/ap/xquik-api") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["api"]["id"], "xquik-api") + + +if __name__ == "__main__": + unittest.main() diff --git a/catalog/apis.json b/catalog/apis.json index f573513..4be1540 100644 --- a/catalog/apis.json +++ b/catalog/apis.json @@ -320,6 +320,22 @@ "tags": ["twitter", "tweets", "social", "trending", "analytics"], "popularity": 5 }, + { + "id": "xquik-api", + "name": "Xquik", + "description": "REST API for X data extraction, account monitoring, webhooks, and automation workflows. Includes MCP support and SDKs for multiple languages.", + "category": "Social Media", + "base_url": "https://xquik.com/api/v1", + "docs_url": "https://docs.xquik.com/api-reference/overview", + "auth": "API Key", + "price": "Paid", + "rate_limit": "Plan based", + "endpoint_example": "/x/tweets/search", + "response_format": "JSON", + "cors": false, + "tags": ["x", "twitter", "social-media", "webhooks", "automation", "mcp"], + "popularity": 4 + }, { "id": "telegram-bot-api", "name": "Telegram Bot API", diff --git a/frontend/index.html b/frontend/index.html index 0f49c3a..2ff4f86 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - API Hub — Discover Public APIs + API Hub - Discover Public APIs