Commit Graph

5 Commits

Author SHA1 Message Date
72907dae74 Scan-to-order: label from the customer's camera to "buy it here"
POST /v1/mob/scan/lookup   label + customer → catalogue match, sizes, and
                           every registered store that sells it with live
                           stock, in-stock first / nearest first, one
                           recommended
POST /v1/mob/scan/confirm  chosen store + size + qty → re-read the ledger;
                           ok, or the next-nearest store with enough of the
                           same product
GET  /v1/mob/scan/stores   registered stores nearest first

Recognition is pgvector cosine search over every brand_* table (each
with its own index, merged) plus a word match that settles near-ties
and works alone when no model is configured. The embedder is chosen by
EMBEDDING_PROVIDER (OpenAI-compatible or Gemini) and must be the model
that indexed the catalogue: verified 2026-09-15 as all-MiniLM-L6-v2 over
search_query, served by the cluster's Ollama as `all-minilm`; the first
search refuses a width mismatch by name.

Customer, stores and catalogue are read concurrently under a 5 s cap; a
slow model degrades to a text answer. Vectors and ranked hits are cached
in Redis and in-process; live stock never is. Availability uses the same
rules as the customer catalogue (approve, publishedat, ledger balance,
outlet price else retail). No stock reservation: confirm re-reads.

scratch/cataloguedims reports the catalogue's embedding width and fill.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:04:34 +05:30
4474479735 Load .env.<APP_ENV>, validate config at boot, keep secrets out of the image
`main.go` only ever loaded `.env`; the `APP_ENV` switch described in
`.env.local` / `.env.production` did not exist, and a missing variable
surfaced one restart at a time as a log.Fatalf inside db.Connect.

config.Load now picks `.env.<APP_ENV>` (default local) then `.env`, with
real environment winning, reads every setting into one typed Config and
reports everything missing in one message. Production insists on a POS
signing secret; local warns when DB_HOST is not a local address. db,
redis and the image store take the Config instead of reading env
themselves.

Also:
- livehub read MQTT_USERNAME while everything else uses MQTT_USER, so the
  console stream connected to the broker unauthenticated. Both accepted.
- .dockerignore: `COPY . .` was baking .env.production into the image.
  Dockerfile sets APP_ENV=production.
- Drop utils/config.go (dead viper loader) and create_table.go (unused,
  hardcoded production DSN); go mod tidy removes viper.
- .env.example lists every variable the code reads; docs/ENVIRONMENT.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:04:33 +05:30
edb9c2803f lat and long 2026-08-27 13:59:16 +05:30
Suriya
12165d5e58 Give the POS a real sign-in, and stop believing the store id on the wire
The POS surface was open. A till named its own outlet — `store_id` in a query
or in an ingest batch — and was believed, so one number changed in Settings
read another tenant's catalogue or posted bills into their books. There was no
middleware in the codebase at all, and the `JWT_SECRET_KEY` in the config was
read and never used.

Products were never mis-scoped: `resolvePosStore` already derived the tenant
from the location and the catalogue query already filtered on both. The tenant
was never taken from the wire. What was missing was any check that the caller
was entitled to the location they named.

So the outlet now comes *out* of a sign-in rather than going *in* from the
till. `POST /pos/login` authenticates against the same `app_users` rows the web
console uses — one account store, so deactivating a leaver closes both doors —
and answers with the outlets that account may reach, sealed in an HMAC-SHA256
token the terminal cannot edit.

Two checks then guard everything else, in order: the token verifies, and the
outlet named in the request belongs to the token's tenant. The second is the
one that matters — a valid token is a licence to name *your* outlets, not any.

Notes on the awkward parts:

- The guard reads the outlet from the body as well as the query. The two routes
  that write carry `store_id` in a JSON batch and never in the URL, so a
  query-only check would have left exactly the dangerous call unguarded.
- Three spellings of one thing survive — `store_id`, `locationid`,
  `location_id`. All three are read rather than normalised, because renaming
  them breaks terminals already in the field.
- `POS_AUTH_REQUIRED` defaults to false. Tills are billing real customers
  against the open endpoints right now and enforcing at deploy would stop every
  one mid-trade. A token is still verified when sent, and a wrong-tenant token
  still refused; the flag only governs requests carrying none.
- `POS_TOKEN_SECRET` has no baked-in fallback and fails loudly. A development
  secret in source is the same as no signature at all.
- `configid` is inferred when the till does not send it, because a person at a
  counter has no way to know theirs. `authname` is not unique in this schema —
  live data has one address twice under one configid — so an ambiguous match is
  refused rather than resolved by LIMIT 1, which could bill into the wrong
  tenant's books.

Verified against live data: 58 accounts across 34 tenants can open a till, an
account pinned to a location resolves to it alone, a tenant-level account gets
all six of its outlets, and a cross-tenant outlet request is refused.

Passwords are still plaintext platform-wide. Flagged at the comparison site;
fixing it is a migration touching every login path, not this endpoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:46:38 +05:30
Gokul
0d42ac84e1 initial commit 2026-05-25 11:52:26 +05:30