175 lines
3.3 KiB
Go
175 lines
3.3 KiB
Go
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)
|
|
}
|