package main import ( "errors" "fmt" "math" "os" "sync" ) type DiagramNode struct { ID string `json:"id"` Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` X float64 `json:"x"` Y float64 `json:"y"` } type DiagramEdge struct { ID string `json:"id"` Source string `json:"source"` Target string `json:"target"` Label string `json:"label"` Head string `json:"head"` Tail string `json:"tail"` } type Diagram struct { Revision int `json:"revision"` Nodes []DiagramNode `json:"nodes"` Edges []DiagramEdge `json:"edges"` } type diagramStore struct { dir string mu sync.Mutex } var errDiagramConflict = errors.New("diagram revision conflict") func draftDiagram(u UseCase) Diagram { if u.Workflow != nil { return workflowDiagram(u) } d := Diagram{Nodes: []DiagramNode{{ID: "start", Type: "start", Title: "เริ่มต้น", Description: u.Start, X: 100, Y: 60}}, Edges: []DiagramEdge{}} for i, f := range u.Flow { d.Nodes = append(d.Nodes, DiagramNode{ID: fmt.Sprintf("step-%d", i+1), Type: "process", Title: fmt.Sprintf("%d. %s", i+1, f[0]), Description: f[1], X: float64(440 + i*340), Y: 60}) } d.Nodes = append(d.Nodes, DiagramNode{ID: "end", Type: "end", Title: "สิ้นสุด", Description: u.Outcome, X: float64(440 + len(u.Flow)*340), Y: 60}) for i := 1; i < len(d.Nodes); i++ { d.Edges = append(d.Edges, DiagramEdge{ID: fmt.Sprintf("edge-%d", i), Source: d.Nodes[i-1].ID, Target: d.Nodes[i].ID, Head: "arrow", Tail: "none"}) } return d } func validateDiagram(d Diagram) error { bad := func() error { return errors.New("diagram ไม่ถูกต้อง: ตรวจประเภท shape, รหัสซ้ำ, พิกัด หรือเส้นเชื่อม") } if d.Nodes == nil || d.Edges == nil || d.Revision < 0 || d.Revision > 1000000000 || len(d.Nodes) > 500 || len(d.Edges) > 2000 { return bad() } ids := map[string]bool{} edges := map[string]bool{} for _, n := range d.Nodes { if n.ID == "" || len(n.ID) > 100 || ids[n.ID] || len(n.Title) > 500 || len(n.Description) > 20000 { return bad() } if n.Type != "start" && n.Type != "end" && n.Type != "process" && n.Type != "decision" { return bad() } for _, p := range []float64{n.X, n.Y} { if math.IsNaN(p) || math.IsInf(p, 0) || p < 0 || p > 100000 { return bad() } } ids[n.ID] = true } arrow := func(s string) bool { return s == "none" || s == "arrow" || s == "open" || s == "diamond" } for _, e := range d.Edges { if e.ID == "" || len(e.ID) > 100 || edges[e.ID] || !ids[e.Source] || !ids[e.Target] || !arrow(e.Head) || !arrow(e.Tail) || len(e.Label) > 2000 { return bad() } edges[e.ID] = true } return nil } func (ds *diagramStore) load(id string, u UseCase) (Diagram, error) { ds.mu.Lock() defer ds.mu.Unlock() d, err := ds.read(id) if errors.Is(err, os.ErrNotExist) { return draftDiagram(u), nil } return d, err } func (ds *diagramStore) read(id string) (Diagram, error) { doc, err := ds.readDocument(id) return doc.Diagram, err } func (ds *diagramStore) save(id string, d Diagram) (Diagram, error) { ds.mu.Lock() defer ds.mu.Unlock() if err := validateDiagram(d); err != nil { return d, err } doc, err := ds.readDocument(id) if err != nil && !errors.Is(err, os.ErrNotExist) { return d, err } if doc.Revision != d.Revision { return d, errDiagramConflict } return ds.writeRevision(id, doc, d, "save", 0) }