feat(export): JSON full-backup download (pantry + saved recipes)
Adds GET /export/json that bundles inventory and saved recipes into a single timestamped JSON file for data portability. The export envelope includes schema version and export timestamp so future import logic can handle version differences. Frontend: new primary-styled JSON download button in the Export card with a short description of what is included. Closes #62
This commit is contained in:
parent
2ad71f2636
commit
c8fdc21c29
2 changed files with 43 additions and 2 deletions
|
|
@ -1,9 +1,11 @@
|
|||
"""Export endpoints — CSV/Excel of receipt and inventory data."""
|
||||
"""Export endpoints — CSV and JSON export of user data."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
|
@ -45,3 +47,33 @@ async def export_inventory_csv(store: Store = Depends(get_store)):
|
|||
media_type="text/csv",
|
||||
headers={"Content-Disposition": "attachment; filename=inventory.csv"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/json")
|
||||
async def export_full_json(store: Store = Depends(get_store)):
|
||||
"""Export full pantry inventory + saved recipes as a single JSON file.
|
||||
|
||||
Intended for data portability — users can import this into another
|
||||
Kiwi instance or keep it as an offline backup.
|
||||
"""
|
||||
inventory, saved = await asyncio.gather(
|
||||
asyncio.to_thread(store.list_inventory),
|
||||
asyncio.to_thread(store.get_saved_recipes, 1000, 0),
|
||||
)
|
||||
|
||||
export_doc = {
|
||||
"kiwi_export": {
|
||||
"version": "1.0",
|
||||
"exported_at": datetime.now(timezone.utc).isoformat(),
|
||||
"inventory": [dict(row) for row in inventory],
|
||||
"saved_recipes": [dict(row) for row in saved],
|
||||
}
|
||||
}
|
||||
|
||||
body = json.dumps(export_doc, default=str, indent=2)
|
||||
filename = f"kiwi-export-{datetime.now(timezone.utc).strftime('%Y%m%d')}.json"
|
||||
return StreamingResponse(
|
||||
iter([body]),
|
||||
media_type="application/json",
|
||||
headers={"Content-Disposition": f"attachment; filename={filename}"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -392,10 +392,14 @@
|
|||
<!-- Export -->
|
||||
<div class="card export-card">
|
||||
<h2 class="section-title">Export</h2>
|
||||
<div class="flex gap-sm" style="margin-top: var(--spacing-sm)">
|
||||
<div class="flex gap-sm flex-wrap" style="margin-top: var(--spacing-sm)">
|
||||
<button @click="exportJSON" class="btn btn-primary">Download JSON (full backup)</button>
|
||||
<button @click="exportCSV" class="btn btn-secondary">Download CSV</button>
|
||||
<button @click="exportExcel" class="btn btn-secondary">Download Excel</button>
|
||||
</div>
|
||||
<p class="text-sm text-secondary" style="margin-top: var(--spacing-xs)">
|
||||
JSON includes pantry + saved recipes. Import it into another Kiwi instance any time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
|
|
@ -847,6 +851,11 @@ function exportExcel() {
|
|||
window.open(`${apiUrl}/export/inventory/excel`, '_blank')
|
||||
}
|
||||
|
||||
function exportJSON() {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || '/api/v1'
|
||||
window.open(`${apiUrl}/export/json`, '_blank')
|
||||
}
|
||||
|
||||
// Full date string for tooltip (accessible label)
|
||||
function formatDateFull(dateStr: string): string {
|
||||
const date = new Date(dateStr)
|
||||
|
|
|
|||
Loading…
Reference in a new issue