bgpwtf/cccampix: draw the rest of the fucking owl

Change-Id: I49fd5906e69512e8f2d414f406edc0179522f225
diff --git a/bgpwtf/cccampix/verifier/model/checkable_peers.go b/bgpwtf/cccampix/verifier/model/checkable_peers.go
new file mode 100644
index 0000000..2fe133f
--- /dev/null
+++ b/bgpwtf/cccampix/verifier/model/checkable_peers.go
@@ -0,0 +1,76 @@
+package model
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"strconv"
+)
+
+func (m *sqlModel) GetCheckablePeers(ctx context.Context) ([]*Peer, error) {
+
+	data := []struct {
+		sqlPeer       `db:"peers"`
+		sqlPeerRouter `db:"peer_routers"`
+	}{}
+	q := `
+		SELECT
+			peers.id "peers.id",
+			peers.asn "peers.asn",
+			peers.name "peers.name",
+
+			peer_routers.peer_id "peer_routers.peer_id",
+			peer_routers.v6 "peer_routers.v6",
+			peer_routers.v4 "peer_routers.v4"
+		FROM peers
+		LEFT JOIN peer_routers
+		ON peer_routers.peer_id = peers.id
+	`
+	if err := m.db.SelectContext(ctx, &data, q); err != nil {
+		return nil, fmt.Errorf("SELECT peers/peerRouters: %v", err)
+	}
+
+	// Collapse peers into map
+	// ID -> Peer
+	peers := make(map[string]*Peer)
+
+	for _, row := range data {
+		peer, ok := peers[row.sqlPeer.ID]
+		if !ok {
+			asn, err := strconv.ParseInt(row.sqlPeer.ASN, 10, 64)
+			if err != nil {
+				return nil, fmt.Errorf("data corruption: invalid ASN %q", row.sqlPeer.ASN)
+			}
+			peer = &Peer{
+				ASN:     asn,
+				Name:    row.sqlPeer.Name,
+				Routers: []*Router{},
+			}
+			peers[row.sqlPeer.ID] = peer
+		}
+
+		var v6 net.IP
+		var v4 net.IP
+
+		if row.sqlPeerRouter.V6.Valid {
+			v6 = net.ParseIP(row.sqlPeerRouter.V6.String)
+		}
+		if row.sqlPeerRouter.V4.Valid {
+			v4 = net.ParseIP(row.sqlPeerRouter.V4.String)
+		}
+
+		peer.Routers = append(peer.Routers, &Router{
+			V6: v6,
+			V4: v4,
+		})
+	}
+
+	res := make([]*Peer, len(peers))
+	i := 0
+	for _, peer := range peers {
+		res[i] = peer
+		i += 1
+	}
+
+	return res, nil
+}