Add Git source management to WebUI
This commit is contained in:
+80
-9
@@ -45,6 +45,16 @@ class SyncSourcesRequest(BaseModel):
|
||||
override: bool = False
|
||||
|
||||
|
||||
class GitSourceRequest(BaseModel):
|
||||
library_id: str = Field(..., min_length=1)
|
||||
repo_url: str = Field(..., min_length=1)
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
branch: str = "main"
|
||||
include_paths: Optional[list[str]] = None
|
||||
exclude_paths: Optional[list[str]] = None
|
||||
|
||||
|
||||
DOCUMENT_EXTENSIONS = {
|
||||
".md",
|
||||
".txt",
|
||||
@@ -190,6 +200,40 @@ def sources_config_path() -> Path:
|
||||
return Path(__file__).resolve().parents[2] / "docs_sources.yaml"
|
||||
|
||||
|
||||
def clean_source_paths(paths: Optional[list[str]]) -> list[str]:
|
||||
cleaned = []
|
||||
for raw_path in paths or []:
|
||||
path = raw_path.strip().strip("/")
|
||||
if not path or path == "." or ".." in Path(path).parts or Path(path).is_absolute():
|
||||
continue
|
||||
cleaned.append(path)
|
||||
return cleaned
|
||||
|
||||
|
||||
def load_sources_config() -> dict:
|
||||
path = sources_config_path()
|
||||
if not path.exists():
|
||||
return {"sources": []}
|
||||
|
||||
with path.open() as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
if isinstance(data, list):
|
||||
return {"sources": data}
|
||||
if not isinstance(data, dict):
|
||||
return {"sources": []}
|
||||
sources = data.get("sources", [])
|
||||
data["sources"] = sources if isinstance(sources, list) else []
|
||||
return data
|
||||
|
||||
|
||||
def save_sources_config(data: dict) -> None:
|
||||
path = sources_config_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w") as f:
|
||||
yaml.safe_dump(data, f, sort_keys=False)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "ok", "service": "docs-api"}
|
||||
@@ -341,18 +385,45 @@ async def api_upload(library_id: str, file: UploadFile = File(...)):
|
||||
@app.get("/api/v1/sources")
|
||||
@app.get("/sources/config")
|
||||
async def api_list_sources():
|
||||
path = sources_config_path()
|
||||
if not path.exists():
|
||||
return {"success": True, "sources": [], "count": 0}
|
||||
|
||||
with path.open() as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
sources = data.get("sources", data if isinstance(data, list) else [])
|
||||
if not isinstance(sources, list):
|
||||
sources = []
|
||||
data = load_sources_config()
|
||||
sources = data["sources"]
|
||||
return {"success": True, "sources": sources, "count": len(sources)}
|
||||
|
||||
|
||||
@app.post("/api/v1/sources")
|
||||
async def api_add_source(source: GitSourceRequest):
|
||||
library_id = safe_library_id(source.library_id)
|
||||
branch = source.branch.strip() or "main"
|
||||
include_paths = clean_source_paths(source.include_paths)
|
||||
exclude_paths = clean_source_paths(source.exclude_paths) or ["node_modules", ".git"]
|
||||
|
||||
source_entry = {
|
||||
"library_id": library_id,
|
||||
"name": (source.name or library_id).strip(),
|
||||
"description": (source.description or "").strip(),
|
||||
"repo_url": source.repo_url.strip(),
|
||||
"branch": branch,
|
||||
"include_paths": include_paths or ["docs"],
|
||||
"exclude_paths": exclude_paths,
|
||||
}
|
||||
|
||||
data = load_sources_config()
|
||||
sources = data["sources"]
|
||||
existing_index = next(
|
||||
(index for index, item in enumerate(sources) if item.get("library_id") == library_id),
|
||||
None,
|
||||
)
|
||||
if existing_index is None:
|
||||
sources.append(source_entry)
|
||||
created = True
|
||||
else:
|
||||
sources[existing_index] = source_entry
|
||||
created = False
|
||||
|
||||
save_sources_config(data)
|
||||
return {"success": True, "created": created, "source": source_entry}
|
||||
|
||||
|
||||
@app.post("/sources/sync")
|
||||
async def sync_sources_api(payload: Optional[SyncSourcesRequest] = None):
|
||||
source_data = await api_list_sources()
|
||||
|
||||
Reference in New Issue
Block a user