|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | import asyncio |
| 6 | +import io |
6 | 7 | import logging |
| 8 | +import zipfile |
| 9 | +from pathlib import PurePosixPath |
7 | 10 |
|
8 | 11 | from sqlalchemy import func, select |
9 | 12 | from sqlalchemy.ext.asyncio import AsyncSession |
10 | 13 |
|
11 | 14 | from fastapi import APIRouter, Depends, HTTPException, Request |
| 15 | +from fastapi.responses import StreamingResponse |
12 | 16 | from repowise.core.persistence import crud |
13 | 17 | from repowise.core.persistence.models import ( |
14 | 18 | DeadCodeFinding, |
@@ -240,3 +244,42 @@ def _on_done(t: asyncio.Task) -> None: |
240 | 244 | logger.error("background_job_failed", exc_info=t.exception()) |
241 | 245 |
|
242 | 246 | task.add_done_callback(_on_done) |
| 247 | + |
| 248 | + |
| 249 | +@router.get("/{repo_id}/export") |
| 250 | +async def export_wiki( |
| 251 | + repo_id: str, |
| 252 | + session: AsyncSession = Depends(get_db_session), # noqa: B008 |
| 253 | +) -> StreamingResponse: |
| 254 | + """Export all wiki pages as a ZIP of markdown files with folder structure.""" |
| 255 | + repo = await crud.get_repository(session, repo_id) |
| 256 | + if repo is None: |
| 257 | + raise HTTPException(status_code=404, detail="Repository not found") |
| 258 | + |
| 259 | + pages = await crud.list_pages(session, repo_id, limit=10000) |
| 260 | + if not pages: |
| 261 | + raise HTTPException(status_code=404, detail="No pages to export") |
| 262 | + |
| 263 | + buf = io.BytesIO() |
| 264 | + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: |
| 265 | + for page in pages: |
| 266 | + target = page.target_path or page.id |
| 267 | + safe = ( |
| 268 | + target.replace("::", "/") |
| 269 | + .replace("->", "--") |
| 270 | + .replace("\\", "/") |
| 271 | + ) |
| 272 | + path = PurePosixPath("wiki") / page.page_type / safe |
| 273 | + if path.suffix != ".md": |
| 274 | + path = path.with_suffix(path.suffix + ".md") |
| 275 | + |
| 276 | + content = f"# {page.title}\n\n{page.content}" |
| 277 | + zf.writestr(str(path), content) |
| 278 | + |
| 279 | + buf.seek(0) |
| 280 | + filename = f"{repo.name}-wiki.zip" |
| 281 | + return StreamingResponse( |
| 282 | + buf, |
| 283 | + media_type="application/zip", |
| 284 | + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, |
| 285 | + ) |
0 commit comments