From 7dbdd2dc5b97277e65c80260f25f0a5ad5a83426 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 9 Sep 2026 08:23:02 +0200 Subject: [PATCH] CoreCashbook V1.0.0 --- Dockerfile | 27 ++ data/core.sqlite | Bin 0 -> 36864 bytes data/cron.log | 16 + data/php_errors.log | 8 + data/receipts/rechnung_6aa030a5ae2c3.pdf | Bin 0 -> 89415 bytes data/receipts/rechnung_6aa0315de4fa6.pdf | Bin 0 -> 24324 bytes data/receipts/rechnung_6aa031e34521d.pdf | Bin 0 -> 68916 bytes data/receipts/rechnung_6aa0327955014.pdf | Bin 0 -> 23978 bytes data/receipts/rechnung_6aa0efce2bcdd.pdf | Bin 0 -> 128823 bytes docker-compose.yaml | 13 + src/.htaccess | 4 + src/app.html | 476 +++++++++++++++++++++++ src/auth.php | 147 +++++++ src/cashbook.php | 256 ++++++++++++ src/cron.php | 72 ++++ src/database.php | 52 +++ src/index.php | 33 ++ src/router.php | 45 +++ 18 files changed, 1149 insertions(+) create mode 100755 Dockerfile create mode 100644 data/core.sqlite create mode 100644 data/cron.log create mode 100644 data/php_errors.log create mode 100644 data/receipts/rechnung_6aa030a5ae2c3.pdf create mode 100644 data/receipts/rechnung_6aa0315de4fa6.pdf create mode 100644 data/receipts/rechnung_6aa031e34521d.pdf create mode 100644 data/receipts/rechnung_6aa0327955014.pdf create mode 100644 data/receipts/rechnung_6aa0efce2bcdd.pdf create mode 100755 docker-compose.yaml create mode 100755 src/.htaccess create mode 100644 src/app.html create mode 100755 src/auth.php create mode 100644 src/cashbook.php create mode 100644 src/cron.php create mode 100644 src/database.php create mode 100644 src/index.php create mode 100644 src/router.php diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..f2509b3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Wir nutzen ein schlankes, offizielles PHP 8.2 Image mit Apache +FROM php:8.2-apache + +# Aktiviere Apache Rewrite und SSL +RUN a2enmod rewrite ssl + +# ERLAUBE .htaccess OVERRIDES +RUN sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf + +# Installiere SQLite, PDO und OpenSSL +RUN apt-get update && apt-get install -y libsqlite3-dev openssl \ + && docker-php-ext-install pdo pdo_sqlite \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Generiere ein Self-Signed Zertifikat (Gebrandet auf Tessmann Digital) +# Die Pfade entsprechen genau denen, die Apache in der default-ssl.conf erwartet +RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout /etc/ssl/private/ssl-cert-snakeoil.key \ + -out /etc/ssl/certs/ssl-cert-snakeoil.pem \ + -subj "/C=DE/ST=Brandenburg/L=Gruenheide/O=Tessmann Digital/CN=localhost" + +# Aktiviere den virtuellen SSL-Host von Apache +RUN a2ensite default-ssl + +# Setze das Arbeitsverzeichnis +WORKDIR /var/www/html \ No newline at end of file diff --git a/data/core.sqlite b/data/core.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..12c7ad733ee0b61482da4a743c961e47b1987e13 GIT binary patch literal 36864 zcmeI5OKcn06^3U-S)^oB^r9${3QjL!E0iLUng`!O0nrjo38rK{C_jn-#E^4IO*s#0 z9*$(R3py>j36M?EW%u25(M8dP7eU>u(kvPz=(^o>(Qe~**)zi#KGevmyJ#Ba3^G)WAZnj}e2dsMe* za#T#Q)$`?s7bVSbben+I+NjpyRat`Hx;9-mh8Bm{T5zpnm}j1%kx0dAn6%}XrlNO- z&TUril#gn4!j0~0oAkt?ddn~GIieZ5eXO1(S4r}WN+#@$teF(-$qo4R&(%}k-l>qF z){Ucs0|3=q$vz&aBmk->>48m|S}haqfY|xhL48jei%MXE zUw&GOgyRbf?2qqyVONb#(=fXJW_BXj{>lG3&M%{G*^+5DR0HS-s=pvMRF}W>9$A69 z^cHP&wTr^vbIhJ$4cg8qa;;Ym#d7SQ>@UALBgEI(Q}s$Xe&Yt)AB=4lwQcCeRI>7Zkck7cXS<-CiJCDl1M3N5eA%)~ctu#BS1^wc_0;Tf+XIkw>) zH-h>be6SbpSEs}AM1p<#$m@iXBP(ua=vbq@D-#+==gHDox4w9=C5}bS#^}~n?}RRC zelH+{q23=Zpfh%;9ULecg)XJ_{(})}c`q~*Us+)Xe)z6igAtLQJDi^I=-kC6SK2C+ zT<*WYhU4)#`*aXH*AL74F*Q-%JM}6shQ5=d-R=FOv9AKslPv2U!;~Axk~J7%M`IB& zjo@1+7(|n1*)bCImex;`XK}tZeMp4191w0!RP}AOR$R1dsp{ zKmter2_OL^fCT<;2-MgaCf>gimt;-RZwY6&cB!C2v01`j~NB{{S z0VIF~kN^@u0!ZL;5r~B1v)=Q6H1<~}6#I~gy@-7n`|NT#iQXUqB!C2v01`j~NB{{S z0VIF~kN^_6R0QUyLJ4;CLPqF%D746qzZe$-cilBuA}l=rdoKdG^Z!G!x0%>Sv7f^R z7bJiLkN^@u0!RP}AOR$R1dsp{KmthM6$unVVP-a-SP{Lyv>(AM`lM!b6rC>$X+Bp- z^93%=i$#GGaw)C|AHrozB3H_0#3`?2;@?VgX(215GwIB*P=WCI633UgyvTk@p~85f zOo_{s#GL#55C8NJ7bJiLkN^@u0!RP}AOR$R1dsp{KmthM5)*LW|HtS5OB`Ty4GACt zB!C2v01`j~NB{{S0VIF~kN^;fUP~~Mxfe{#ng7fDUi7o*Ti1Ru_u{I3b$ae+bEVne z&lV9A5h|G=ch@aVUOVZ`DR9!Na_ChdM zme16wO1p-pIl9vIas&Fw6oWfBn1Y)tiF_$5ZWxf$4N{i{TT)h49nzTHQ6anH4$%~w zRBx~J-<$*UiEv^8%$ILkf%#o(x11iizjwI)?f2ha`uunA4^0%e49iUzci!}1$}r63 z%|S}WW8Kj^jhrNLwB4dYvn9*vp4AH#g)#{!thaso_AE)?@3 zpUv`Fk^%)J%W$huv zfaT)>u0o2>9NN^OrVfr)Y4iEBW0UGbmR`7E(lno`QjNM9@krHt{!BlnicJ06N&ERT zlgJ7s<-iDmoR;LS3I#d?^Zj@j#I1!B-;F>kTBE+W9jODvQ#GkcRZ@nWd+Yok|MrvD ze|s<#kl&HaF8!i#deXQeNNY;1W8!)V?s=gbRNFRG(3B##P3op?7r0f9v@~)`wx0jy zm=b<)d^Q8ZUSkLC>ow6AwrOaRqF)<9x}Z%BMC3|*exgl@LcW;Ia(u>ZQ-|<&xosTQ z56bm>BxqH8s$=zI(}Re4ZF}LZz&4c?H?67cH@x)+pFa6>^;h2t#0dG9iV;hhb7FWZ zX0ihBHSjHJKhde_H|etDrHmyvgprepare('INSERT INTO tra...') +#1 {main} + thrown in /var/www/html/cron.php on line 41 +Starte CoreCashbook Cronjob... +Cronjob beendet. Es wurden 0 neue Buchungen erzeugt. diff --git a/data/php_errors.log b/data/php_errors.log new file mode 100644 index 0000000..3b38c5e --- /dev/null +++ b/data/php_errors.log @@ -0,0 +1,8 @@ +[08-Sep-2026 14:04:36 UTC] Exception: Call to undefined function handleGetDashboard() in /var/www/html/router.php on line 23 +[08-Sep-2026 14:04:44 UTC] Exception: Call to undefined function handleGetTransactions() in /var/www/html/router.php on line 24 +[08-Sep-2026 14:59:28 UTC] Exception: Call to undefined function handleGetDashboard() in /var/www/html/router.php on line 23 +[08-Sep-2026 14:59:30 UTC] Exception: Call to undefined function handleGetTransactions() in /var/www/html/router.php on line 24 +[08-Sep-2026 15:46:39 UTC] Exception: Call to undefined function handleGetDashboard() in /var/www/html/router.php on line 23 +[08-Sep-2026 15:46:43 UTC] Exception: Call to undefined function handleGetTransactions() in /var/www/html/router.php on line 24 +[08-Sep-2026 15:48:20 UTC] Exception: Call to undefined function handleCreateTransaction() in /var/www/html/router.php on line 25 +[08-Sep-2026 15:48:33 UTC] Exception: Call to undefined function handleCreateTransaction() in /var/www/html/router.php on line 25 diff --git a/data/receipts/rechnung_6aa030a5ae2c3.pdf b/data/receipts/rechnung_6aa030a5ae2c3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7778ec2994bba7238dd151086cfcaba7f86e5327 GIT binary patch literal 89415 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 + + + + + +

+ + + + +
+

CoreCashbook

+
Login fehlgeschlagen.
+
+
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/src/auth.php b/src/auth.php new file mode 100755 index 0000000..50ff48c --- /dev/null +++ b/src/auth.php @@ -0,0 +1,147 @@ +prepare("SELECT * FROM users WHERE username = ?"); + $stmt->execute([$username]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password_hash'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$user['id'], $user['username'], 'User Login', $_SERVER['REMOTE_ADDR']]); + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false]); + } + exit; +} + +function handleLogout() { + global $pdo; @session_start(); + if (isset($_SESSION['user_id'])) { + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$_SESSION['user_id'], $_SESSION['username'], 'User Logout', 'System']); + } + session_destroy(); + echo json_encode(['success' => true]); + exit; +} + +function handleGetLogs() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + $stmt = $pdo->query("SELECT * FROM audit_logs ORDER BY id DESC LIMIT 50"); + echo json_encode(['success' => true, 'data' => $stmt->fetchAll()]); exit; +} + +function handleGetSettings() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + $stmt = $pdo->query("SELECT * FROM settings ORDER BY config_key ASC"); + echo json_encode(['success' => true, 'data' => $stmt->fetchAll()]); exit; +} + +function handleSaveSettings() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + + $key = trim($_POST['config_key'] ?? ''); + $value = trim($_POST['config_value'] ?? ''); + if ($key === '') { echo json_encode(['success' => false, 'error' => 'Schlüssel darf nicht leer sein.']); exit; } + + $pdo->prepare("REPLACE INTO settings (config_key, config_value) VALUES (?, ?)")->execute([$key, $value]); + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$_SESSION['user_id'], $_SESSION['username'], 'Setting Update', "Key: $key"]); + + echo json_encode(['success' => true]); exit; +} + +function handleDeleteSetting() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + $key = trim($_POST['config_key'] ?? ''); + $pdo->prepare("DELETE FROM settings WHERE config_key = ?")->execute([$key]); + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$_SESSION['user_id'], $_SESSION['username'], 'Setting gelöscht', "Key: $key"]); + echo json_encode(['success' => true]); exit; +} + +function handleChangePassword() { + global $pdo; @session_start(); + $userId = $_SESSION['user_id'] ?? 0; + if (!$userId) { http_response_code(401); exit; } + + $oldPass = $_POST['old_password'] ?? ''; + $newPass = $_POST['new_password'] ?? ''; + + $stmt = $pdo->prepare("SELECT password_hash FROM users WHERE id = ?"); + $stmt->execute([$userId]); + $user = $stmt->fetch(); + + if ($user && password_verify($oldPass, $user['password_hash'])) { + if (strlen($newPass) < 4) { echo json_encode(['success' => false, 'error' => 'Min 4 Zeichen.']); exit; } + $newHash = password_hash($newPass, PASSWORD_DEFAULT); + + $pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")->execute([$newHash, $userId]); + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$userId, $_SESSION['username'], 'Passwort geändert', 'Self-Service']); + + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Altes Passwort falsch.']); + } + exit; +} + +function handleGetUsers() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + $stmt = $pdo->query("SELECT id, username, role FROM users ORDER BY id ASC"); + echo json_encode(['success' => true, 'data' => $stmt->fetchAll()]); exit; +} + +function handleCreateUser() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + + $username = trim($_POST['username'] ?? ''); + $password = $_POST['password'] ?? ''; + $role = $_POST['role'] === 'admin' ? 'admin' : 'user'; + + try { + $hash = password_hash($password, PASSWORD_DEFAULT); + $pdo->prepare("INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)")->execute([$username, $hash, $role]); + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$_SESSION['user_id'], $_SESSION['username'], 'User erstellt', "User: $username"]); + echo json_encode(['success' => true]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Fehler (evtl. existiert der Name schon).']); + } + exit; +} + +function handleDeleteUser() { + global $pdo; @session_start(); + if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; } + + $deleteId = $_POST['id'] ?? 0; + + // Selbstschutz: Man darf sich nicht selbst löschen! + if ($deleteId == $_SESSION['user_id']) { + echo json_encode(['success' => false, 'error' => 'Du kannst dich nicht selbst löschen.']); + exit; + } + + $pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$deleteId]); + $pdo->prepare("INSERT INTO audit_logs (user_id, username, action, target) VALUES (?, ?, ?, ?)") + ->execute([$_SESSION['user_id'], $_SESSION['username'], 'User gelöscht', "User ID: $deleteId"]); + + echo json_encode(['success' => true]); exit; +} \ No newline at end of file diff --git a/src/cashbook.php b/src/cashbook.php new file mode 100644 index 0000000..9349eeb --- /dev/null +++ b/src/cashbook.php @@ -0,0 +1,256 @@ +query("SELECT DISTINCT category FROM transactions WHERE category IS NOT NULL AND category != '' ORDER BY category ASC"); + $categories = $stmt->fetchAll(PDO::FETCH_COLUMN); + echo json_encode(['success' => true, 'data' => $categories]); exit; +} + +function handleGetDashboard() { + global $pdo; isAuthenticated(); + $category = $_GET['category'] ?? ''; + + // Nur BEZAHLTE Buchungen fließen ins Dashboard ein + if ($category !== '') { + $stmtBal = $pdo->prepare("SELECT SUM(CASE WHEN type = 'income' THEN amount ELSE -amount END) FROM transactions WHERE category = ? AND is_paid = 1"); + $stmtBal->execute([$category]); + $balance = $stmtBal->fetchColumn() ?: 0.00; + + $stmtChart = $pdo->prepare("SELECT strftime('%Y-%m', date) as month, type, SUM(amount) as total FROM transactions WHERE category = ? AND is_paid = 1 GROUP BY month, type ORDER BY month ASC"); + $stmtChart->execute([$category]); + $chartRaw = $stmtChart->fetchAll(); + } else { + $stmtBal = $pdo->query("SELECT SUM(CASE WHEN type = 'income' THEN amount ELSE -amount END) FROM transactions WHERE is_paid = 1"); + $balance = $stmtBal->fetchColumn() ?: 0.00; + + $stmtChart = $pdo->query("SELECT strftime('%Y-%m', date) as month, type, SUM(amount) as total FROM transactions WHERE is_paid = 1 GROUP BY month, type ORDER BY month ASC"); + $chartRaw = $stmtChart->fetchAll(); + } + + $chartData = []; + foreach ($chartRaw as $row) { + $m = $row['month']; + if (!isset($chartData[$m])) $chartData[$m] = ['income' => 0, 'expense' => 0]; + $chartData[$m][$row['type']] = (float)$row['total']; + } + + echo json_encode(['success' => true, 'balance' => $balance, 'chart' => $chartData]); exit; +} + +function handleGetTransactions() { + global $pdo; isAuthenticated(); + $category = $_GET['category'] ?? ''; + + if ($category !== '') { + $stmt = $pdo->prepare("SELECT * FROM transactions WHERE category = ? ORDER BY date DESC, id DESC"); + $stmt->execute([$category]); + } else { + $stmt = $pdo->query("SELECT * FROM transactions ORDER BY date DESC, id DESC"); + } + + echo json_encode(['success' => true, 'data' => $stmt->fetchAll()]); exit; +} + +function handleCreateTransaction() { + global $pdo; isAuthenticated(); + + $title = trim($_POST['title'] ?? ''); + $company = trim($_POST['company'] ?? ''); + $description = trim($_POST['description'] ?? ''); + $amount = (float)str_replace(',', '.', $_POST['amount'] ?? 0); + $type = $_POST['type'] ?? 'expense'; + $date = $_POST['date'] ?? date('Y-m-d'); + $category = trim($_POST['category'] ?? ''); + $is_recurring = (int)($_POST['is_recurring'] ?? 0); + $frequency = $_POST['frequency'] ?? ''; + $is_paid = (int)($_POST['is_paid'] ?? 1); // NEU + + if ($title === '' || $amount <= 0) { echo json_encode(['success' => false, 'error' => 'Titel und Betrag erforderlich.']); exit; } + + $receiptPath = null; + if (isset($_FILES['receipt']) && $_FILES['receipt']['error'] === UPLOAD_ERR_OK) { + if ($_FILES['receipt']['type'] === 'application/pdf') { + $uploadDir = '/var/www/data/receipts/'; + if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true); + $filename = uniqid('rechnung_') . '.pdf'; + if (move_uploaded_file($_FILES['receipt']['tmp_name'], $uploadDir . $filename)) { + $receiptPath = $filename; + } + } else { + echo json_encode(['success' => false, 'error' => 'Nur PDF-Dateien sind erlaubt.']); exit; + } + } + + $pdo->prepare("INSERT INTO transactions (title, amount, type, date, category, is_recurring, frequency, receipt_path, company, description, is_paid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + ->execute([$title, $amount, $type, $date, $category, $is_recurring, $frequency, $receiptPath, $company, $description, $is_paid]); + + echo json_encode(['success' => true]); exit; +} + +function handleEditTransaction() { + global $pdo; isAuthenticated(); + + $id = (int)($_POST['id'] ?? 0); + $title = trim($_POST['title'] ?? ''); + $company = trim($_POST['company'] ?? ''); + $description = trim($_POST['description'] ?? ''); + $amount = (float)str_replace(',', '.', $_POST['amount'] ?? 0); + $type = $_POST['type'] ?? 'expense'; + $date = $_POST['date'] ?? date('Y-m-d'); + $category = trim($_POST['category'] ?? ''); + $is_recurring = (int)($_POST['is_recurring'] ?? 0); + $frequency = $_POST['frequency'] ?? ''; + $is_paid = (int)($_POST['is_paid'] ?? 1); // NEU + $removeReceipt = (int)($_POST['remove_receipt'] ?? 0); + + if ($id <= 0 || $title === '' || $amount <= 0) { echo json_encode(['success' => false, 'error' => 'Ungültige Daten.']); exit; } + + $stmt = $pdo->prepare("SELECT receipt_path FROM transactions WHERE id = ?"); + $stmt->execute([$id]); + $tx = $stmt->fetch(); + if (!$tx) { echo json_encode(['success' => false, 'error' => 'Buchung nicht gefunden.']); exit; } + + $receiptPath = $tx['receipt_path']; + + if ($removeReceipt === 1 && !empty($receiptPath)) { + $file = '/var/www/data/receipts/' . $receiptPath; + if (file_exists($file)) unlink($file); + $receiptPath = null; + } + + if (isset($_FILES['receipt']) && $_FILES['receipt']['error'] === UPLOAD_ERR_OK) { + if ($_FILES['receipt']['type'] === 'application/pdf') { + if (!empty($receiptPath)) { + $oldFile = '/var/www/data/receipts/' . $receiptPath; + if (file_exists($oldFile)) unlink($oldFile); + } + $uploadDir = '/var/www/data/receipts/'; + if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true); + $filename = uniqid('rechnung_') . '.pdf'; + if (move_uploaded_file($_FILES['receipt']['tmp_name'], $uploadDir . $filename)) { + $receiptPath = $filename; + } + } else { + echo json_encode(['success' => false, 'error' => 'Nur PDF-Dateien sind erlaubt.']); exit; + } + } + + $pdo->prepare("UPDATE transactions SET title=?, amount=?, type=?, date=?, category=?, is_recurring=?, frequency=?, receipt_path=?, company=?, description=?, is_paid=? WHERE id=?") + ->execute([$title, $amount, $type, $date, $category, $is_recurring, $frequency, $receiptPath, $company, $description, $is_paid, $id]); + + echo json_encode(['success' => true]); exit; +} + +// NEU: Toggle Status +function handleTogglePaidTransaction() { + global $pdo; isAuthenticated(); + $id = (int)($_POST['id'] ?? 0); + $status = (int)($_POST['status'] ?? 0); + $pdo->prepare("UPDATE transactions SET is_paid = ? WHERE id = ?")->execute([$status, $id]); + echo json_encode(['success' => true]); exit; +} + +function handleGetReceipt() { + global $pdo; isAuthenticated(); + $id = $_GET['id'] ?? 0; + + $stmt = $pdo->prepare("SELECT receipt_path FROM transactions WHERE id = ?"); + $stmt->execute([$id]); + $tx = $stmt->fetch(); + + if ($tx && !empty($tx['receipt_path'])) { + $file = '/var/www/data/receipts/' . $tx['receipt_path']; + if (file_exists($file)) { + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $tx['receipt_path'] . '"'); + readfile($file); + exit; + } + } + http_response_code(404); echo "Rechnung nicht gefunden."; exit; +} + +function handleDeleteTransaction() { + global $pdo; isAuthenticated(); + $id = $_POST['id'] ?? 0; + + $stmt = $pdo->prepare("SELECT receipt_path FROM transactions WHERE id = ?"); + $stmt->execute([$id]); + $tx = $stmt->fetch(); + if ($tx && !empty($tx['receipt_path'])) { + $file = '/var/www/data/receipts/' . $tx['receipt_path']; + if (file_exists($file)) unlink($file); + } + + $pdo->prepare("DELETE FROM transactions WHERE id = ?")->execute([$id]); + echo json_encode(['success' => true]); exit; +} + +function handleGetLogbook() { + global $pdo; isAuthenticated(); + $stmt = $pdo->query("SELECT * FROM logbook ORDER BY date DESC, id DESC"); + echo json_encode(['success' => true, 'data' => $stmt->fetchAll()]); exit; +} + +function handleCreateLogbook() { + global $pdo; isAuthenticated(); + + $date = $_POST['date'] ?? date('Y-m-d'); + $start = trim($_POST['start_location'] ?? ''); + $end = trim($_POST['end_location'] ?? ''); + $km = (float)str_replace(',', '.', $_POST['distance_km'] ?? 0); + $purpose = trim($_POST['purpose'] ?? ''); + + if ($start === '' || $end === '' || $km <= 0) { echo json_encode(['success' => false, 'error' => 'Alle Felder müssen ausgefüllt sein.']); exit; } + + $pdo->prepare("INSERT INTO logbook (date, start_location, end_location, distance_km, purpose) VALUES (?, ?, ?, ?, ?)") + ->execute([$date, $start, $end, $km, $purpose]); + + echo json_encode(['success' => true]); exit; +} + +function handleDeleteLogbook() { + global $pdo; isAuthenticated(); + $id = $_POST['id'] ?? 0; + $pdo->prepare("DELETE FROM logbook WHERE id = ?")->execute([$id]); + echo json_encode(['success' => true]); exit; +} + +function handleExportTransactions() { + global $pdo; isAuthenticated(); + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=CoreCashbook_Transaktionen_' . date('Y-m-d') . '.csv'); + $output = fopen('php://output', 'w'); + fputcsv($output, ['ID', 'Datum', 'Typ', 'Titel', 'Firma', 'Beschreibung', 'Kategorie', 'Betrag (EUR)', 'Status', 'Wiederkehrend', 'Intervall'], ';'); + $stmt = $pdo->query("SELECT id, date, type, title, company, description, category, amount, is_paid, is_recurring, frequency FROM transactions ORDER BY date DESC"); + while ($row = $stmt->fetch()) { + $row['amount'] = number_format($row['amount'], 2, ',', ''); + $row['type'] = $row['type'] === 'income' ? 'Einnahme' : 'Ausgabe'; + $row['is_paid'] = $row['is_paid'] ? 'Bezahlt' : 'Offen'; + $row['is_recurring'] = $row['is_recurring'] ? 'Ja' : 'Nein'; + fputcsv($output, $row, ';'); + } + fclose($output); exit; +} + +function handleExportLogbook() { + global $pdo; isAuthenticated(); + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=CoreCashbook_Fahrtenbuch_' . date('Y-m-d') . '.csv'); + $output = fopen('php://output', 'w'); + fputcsv($output, ['ID', 'Datum', 'Start', 'Ziel', 'Distanz (km)', 'Zweck'], ';'); + $stmt = $pdo->query("SELECT id, date, start_location, end_location, distance_km, purpose FROM logbook ORDER BY date DESC"); + while ($row = $stmt->fetch()) { + $row['distance_km'] = number_format($row['distance_km'], 1, ',', ''); + fputcsv($output, $row, ';'); + } + fclose($output); exit; +} +?> \ No newline at end of file diff --git a/src/cron.php b/src/cron.php new file mode 100644 index 0000000..91a2280 --- /dev/null +++ b/src/cron.php @@ -0,0 +1,72 @@ +query(" + SELECT t1.* FROM transactions t1 + JOIN (SELECT MAX(id) as max_id FROM transactions WHERE is_recurring = 1 GROUP BY title) t2 + ON t1.id = t2.max_id +"); +$templates = $stmt->fetchAll(); +$count = 0; + +foreach ($templates as $tpl) { + $tplTime = strtotime($tpl['date']); + $tplDay = date('d', $tplTime); + $tplMonth = date('m', $tplTime); + + $y = $currentYear; + $m = $currentMonth; + if ($tpl['frequency'] === 'yearly') { + $m = $tplMonth; + } + + // Tag-Korrektur (z.B. den 31. gibt es im Februar nicht) + $d = $tplDay; + if (!checkdate($m, $d, $y)) { + $d = date('t', strtotime("$y-$m-01")); + } + + $targetDate = sprintf('%04d-%02d-%02d', $y, $m, $d); + + // 1. Prüfen: Ist das Zieldatum heute oder vergangen? + if (strtotime($currentDateStr) < strtotime($targetDate)) { + continue; + } + + // 2. Prüfen: Gibt es diesen Monat schon EINE Buchung mit diesem Titel? + $targetYearMonth = sprintf('%04d-%02d', $y, $m); + $likeStr = $targetYearMonth . '-%'; + $checkStmt = $pdo->prepare("SELECT COUNT(*) FROM transactions WHERE title = ? AND date LIKE ?"); + $checkStmt->execute([$tpl['title'], $likeStr]); + $exists = $checkStmt->fetchColumn(); + + if ($exists == 0) { + // Neu anlegen! Wiederkehrend bleibt 1 (wie gewünscht), Status auf 0 (Offen) + $insert = $pdo->prepare("INSERT INTO transactions (title, company, description, amount, type, date, category, is_recurring, frequency, is_paid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)"); + $insert->execute([ + $tpl['title'], + $tpl['company'], + $tpl['description'], + $tpl['amount'], + $tpl['type'], + $targetDate, + $tpl['category'], + $tpl['is_recurring'], + $tpl['frequency'] + ]); + + echo "- Gebucht: " . $tpl['title'] . " für den $targetDate (" . $tpl['amount'] . " EUR)\n"; + $count++; + } +} +echo "Cronjob beendet. Es wurden $count neue Buchungen erzeugt.\n"; +?> \ No newline at end of file diff --git a/src/database.php b/src/database.php new file mode 100644 index 0000000..1e6477b --- /dev/null +++ b/src/database.php @@ -0,0 +1,52 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + + $pdo->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT DEFAULT 'user')"); + $pdo->exec("CREATE TABLE IF NOT EXISTS audit_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, username TEXT, action TEXT, target TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS settings (config_key TEXT PRIMARY KEY, config_value TEXT NOT NULL)"); + + $pdo->exec("CREATE TABLE IF NOT EXISTS transactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + title TEXT NOT NULL, + amount REAL NOT NULL, + date DATE NOT NULL, + category TEXT, + is_recurring INTEGER DEFAULT 0, + frequency TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )"); + + // Dynamische Spalten + try { $pdo->exec("ALTER TABLE transactions ADD COLUMN receipt_path TEXT"); } catch (PDOException $e) {} + try { $pdo->exec("ALTER TABLE transactions ADD COLUMN company TEXT"); } catch (PDOException $e) {} + try { $pdo->exec("ALTER TABLE transactions ADD COLUMN description TEXT"); } catch (PDOException $e) {} + try { $pdo->exec("ALTER TABLE transactions ADD COLUMN is_paid INTEGER DEFAULT 1"); } catch (PDOException $e) {} // NEU + + $pdo->exec("CREATE TABLE IF NOT EXISTS logbook ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date DATE NOT NULL, + start_location TEXT NOT NULL, + end_location TEXT NOT NULL, + distance_km REAL NOT NULL, + purpose TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )"); + + $stmt = $pdo->query("SELECT COUNT(*) FROM users"); + if ($stmt->fetchColumn() == 0) { + $hash = password_hash('admin', PASSWORD_DEFAULT); + $insert = $pdo->prepare("INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)"); + $insert->execute(['admin', $hash, 'admin']); + } + +} catch (PDOException $e) { + die(json_encode(['success' => false, 'error' => 'Datenbankfehler: ' . $e->getMessage()])); +} +?> \ No newline at end of file diff --git a/src/index.php b/src/index.php new file mode 100644 index 0000000..ec4cace --- /dev/null +++ b/src/index.php @@ -0,0 +1,33 @@ +getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); + $request = $_SERVER['REQUEST_URI'] ?? ''; + if (strpos($request, '/api/') === 0) { + http_response_code(500); + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'error' => 'Interner Systemfehler. Bitte Logs prüfen.']); + } else { + http_response_code(500); + echo "

500 - Systemfehler

"; + } + exit; +}); + +header('Content-Type: application/json'); +require_once 'database.php'; +require_once 'auth.php'; +require_once 'cashbook.php'; // <--- HIER IST DER FIX +require_once 'router.php'; +?> \ No newline at end of file diff --git a/src/router.php b/src/router.php new file mode 100644 index 0000000..11a7337 --- /dev/null +++ b/src/router.php @@ -0,0 +1,45 @@ + true, 'status' => 'online', 'system' => 'CoreCashbook']); +} elseif ($path === '/api/logs' && $method === 'GET') { handleGetLogs(); +} elseif ($path === '/api/settings' && $method === 'GET') { handleGetSettings(); +} elseif ($path === '/api/settings' && $method === 'POST') { handleSaveSettings(); +} elseif ($path === '/api/settings/delete' && $method === 'POST') { handleDeleteSetting(); +} elseif ($path === '/api/password' && $method === 'POST') { handleChangePassword(); +} elseif ($path === '/api/users' && $method === 'GET') { handleGetUsers(); +} elseif ($path === '/api/users' && $method === 'POST') { handleCreateUser(); +} elseif ($path === '/api/users/delete' && $method === 'POST') { handleDeleteUser(); +// --- CORECASHBOOK ROUTEN --- +} elseif ($path === '/api/cashbook/dashboard' && $method === 'GET') { handleGetDashboard(); +} elseif ($path === '/api/cashbook/categories' && $method === 'GET') { handleGetCategories(); +} elseif ($path === '/api/cashbook/transactions' && $method === 'GET') { handleGetTransactions(); +} elseif ($path === '/api/cashbook/transactions' && $method === 'POST') { handleCreateTransaction(); +} elseif ($path === '/api/cashbook/transactions/edit' && $method === 'POST') { handleEditTransaction(); +} elseif ($path === '/api/cashbook/transactions/toggle-paid' && $method === 'POST') { handleTogglePaidTransaction(); // NEU +} elseif ($path === '/api/cashbook/transactions/delete' && $method === 'POST') { handleDeleteTransaction(); +} elseif ($path === '/api/cashbook/receipt' && $method === 'GET') { handleGetReceipt(); +} elseif ($path === '/api/cashbook/logbook' && $method === 'GET') { handleGetLogbook(); +} elseif ($path === '/api/cashbook/logbook' && $method === 'POST') { handleCreateLogbook(); +} elseif ($path === '/api/cashbook/logbook/delete' && $method === 'POST') { handleDeleteLogbook(); +} elseif ($path === '/api/cashbook/export/transactions' && $method === 'GET') { handleExportTransactions(); +} elseif ($path === '/api/cashbook/export/logbook' && $method === 'GET') { handleExportLogbook(); +} elseif ($path === '/api/me' && $method === 'GET') { + @session_start(); + if (isset($_SESSION['user_id'])) { echo json_encode(['success' => true, 'username' => $_SESSION['username']]); } + else { http_response_code(401); echo json_encode(['success' => false, 'error' => 'Not authenticated']); } + exit; +} else { + http_response_code(404); + echo json_encode(['success' => false, 'error' => 'Endpoint not found']); +} +?> \ No newline at end of file