◐ Off-By-One · answer catalog

go-db-edge-fk-root-node

1 answer(s)godocker

go-db-edge-fk-root-node

📦 Source in repository (JSON)

Answer

BUG-029 — root node create 503 (Go service)

Root cause: CreateNode unconditionally inserted an edges row with source_id = req.ParentID. For a root node ParentID == uuid.Nil, and since no nodes row has id = uuid.Nil, the DB FK constraint rejected the insert — surfacing as HTTP 503.

Fix (service.go): wrap the edge insert in if ParentID != Nil, and make the response's Edge field a pointer so root nodes return a nil edge:

// CreateNodeResponse.Edge is a POINTER type: root nodes have no edge,
// so Edge is nil (JSON null) instead of an empty struct.
type CreateNodeResponse struct {
    Node *Node `json:"node"`
    Edge *Edge `json:"edge"`
}

func (s *Service) CreateNode(ctx context.Context, req CreateNodeRequest) (*CreateNodeResponse, error) {
    node := Node{ID: uuid.New(), ParentID: req.ParentID}
    if err := s.store.InsertNode(ctx, node); err != nil {
        return nil, err
    }

    var edge *Edge // pointer type: nil for root nodes
    if req.ParentID != uuid.Nil { // BUG-029: only insert edge for non-root
        if !s.store.NodeExists(ctx, req.ParentID) {
            return nil, ErrParentNotFound // clean 404, not a leaked FK 503
        }
        e := Edge{ID: uuid.New(), SourceID: req.ParentID, TargetID: node.ID}
        if err := s.store.InsertEdge(ctx, e); err != nil {
            return nil, err
        }
        edge = &e
    }

    return &CreateNodeResponse{Node: &node, Edge: edge}, nil
}

HTTP result: POST {} → 201 with "edge": null; POST {"parent_id":"<existing>"} → 201 with a populated edge; POST {"parent_id":"<missing>"} → 404.

BUG-030 — usePresence hardcoded "viewer" (JS)

Root cause: the presence hook defaulted the local participant's permission to 'viewer', so the composer (gated on permission === 'editor') was permanently read-only.

Fix (presence.js):

const LOCAL_PERMISSION_DEFAULT = 'editor'; // was: 'viewer'

function usePresence(options = {}) {
  const permission = options.permission ?? LOCAL_PERMISSION_DEFAULT;
  const composer = {
    readOnly: permission !== 'editor',
    canEdit: permission === 'editor',
  };
  return { local: { permission }, composer, isEditor: permission === 'editor', isViewer: permission === 'viewer' };
}

Evidence & signatures

Verified by building a dependency-free Go module (in-memory store simulating the Postgres FK: `edges.source_id` must reference an existing node and must not be `uuid.Nil`) plus `node:test` for the JS hook.

| Test | Result |
|---|---|
| Buggy root create returns `ErrForeignKeyViolation`, maps to **503** (BUG-029 repro, service + HTTP) | PASS ×2 |
| Root create succeeds with `Edge == nil` (pointer), **0 edge rows** inserted, node count = 1 | PASS |
| Root JSON serializes as `"edge": null` (not `{}`) | PASS |
| Child create: edge inserted with `source_id == parentID`, `target_id == nodeID`, edge count = 1 | PASS |
| Child create with missing parent → `ErrParentNotFound` (404), **no orphan edge leaked** | PASS |
| HTTP end-to-end: root → 201/edge null; child → 201/edge present | PASS ×2 |
| JS: default local presence = `editor`, composer writable | PASS ×2 |
| JS: explicit `viewer` honored (read-only); regression check on old default | PASS ×2 |

Edge cases exercised: empty body vs. explicit all-zero `parent_id` (both root, no edge); omitted `parent_id` JSON; missing-parent (no FK 503 leak, no orphan row); pointer-nil vs. empty-struct distinction in JSON; explicit permission override. `go vet` and `gofmt` clean; `go test` 8/8, `node --test` 4/4.
{"model": "deepseek-v4-flash", "problem_class": "go-db-edge-fk-root-node", "result": "passed", "tests": 12}
Generated from the verified corpus · MIT licensedBack to the catalog