added export/import functionality

main
oscar 1 month ago
parent a1bbcdd6ab
commit 908e481271

@ -1,8 +1,9 @@
from __future__ import annotations from __future__ import annotations
import os, sqlite3, csv, io, json, datetime as dt import os, sqlite3, csv, io, json, datetime as dt
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from flask import Flask, g, request, redirect, url_for, render_template, send_file, jsonify from flask import Flask, g, request, redirect, url_for, render_template, send_file, jsonify, Response
from config import * from config import *
import sqlite3
app = Flask(__name__) app = Flask(__name__)
@ -281,25 +282,54 @@ def edit(entry_id: int):
_audit("edit", entry_id, old_row=_row_to_dict(old), new_row=_row_to_dict(new)) _audit("edit", entry_id, old_row=_row_to_dict(old), new_row=_row_to_dict(new))
return redirect(url_for("index")) return redirect(url_for("index"))
@app.get("/export.csv") @app.get("/export")
def export_csv(): def export_db():
db = get_db() if not os.path.exists(DB_PATH):
rows = db.execute(""" return {"error": "Database not found"}, 404
SELECT id, created_at, kind, total, payer, a_share, method, note
FROM entries conn = sqlite3.connect(DB_PATH)
ORDER BY datetime(created_at) DESC, id DESC sio = io.StringIO()
""").fetchall()
buff = io.StringIO() for line in conn.iterdump():
w = csv.writer(buff) sio.write(f"{line}\n")
w.writerow(["id","created_at","kind","payer","a_share_pct","total","method","note","delta"])
for r in rows: conn.close()
a_share_pct = float(r["a_share"]) * 100.0 sio.seek(0)
delta = _delta_for_entry(r["kind"], r["total"], r["payer"], r["a_share"])
w.writerow([r["id"], r["created_at"], r["kind"], r["payer"], f"{a_share_pct:.2f}", return Response(
f"{r['total']:.2f}", r["method"], r["note"] or "", f"{delta:.2f}"]) sio.getvalue(),
buff.seek(0) mimetype="text/sql",
return send_file(io.BytesIO(buff.read().encode("utf-8")), mimetype="text/csv", headers={
as_attachment=True, download_name="splitbuddy_export.csv") "Content-Disposition": "attachment; filename=splitbuddy_export.sql"
}
)
@app.post("/import")
def import_db():
if "file" not in request.files:
return {"error": "No file uploaded"}, 400
file = request.files["file"]
sql_text = file.read().decode("utf-8")
# delete old DB
if os.path.exists(DB_PATH):
os.remove(DB_PATH)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
try:
cur.executescript(sql_text)
conn.commit()
except Exception as e:
conn.close()
return {"error": str(e)}, 500
conn.close()
return {"status": "import successful"}
# ----- Audit viewers ----- # ----- Audit viewers -----
@app.get("/audit") @app.get("/audit")

@ -9,11 +9,14 @@
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<script defer src="{{ url_for('static', filename='app.js') }}"></script> <script defer src="{{ url_for('static', filename='app.js') }}"></script>
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
<header> <header>
<div class="h1">SplitBuddy</div> <div class="h1">SplitBuddy</div>
<div> <div>
<!-- balance indicator -->
<span class="pill {{ 'bad' if summary.total>0 else ('ok' if summary.total<0 else '') }}"> <span class="pill {{ 'bad' if summary.total>0 else ('ok' if summary.total<0 else '') }}">
{% if summary.total > 0 %} {% if summary.total > 0 %}
{{ A }} owes {{ B }} <strong>{{ currency }}{{ '%.2f'|format(summary.total) }}</strong> {{ A }} owes {{ B }} <strong>{{ currency }}{{ '%.2f'|format(summary.total) }}</strong>
@ -22,11 +25,24 @@
{% else %}All settled ✨{% endif %} {% else %}All settled ✨{% endif %}
</span> </span>
<span class="pill muted">Balance: {{ currency }}{{ '%.2f'|format(summary.total) }}</span> <span class="pill muted">Balance: {{ currency }}{{ '%.2f'|format(summary.total) }}</span>
<a class="pill" href="{{ url_for('export_csv') }}">Export CSV</a>
<!-- export / import SQL buttons -->
<a class="pill" href="{{ url_for('export_db') }}">Export SQL</a>
<form action="{{ url_for('import_db') }}" method="POST" enctype="multipart/form-data" style="display:inline">
<label class="pill" style="cursor:pointer">
Import SQL
<input type="file" name="file" accept=".sql"
onchange="this.form.submit()" style="display:none">
</label>
</form>
</div> </div>
</header> </header>
<div class="grid"> <div class="grid">
<!-- add entry form -->
<section class="card"> <section class="card">
<h2>Add entry</h2> <h2>Add entry</h2>
<form method="post" action="{{ url_for('add') }}"> <form method="post" action="{{ url_for('add') }}">
@ -61,6 +77,7 @@
</form> </form>
</section> </section>
<!-- stats -->
<section class="card"> <section class="card">
<h2>Stats</h2> <h2>Stats</h2>
<div class="muted">Entries: {{ summary.count }}</div> <div class="muted">Entries: {{ summary.count }}</div>
@ -73,6 +90,7 @@
</section> </section>
</div> </div>
<!-- ledger -->
<section class="card" style="margin-top:16px"> <section class="card" style="margin-top:16px">
<h2>Ledger</h2> <h2>Ledger</h2>
<table> <table>

Loading…
Cancel
Save