From 7d268f642567319720a686a5dcd23139df8bac0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Thu, 4 Jun 2026 13:45:48 +0800 Subject: [PATCH] up --- db.go | 96 ++++++++++++++++++++++++++++++++------ db_test.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 213 insertions(+), 17 deletions(-) diff --git a/db.go b/db.go index c4d8d12..5d14548 100644 --- a/db.go +++ b/db.go @@ -399,6 +399,9 @@ func (s *store) UpsertNodeInfo(record map[string]any) error { if err := s.upsertNodeInfoRecord(node); err != nil { return fmt.Errorf("upsert nodeinfo %s: %w", node.NodeID, err) } + if err := s.updateMapReportFromNodeInfo(node); err != nil { + return fmt.Errorf("update map_report from nodeinfo %s: %w", node.NodeID, err) + } return nil } @@ -410,6 +413,9 @@ func (s *store) UpsertMapReport(record map[string]any) error { if err := s.upsertMapReportRecord(report); err != nil { return fmt.Errorf("upsert map_report %s: %w", report.NodeID, err) } + if err := s.updateNodeInfoFromMapReport(report); err != nil { + return fmt.Errorf("update nodeinfo from map_report %s: %w", report.NodeID, err) + } return nil } @@ -432,21 +438,25 @@ func (s *store) upsertNodeInfoRecord(node *nodeInfoRecord) error { func (s *store) upsertMapReportRecord(report *mapReportRecord) error { return s.db.Transaction(func(tx *gorm.DB) error { - var existing mapReportRecord - err := tx.Where("node_id = ?", report.NodeID).Take(&existing).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - if err := tx.Create(report).Error; err != nil { - return s.updateMapReportRecord(tx, report) - } - return nil - } - if err != nil { - return err - } - return s.updateMapReportRecord(tx, report) + return s.upsertMapReportRecordTx(tx, report) }) } +func (s *store) upsertMapReportRecordTx(tx *gorm.DB, report *mapReportRecord) error { + var existing mapReportRecord + err := tx.Where("node_id = ?", report.NodeID).Take(&existing).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + if err := tx.Create(report).Error; err != nil { + return s.updateMapReportRecord(tx, report) + } + return nil + } + if err != nil { + return err + } + return s.updateMapReportRecord(tx, report) +} + func (s *store) updateNodeInfoRecord(tx *gorm.DB, node *nodeInfoRecord) error { updates := nodeInfoUpdates(node) return tx.Model(&nodeInfoRecord{}).Where("node_id = ?", node.NodeID).Updates(updates).Error @@ -457,6 +467,30 @@ func (s *store) updateMapReportRecord(tx *gorm.DB, report *mapReportRecord) erro return tx.Model(&mapReportRecord{}).Where("node_id = ?", report.NodeID).Updates(updates).Error } +func (s *store) updateMapReportFromNodeInfo(node *nodeInfoRecord) error { + updates := map[string]any{ + "node_num": node.NodeNum, + "updated_at": time.Now(), + } + addStringUpdate(updates, "long_name", node.LongName) + addStringUpdate(updates, "short_name", node.ShortName) + addStringUpdate(updates, "hw_model", node.HWModel) + addStringUpdate(updates, "role", node.Role) + return s.db.Model(&mapReportRecord{}).Where("node_id = ?", node.NodeID).Updates(updates).Error +} + +func (s *store) updateNodeInfoFromMapReport(report *mapReportRecord) error { + updates := map[string]any{ + "node_num": report.NodeNum, + "updated_at": time.Now(), + } + addStringUpdate(updates, "long_name", report.LongName) + addStringUpdate(updates, "short_name", report.ShortName) + addStringUpdate(updates, "hw_model", report.HWModel) + addStringUpdate(updates, "role", report.Role) + return s.db.Model(&nodeInfoRecord{}).Where("node_id = ?", report.NodeID).Updates(updates).Error +} + func nodeInfoUpdates(node *nodeInfoRecord) map[string]any { updates := map[string]any{ "node_num": node.NodeNum, @@ -511,10 +545,42 @@ func (s *store) InsertPosition(record map[string]any, clientInfo mqttClientInfo) if err != nil { return err } - if err := s.db.Create(position).Error; err != nil { - return fmt.Errorf("insert position from %s: %w", position.FromID, err) + return s.db.Transaction(func(tx *gorm.DB) error { + if err := tx.Create(position).Error; err != nil { + return fmt.Errorf("insert position from %s: %w", position.FromID, err) + } + if err := s.upsertMapReportFromPosition(tx, position); err != nil { + return fmt.Errorf("upsert map_report from position %s: %w", position.FromID, err) + } + return nil + }) +} + +func (s *store) upsertMapReportFromPosition(tx *gorm.DB, position *positionRecord) error { + report := &mapReportRecord{ + NodeID: position.FromID, + NodeNum: position.FromNum, + Latitude: position.Latitude, + Longitude: position.Longitude, + Altitude: position.Altitude, + PositionPrecision: position.PrecisionBits, + ContentJSON: position.ContentJSON, } - return nil + + var existing mapReportRecord + err := tx.Where("node_id = ?", position.FromID).Take(&existing).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return tx.Create(report).Error + } + if err != nil { + return err + } + updates := map[string]any{"node_num": position.FromNum, "updated_at": time.Now()} + addFloat64Update(updates, "latitude", position.Latitude) + addFloat64Update(updates, "longitude", position.Longitude) + addInt64Update(updates, "altitude", position.Altitude) + addInt64Update(updates, "position_precision", position.PrecisionBits) + return tx.Model(&mapReportRecord{}).Where("node_id = ?", position.FromID).Updates(updates).Error } func (s *store) InsertTelemetry(record map[string]any, clientInfo mqttClientInfo) error { diff --git a/db_test.go b/db_test.go index a81b183..4e3702f 100644 --- a/db_test.go +++ b/db_test.go @@ -121,8 +121,8 @@ func TestNodeInfoAndMapReportAreStoredSeparately(t *testing.T) { if err := rawTestDB(t, st).QueryRow("SELECT long_name, user_id, public_key FROM nodeinfo WHERE node_id = ?", "!12345678").Scan(&nodeLongName, &userID, &publicKey); err != nil { t.Fatal(err) } - if nodeLongName != "node name" || userID != "!12345678" || publicKey != "abcd" { - t.Fatalf("nodeinfo row = %q/%q/%q, want node fields", nodeLongName, userID, publicKey) + if nodeLongName != "map name" || userID != "!12345678" || publicKey != "abcd" { + t.Fatalf("nodeinfo row = %q/%q/%q, want synced map name plus node-only fields", nodeLongName, userID, publicKey) } var mapLongName, firmware string @@ -135,6 +135,89 @@ func TestNodeInfoAndMapReportAreStoredSeparately(t *testing.T) { } } +func TestUpsertNodeInfoUpdatesExistingMapReportFields(t *testing.T) { + st := openTestStore(t) + defer st.Close() + + if err := st.UpsertMapReport(mapReportTestRecord("map name")); err != nil { + t.Fatalf("UpsertMapReport() error = %v", err) + } + node := nodeInfoTestRecord("node name") + node["short_name"] = "nod" + node["hw_model"] = "NODE_HW" + node["role"] = "CLIENT" + if err := st.UpsertNodeInfo(node); err != nil { + t.Fatalf("UpsertNodeInfo() error = %v", err) + } + + var longName, shortName, hwModel, role, firmware string + var latitude float64 + if err := rawTestDB(t, st).QueryRow("SELECT long_name, short_name, hw_model, role, firmware_version, latitude FROM map_report WHERE node_id = ?", "!12345678").Scan(&longName, &shortName, &hwModel, &role, &firmware, &latitude); err != nil { + t.Fatal(err) + } + if longName != "node name" || shortName != "nod" || hwModel != "NODE_HW" || role != "CLIENT" || firmware != "1.2.3" || latitude != 42.5 { + t.Fatalf("map_report row = %q/%q/%q/%q firmware %q lat %v, want node fields plus existing map fields", longName, shortName, hwModel, role, firmware, latitude) + } +} + +func TestUpsertNodeInfoDoesNotCreateMapReport(t *testing.T) { + st := openTestStore(t) + defer st.Close() + + if err := st.UpsertNodeInfo(nodeInfoTestRecord("node name")); err != nil { + t.Fatalf("UpsertNodeInfo() error = %v", err) + } + + var count int + if err := rawTestDB(t, st).QueryRow("SELECT COUNT(*) FROM map_report WHERE node_id = ?", "!12345678").Scan(&count); err != nil { + t.Fatal(err) + } + if count != 0 { + t.Fatalf("map_report count = %d, want 0", count) + } +} + +func TestUpsertMapReportUpdatesExistingNodeInfoFields(t *testing.T) { + st := openTestStore(t) + defer st.Close() + + if err := st.UpsertNodeInfo(nodeInfoTestRecord("node name")); err != nil { + t.Fatalf("UpsertNodeInfo() error = %v", err) + } + report := mapReportTestRecord("map name") + report["short_name"] = "map" + report["hw_model"] = "MAP_HW" + report["role"] = "CLIENT_MUTE" + if err := st.UpsertMapReport(report); err != nil { + t.Fatalf("UpsertMapReport() error = %v", err) + } + + var longName, shortName, hwModel, role, userID, publicKey string + if err := rawTestDB(t, st).QueryRow("SELECT long_name, short_name, hw_model, role, user_id, public_key FROM nodeinfo WHERE node_id = ?", "!12345678").Scan(&longName, &shortName, &hwModel, &role, &userID, &publicKey); err != nil { + t.Fatal(err) + } + if longName != "map name" || shortName != "map" || hwModel != "MAP_HW" || role != "CLIENT_MUTE" || userID != "!12345678" || publicKey != "abcd" { + t.Fatalf("nodeinfo row = %q/%q/%q/%q user %q key %q, want map fields plus existing node-only fields", longName, shortName, hwModel, role, userID, publicKey) + } +} + +func TestUpsertMapReportDoesNotCreateNodeInfo(t *testing.T) { + st := openTestStore(t) + defer st.Close() + + if err := st.UpsertMapReport(mapReportTestRecord("map name")); err != nil { + t.Fatalf("UpsertMapReport() error = %v", err) + } + + var count int + if err := rawTestDB(t, st).QueryRow("SELECT COUNT(*) FROM nodeinfo WHERE node_id = ?", "!12345678").Scan(&count); err != nil { + t.Fatal(err) + } + if count != 0 { + t.Fatalf("nodeinfo count = %d, want 0", count) + } +} + func TestDeleteNodeDeletesNodeInfoAndMapReport(t *testing.T) { st := openTestStore(t) defer st.Close() @@ -556,6 +639,53 @@ func TestInsertPositionAppendsRows(t *testing.T) { } } +func TestInsertPositionCreatesMapReportWhenMissing(t *testing.T) { + st := openTestStore(t) + defer st.Close() + + if err := st.InsertPosition(positionTestRecord(), mqttClientInfo{}); err != nil { + t.Fatalf("InsertPosition() error = %v", err) + } + + var nodeID string + var nodeNum int64 + var latitude, longitude float64 + var altitude, precision int64 + if err := rawTestDB(t, st).QueryRow("SELECT node_id, node_num, latitude, longitude, altitude, position_precision FROM map_report WHERE node_id = ?", "!12345678").Scan(&nodeID, &nodeNum, &latitude, &longitude, &altitude, &precision); err != nil { + t.Fatal(err) + } + if nodeID != "!12345678" || nodeNum != 0x12345678 || latitude != 42.5 || longitude != -83.1 || altitude != 200 || precision != 16 { + t.Fatalf("map_report from position = %q/%d lat %v lon %v alt %v precision %v", nodeID, nodeNum, latitude, longitude, altitude, precision) + } +} + +func TestInsertPositionUpdatesExistingMapReportCoordinates(t *testing.T) { + st := openTestStore(t) + defer st.Close() + + if err := st.UpsertMapReport(mapReportTestRecord("map name")); err != nil { + t.Fatalf("UpsertMapReport() error = %v", err) + } + position := positionTestRecord() + position["latitude"] = 30.25 + position["longitude"] = 120.75 + position["altitude"] = int32(88) + position["precision_bits"] = uint32(10) + if err := st.InsertPosition(position, mqttClientInfo{}); err != nil { + t.Fatalf("InsertPosition() error = %v", err) + } + + var longName string + var latitude, longitude float64 + var altitude, precision int64 + if err := rawTestDB(t, st).QueryRow("SELECT long_name, latitude, longitude, altitude, position_precision FROM map_report WHERE node_id = ?", "!12345678").Scan(&longName, &latitude, &longitude, &altitude, &precision); err != nil { + t.Fatal(err) + } + if longName != "map name" || latitude != 30.25 || longitude != 120.75 || altitude != 88 || precision != 10 { + t.Fatalf("map_report after position = %q lat %v lon %v alt %v precision %v", longName, latitude, longitude, altitude, precision) + } +} + func TestInsertTelemetryAppendsRowsAndStoresMetricsJSON(t *testing.T) { st := openTestStore(t) defer st.Close()