Add CP source adapter registry with multi-worker queues and LLM extract.
Replace legacy root backend/frontend with Telegram, Crawl4AI, and VIINA adapters routed by Redis job families. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,13 +2,16 @@ FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
COPY centers/analytics/api/requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app/ ./app/
|
||||
COPY contracts/ ./contracts/
|
||||
COPY centers/analytics/api/app/ ./app/
|
||||
|
||||
RUN mkdir -p /data
|
||||
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
# Monorepo / Docker: contracts live next to app or at repo root
|
||||
_HERE = Path(__file__).resolve()
|
||||
for _candidate in (_HERE.parent, *_HERE.parents):
|
||||
if (_candidate / "contracts").is_dir() and str(_candidate) not in sys.path:
|
||||
sys.path.insert(0, str(_candidate))
|
||||
break
|
||||
|
||||
from .database import Base, engine, get_db
|
||||
from .routers import admin, internal, map, objects, v1
|
||||
from .seed import seed_objects, seed_test_consumer
|
||||
|
||||
@@ -37,11 +37,23 @@ from ..services.jobs import enqueue_job
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
|
||||
def _validated_source_config(source_type: str, source_config: dict) -> dict:
|
||||
try:
|
||||
from contracts.queues import family_for_source
|
||||
from contracts.sources import parse_source_config
|
||||
|
||||
family_for_source(source_type)
|
||||
return parse_source_config(source_type, source_config or {}).model_dump()
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/jobs", response_model=ParseJobRead, status_code=201)
|
||||
def create_parse_job(payload: ParseJobCreate, db: Session = Depends(get_db)):
|
||||
source_config = _validated_source_config(payload.source_type, payload.source_config)
|
||||
job = ParseJob(
|
||||
source_type=payload.source_type,
|
||||
source_config=payload.source_config,
|
||||
source_config=source_config,
|
||||
schedule=payload.schedule,
|
||||
interval_seconds=payload.interval_seconds,
|
||||
is_active=payload.is_active,
|
||||
@@ -96,7 +108,7 @@ def update_parse_job(
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
|
||||
if payload.source_config is not None:
|
||||
job.source_config = payload.source_config
|
||||
job.source_config = _validated_source_config(job.source_type, payload.source_config)
|
||||
if payload.interval_seconds is not None:
|
||||
job.interval_seconds = payload.interval_seconds
|
||||
if payload.is_active is not None:
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import redis
|
||||
|
||||
# Allow importing shared contracts from monorepo root in local runs
|
||||
_HERE = Path(__file__).resolve()
|
||||
for _candidate in (_HERE.parent, *_HERE.parents):
|
||||
if (_candidate / "contracts").is_dir():
|
||||
if str(_candidate) not in sys.path:
|
||||
sys.path.insert(0, str(_candidate))
|
||||
break
|
||||
|
||||
from contracts.queues import queue_key_for_source # noqa: E402
|
||||
from contracts.sources import parse_source_config # noqa: E402
|
||||
|
||||
REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
|
||||
JOB_QUEUE_KEY = "cp:jobs"
|
||||
|
||||
|
||||
def get_redis() -> redis.Redis:
|
||||
@@ -12,9 +24,17 @@ def get_redis() -> redis.Redis:
|
||||
|
||||
|
||||
def enqueue_job(job_id: int, source_type: str, source_config: dict) -> None:
|
||||
# Validate known configs early; unknown types still raise from queue_key_for_source
|
||||
try:
|
||||
parse_source_config(source_type, source_config or {})
|
||||
except Exception:
|
||||
# Keep enqueue resilient for legacy rows; worker validates again
|
||||
pass
|
||||
|
||||
payload = {
|
||||
"job_id": job_id,
|
||||
"source_type": source_type,
|
||||
"source_config": source_config,
|
||||
"source_config": source_config or {},
|
||||
}
|
||||
get_redis().rpush(JOB_QUEUE_KEY, json.dumps(payload))
|
||||
key = queue_key_for_source(source_type)
|
||||
get_redis().rpush(key, json.dumps(payload))
|
||||
|
||||
Reference in New Issue
Block a user