fix(marketplace): isolate api under marketplace prefix

This commit is contained in:
unclecode
2025-10-09 22:26:15 +08:00
parent 2c373f0642
commit 5a4f21fad9
7 changed files with 42 additions and 38 deletions

View File

@@ -30,10 +30,10 @@ const { API_BASE, API_ORIGIN } = (() => {
if (origin) { if (origin) {
const normalized = cleanOrigin(origin); const normalized = cleanOrigin(origin);
return { API_BASE: `${normalized}/api`, API_ORIGIN: normalized }; return { API_BASE: `${normalized}/marketplace/api`, API_ORIGIN: normalized };
} }
return { API_BASE: '/api', API_ORIGIN: '' }; return { API_BASE: '/marketplace/api', API_ORIGIN: '' };
})(); })();
const resolveAssetUrl = (path) => { const resolveAssetUrl = (path) => {

View File

@@ -210,6 +210,6 @@
</div> </div>
</div> </div>
<script src="admin.js?v=1759334000"></script> <script src="admin.js?v=1759335000"></script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
// App Detail Page JavaScript // App Detail Page JavaScript
const API_BASE = '/api'; const API_BASE = '/marketplace/api';
class AppDetailPage { class AppDetailPage {
constructor() { constructor() {

View File

@@ -1,4 +1,4 @@
from fastapi import FastAPI, HTTPException, Query, Depends, Body, UploadFile, File, Form from fastapi import FastAPI, HTTPException, Query, Depends, Body, UploadFile, File, Form, APIRouter
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@@ -16,6 +16,7 @@ from datetime import datetime, timedelta
from config import Config from config import Config
app = FastAPI(title="Crawl4AI Marketplace API") app = FastAPI(title="Crawl4AI Marketplace API")
router = APIRouter(prefix="/marketplace/api")
# Security setup # Security setup
security = HTTPBearer() security = HTTPBearer()
@@ -84,7 +85,7 @@ def to_int(value, default=0):
# ============= PUBLIC ENDPOINTS ============= # ============= PUBLIC ENDPOINTS =============
@app.get("/api/apps") @router.get("/apps")
async def get_apps( async def get_apps(
category: Optional[str] = None, category: Optional[str] = None,
type: Optional[str] = None, type: Optional[str] = None,
@@ -114,7 +115,7 @@ async def get_apps(
return json_response(apps) return json_response(apps)
@app.get("/api/apps/{slug}") @router.get("/apps/{slug}")
async def get_app(slug: str): async def get_app(slug: str):
"""Get single app by slug""" """Get single app by slug"""
apps = db.get_all('apps', where=f"slug = '{slug}'", limit=1) apps = db.get_all('apps', where=f"slug = '{slug}'", limit=1)
@@ -127,7 +128,7 @@ async def get_app(slug: str):
return json_response(app) return json_response(app)
@app.get("/api/articles") @router.get("/articles")
async def get_articles( async def get_articles(
category: Optional[str] = None, category: Optional[str] = None,
limit: int = Query(default=20, le=10000), limit: int = Query(default=20, le=10000),
@@ -146,7 +147,7 @@ async def get_articles(
return json_response(articles) return json_response(articles)
@app.get("/api/articles/{slug}") @router.get("/articles/{slug}")
async def get_article(slug: str): async def get_article(slug: str):
"""Get single article by slug""" """Get single article by slug"""
articles = db.get_all('articles', where=f"slug = '{slug}'", limit=1) articles = db.get_all('articles', where=f"slug = '{slug}'", limit=1)
@@ -161,7 +162,7 @@ async def get_article(slug: str):
return json_response(article) return json_response(article)
@app.get("/api/categories") @router.get("/categories")
async def get_categories(): async def get_categories():
"""Get all categories ordered by index""" """Get all categories ordered by index"""
categories = db.get_all('categories', limit=50) categories = db.get_all('categories', limit=50)
@@ -170,7 +171,7 @@ async def get_categories():
categories.sort(key=lambda x: x.get('order_index', 0)) categories.sort(key=lambda x: x.get('order_index', 0))
return json_response(categories, cache_time=7200) return json_response(categories, cache_time=7200)
@app.get("/api/sponsors") @router.get("/sponsors")
async def get_sponsors(active: Optional[bool] = True): async def get_sponsors(active: Optional[bool] = True):
"""Get sponsors, default active only""" """Get sponsors, default active only"""
where = f"active = {1 if active else 0}" if active is not None else None where = f"active = {1 if active else 0}" if active is not None else None
@@ -185,7 +186,7 @@ async def get_sponsors(active: Optional[bool] = True):
return json_response(sponsors) return json_response(sponsors)
@app.get("/api/search") @router.get("/search")
async def search(q: str = Query(min_length=2)): async def search(q: str = Query(min_length=2)):
"""Search across apps and articles""" """Search across apps and articles"""
if len(q) < 2: if len(q) < 2:
@@ -206,7 +207,7 @@ async def search(q: str = Query(min_length=2)):
return json_response(results, cache_time=1800) return json_response(results, cache_time=1800)
@app.get("/api/stats") @router.get("/stats")
async def get_stats(): async def get_stats():
"""Get marketplace statistics""" """Get marketplace statistics"""
stats = { stats = {
@@ -227,7 +228,7 @@ def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
return token return token
@app.post("/api/admin/upload-image", dependencies=[Depends(verify_token)]) @router.post("/admin/upload-image", dependencies=[Depends(verify_token)])
async def upload_image(file: UploadFile = File(...), folder: str = Form("sponsors")): async def upload_image(file: UploadFile = File(...), folder: str = Form("sponsors")):
"""Upload image files for admin assets""" """Upload image files for admin assets"""
folder = (folder or "").strip().lower() folder = (folder or "").strip().lower()
@@ -251,7 +252,7 @@ async def upload_image(file: UploadFile = File(...), folder: str = Form("sponsor
return {"url": f"/uploads/{folder}/{filename}"} return {"url": f"/uploads/{folder}/{filename}"}
@app.post("/api/admin/login") @router.post("/admin/login")
async def admin_login(password: str = Body(..., embed=True)): async def admin_login(password: str = Body(..., embed=True)):
"""Admin login with password""" """Admin login with password"""
provided_hash = hashlib.sha256(password.encode()).hexdigest() provided_hash = hashlib.sha256(password.encode()).hexdigest()
@@ -272,7 +273,7 @@ async def admin_login(password: str = Body(..., embed=True)):
# ============= ADMIN ENDPOINTS ============= # ============= ADMIN ENDPOINTS =============
@app.get("/api/admin/stats", dependencies=[Depends(verify_token)]) @router.get("/admin/stats", dependencies=[Depends(verify_token)])
async def get_admin_stats(): async def get_admin_stats():
"""Get detailed admin statistics""" """Get detailed admin statistics"""
stats = { stats = {
@@ -292,7 +293,7 @@ async def get_admin_stats():
return stats return stats
# Apps CRUD # Apps CRUD
@app.post("/api/admin/apps", dependencies=[Depends(verify_token)]) @router.post("/admin/apps", dependencies=[Depends(verify_token)])
async def create_app(app_data: Dict[str, Any]): async def create_app(app_data: Dict[str, Any]):
"""Create new app""" """Create new app"""
try: try:
@@ -311,7 +312,7 @@ async def create_app(app_data: Dict[str, Any]):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.put("/api/admin/apps/{app_id}", dependencies=[Depends(verify_token)]) @router.put("/admin/apps/{app_id}", dependencies=[Depends(verify_token)])
async def update_app(app_id: int, app_data: Dict[str, Any]): async def update_app(app_id: int, app_data: Dict[str, Any]):
"""Update app""" """Update app"""
try: try:
@@ -329,7 +330,7 @@ async def update_app(app_id: int, app_data: Dict[str, Any]):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.delete("/api/admin/apps/{app_id}", dependencies=[Depends(verify_token)]) @router.delete("/admin/apps/{app_id}", dependencies=[Depends(verify_token)])
async def delete_app(app_id: int): async def delete_app(app_id: int):
"""Delete app""" """Delete app"""
cursor = db.conn.cursor() cursor = db.conn.cursor()
@@ -338,7 +339,7 @@ async def delete_app(app_id: int):
return {"message": "App deleted"} return {"message": "App deleted"}
# Articles CRUD # Articles CRUD
@app.post("/api/admin/articles", dependencies=[Depends(verify_token)]) @router.post("/admin/articles", dependencies=[Depends(verify_token)])
async def create_article(article_data: Dict[str, Any]): async def create_article(article_data: Dict[str, Any]):
"""Create new article""" """Create new article"""
try: try:
@@ -356,7 +357,7 @@ async def create_article(article_data: Dict[str, Any]):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.put("/api/admin/articles/{article_id}", dependencies=[Depends(verify_token)]) @router.put("/admin/articles/{article_id}", dependencies=[Depends(verify_token)])
async def update_article(article_id: int, article_data: Dict[str, Any]): async def update_article(article_id: int, article_data: Dict[str, Any]):
"""Update article""" """Update article"""
try: try:
@@ -373,7 +374,7 @@ async def update_article(article_id: int, article_data: Dict[str, Any]):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.delete("/api/admin/articles/{article_id}", dependencies=[Depends(verify_token)]) @router.delete("/admin/articles/{article_id}", dependencies=[Depends(verify_token)])
async def delete_article(article_id: int): async def delete_article(article_id: int):
"""Delete article""" """Delete article"""
cursor = db.conn.cursor() cursor = db.conn.cursor()
@@ -382,7 +383,7 @@ async def delete_article(article_id: int):
return {"message": "Article deleted"} return {"message": "Article deleted"}
# Categories CRUD # Categories CRUD
@app.post("/api/admin/categories", dependencies=[Depends(verify_token)]) @router.post("/admin/categories", dependencies=[Depends(verify_token)])
async def create_category(category_data: Dict[str, Any]): async def create_category(category_data: Dict[str, Any]):
"""Create new category""" """Create new category"""
try: try:
@@ -399,7 +400,7 @@ async def create_category(category_data: Dict[str, Any]):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.put("/api/admin/categories/{cat_id}", dependencies=[Depends(verify_token)]) @router.put("/admin/categories/{cat_id}", dependencies=[Depends(verify_token)])
async def update_category(cat_id: int, category_data: Dict[str, Any]): async def update_category(cat_id: int, category_data: Dict[str, Any]):
"""Update category""" """Update category"""
try: try:
@@ -417,7 +418,7 @@ async def update_category(cat_id: int, category_data: Dict[str, Any]):
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.delete("/api/admin/categories/{cat_id}", dependencies=[Depends(verify_token)]) @router.delete("/admin/categories/{cat_id}", dependencies=[Depends(verify_token)])
async def delete_category(cat_id: int): async def delete_category(cat_id: int):
"""Delete category""" """Delete category"""
try: try:
@@ -429,7 +430,7 @@ async def delete_category(cat_id: int):
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
# Sponsors CRUD # Sponsors CRUD
@app.post("/api/admin/sponsors", dependencies=[Depends(verify_token)]) @router.post("/admin/sponsors", dependencies=[Depends(verify_token)])
async def create_sponsor(sponsor_data: Dict[str, Any]): async def create_sponsor(sponsor_data: Dict[str, Any]):
"""Create new sponsor""" """Create new sponsor"""
try: try:
@@ -443,7 +444,7 @@ async def create_sponsor(sponsor_data: Dict[str, Any]):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.put("/api/admin/sponsors/{sponsor_id}", dependencies=[Depends(verify_token)]) @router.put("/admin/sponsors/{sponsor_id}", dependencies=[Depends(verify_token)])
async def update_sponsor(sponsor_id: int, sponsor_data: Dict[str, Any]): async def update_sponsor(sponsor_id: int, sponsor_data: Dict[str, Any]):
"""Update sponsor""" """Update sponsor"""
try: try:
@@ -457,7 +458,7 @@ async def update_sponsor(sponsor_id: int, sponsor_data: Dict[str, Any]):
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
@app.delete("/api/admin/sponsors/{sponsor_id}", dependencies=[Depends(verify_token)]) @router.delete("/admin/sponsors/{sponsor_id}", dependencies=[Depends(verify_token)])
async def delete_sponsor(sponsor_id: int): async def delete_sponsor(sponsor_id: int):
"""Delete sponsor""" """Delete sponsor"""
try: try:
@@ -468,6 +469,9 @@ async def delete_sponsor(sponsor_id: int):
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
app.include_router(router)
@app.get("/") @app.get("/")
async def root(): async def root():
"""API info""" """API info"""
@@ -475,12 +479,12 @@ async def root():
"name": "Crawl4AI Marketplace API", "name": "Crawl4AI Marketplace API",
"version": "1.0.0", "version": "1.0.0",
"endpoints": [ "endpoints": [
"/api/apps", "/marketplace/api/apps",
"/api/articles", "/marketplace/api/articles",
"/api/categories", "/marketplace/api/categories",
"/api/sponsors", "/marketplace/api/sponsors",
"/api/search?q=query", "/marketplace/api/search?q=query",
"/api/stats" "/marketplace/api/stats"
] ]
} }

View File

@@ -1,5 +1,5 @@
// App Detail Page JavaScript // App Detail Page JavaScript
const API_BASE = '/api'; const API_BASE = '/marketplace/api';
class AppDetailPage { class AppDetailPage {
constructor() { constructor() {

View File

@@ -1,5 +1,5 @@
// Marketplace JS - Magazine Layout // Marketplace JS - Magazine Layout
const API_BASE = '/api'; const API_BASE = '/marketplace/api';
const CACHE_TTL = 3600000; // 1 hour in ms const CACHE_TTL = 3600000; // 1 hour in ms
class MarketplaceCache { class MarketplaceCache {

View File

@@ -3,9 +3,9 @@ const { API_BASE, API_ORIGIN } = (() => {
const { hostname, port } = window.location; const { hostname, port } = window.location;
if ((hostname === 'localhost' || hostname === '127.0.0.1') && port === '8000') { if ((hostname === 'localhost' || hostname === '127.0.0.1') && port === '8000') {
const origin = 'http://127.0.0.1:8100'; const origin = 'http://127.0.0.1:8100';
return { API_BASE: `${origin}/api`, API_ORIGIN: origin }; return { API_BASE: `${origin}/marketplace/api`, API_ORIGIN: origin };
} }
return { API_BASE: '/api', API_ORIGIN: '' }; return { API_BASE: '/marketplace/api', API_ORIGIN: '' };
})(); })();
const resolveAssetUrl = (path) => { const resolveAssetUrl = (path) => {