- End-to-end system for retail stations that scan barcodes and request product data over MQTT.
- Secure backend updates the product catalog over HTTPS (mTLS) and imports into PostgreSQL.
- Operator CLI converts legacy MDB files to CSV, uploads to the server, and waits for import completion.
Station -> MQTT (TLS) -> mqtt_product_api -> PostgreSQL
^ |
| v
+---- MQTT response <--------------+
CLI -> HTTPS (mTLS) -> http_db_update_server -> PostgreSQL
| ^
+---- job status polling -------+
Core services (compose)
postgres_db: PostgreSQL with TLS and client cert auth (pg_hba.conf).mosquitto_broker: MQTT broker with mTLS client cert auth.mqtt_product_api: subscribes to product requests and publishes replies.http_db_update_server: accepts catalog uploads and runs async import jobs.cli_control_app: operator CLI to upload catalogs and send global station commands.
- Station publishes an empty payload to
product/<station_mac>/<barcode>. mqtt_product_apiconsumes the request, queries PostgreSQL bybarcode, and publishes a response toproduct/<station_mac>.- Stations display product data locally.
Response payload:
{
"name": "...",
"barcode": "...",
"price": "...",
"stock": "...",
"unitOfMeasure": "...",
"unitOfMeasureCoef": "...",
"valid": true
}Notes
- If a product is missing, the response sets
valid=falseand echoes the requestedbarcode. mqtt_product_apinormalizes diacritics onnameandpricebefore responding.- Requests are processed by a worker pool; when the queue is full, new requests are dropped with a warning.
- CLI converts MDB to CSV using
cmd/cli_control_app/mdb_to_csv.sh. - CLI uploads the CSV to
http_db_update_serverover HTTPS. - Server returns
202 Acceptedwith ajob_id. - CLI polls the status endpoint until the job is
completedorfailed. - Server imports into PostgreSQL (drop + create table + COPY stream).
Lookup topic
MQTT_TOPIC_REQUEST(default:product/+/+)- Stations publish lookup requests as
product/<station_mac>/<barcode>.
MQTT commands sent to all stations (CLI)* The CLI publishes directly to the topic configured in env:
| Command | Topic | Payload | Retained |
|---|---|---|---|
sfwe |
MQTT_FIRMWARE_UPDATE_TOPIC (default station/firmware_update) |
start |
false |
sw |
MQTT_STATUS_TOPIC (default station/status) |
wake |
true |
ss |
MQTT_STATUS_TOPIC |
sleep |
true |
QoS is always 1.
Upload endpoint
POST <HTTP_SERVER_UPDATE_ENDPOINT>(default:/update_db)- Accepts
multipart/form-datawith field namefile - Returns
202 Acceptedwith JSON:{ "job_id": "<uuid>", "message": "Upload accepted" }
Status endpoint
GET <HTTP_SERVER_UPDATE_STATUS_ENDPOINT>?id=<job_id>(default:/update_status)- Returns JSON:
{ "state": "pending|processing|completed|failed", "message": "..." }
Important behavior
- Job statuses live in memory (
sync.Map). Ifhttp_db_update_serverrestarts, in-flight job data is lost.
Parser (internal/parser)
- Interface-driven design:
CatalogParserstreams records from a source. - Factory (
parser.New) chooses a parser based on file extension. - Current implementation: CSV parser only. Adding a new parser means extending the factory.
Database (internal/database)
- Interface-driven:
HandlerdefinesFetchandImportCatalog. - Current implementation: PostgreSQL.
- Import behavior: drop existing table, recreate, then COPY stream into the table.
How parser and DB align
mdb_to_csv.shoutputs six columns in this order:Nazev, EAN, ProdejDPH, MJ2, MJ2Koef, StavZinternal/parser/csvmaps them to:Name, Barcode, Price, UnitOfMeasure, UnitOfMeasureCoef, Stockinternal/database/postgreswrites them into the table columns:name, barcode, price, unit_of_measure, unit_of_measure_koef, stock
cmd/cli_control_app/mdb_to_csv.sh
- Uses
mdb-exportto dump theSKztable from an MDB file. - Selects and normalizes required columns in a strict order.
- Outputs a semicolon-delimited CSV compatible with the CSV parser.
- The CLI container installs
mdbtoolsandmdbtools-utilssomdb-exportis available.
- Generates a CA, server certificate, and client certificate.
- Creates Podman secrets for:
server_key(TLS private key)client_key(client TLS private key)db_useranddb_password
- Generates
mosquitto/mosquitto.confwith mTLS client cert auth.
server_key: mounted intopostgres_db,mosquitto_broker, andhttp_db_update_serveras the TLS private key.client_key: mounted intohttp_db_update_server,mqtt_product_api, andcli_control_appfor mTLS client auth.db_user/db_password: used by PostgreSQL and read by services via secret files.
conf_gen.sh requires a server IP argument:
./conf_gen.sh <server_ip>
That IP is baked into the TLS certificate subjectAltName. If it does not match the IP used by clients (MQTT_HOST_IP and HTTP_SERVER_IP), TLS will fail with certificate errors. Update the IP and re-run the script any time the server IP changes.
Example env variable configurations.
GO_IMAGE:docker.io/library/tagALPINE_IMAGE:docker.io/library/tagPOSTGRES_IMAGE:docker.io/library/tagMOSQUITTO_IMAGE:docker.io/tagTARGETARCH:amd64WORKDIR:/app(must match container paths and volume mounts)
POSTGRES_USER_FILE:/run/secrets/db_userPOSTGRES_PASSWORD_FILE:/run/secrets/db_passwordPOSTGRES_DB:dbPOSTGRES_HOST:postgres_db(internal container DNS)POSTGRES_PORT:5432DB_TABLE_NAME:table_nameDB_MAX_OPEN_CONNS:10DB_MAX_IDLE_CONNS:10DB_CONN_MAX_LIFETIME:10m
TLS_CA_PATH:/app/certs/ca.crtTLS_CLIENT_CERT_PATH:/app/certs/client.crtTLS_CLIENT_KEY_PATH:/run/secrets/client_keyTLS_SERVER_CERT_PATH:/app/certs/server.crtTLS_SERVER_KEY_PATH:/run/secrets/server_key
MQTT_PROTOCOL:tcpsMQTT_HOST:mosquitto_broker(internal container DNS)MQTT_PORT:8883MQTT_API_CLIENT_ID:mqtt_database_apiMQTT_TOPIC_REQUEST:product/+/+MQTT_FIRMWARE_UPDATE_TOPIC:station/firmware_updateMQTT_STATUS_TOPIC:station/statusMQTT_CLIAPP_CLIENT_ID:cli_admin_console
HTTP_SERVER_HOST:0.0.0.0(server bind address inside container)HTTP_SERVER_PORT:8443HTTP_SERVER_UPDATE_ENDPOINT:/update_dbHTTP_SERVER_UPDATE_STATUS_ENDPOINT:/update_statusHTTP_MAX_UPLOAD_SIZE:5368709120~5GB
APP_WORKER_COUNT:3(goroutines processing MQTT requests)APP_JOB_QUEUE_SIZE:100(buffer size for incoming MQTT jobs)APP_DB_TIMEOUT:2s(per-request DB timeout)
MQTT_HOST_IP:1.2.2.1(IP for CLI to reach MQTT API)HTTP_SERVER_IP:1.2.2.1(IP for CLI to reach HTTP server)HOST_MDB_DIR:/host/pathCONTAINER_MDB_DIR:/dataMDB_FILENAME:xx.mdbMDB_FILEPATH:/data/xx.mdb(built fromCONTAINER_MDB_DIR+MDB_FILENAME)
Note: any IP fields used by the CLI must match the IP passed to conf_gen.sh.
Run:
./conf_gen.sh <server_ip>
podman compose build
podman compose up -d postgres_db mosquitto_broker http_db_update_server mqtt_product_api
podman compose run --rm cli_control_app
lslist commandsupdconvert MDB to CSV and upload to serversfweenable firmware update modesw/ssmark stations wake/sleep (retained)eexit
- HTTPS is enforced for uploads and status polling; certificates must match the server IP.
- Job status is in-memory; restarting
http_db_update_serverloses job history. - The catalog import replaces the entire products table on each run.
- MQTT requests are processed in a worker pool; when the queue is full, requests are dropped with a warning.