Files
skill-seekers-reference/api/main.py
2026-01-17 17:48:15 +00:00

205 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""
Skill Seekers Config API
FastAPI backend for listing available skill configs
"""
from pathlib import Path
from typing import Any
from config_analyzer import ConfigAnalyzer
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
app = FastAPI(
title="Skill Seekers Config API",
description="API for discovering and downloading Skill Seekers configuration files",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
)
# CORS middleware - allow all origins for public API
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Initialize config analyzer
# Try configs_repo first (production), fallback to configs (local development)
CONFIG_DIR = Path(__file__).parent / "configs_repo" / "official"
if not CONFIG_DIR.exists():
CONFIG_DIR = Path(__file__).parent.parent / "configs"
analyzer = ConfigAnalyzer(CONFIG_DIR)
@app.get("/")
async def root():
"""Root endpoint - API information"""
return {
"name": "Skill Seekers Config API",
"version": "1.0.0",
"endpoints": {
"/api/configs": "List all available configs",
"/api/configs/{name}": "Get specific config details",
"/api/categories": "List all categories",
"/api/download/{name}": "Download config file",
"/docs": "API documentation",
},
"repository": "https://github.com/yusufkaraaslan/Skill_Seekers",
"configs_repository": "https://github.com/yusufkaraaslan/skill-seekers-configs",
"website": "https://api.skillseekersweb.com",
}
@app.get("/api/configs")
async def list_configs(
category: str | None = None, tag: str | None = None, type: str | None = None
) -> dict[str, Any]:
"""
List all available configs with metadata
Query Parameters:
- category: Filter by category (e.g., "web-frameworks")
- tag: Filter by tag (e.g., "javascript")
- type: Filter by type ("single-source" or "unified")
Returns:
- version: API version
- total: Total number of configs
- filters: Applied filters
- configs: List of config metadata
"""
try:
# Get all configs
all_configs = analyzer.analyze_all_configs()
# Apply filters
configs = all_configs
filters_applied = {}
if category:
configs = [c for c in configs if c.get("category") == category]
filters_applied["category"] = category
if tag:
configs = [c for c in configs if tag in c.get("tags", [])]
filters_applied["tag"] = tag
if type:
configs = [c for c in configs if c.get("type") == type]
filters_applied["type"] = type
return {
"version": "1.0.0",
"total": len(configs),
"filters": filters_applied if filters_applied else None,
"configs": configs,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error analyzing configs: {str(e)}")
@app.get("/api/configs/{name}")
async def get_config(name: str) -> dict[str, Any]:
"""
Get detailed information about a specific config
Path Parameters:
- name: Config name (e.g., "react", "django")
Returns:
- Full config metadata including all fields
"""
try:
config = analyzer.get_config_by_name(name)
if not config:
raise HTTPException(status_code=404, detail=f"Config '{name}' not found")
return config
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error loading config: {str(e)}")
@app.get("/api/categories")
async def list_categories() -> dict[str, Any]:
"""
List all available categories with config counts
Returns:
- categories: Dict of category names to config counts
- total_categories: Total number of categories
"""
try:
configs = analyzer.analyze_all_configs()
# Count configs per category
category_counts = {}
for config in configs:
cat = config.get("category", "uncategorized")
category_counts[cat] = category_counts.get(cat, 0) + 1
return {"total_categories": len(category_counts), "categories": category_counts}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error analyzing categories: {str(e)}")
@app.get("/api/download/{config_name}")
async def download_config(config_name: str):
"""
Download a specific config file
Path Parameters:
- config_name: Config filename (e.g., "react.json", "django.json")
Returns:
- JSON file for download
"""
try:
# Validate filename (prevent directory traversal)
if ".." in config_name or "/" in config_name or "\\" in config_name:
raise HTTPException(status_code=400, detail="Invalid config name")
# Ensure .json extension
if not config_name.endswith(".json"):
config_name = f"{config_name}.json"
# Search recursively in all subdirectories
config_path = None
for found_path in CONFIG_DIR.rglob(config_name):
config_path = found_path
break
if not config_path or not config_path.exists():
raise HTTPException(status_code=404, detail=f"Config file '{config_name}' not found")
return FileResponse(path=config_path, media_type="application/json", filename=config_name)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error downloading config: {str(e)}")
@app.get("/health")
async def health_check():
"""Health check endpoint for monitoring"""
return {"status": "healthy", "service": "skill-seekers-api"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)