|
|
|
|
@ -1,8 +1,9 @@
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
import os, sqlite3, csv, io, json, datetime as dt
|
|
|
|
|
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 *
|
|
|
|
|
import sqlite3
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
|
|
|
|
|
@app.get("/export.csv")
|
|
|
|
|
def export_csv():
|
|
|
|
|
db = get_db()
|
|
|
|
|
rows = db.execute("""
|
|
|
|
|
SELECT id, created_at, kind, total, payer, a_share, method, note
|
|
|
|
|
FROM entries
|
|
|
|
|
ORDER BY datetime(created_at) DESC, id DESC
|
|
|
|
|
""").fetchall()
|
|
|
|
|
buff = io.StringIO()
|
|
|
|
|
w = csv.writer(buff)
|
|
|
|
|
w.writerow(["id","created_at","kind","payer","a_share_pct","total","method","note","delta"])
|
|
|
|
|
for r in rows:
|
|
|
|
|
a_share_pct = float(r["a_share"]) * 100.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}",
|
|
|
|
|
f"{r['total']:.2f}", r["method"], r["note"] or "", f"{delta:.2f}"])
|
|
|
|
|
buff.seek(0)
|
|
|
|
|
return send_file(io.BytesIO(buff.read().encode("utf-8")), mimetype="text/csv",
|
|
|
|
|
as_attachment=True, download_name="splitbuddy_export.csv")
|
|
|
|
|
@app.get("/export")
|
|
|
|
|
def export_db():
|
|
|
|
|
if not os.path.exists(DB_PATH):
|
|
|
|
|
return {"error": "Database not found"}, 404
|
|
|
|
|
|
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
|
|
|
sio = io.StringIO()
|
|
|
|
|
|
|
|
|
|
for line in conn.iterdump():
|
|
|
|
|
sio.write(f"{line}\n")
|
|
|
|
|
|
|
|
|
|
conn.close()
|
|
|
|
|
sio.seek(0)
|
|
|
|
|
|
|
|
|
|
return Response(
|
|
|
|
|
sio.getvalue(),
|
|
|
|
|
mimetype="text/sql",
|
|
|
|
|
headers={
|
|
|
|
|
"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 -----
|
|
|
|
|
@app.get("/audit")
|
|
|
|
|
|