diff --git a/packages/server/src/repowise/server/routers/repos.py b/packages/server/src/repowise/server/routers/repos.py index d2a1ecd..bd616ce 100644 --- a/packages/server/src/repowise/server/routers/repos.py +++ b/packages/server/src/repowise/server/routers/repos.py @@ -3,12 +3,16 @@ from __future__ import annotations import asyncio +import io import logging +import zipfile +from pathlib import PurePosixPath from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi.responses import StreamingResponse from repowise.core.persistence import crud from repowise.core.persistence.models import ( DeadCodeFinding, @@ -240,3 +244,42 @@ def _on_done(t: asyncio.Task) -> None: logger.error("background_job_failed", exc_info=t.exception()) task.add_done_callback(_on_done) + + +@router.get("/{repo_id}/export") +async def export_wiki( + repo_id: str, + session: AsyncSession = Depends(get_db_session), # noqa: B008 +) -> StreamingResponse: + """Export all wiki pages as a ZIP of markdown files with folder structure.""" + repo = await crud.get_repository(session, repo_id) + if repo is None: + raise HTTPException(status_code=404, detail="Repository not found") + + pages = (await session.execute(select(Page).where(Page.repository_id == repo_id))).scalars().all() + if not pages: + raise HTTPException(status_code=404, detail="No pages to export") + + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: + for page in pages: + target = page.target_path or page.id + safe = ( + target.replace("::", "/") + .replace("->", "--") + .replace("\\", "/") + ) + path = PurePosixPath("wiki") / page.page_type / safe + if path.suffix != ".md": + path = path.with_suffix(path.suffix + ".md") + + content = f"# {page.title}\n\n{page.content}" + zf.writestr(str(path), content) + + buf.seek(0) + filename = f"{repo.name}-wiki.zip" + return StreamingResponse( + buf, + media_type="application/zip", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) diff --git a/packages/web/src/app/repos/[id]/docs/page.tsx b/packages/web/src/app/repos/[id]/docs/page.tsx index e52d988..b075bc0 100644 --- a/packages/web/src/app/repos/[id]/docs/page.tsx +++ b/packages/web/src/app/repos/[id]/docs/page.tsx @@ -1,7 +1,7 @@ "use client"; import { use, useState } from "react"; -import { Download, Loader2 } from "lucide-react"; +import { Download, FolderArchive, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DocsExplorer } from "@/components/docs/docs-explorer"; import { listAllPages } from "@/lib/api/pages"; @@ -41,20 +41,27 @@ export default function DocsPage({ Browse AI-generated documentation for every file, module, and symbol.
- +