Files
dobetternorge-tools/api/dashboard/feedback.php
T
daveadmin 519bdbb6e5 feat(tools): owner feedback review surface + tool_feedback migration
Adds the missing migration for the tool_feedback table (dobetternorge_maindb)
that the in-result feedback widget writes to, repoints api/feedback.php to
dbnmDb() for consistency with the engine-config table, and adds an owner-only
dashboard (page + read API + nav) summarising ratings and notes by tool/engine.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 15:19:45 +02:00

99 lines
3.2 KiB
PHP

<?php
/**
* /api/dashboard/feedback.php — owner-only tool-feedback review surface (read).
*
* GET → { ok, totals, by_tool, by_engine, recent }
*
* Reads dobetternorge_maindb.tool_feedback (written by api/feedback.php).
* Owner-gated via dbnToolsIsOwner(). Comments are voluntary, case-content-free.
*/
declare(strict_types=1);
require_once dirname(__DIR__, 2) . '/includes/bootstrap.php';
dbnToolsRequireAuth();
if (!dbnToolsIsOwner()) {
dbnToolsError('Owner access required.', 403, 'forbidden');
}
try {
$db = dbnmDb();
$totalsRow = $db->query(
"SELECT
COUNT(*) AS total,
SUM(rating = 'positive') AS positive,
SUM(rating = 'negative') AS negative
FROM tool_feedback"
)->fetch();
$byTool = $db->query(
"SELECT
tool,
COUNT(*) AS total,
SUM(rating = 'positive') AS positive,
SUM(rating = 'negative') AS negative,
MAX(created_at) AS last_at
FROM tool_feedback
GROUP BY tool
ORDER BY total DESC, tool ASC"
)->fetchAll();
$byEngine = $db->query(
"SELECT
COALESCE(NULLIF(engine, ''), '(unknown)') AS engine,
COUNT(*) AS total,
SUM(rating = 'positive') AS positive,
SUM(rating = 'negative') AS negative
FROM tool_feedback
GROUP BY engine
ORDER BY total DESC, engine ASC"
)->fetchAll();
$recent = $db->query(
"SELECT tool, rating, engine, missed_or_wrong, created_at
FROM tool_feedback
WHERE missed_or_wrong IS NOT NULL AND missed_or_wrong <> ''
ORDER BY created_at DESC
LIMIT 50"
)->fetchAll();
$toInt = static fn($v): int => (int) ($v ?? 0);
dbnToolsRespond([
'ok' => true,
'totals' => [
'total' => $toInt($totalsRow['total'] ?? 0),
'positive' => $toInt($totalsRow['positive'] ?? 0),
'negative' => $toInt($totalsRow['negative'] ?? 0),
],
'by_tool' => array_map(static fn(array $r): array => [
'tool' => $r['tool'],
'total' => $toInt($r['total']),
'positive' => $toInt($r['positive']),
'negative' => $toInt($r['negative']),
'last_at' => $r['last_at'],
], $byTool),
'by_engine' => array_map(static fn(array $r): array => [
'engine' => $r['engine'],
'total' => $toInt($r['total']),
'positive' => $toInt($r['positive']),
'negative' => $toInt($r['negative']),
], $byEngine),
'recent' => array_map(static fn(array $r): array => [
'tool' => $r['tool'],
'rating' => $r['rating'],
'engine' => $r['engine'],
'missed_or_wrong' => $r['missed_or_wrong'],
'created_at' => $r['created_at'],
], $recent),
]);
} catch (DbnToolsHttpException $e) {
dbnToolsError($e->getMessage(), $e->status, $e->errorCode, $e->extra ?? []);
} catch (Throwable $e) {
error_log('[dbn-feedback] ' . $e->getMessage());
dbnToolsError('Feedback review failed.', 500, 'op_failed');
}