package service import ( "fmt" "strings" "github.com/neo4j/neo4j-go-driver/v5/neo4j" "knowledge-graph-backend/internal/model" ) func neo4jNodeToModel(n neo4j.Node) model.Node { props := n.Props node := model.Node{ ID: getStr(props, "id"), Label: getStr(props, "label"), Type: getStr(props, "type"), Properties: make(map[string]interface{}), } if v, ok := props["x"]; ok && v != nil { node.X = getFloat64(v) } if v, ok := props["y"]; ok && v != nil { node.Y = getFloat64(v) } if v, ok := props["style"]; ok && v != nil { if s, ok := v.(map[string]interface{}); ok { node.Style = s } } for k, v := range props { switch k { case "id", "label", "type", "x", "y", "style": default: node.Properties[k] = v } } return node } func neo4jRelToModel(r neo4j.Relationship) model.Edge { props := r.Props edge := model.Edge{ ID: getStr(props, "id"), Label: getStr(props, "label"), Type: r.Type, Properties: make(map[string]interface{}), } if v, ok := props["style"]; ok && v != nil { if s, ok := v.(map[string]interface{}); ok { edge.Style = s } } for k, v := range props { switch k { case "id", "label", "style": default: edge.Properties[k] = v } } return edge } func getStr(props map[string]any, key string) string { if v, ok := props[key]; ok && v != nil { if s, ok := v.(string); ok { return s } } return "" } func getFloat64(v any) float64 { switch val := v.(type) { case float64: return val case int64: return float64(val) case int: return float64(val) } return 0 } func getInt(record *neo4j.Record, key string) int { v, _ := record.Get(key) switch val := v.(type) { case int64: return int(val) case int: return val case float64: return int(val) } return 0 } func buildNodeProps(req model.CreateNodeRequest) map[string]any { props := map[string]any{"id": req.ID, "label": req.Label} if req.Type != "" { props["type"] = req.Type } if req.X != 0 { props["x"] = req.X } if req.Y != 0 { props["y"] = req.Y } for k, v := range req.Properties { if isReservedNodeProperty(k) { continue } props[k] = v } return props } func buildEdgeProps(req model.CreateEdgeRequest) map[string]any { props := map[string]any{"id": req.ID, "label": req.Label} for k, v := range req.Properties { if isReservedEdgeProperty(k) { continue } props[k] = v } return props } func isReservedNodeProperty(key string) bool { switch key { case "id", "label", "type", "x", "y", "style": return true default: return false } } func isReservedEdgeProperty(key string) bool { switch key { case "id", "label", "type", "style", "source", "target": return true default: return false } } func sanitizeLabel(label string) string { if label == "" { return "" } var b strings.Builder for i, r := range label { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' { b.WriteRune(r) continue } if i == 0 { continue } b.WriteRune('_') } return strings.Trim(b.String(), "_") } func sanitizeRelationshipType(relType string) string { if relType == "" { return "" } return sanitizeLabel(strings.ToUpper(relType)) } func wrapNotFound(entity string, id string) error { return fmt.Errorf("%s with id %s not found", entity, id) }