
| Current Path : /home/cgabriel/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : //home/cgabriel/ktoapi.py |
import os,re,sys,glob,time,random
from pathlib import Path
import hashlib
import json
from flask import Flask
from flask import render_template
from flask import request
from flask import jsonify
from flask import abort
from flask import session
from flask import make_response
from flask import send_file
from datetime import timedelta
from konto.base.konto import Konto
app = Flask(__name__)
BASE_DIR = os.environ.get("HOMEDIR", ".")
# --- Session / Auth -------------------------------------------------
app.secret_key = os.environ.get("FLASK_SECRET", "dev-secret-change-me")
app.permanent_session_lifetime = timedelta(days=7)
#***************************************************************************
def _userfile(username,customer):
if not username:
return None
# minimal hardening: keep it a filename
username = re.sub(r'[^A-Za-z0-9_.-]', '', username)
return(BASE_DIR + "/" + customer + "/" + "_users_" + "/" + username + ".json")
#***************************************************************************
def load_user(username,customer='----'):
if customer == '----':
customer = session['customer']
uf = _userfile(username,customer)
if not uf:
return None
try:
with open(uf, "r", encoding="utf-8") as f:
return json.load(f)
except:
session.pop("user",None)
return None
#***************************************************************************
def pw_hash(password, salt):
# minimalistic on purpose
return hashlib.sha256((salt + password).encode("utf-8")).hexdigest()
#***************************************************************************
def require_login():
u = session.get("user")
if not u:
abort(401)
udata = load_user(u)
if not udata:
session.pop("user", None)
abort(401)
return u, udata
#***************************************************************************
def allowed_mandanten(udata):
allowed = udata.get("mandanten", {}) or {}
# only those that actually exist as dirs
return(allowed)
# existing = set([p.name for p in BASE_DIR.iterdir() if p.is_dir() and p.name != "_users"])
# return [m for m in allowed if m in existing]
#***************************************************************************
def require_mandant_access(mandant):
u, udata = require_login()
if mandant not in allowed_mandanten(udata):
abort(403)
return u, udata
#***************************************************************************
@app.route("/api/login", methods=["POST"])
def api_login():
data = request.get_json(silent=True) or {}
customer = (data.get("customer") or "").strip()
username = (data.get("username") or "").strip()
password = data.get("password") or ""
udata = load_user(username,customer)
if not udata:
abort(401)
salt = udata.get("salt", "")
expected = udata.get("password_hash", "")
if not expected or pw_hash(password, salt) != expected:
abort(401)
session.permanent = True
session["user"] = username
session["customer"] = customer
return jsonify({"ok": True, "user": username})
#***************************************************************************
@app.route("/api/logout", methods=["POST"])
def api_logout():
session.pop("user", None)
return jsonify({"ok": True})
#***************************************************************************
# --- UI -------------------------------------------------------------
@app.route("/")
def bookings():
user = session.get("user")
mandanten = []
logged_in = False
if user:
udata = load_user(user)
if udata:
mandanten = allowed_mandanten(udata)
mandanten = list(mandanten.keys())
mandanten.sort()
logged_in = True
else:
session.pop("user", None)
return render_template("bookings.html", mandanten=mandanten, logged_in=logged_in, username=user or "")
#***************************************************************************
# --- Data API --------------------------------------------------------
@app.route('/api/context')
def api_context():
mandant = request.args.get('mandant')
subdir = request.args.get('subdir', '')
kpattern = request.args.get('kpattern', '')
u, udata = require_mandant_access(mandant)
wmode = allowed_mandanten(udata)[mandant]
mandant_path = BASE_DIR + "/" + session["customer"] + "/" + mandant + "/" + subdir
# Dateien gruppiert nach Endung
files = { "._d_": [".",".."] }
for f in os.listdir(mandant_path):
ext = ""
if os.path.isfile(mandant_path + "/" + f):
m = re.search(r"^(.*?)\.(kto\.|)([^\.]+)$",f)
if m:
ext = "." + m.group(2) + m.group(3)
ext = ext.lower()
else:
ext = "._f_"
if os.path.isdir(mandant_path + "/" + f):
ext = "._d_"
if ext not in [".pdf",".kto.html",".csv","._d_"]:
ext = ".xxx"
if ext not in files:
files[ext] = []
files[ext].append(f)
# Aktive .kto.html-Datei (erste im Verzeichnis)
ktofile = ""
if ".kto.html" in files:
for ktofile1 in files[".kto.html"]:
if kpattern in ktofile1:
ktofile = ktofile1
break
active = { 'konto':'', 'hash':'', 'saldo':'', 'buchungen':[], 'baum':[] }
if not ktofile == "":
active = parse_kto_file(mandant_path + "/" + ktofile)
for ext in files:
files[ext].sort()
# if re.search(r"^\.[a-zA-Z]+$",ext):
# files[ext].insert(0,ext[1:].upper())
ppar = re.sub(r"^(.*?)([^\\\/]+[\\\/])$","\\1",subdir)
wmode = str(int(allowed_mandanten(udata)[mandant] == "rw"))
return jsonify({
'files': files,
'active': active,
'ppar': ppar,
'wmode': wmode,
})
#***************************************************************************
def parse_kto_file(filepath):
"""Parst eine .kto.html Datei für Tabulator und liefert Baum 1:1 als Text."""
with open(filepath, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f]
if len(lines) < 3:
return {'konto':'','hash':'','saldo':'','buchungen':[],'baum':[]}
# Zeile 1: Konto, Hash, Saldo
konto, hash_val, saldo = lines[0].split()
konto = re.sub(r"\<PRE\>","",konto)
# Buchungszeilen: ab Zeile 3 bis erste Leerzeile
buchungen = []
lfdnr = 0
for i in range(2, len(lines)):
line = lines[i]
if not line.strip():
buchungen_end = i
break
m = re.match(r'^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*?)\s*$', line)
if not m:
continue
lfdnr = lfdnr + 1
buchungen.append({
'lfdnr': ("%08u" % lfdnr),
'datum': m.group(1),
'betrag': m.group(2),
'konto1': m.group(3),
'konto2': m.group(4),
'saldo': m.group(5),
'bemerkung': m.group(6)
})
else:
buchungen_end = 2 + len(buchungen)
# Baum: alle Zeilen nach Buchungen + Leerzeilen
baum_lines = lines[buchungen_end+1:] if buchungen_end+1 < len(lines) else []
# Wir geben die Zeilen 1:1 weiter
return {
'konto': konto,
'hash': hash_val,
'saldo': saldo,
'buchungen': buchungen,
'baum': baum_lines
}
#***************************************************************************
@app.route("/satellite", methods=["GET","POST"])
def satellite():
return(render_template("satellite.html"))
#*********************************************************************************
@app.route("/api/file", methods=["GET","POST"])
def file_api():
if request.method == "GET":
action = request.args.get("action")
mandant = request.args.get("mandant")
subdir = request.args.get("subdir","")
filename = request.args.get("file")
newname = request.args.get("new","")
path = BASE_DIR + "/" + session["customer"] + "/" + mandant + "/" + subdir + filename
# u, udata = require_mandant_access(mandant)
# if allowed_mandanten(udata)[mandant] != "rw":
# abort(403)
if not newname == "":
pathnew = BASE_DIR + "/" + session["customer"] + "/" + mandant + "/" + subdir + newname
os.rename(path,pathnew)
return jsonify({"ok":True})
elif action == "download":
return send_file(path,as_attachment=True)
else:
ending = "txt"
m = re.search(r"^(.*)\.(.*)$",filename)
if m:
ending = m.group(2).lower()
if ending in ["pdf","txt"]:
return send_file(path,as_attachment=False)
else:
return send_file(path,mimetype="text/plain",as_attachment=False)
else:
data = request.json
action = data.get("action")
mandant = data.get("mandant")
subdir = data.get("subdir","")
filename = data.get("file")
path = BASE_DIR + "/" + session["customer"] + "/" + mandant + "/" + subdir + filename
if action == "delete":
if os.path.exists(path):
os.remove(path)
return jsonify({"ok":True})
#*********************************************************************************
@app.route('/api/xxfile_view')
def xxfile_view():
filename = request.form.get('filename')
subdir = request.form.get('subdir')
mandant = request.form.get('mandant')
f = request.args.get('file')
p = os.path.join(BASE_DIR + "/" + session["customer"], f)
if not os.path.exists(p): return ''
with open(p,'r',errors='ignore') as fh:
return fh.read()
#*********************************************************************************
@app.route('/api/xxfile_download')
def xxfile_download():
f = request.args.get('file')
return send_file(os.path.join(BASE_DIR + "/" + session["customer"],f), as_attachment=True)
#*********************************************************************************
@app.route('/api/xxfile_delete', methods=['POST'])
def xxfile_delete():
j = request.json
os.remove(os.path.join(BASE_DIR + "/" + session["customer"],j['file']))
return jsonify({'ok':True})
#*********************************************************************************
@app.route('/api/xxfile_rename', methods=['POST'])
def xxfile_rename():
j = request.json
os.rename(os.path.join(BASE_DIR + "/" + session["customer"],j['file']), os.path.join(BASE_DIR + "/" + session["customer"],j['newname']))
return jsonify({'ok':True})
#*********************************************************************************
@app.route('/api/file_upload', methods=['GET','POST'])
def file_upload():
filename = request.form.get('filename')
subdir = request.form.get('subdir')
mandant = request.form.get('mandant')
u, udata = require_mandant_access(mandant)
if allowed_mandanten(udata)[mandant] != "rw":
abort(403)
f = request.files['file']
f.save(BASE_DIR + "/" + session["customer"] +"/" + mandant + "/" + subdir + filename)
return jsonify({'ok':True})
#*********************************************************************************
@app.route("/api/dir", methods=["POST"])
def dir_action():
data = request.json
action = data.get("action")
mandant = data.get("mandant")
subdir = data.get("subdir")
newname = data.get("newname")
base = BASE_DIR + "/" + session["customer"] +"/" + mandant + "/" + subdir
if action == "rename":
newpath = BASE_DIR + "/" + session["customer"] +"/" + mandant + "/" + newname
os.rename(base, newpath)
if action == "create":
os.mkdir(base + "/" + newname)
if action == "delete":
if not os.listdir(base):
os.rmdir(base)
return "OK"
#*********************************************************************************
if __name__ == "__main__":
app.run(debug=True)