Snapshot of the working tree exactly as it stood, no edits. This is the predecessor
PRICEGOD was rewritten from ("kept intact, untouched" per pricegod/README.md), and it
is still the only place the DYMO scale is actually implemented —
rfid-daemon/index.js:1068-1265: HID discovery, parseScaleReport, one-shot read, and a
streaming /weight + /scale/start|stop session whose JSON shape PRICEGOD's daemon.js
already speaks.
Preserved verbatim on purpose (hence -og), including the known bug: DYMO_PIDS at
index.js:1082 is [0x8003, 0x8004], so it cannot see the bench M25 (0x8009). Fix that
in whatever daemon inherits the scale, not here.
node_modules stays ignored (22M of the 24M tree). No credentials in the import: the
two PEM markers in utils.js/sheets.js only strip headers off a key read from settings,
and mrpadmin / johnking are an SSH and a Postgres username, both key/trust auth.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
168 lines
5.9 KiB
Markdown
168 lines
5.9 KiB
Markdown
# Inventory SKU Lookup — Design Spec
|
|
**Date:** 2026-04-12
|
|
|
|
## Overview
|
|
|
|
When the popup opens on a Discogs release page and the feature is enabled, it SSHes (via Tailscale) to the inventory MariaDB, looks up SKUs for the current `release_id`, and auto-fills the SKU custom collection field(s) on the Discogs page. If there is a row/box count mismatch, a picker UI lets the user manually assign each SKU to the correct collection box.
|
|
|
|
---
|
|
|
|
## Database Schema
|
|
|
|
Table: `wp_rmp_disc_inventory` (on MariaDB at `100.123.123.64`)
|
|
|
|
| Column | Type | Notes |
|
|
|---|---|---|
|
|
| `sku` | varchar(255) PK | Timestamp-format integer e.g. `20250515100402` — lower = older |
|
|
| `release_id` | int(11) INDEX | Discogs release ID |
|
|
| `media_condition` | text nullable | |
|
|
| `sleeve_condition` | text nullable | |
|
|
| `price` | decimal(10,2) | May drift vs Discogs — display only, never written to Discogs |
|
|
|
|
Query:
|
|
```sql
|
|
SELECT sku, price, media_condition, sleeve_condition
|
|
FROM wp_rmp_disc_inventory
|
|
WHERE release_id = ?
|
|
ORDER BY sku ASC
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Discogs page (content.js)
|
|
↕ sendMessage
|
|
Popup (popup.js)
|
|
↕ fetch localhost:7790
|
|
rfid-daemon (index.js) ──ssh2──▶ mrpadmin@100.123.123.64
|
|
──mysql2─▶ MariaDB 127.0.0.1:3306 (via tunnel)
|
|
```
|
|
|
|
The existing `updateDiscogsSku` message (`chrome.tabs.sendMessage`) already handles writing a SKU value to a specific collection box index — no changes needed to `content.js`.
|
|
|
|
`state.collectionBoxCount` and `state.collectionBoxSkus` are already tracked in popup.js and populated when the popup loads on a Discogs release page.
|
|
|
|
**Distinct from existing MONSTERWIKI inventory import (`handleImportInventory` / INV button):** that feature talks to the Python server on `100.91.239.7:5002` (PostgreSQL, different dataset) and is triggered manually. This new feature is auto-triggered on popup open, talks to the WordPress MariaDB at `100.123.123.64`, and only writes SKU (not price).
|
|
|
|
---
|
|
|
|
## 1. Settings UI (Connect section, popup.html)
|
|
|
|
Add a new collapsible sub-section "Inventory SKU Lookup" inside the existing Connect settings block.
|
|
|
|
**Fields:**
|
|
|
|
| Field | Element ID | Default |
|
|
|---|---|---|
|
|
| Enable toggle | `inventorySkuEnabled` (checkbox) | unchecked |
|
|
| DB Host | `inventory-db-host` | `100.123.123.64` |
|
|
| DB Name | `inventory-db-name` | `wp_rmp_disc_` |
|
|
| DB User | `inventory-db-user` | *(empty)* |
|
|
| DB Password | `inventory-db-pass` (type=password) | *(empty)* |
|
|
| SSH Key Path | `inventory-ssh-key` | `~/.ssh/id_rsa` |
|
|
|
|
Saved and loaded by the existing "Save Connect Settings" button alongside Discogs token, Google settings, etc. Values stored in `chrome.storage.local` under key `inventorySettings`.
|
|
|
|
---
|
|
|
|
## 2. rfid-daemon: `/inventory-lookup` endpoint
|
|
|
|
### New npm dependencies
|
|
- `ssh2` — SSH tunnel
|
|
- `mysql2` — MariaDB client (promise API)
|
|
|
|
### Endpoint
|
|
|
|
```
|
|
GET /inventory-lookup?release_id=&dbHost=&dbName=&dbUser=&dbPass=&sshKeyPath=
|
|
```
|
|
|
|
### Behaviour
|
|
|
|
1. Read SSH private key from `sshKeyPath` on disk (resolved from `~` to `os.homedir()`)
|
|
2. Open SSH connection to `mrpadmin@dbHost` using the private key
|
|
3. Forward a random local port → `127.0.0.1:3306` on the remote host via `ssh2` `forwardOut`
|
|
4. Connect `mysql2` to `127.0.0.1:<localPort>` with `dbUser`, `dbPass`, `dbName`
|
|
5. Execute query with `release_id` as the bound parameter
|
|
6. Close tunnel and DB connection
|
|
7. Return:
|
|
```json
|
|
{ "rows": [ { "sku": "20250515100402", "price": "12.00", "media_condition": "VG+", "sleeve_condition": "VG" } ] }
|
|
```
|
|
|
|
Errors return `{ "error": "<message>" }` with HTTP 500.
|
|
|
|
The tunnel is opened fresh per request (no persistent connection) — requests are infrequent (one per popup open).
|
|
|
|
---
|
|
|
|
## 3. Popup logic (popup.js)
|
|
|
|
### Trigger
|
|
Runs automatically when the popup finishes loading on a Discogs release page (`state.discogsTabId` is set and `state.releaseData.id` exists) and `inventorySettings.enabled` is `true`.
|
|
|
|
### Flow
|
|
|
|
```
|
|
load inventorySettings from chrome.storage.local
|
|
if not enabled → stop
|
|
|
|
GET /inventory-lookup?release_id=<id>&...creds...
|
|
if error → show error message in popup, stop
|
|
|
|
count rows = result.rows.length
|
|
count boxes = number of collection boxes on page (state.collectionBoxCount or equivalent)
|
|
|
|
if rows == 0 → silent, stop
|
|
if rows == 1 → writeSkuToBox(rows[0].sku, state.collectionBoxCount - 1) [silent, last box]
|
|
if rows == boxes → for each i: writeSkuToBox(rows[i].sku, i) [silent, earliest SKU → first box]
|
|
else → show mismatch picker UI
|
|
```
|
|
|
|
### writeSkuToBox(sku, boxIndex)
|
|
Calls `chrome.tabs.sendMessage(state.discogsTabId, { action: 'updateDiscogsSku', sku, boxIndex })` — existing content.js handler, no changes needed.
|
|
|
|
---
|
|
|
|
## 4. Mismatch Picker UI
|
|
|
|
Rendered inside the popup when `rows ≠ boxes` (and not the 1-row case).
|
|
|
|
**Layout:**
|
|
|
|
```
|
|
⚠ SKU mismatch: 2 DB rows, 3 collection boxes
|
|
|
|
[ 20250515100402 · $12.00 · VG+ · VG ] ← clickable card
|
|
[ 20260101090000 · $18.50 · NM · NM ] ← clickable card
|
|
|
|
Write selected to box: [1] [2] [3]
|
|
```
|
|
|
|
**Interaction:**
|
|
- Clicking a card selects it (highlighted border/background), deselects others
|
|
- The box number buttons are always visible (one per collection box, labelled 1-based)
|
|
- Clicking a box number button calls `writeSkuToBox(selectedSku, boxIndex)` for the selected card
|
|
- After writing, that card gets a small checkmark indicator; user can continue selecting and writing other rows
|
|
- No card selected + box button click → no-op (button visually disabled until a card is selected)
|
|
|
|
---
|
|
|
|
## 5. Error / Status Display
|
|
|
|
A small status line is added to the Discogs tab in the popup (below existing content) to show:
|
|
- Nothing (default)
|
|
- `"SKU mismatch: N rows, M boxes"` — triggers picker
|
|
- `"Inventory lookup failed: <reason>"` — SSH/DB error
|
|
- `"SKUs filled"` — after successful auto-fill (fades after 3s)
|
|
|
|
---
|
|
|
|
## Out of Scope
|
|
|
|
- Writing price to Discogs (price may drift — display only in picker)
|
|
- Caching DB results between popup opens
|
|
- Handling multiple Discogs tabs simultaneously
|