Restructure the shopping-list feature end to end:
- New public/plugins/shopping/ plugin with backend.php (proxy
routes registered via PluginManager::onApiRoute), shopping.js
(self-contained; injects the header Shopping button, owns the
picker dialog + sync loop via hyprcrd.hooks), plugin.json and a
placeholder css file. Core no longer knows about shopping —
index.php loses the static button and the proxy block, app.js
loses the initShopping wiring, the old js/modules/shopping.js
is gone.
- Phone view moves out of a standalone /shopping page and into
the relay UI as a regular `view-shopping` behind a nav-burger
entry (relay/ui/layout.php + app.js + style.css). Same session
auth as compose/chat/account — no separate login. Auto-refresh
every 4 s while the view is active; no manual refresh button.
Headings "# h1 … ###### h6" render between items for grouping.
Orphan section headings (no open items under them) are hidden.
- Sync: drop timestamp cursors (broke across SQLite-UTC / PHP
timezones and silently blocked pushes after a pull). Both sides
diff the (uid, title, body) triple instead — timezone-agnostic
and authoritative. Client ticks push-before-pull so fresh local
edits land on the relay before a stale pull can overwrite them.
Relay drops its "server newer" guard and stamps server times
with gmdate so anything left comparing to SQLite is UTC.
- Client also ticks on cards.reloaded, not just sync.tick, so
any edit path (editor save, checkbox toggle, context menu,
undo) pushes the update immediately.
- Tests: updated for the plugin (still uses the same selectors
because the plugin injects the same ids), plus a plugin-
registration check and "(no active list)" option assertion.
- Help section copy updated — references the Shopping header
button and the shopping tab in the relay UI rather than the
gone /shopping URL.
Both #einkauf and #shopping work as trigger tags; phone is
check-only, editing stays in hyprcrd. Version bumped to 1.9.21.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
68 lines
2.3 KiB
PHP
68 lines
2.3 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Shopping Plugin: backend.
|
|
*
|
|
* Thin forwarder from the client to the relay's /api/shopping*
|
|
* endpoints. The phone-side view is part of the relay UI; this
|
|
* plugin just pipes hyprcrd's push/pull through the configured
|
|
* relay credentials.
|
|
*
|
|
* Routes:
|
|
* GET /api/shopping — pull active list state from relay
|
|
* POST /api/shopping — push active card {uid,title,body}
|
|
* POST /api/shopping/check — (mostly phone-only; proxied for
|
|
* completeness/tests — relay will
|
|
* 401 for client-auth callers)
|
|
*/
|
|
|
|
function shoppingJson($data, int $code = 200): void {
|
|
http_response_code($code);
|
|
header('Content-Type: application/json');
|
|
echo json_encode($data);
|
|
exit;
|
|
}
|
|
|
|
function shoppingRelay(): array {
|
|
$db = Database::connect();
|
|
return [
|
|
'url' => $db->query("SELECT value FROM settings WHERE key = 'relay_url'")->fetchColumn() ?: '',
|
|
'user' => $db->query("SELECT value FROM settings WHERE key = 'relay_username'")->fetchColumn() ?: '',
|
|
'key' => $db->query("SELECT value FROM settings WHERE key = 'relay_key'")->fetchColumn() ?: '',
|
|
];
|
|
}
|
|
|
|
PluginManager::instance()->onApiRoute(function (string $route, string $method) {
|
|
if ($route !== '/api/shopping' && $route !== '/api/shopping/check') return;
|
|
if (!in_array($method, ['GET', 'POST'], true)) return;
|
|
|
|
$r = shoppingRelay();
|
|
if (!$r['url'] || !$r['user'] || !$r['key']) {
|
|
shoppingJson(['ok' => false, 'error' => 'relay not configured']);
|
|
}
|
|
|
|
$ch = curl_init(rtrim($r['url'], '/') . $route);
|
|
$opts = [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Content-Type: application/json',
|
|
'X-Relay-User: ' . $r['user'],
|
|
'X-Relay-Key: ' . $r['key'],
|
|
],
|
|
CURLOPT_TIMEOUT => 10,
|
|
];
|
|
if ($method === 'POST') {
|
|
$opts[CURLOPT_POST] = true;
|
|
$opts[CURLOPT_POSTFIELDS] = file_get_contents('php://input') ?: '{}';
|
|
}
|
|
curl_setopt_array($ch, $opts);
|
|
$response = curl_exec($ch);
|
|
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
http_response_code($status ?: 502);
|
|
header('Content-Type: application/json');
|
|
echo is_string($response) ? $response : json_encode(['ok' => false, 'error' => 'relay unreachable']);
|
|
exit;
|
|
});
|