67 lines
2.8 KiB
Go
67 lines
2.8 KiB
Go
package platform
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"net/http"
|
|
)
|
|
|
|
type subgraphRelation struct {
|
|
Parent string `json:"parent_board_id"`
|
|
Frame string `json:"frame_id"`
|
|
Child string `json:"child_board_id"`
|
|
Archived bool `json:"archived"`
|
|
}
|
|
type subgraphRelationChange struct {
|
|
Before subgraphRelation `json:"before"`
|
|
After subgraphRelation `json:"after"`
|
|
}
|
|
|
|
func readSubgraphRelation(r *http.Request, tx *sql.Tx, project, workspace, child string) (subgraphRelation, error) {
|
|
result := subgraphRelation{Child: child}
|
|
err := tx.QueryRowContext(r.Context(), `SELECT parent_board_id::text,frame_id,archived_at IS NOT NULL FROM canvas_subgraphs WHERE project=$1 AND workspace_id=$2 AND child_board_id=$3 FOR UPDATE`, project, workspace, child).Scan(&result.Parent, &result.Frame, &result.Archived)
|
|
return result, err
|
|
}
|
|
func writeSubgraphRelation(r *http.Request, tx *sql.Tx, project, workspace string, relation subgraphRelation) error {
|
|
before, err := readSubgraphRelation(r, tx, project, workspace, relation.Child)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if before != relation {
|
|
if !relation.Archived {
|
|
if err = validateSubgraphPlacement(r, tx, project, workspace, relation.Parent, relation.Child); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err = invalidateCanvasAISubtree(r, tx, project, workspace, relation.Child); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, err = tx.ExecContext(r.Context(), `UPDATE canvas_subgraphs SET parent_board_id=$4,frame_id=$5,archived_at=CASE WHEN $6 THEN COALESCE(archived_at,now()) ELSE NULL END WHERE project=$1 AND workspace_id=$2 AND child_board_id=$3`, project, workspace, relation.Child, relation.Parent, relation.Frame, relation.Archived)
|
|
return err
|
|
}
|
|
|
|
var errSubgraphCycle = errors.New("subgraph_cycle")
|
|
var errSubgraphDepth = errors.New("subgraph_depth_exceeded")
|
|
|
|
func validateSubgraphPlacement(r *http.Request, tx *sql.Tx, project, workspace, parent, child string) error {
|
|
var cycle bool
|
|
var depth int
|
|
err := tx.QueryRowContext(r.Context(), `WITH RECURSIVE ancestors(id,depth) AS(
|
|
SELECT $3::uuid,0 UNION ALL SELECT s.parent_board_id,a.depth+1 FROM ancestors a JOIN canvas_subgraphs s ON s.project=$1 AND s.workspace_id=$2 AND s.child_board_id=a.id AND s.archived_at IS NULL WHERE a.depth<8
|
|
), descendants(id,depth) AS(
|
|
SELECT $4::uuid,0 UNION ALL SELECT s.child_board_id,d.depth+1 FROM descendants d JOIN canvas_subgraphs s ON s.project=$1 AND s.workspace_id=$2 AND s.parent_board_id=d.id AND s.archived_at IS NULL WHERE d.depth<8
|
|
) SELECT EXISTS(SELECT 1 FROM ancestors WHERE id=$4::uuid), (SELECT max(depth) FROM ancestors)+1+(SELECT max(depth) FROM descendants)`, project, workspace, parent, child).Scan(&cycle, &depth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cycle {
|
|
return errSubgraphCycle
|
|
}
|
|
if depth > 8 {
|
|
return errSubgraphDepth
|
|
}
|
|
return nil
|
|
}
|