From 3863e2d63d6ab05097f0dc095683dc56278b23da Mon Sep 17 00:00:00 2001 From: l5y <220195275+l5yth@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:45:42 +0200 Subject: [PATCH] Populate chat metadata for unknown nodes (#182) * Populate chat metadata for unknown nodes * run rufo * fix comments * run rufo --- web/app.rb | 17 ++++++++----- web/spec/app_spec.rb | 60 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/web/app.rb b/web/app.rb index 1aa0bbe..1b963e3 100644 --- a/web/app.rb +++ b/web/app.rb @@ -585,7 +585,7 @@ end # @param node_ref [Object] raw identifier extracted from the payload. # @param fallback_num [Object] optional numeric reference used when the # identifier is missing. -def ensure_unknown_node(db, node_ref, fallback_num = nil) +def ensure_unknown_node(db, node_ref, fallback_num = nil, heard_time: nil) parts = canonical_node_parts(node_ref, fallback_num) return unless parts @@ -598,16 +598,21 @@ def ensure_unknown_node(db, node_ref, fallback_num = nil) return if existing long_name = "Meshtastic #{short_id}" + heard_time = coerce_integer(heard_time) + inserted = false with_busy_retry do db.execute( <<~SQL, - INSERT OR IGNORE INTO nodes(node_id,num,short_name,long_name,role) - VALUES (?,?,?,?,?) + INSERT OR IGNORE INTO nodes(node_id,num,short_name,long_name,role,last_heard,first_heard) + VALUES (?,?,?,?,?,?,?) SQL - [node_id, node_num, short_id, long_name, "CLIENT_HIDDEN"], + [node_id, node_num, short_id, long_name, "CLIENT_HIDDEN", heard_time, heard_time], ) + inserted = db.changes.positive? end + + inserted end # Insert or update a node row with the most recent metrics. @@ -849,7 +854,7 @@ def insert_position(db, payload) canonical = normalize_node_id(db, node_id || node_num) node_id = canonical if canonical - ensure_unknown_node(db, node_id || node_num, node_num) + ensure_unknown_node(db, node_id || node_num, node_num, heard_time: rx_time) to_id = string_or_nil(payload["to_id"] || payload["to"]) @@ -1044,7 +1049,7 @@ def insert_message(db, m) encrypted = string_or_nil(m["encrypted"]) - ensure_unknown_node(db, from_id || raw_from_id, m["from_num"]) + ensure_unknown_node(db, from_id || raw_from_id, m["from_num"], heard_time: rx_time) row = [ msg_id, diff --git a/web/spec/app_spec.rb b/web/spec/app_spec.rb index 2eb6d8f..2f89638 100644 --- a/web/spec/app_spec.rb +++ b/web/spec/app_spec.rb @@ -414,6 +414,62 @@ RSpec.describe "Potato Mesh Sinatra app" do end end + describe "#ensure_unknown_node" do + it "creates a hidden placeholder with timestamps for chat notifications" do + with_db do |db| + created = ensure_unknown_node(db, "!1234abcd", nil, heard_time: reference_time.to_i) + expect(created).to be_truthy + end + + with_db(readonly: true) do |db| + db.results_as_hash = true + row = db.get_first_row( + <<~SQL, + SELECT short_name, long_name, role, last_heard, first_heard + FROM nodes + WHERE node_id = ? + SQL + ["!1234abcd"], + ) + + expect(row["short_name"]).to eq("ABCD") + expect(row["long_name"]).to eq("Meshtastic ABCD") + expect(row["role"]).to eq("CLIENT_HIDDEN") + expect(row["last_heard"]).to eq(reference_time.to_i) + expect(row["first_heard"]).to eq(reference_time.to_i) + end + end + + it "leaves timestamps nil when no receive time is provided" do + with_db do |db| + created = ensure_unknown_node(db, "!1111beef", nil) + expect(created).to be_truthy + end + + with_db(readonly: true) do |db| + db.results_as_hash = true + row = db.get_first_row( + <<~SQL, + SELECT last_heard, first_heard + FROM nodes + WHERE node_id = ? + SQL + ["!1111beef"], + ) + + expect(row["last_heard"]).to be_nil + expect(row["first_heard"]).to be_nil + end + end + + it "returns false when the node already exists" do + with_db do |db| + expect(ensure_unknown_node(db, "!0000c0de", nil)).to be_truthy + expect(ensure_unknown_node(db, "!0000c0de", nil)).to be_falsey + end + end + end + describe "POST /api/messages" do it "persists messages from fixture data" do import_nodes_fixture @@ -470,7 +526,7 @@ RSpec.describe "Potato Mesh Sinatra app" do with_db(readonly: true) do |db| db.results_as_hash = true row = db.get_first_row( - "SELECT node_id, num, short_name, long_name, role FROM nodes WHERE node_id = ?", + "SELECT node_id, num, short_name, long_name, role, last_heard, first_heard FROM nodes WHERE node_id = ?", ["!feedf00d"], ) @@ -480,6 +536,8 @@ RSpec.describe "Potato Mesh Sinatra app" do expect(row["short_name"]).to eq("F00D") expect(row["long_name"]).to eq("Meshtastic F00D") expect(row["role"]).to eq("CLIENT_HIDDEN") + expect(row["last_heard"]).to eq(payload["rx_time"]) + expect(row["first_heard"]).to eq(payload["rx_time"]) end end