first commit

This commit is contained in:
Klaas Börgmann 2025-02-25 17:12:12 +01:00
commit 1de2c23995
7 changed files with 404 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

6
.env_example Normal file
View File

@ -0,0 +1,6 @@
DB_HOST=localhost
DB_PORT=5432
DB_NAME=postgresql
DB_USER=postgresql
DB_PASSWORD=supersafepassword

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

0
LICENSE.md Normal file
View File

0
README.MD Normal file
View File

170
api.py Normal file
View File

@ -0,0 +1,170 @@
import jwt as pyjwt
import datetime
import bcrypt
import psycopg2
import psycopg2.extras
from flask import Flask, request, jsonify
import os
from dotenv import load_dotenv
# Flask App erstellen
app = Flask(__name__)
app.config['SECRET_KEY'] = 'supergeheimeschluessel'
# .env-Datei laden
load_dotenv()
# Zugriff auf die Umgebungsvariablen
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
# Verbindung zur PostgreSQL-Datenbank herstellen
def connect_db():
return psycopg2.connect(
dbname=DB_NAME,
user=DB_USER,
password=DB_PASSWORD,
host=DB_HOST,
port=DB_PORT
)
def create_tables():
conn = connect_db()
cur = conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
email TEXT UNIQUE NOT NULL,
reset_password_token TEXT,
reset_password_expires TIMESTAMP
);
''')
conn.commit()
cur.close()
conn.close()
def generate_jwt(user_id):
payload = {
'user_id': user_id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
return pyjwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
def verify_jwt(token):
try:
payload = pyjwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
return payload['user_id']
except pyjwt.ExpiredSignatureError:
return None
except pyjwt.InvalidTokenError:
return None
def hash_password(password):
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
def check_password(password, hashed):
return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
@app.route('/register', methods=['POST'])
def register():
data = request.json
username = data.get('username')
email = data.get('email')
password = data.get('password')
if not username or not email or not password:
return jsonify({'error': 'Alle Felder erforderlich'}), 400
hashed_password = hash_password(password)
conn = connect_db()
cur = conn.cursor()
try:
cur.execute("INSERT INTO users (username, email, password) VALUES (%s, %s, %s) RETURNING id", (username, email, hashed_password))
user_id = cur.fetchone()[0]
conn.commit()
except psycopg2.IntegrityError:
return jsonify({'error': 'Benutzername oder E-Mail bereits vergeben'}), 400
finally:
cur.close()
conn.close()
return jsonify({'message': 'Benutzer registriert', 'user_id': user_id})
@app.route('/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')
conn = connect_db()
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute("SELECT * FROM users WHERE username = %s", (username,))
user = cur.fetchone()
cur.close()
conn.close()
if not user or not check_password(password, user['password']):
return jsonify({'error': 'Ungültige Anmeldeinformationen'}), 401
token = generate_jwt(user['id'])
return jsonify({'token': token})
@app.route('/kasse', methods=['GET'])
def get_kasse():
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Kein Token vorhanden'}), 401
user_id = verify_jwt(token.split()[1])
if not user_id:
return jsonify({'error': 'Ungültiges Token'}), 401
conn = connect_db()
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute("SELECT * FROM kasse ORDER BY timestamp DESC")
rows = cur.fetchall()
cur.close()
conn.close()
return jsonify([dict(row) for row in rows])
@app.route('/new_entry', methods=['POST'])
def new_entry():
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Kein Token vorhanden'}), 401
user_id = verify_jwt(token.split()[1])
if not user_id:
return jsonify({'error': 'Ungültiges Token'}), 401
data = request.json
beschreibung = data.get('beschreibung')
wert = data.get('wert')
typ = data.get('typ')
bar = data.get('bar')
if not beschreibung or wert is None or not typ or bar is None:
return jsonify({'error': 'Alle Felder erforderlich'}), 400
conn = connect_db()
cur = conn.cursor()
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar) VALUES (%s, %s, %s, %s) RETURNING id",
(beschreibung, wert, typ, bar))
entry_id = cur.fetchone()[0]
conn.commit()
cur.close()
conn.close()
return jsonify({'message': 'Eintrag erstellt', 'entry_id': entry_id})
if __name__ == '__main__':
create_tables()
app.run(debug=True)

227
main.py Normal file
View File

@ -0,0 +1,227 @@
import psycopg2
import psycopg2.extras
import datetime
import os
from dotenv import load_dotenv
# ANSI Escape Codes für Farben und Unterstreichung
RESET = "\033[0m" # Zurücksetzen auf Standard
RED = "\033[31m" # Rote Schrift
GREEN = "\033[32m" # Grüne Schrift
BLUE = "\033[34m" # Blaue Schrift
BOLD = "\033[1m" # Fett
ITALIC = "\033[3m" # Kursiv
UNDERLINE = "\033[4m" # Unterstreichen
# .env-Datei laden
load_dotenv()
# Zugriff auf die Umgebungsvariablen
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
# Verbindung zur PostgreSQL-Datenbank herstellen
def connect_db():
return psycopg2.connect(
dbname=DB_NAME,
user=DB_USER,
password=DB_PASSWORD,
host=DB_HOST,
port=DB_PORT
)
def create_table():
conn = connect_db()
cur = conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS kasse (
id SERIAL PRIMARY KEY,
beschreibung TEXT NOT NULL,
wert NUMERIC(10,2) NOT NULL,
typ TEXT CHECK (typ IN ('Einnahme', 'Ausgabe')) NOT NULL,
bar BOOLEAN NOT NULL,
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
rechnungsnummer TEXT,
kommentar TEXT,
anlass TEXT
);
''')
conn.commit()
cur.close()
conn.close()
def initial_setup():
conn = connect_db()
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM kasse;")
count = cur.fetchone()[0]
if count == 0:
start_konto = float(input("Startwert Konto: "))
start_bar = float(input("Startwert Bar: "))
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar) VALUES (%s, %s, %s, %s)",
('Startwert Konto', start_konto, 'Einnahme', False))
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar) VALUES (%s, %s, %s, %s)",
('Startwert Bar', start_bar, 'Einnahme', True))
conn.commit()
cur.close()
conn.close()
def parse_timestamp(date_str, time_str):
if not date_str:
date_str = datetime.datetime.now().strftime("%d.%m.%y")
if not time_str:
time_str = datetime.datetime.now().strftime("%H:%M")
return datetime.datetime.strptime(f"{date_str} {time_str}", "%d.%m.%y %H:%M")
def erfassen(typ):
beschreibung = input("Beschreibung: ")
wert = float(input("Wert: "))
bar = input("Bar? (1 = Ja, 0 = Nein): ") == '1'
rechnungsnummer = input("Rechnungsnummer (optional): ") or None
kommentar = input("Kommentar (optional): ") or None
anlass = input("Anlass (optional): ") or None
date_str = input("Datum (TT.MM.JJ, leer für heute): ")
time_str = input("Uhrzeit (HH:MM, leer für jetzt): ")
timestamp = parse_timestamp(date_str, time_str)
conn = connect_db()
cur = conn.cursor()
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, rechnungsnummer, kommentar, anlass) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
(beschreibung, wert, typ, bar, timestamp, rechnungsnummer, kommentar, anlass))
conn.commit()
cur.close()
conn.close()
print("Buchung erfasst!")
exit = input(f"\nEnter -> zurück zum Menü ")
def umbuchung():
wert = float(input("Betrag der Umbuchung: "))
richtung = input("Umbuchung Richtung? (1 = Bar -> Konto, 0 = Konto -> Bar): ") == '1'
date_str = input("Datum (TT.MM.JJ, leer für heute): ")
time_str = input("Uhrzeit (HH:MM, leer für jetzt): ")
timestamp = parse_timestamp(date_str, time_str)
conn = connect_db()
cur = conn.cursor()
if richtung:
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
('Umbuchung nach Konto', wert, 'Ausgabe', True, timestamp, 'Orga', 'SJTKD'))
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
('Umbuchung von Bar', wert, 'Einnahme', False, timestamp, 'Orga', 'SJTKD'))
else:
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
('Umbuchung nach Bar', wert, 'Ausgabe', False, timestamp, 'Orga', 'SJTKD'))
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
('Umbuchung von Konto', wert, 'Einnahme', True, timestamp, 'Orga', 'SJTKD'))
conn.commit()
cur.close()
conn.close()
print("Umbuchung erfasst!")
exit = input(f"\nEnter -> zurück zum Menü ")
def kassenbestand():
conn = connect_db()
cur = conn.cursor()
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Einnahme' AND bar=True")
einnahmen_bar = cur.fetchone()[0] or 0
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Ausgabe' AND bar=True")
ausgaben_bar = cur.fetchone()[0] or 0
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Einnahme' AND bar=False")
einnahmen_konto = cur.fetchone()[0] or 0
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Ausgabe' AND bar=False")
ausgaben_konto = cur.fetchone()[0] or 0
barbestand = einnahmen_bar - ausgaben_bar
kontostand = einnahmen_konto - ausgaben_konto
summe = barbestand + kontostand
print(UNDERLINE + "Kassenbestand:" + RESET)
print(BOLD + "Bar: " + RESET + f"{einnahmen_bar - ausgaben_bar:>10.2f}")
print(BOLD + "Konto: " + RESET + f"{einnahmen_konto - ausgaben_konto:>10.2f}\n==============")
print(BOLD + "Summe: " + RESET + f"{summe:>10.2f}")
cur.close()
conn.close()
exit = input(f"\nEnter -> zurück zum Menü ")
def anzeigen(filter_typ=None):
conn = connect_db()
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
if filter_typ:
cur.execute("SELECT * FROM kasse WHERE typ = %s ORDER BY timestamp ASC", (filter_typ,))
else:
cur.execute("SELECT * FROM kasse ORDER BY timestamp ASC")
rows = cur.fetchall()
for row in rows:
formatted_date = row['timestamp'].strftime("%d.%m.%y")
print(f"{row['id']:<4} {row['beschreibung']:<35} | " + f"{GREEN if row['typ']=='Einnahme' else RED}" + f"{row['wert']:>7}" + RESET + f" | {'Bar' if row['bar'] else 'Konto':<6} | {formatted_date} | {row['rechnungsnummer'] or '':<15} | {row['kommentar'] or '':<10} | {row['anlass'] or '':<10}")
cur.close()
conn.close()
exit = input(f"\nEnter -> zurück zum Menü ")
def anzeigen_anlass():
filter_typ = input("Anlass: ")
conn = connect_db()
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute("SELECT * FROM kasse WHERE anlass = %s ORDER BY timestamp ASC", (filter_typ,))
rows = cur.fetchall()
for row in rows:
formatted_date = row['timestamp'].strftime("%d.%m.%y")
print(f"{row['id']:<4} {row['beschreibung']:<35} | " + f"{GREEN if row['typ']=='Einnahme' else RED}" + f"{row['wert']:>7}" + RESET + f" | {'Bar' if row['bar'] else 'Konto':<6} | {formatted_date} | {row['rechnungsnummer'] or '':<10} | {row['kommentar'] or '':<10}")
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ = 'Einnahme' AND anlass = %s", (filter_typ,))
einnahmen = cur.fetchone()[0] or 0
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Ausgabe' AND anlass = %s", (filter_typ,))
ausgaben = cur.fetchone()[0] or 0
print(UNDERLINE + "\nBilanz:" + RESET)
text="Einnahmen:"
print(BOLD + f"{text:<8}" + RESET + GREEN + f"{einnahmen:>10.2f}" + RESET)
text="Ausgaben: "
print(BOLD + f"{text:<8}" + RESET + RED + f"{ausgaben:>10.2f}" + RESET + f"\n===================")
summe = einnahmen - ausgaben
COLOR = GREEN
if summe<0:
COLOR = RED
text="Summe: "
print(BOLD + f"{text:<8}" + RESET + COLOR + f"{summe:>10.2f}" + RESET)
cur.close()
conn.close()
exit = input(f"\nEnter -> zurück zum Menü ")
def menu():
create_table()
initial_setup()
while True:
print(UNDERLINE + "\nKassenbuch Menü:" + RESET)
print(BOLD + "(1)" + RESET + " Ausgabe erfassen")
print(BOLD + "(2)" + RESET + " Einnahme erfassen")
print(BOLD + "(3)" + RESET + " Kassenbestand anzeigen")
print(BOLD + "(4)" + RESET + " Umsätze anzeigen")
print(BOLD + "(5)" + RESET + " Ausgaben anzeigen")
print(BOLD + "(6)" + RESET + " Einnahmen anzeigen")
print(BOLD + "(7)" + RESET + " Umbuchung durchführen")
print(BOLD + "(8)" + RESET + " Bilanz Veranstaltung")
print(BOLD + "(0)" + RESET + " Beenden")
auswahl = input("Auswahl: ")
print("\n")
if auswahl == '1': erfassen('Ausgabe')
elif auswahl == '2': erfassen('Einnahme')
elif auswahl == '3': kassenbestand()
elif auswahl == '4': anzeigen()
elif auswahl == '5': anzeigen('Ausgabe')
elif auswahl == '6': anzeigen('Einnahme')
elif auswahl == '7': umbuchung()
elif auswahl == '8': anzeigen_anlass()
elif auswahl == '0': break
if __name__ == "__main__":
menu()