api.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. <?php
  2. if (session_status() === PHP_SESSION_NONE) session_start();
  3. if (empty($_SESSION['pl_authed'])) {
  4. http_response_code(401);
  5. header('Content-Type: application/json; charset=utf-8');
  6. echo json_encode(['error' => 'Unauthorized']);
  7. exit;
  8. }
  9. header('Content-Type: application/json; charset=utf-8');
  10. header('Cache-Control: no-store');
  11. $dbPath = __DIR__ . '/_private/pl.db';
  12. if (!file_exists($dbPath) || filesize($dbPath) === 0) {
  13. echo json_encode(['error' => 'Database not initialized. Please upload a CSV file first.']);
  14. exit;
  15. }
  16. try {
  17. $db = new PDO('sqlite:' . $dbPath);
  18. $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  19. $db->exec('PRAGMA query_only = ON');
  20. } catch (Exception $e) {
  21. echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
  22. exit;
  23. }
  24. $action = $_GET['action'] ?? '';
  25. switch ($action) {
  26. case 'months':
  27. $rows = $db->query(
  28. 'SELECT id, label, year, month_num, sort_order, is_total, is_partial
  29. FROM months ORDER BY sort_order'
  30. )->fetchAll(PDO::FETCH_ASSOC);
  31. foreach ($rows as &$r) {
  32. $r['id'] = (int)$r['id'];
  33. $r['year'] = $r['year'] !== null ? (int)$r['year'] : null;
  34. $r['month_num'] = $r['month_num'] !== null ? (int)$r['month_num'] : null;
  35. $r['sort_order']= (int)$r['sort_order'];
  36. $r['is_total'] = (int)$r['is_total'];
  37. $r['is_partial']= (int)$r['is_partial'];
  38. }
  39. echo json_encode($rows, JSON_UNESCAPED_UNICODE);
  40. break;
  41. case 'data':
  42. $idsParam = $_GET['ids'] ?? '';
  43. $idList = array_unique(array_filter(array_map('intval', explode(',', $idsParam))));
  44. if (empty($idList)) {
  45. echo json_encode(['columns' => [], 'rows' => []]);
  46. break;
  47. }
  48. $ph = implode(',', array_fill(0, count($idList), '?'));
  49. // Columns in chronological order
  50. $colStmt = $db->prepare(
  51. "SELECT id, label, year, month_num, sort_order, is_total, is_partial
  52. FROM months WHERE id IN ($ph) ORDER BY sort_order"
  53. );
  54. $colStmt->execute($idList);
  55. $columns = $colStmt->fetchAll(PDO::FETCH_ASSOC);
  56. foreach ($columns as &$c) {
  57. $c['id'] = (int)$c['id'];
  58. $c['year'] = $c['year'] !== null ? (int)$c['year'] : null;
  59. $c['month_num'] = $c['month_num'] !== null ? (int)$c['month_num'] : null;
  60. $c['is_total'] = (int)$c['is_total'];
  61. $c['is_partial']= (int)$c['is_partial'];
  62. }
  63. // All account rows
  64. $accounts = $db->query(
  65. 'SELECT id, name, row_type, indent_level FROM accounts ORDER BY sort_order'
  66. )->fetchAll(PDO::FETCH_ASSOC);
  67. // Values for the requested months
  68. $valStmt = $db->prepare(
  69. "SELECT account_id, month_id, value FROM pl_values WHERE month_id IN ($ph)"
  70. );
  71. $valStmt->execute($idList);
  72. $values = [];
  73. while ($v = $valStmt->fetch(PDO::FETCH_ASSOC)) {
  74. $values[(int)$v['account_id']][(int)$v['month_id']] = (float)$v['value'];
  75. }
  76. $rows = [];
  77. foreach ($accounts as $a) {
  78. $aid = (int)$a['id'];
  79. $vals = $values[$aid] ?? [];
  80. // Convert integer keys to string keys for clean JSON object
  81. $out = new stdClass();
  82. foreach ($vals as $mid => $val) {
  83. $out->$mid = $val;
  84. }
  85. $rows[] = [
  86. 'id' => $aid,
  87. 'name' => $a['name'],
  88. 'type' => $a['row_type'],
  89. 'indent' => (int)$a['indent_level'],
  90. 'values' => $out,
  91. ];
  92. }
  93. echo json_encode(
  94. ['columns' => $columns, 'rows' => $rows],
  95. JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION
  96. );
  97. break;
  98. default:
  99. http_response_code(400);
  100. echo json_encode(['error' => 'Unknown action']);
  101. }