diff --git a/internal/store/store_query.go b/internal/store/store_query.go index 4f9e3ec..70412cd 100644 --- a/internal/store/store_query.go +++ b/internal/store/store_query.go @@ -232,6 +232,53 @@ func (s *Store) DeleteNode(nodeID string) error { }) } +// PurgeNode 在「删除节点」菜单触发时执行:除了 nodeinfo + map_report, +// 还要把 text_message(频道聊天)以及 position/telemetry/routing/traceroute +// 这些以 from_id 关联的数据包记录一起清理。 +// +// 任一表删到记录就视为成功;全部为空才返回 ErrRecordNotFound。 +func (s *Store) PurgeNode(nodeID string) error { + if nodeID == "" { + return gorm.ErrRecordNotFound + } + return s.db.Transaction(func(tx *gorm.DB) error { + var totalAffected int64 + + nodeResult := tx.Where("node_id = ?", nodeID).Delete(&NodeInfoRecord{}) + if nodeResult.Error != nil { + return nodeResult.Error + } + totalAffected += nodeResult.RowsAffected + + reportResult := tx.Where("node_id = ?", nodeID).Delete(&MapReportRecord{}) + if reportResult.Error != nil { + return reportResult.Error + } + totalAffected += reportResult.RowsAffected + + // 以 from_id 关联:聊天消息 + 数据包流水 + fromIDTargets := []any{ + &TextMessageRecord{}, + &PositionRecord{}, + &TelemetryRecord{}, + &RoutingRecord{}, + &TracerouteRecord{}, + } + for _, model := range fromIDTargets { + res := tx.Where("from_id = ?", nodeID).Delete(model) + if res.Error != nil { + return res.Error + } + totalAffected += res.RowsAffected + } + + if totalAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil + }) +} + func applyNodeFilters(q *gorm.DB, opts ListOptions) *gorm.DB { if opts.NodeID != "" { q = q.Where("node_id = ?", opts.NodeID) diff --git a/internal/web/web.go b/internal/web/web.go index deb4ac3..eaf9013 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -338,6 +338,23 @@ func registerAdminRoutes(r gin.IRouter, store *storepkg.Store, sessions *auth.Ma } c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) + // purge:除了 nodeinfo + map_report,还把 text_message 与 position/telemetry/routing/traceroute + // 中按 from_id 关联到该节点的记录一并删除。 + protected.DELETE("/nodes/:id/purge", func(c *gin.Context) { + nodeID := c.Param("id") + if nodeID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid node id"}) + return + } + if err := store.PurgeNode(nodeID); errors.Is(err, gorm.ErrRecordNotFound) { + c.JSON(http.StatusNotFound, gin.H{"error": "node not found"}) + return + } else if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) } func registerNodeInfoRoutes(r gin.IRouter, store *storepkg.Store, path string) { diff --git a/meshmap_frontend/src/App.vue b/meshmap_frontend/src/App.vue index cf4e751..1cd48d0 100644 --- a/meshmap_frontend/src/App.vue +++ b/meshmap_frontend/src/App.vue @@ -1,6 +1,6 @@