ScuttleBot

scuttlebot / internal / api / channels_topology.go
Source Blame History 234 lines
dd3a887… lmata 1 package api
dd3a887… lmata 2
dd3a887… lmata 3 import (
dd3a887… lmata 4 "encoding/json"
dd3a887… lmata 5 "net/http"
dd3a887… lmata 6
dd3a887… lmata 7 "github.com/conflicthq/scuttlebot/internal/topology"
dd3a887… lmata 8 )
dd3a887… lmata 9
f3c383e… noreply 10 // TopologyManager is the interface the API server uses to provision channels
dd3a887… lmata 11 // and query the channel policy. Satisfied by *topology.Manager.
f3c383e… noreply 12 type TopologyManager = topologyManager
dd3a887… lmata 13 type topologyManager interface {
dd3a887… lmata 14 ProvisionChannel(ch topology.ChannelConfig) error
f0853f5… lmata 15 DropChannel(channel string)
dd3a887… lmata 16 Policy() *topology.Policy
0902a34… lmata 17 GrantAccess(nick, channel, level string)
0902a34… lmata 18 RevokeAccess(nick, channel string)
900677e… noreply 19 ListChannels() []topology.ChannelInfo
dd3a887… lmata 20 }
dd3a887… lmata 21
dd3a887… lmata 22 type provisionChannelRequest struct {
ba75f34… noreply 23 Name string `json:"name"`
ba75f34… noreply 24 Topic string `json:"topic,omitempty"`
ba75f34… noreply 25 Ops []string `json:"ops,omitempty"`
ba75f34… noreply 26 Voice []string `json:"voice,omitempty"`
ba75f34… noreply 27 Autojoin []string `json:"autojoin,omitempty"`
ba75f34… noreply 28 Instructions string `json:"instructions,omitempty"`
ba75f34… noreply 29 MirrorDetail string `json:"mirror_detail,omitempty"`
dd3a887… lmata 30 }
dd3a887… lmata 31
dd3a887… lmata 32 type provisionChannelResponse struct {
336984b… lmata 33 Channel string `json:"channel"`
336984b… lmata 34 Type string `json:"type,omitempty"`
336984b… lmata 35 Supervision string `json:"supervision,omitempty"`
336984b… lmata 36 Autojoin []string `json:"autojoin,omitempty"`
dd3a887… lmata 37 }
dd3a887… lmata 38
dd3a887… lmata 39 // handleProvisionChannel handles POST /v1/channels.
dd3a887… lmata 40 // It provisions an IRC channel via ChanServ, applies the autojoin policy for
dd3a887… lmata 41 // the channel's type, and returns the channel name, type, and supervision channel.
dd3a887… lmata 42 func (s *Server) handleProvisionChannel(w http.ResponseWriter, r *http.Request) {
dd3a887… lmata 43 var req provisionChannelRequest
dd3a887… lmata 44 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
dd3a887… lmata 45 writeError(w, http.StatusBadRequest, "invalid request body")
dd3a887… lmata 46 return
dd3a887… lmata 47 }
dd3a887… lmata 48 if err := topology.ValidateName(req.Name); err != nil {
dd3a887… lmata 49 writeError(w, http.StatusBadRequest, err.Error())
dd3a887… lmata 50 return
dd3a887… lmata 51 }
319a0fe… lmata 52 if s.topoMgr == nil {
319a0fe… lmata 53 writeError(w, http.StatusServiceUnavailable, "topology not configured")
319a0fe… lmata 54 return
319a0fe… lmata 55 }
dd3a887… lmata 56
dd3a887… lmata 57 policy := s.topoMgr.Policy()
dd3a887… lmata 58
c189ae5… noreply 59 // Merge autojoin and modes from policy if the caller didn't specify any.
dd3a887… lmata 60 autojoin := req.Autojoin
dd3a887… lmata 61 if len(autojoin) == 0 && policy != nil {
dd3a887… lmata 62 autojoin = policy.AutojoinFor(req.Name)
c189ae5… noreply 63 }
c189ae5… noreply 64 var modes []string
c189ae5… noreply 65 if policy != nil {
c189ae5… noreply 66 modes = policy.ModesFor(req.Name)
dd3a887… lmata 67 }
dd3a887… lmata 68
dd3a887… lmata 69 ch := topology.ChannelConfig{
dd3a887… lmata 70 Name: req.Name,
dd3a887… lmata 71 Topic: req.Topic,
dd3a887… lmata 72 Ops: req.Ops,
dd3a887… lmata 73 Voice: req.Voice,
dd3a887… lmata 74 Autojoin: autojoin,
c189ae5… noreply 75 Modes: modes,
dd3a887… lmata 76 }
dd3a887… lmata 77 if err := s.topoMgr.ProvisionChannel(ch); err != nil {
dd3a887… lmata 78 s.log.Error("provision channel", "channel", req.Name, "err", err)
dd3a887… lmata 79 writeError(w, http.StatusInternalServerError, "provision failed")
dd3a887… lmata 80 return
c189ae5… noreply 81 }
c189ae5… noreply 82
ba75f34… noreply 83 // Save instructions to policies if provided.
ba75f34… noreply 84 if req.Instructions != "" && s.policies != nil {
ba75f34… noreply 85 p := s.policies.Get()
ba75f34… noreply 86 if p.OnJoinMessages == nil {
ba75f34… noreply 87 p.OnJoinMessages = make(map[string]string)
ba75f34… noreply 88 }
ba75f34… noreply 89 p.OnJoinMessages[req.Name] = req.Instructions
ba75f34… noreply 90 if req.MirrorDetail != "" {
ba75f34… noreply 91 if p.Bridge.ChannelDisplay == nil {
ba75f34… noreply 92 p.Bridge.ChannelDisplay = make(map[string]ChannelDisplayConfig)
ba75f34… noreply 93 }
ba75f34… noreply 94 cfg := p.Bridge.ChannelDisplay[req.Name]
ba75f34… noreply 95 cfg.MirrorDetail = req.MirrorDetail
ba75f34… noreply 96 p.Bridge.ChannelDisplay[req.Name] = cfg
ba75f34… noreply 97 }
ba75f34… noreply 98 _ = s.policies.Set(p)
ba75f34… noreply 99 }
ba75f34… noreply 100
dd3a887… lmata 101 resp := provisionChannelResponse{
dd3a887… lmata 102 Channel: req.Name,
dd3a887… lmata 103 Autojoin: autojoin,
dd3a887… lmata 104 }
dd3a887… lmata 105 if policy != nil {
dd3a887… lmata 106 resp.Type = policy.TypeName(req.Name)
dd3a887… lmata 107 resp.Supervision = policy.SupervisionFor(req.Name)
dd3a887… lmata 108 }
dd3a887… lmata 109 writeJSON(w, http.StatusCreated, resp)
dd3a887… lmata 110 }
dd3a887… lmata 111
dd3a887… lmata 112 type channelTypeInfo struct {
dd3a887… lmata 113 Name string `json:"name"`
dd3a887… lmata 114 Prefix string `json:"prefix"`
dd3a887… lmata 115 Autojoin []string `json:"autojoin,omitempty"`
dd3a887… lmata 116 Supervision string `json:"supervision,omitempty"`
dd3a887… lmata 117 Ephemeral bool `json:"ephemeral,omitempty"`
dd3a887… lmata 118 TTLSeconds int64 `json:"ttl_seconds,omitempty"`
dd3a887… lmata 119 }
dd3a887… lmata 120
dd3a887… lmata 121 type topologyResponse struct {
900677e… noreply 122 StaticChannels []string `json:"static_channels"`
900677e… noreply 123 Types []channelTypeInfo `json:"types"`
900677e… noreply 124 ActiveChannels []topology.ChannelInfo `json:"active_channels,omitempty"`
f0853f5… lmata 125 }
f0853f5… lmata 126
f0853f5… lmata 127 // handleDropChannel handles DELETE /v1/topology/channels/{channel}.
f0853f5… lmata 128 // Drops the ChanServ registration of an ephemeral channel.
f0853f5… lmata 129 func (s *Server) handleDropChannel(w http.ResponseWriter, r *http.Request) {
f0853f5… lmata 130 channel := "#" + r.PathValue("channel")
f0853f5… lmata 131 if err := topology.ValidateName(channel); err != nil {
f0853f5… lmata 132 writeError(w, http.StatusBadRequest, err.Error())
f0853f5… lmata 133 return
f0853f5… lmata 134 }
319a0fe… lmata 135 if s.topoMgr == nil {
319a0fe… lmata 136 writeError(w, http.StatusServiceUnavailable, "topology not configured")
319a0fe… lmata 137 return
319a0fe… lmata 138 }
f0853f5… lmata 139 s.topoMgr.DropChannel(channel)
ba75f34… noreply 140 w.WriteHeader(http.StatusNoContent)
ba75f34… noreply 141 }
ba75f34… noreply 142
ba75f34… noreply 143 // handleGetInstructions handles GET /v1/channels/{channel}/instructions.
ba75f34… noreply 144 func (s *Server) handleGetInstructions(w http.ResponseWriter, r *http.Request) {
ba75f34… noreply 145 channel := "#" + r.PathValue("channel")
ba75f34… noreply 146 if s.policies == nil {
ba75f34… noreply 147 writeJSON(w, http.StatusOK, map[string]string{"channel": channel, "instructions": ""})
ba75f34… noreply 148 return
ba75f34… noreply 149 }
ba75f34… noreply 150 p := s.policies.Get()
ba75f34… noreply 151 msg := p.OnJoinMessages[channel]
ba75f34… noreply 152 writeJSON(w, http.StatusOK, map[string]string{"channel": channel, "instructions": msg})
ba75f34… noreply 153 }
ba75f34… noreply 154
ba75f34… noreply 155 // handlePutInstructions handles PUT /v1/channels/{channel}/instructions.
ba75f34… noreply 156 func (s *Server) handlePutInstructions(w http.ResponseWriter, r *http.Request) {
ba75f34… noreply 157 channel := "#" + r.PathValue("channel")
ba75f34… noreply 158 if s.policies == nil {
ba75f34… noreply 159 writeError(w, http.StatusServiceUnavailable, "policies not configured")
ba75f34… noreply 160 return
ba75f34… noreply 161 }
ba75f34… noreply 162 var req struct {
ba75f34… noreply 163 Instructions string `json:"instructions"`
ba75f34… noreply 164 }
ba75f34… noreply 165 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
ba75f34… noreply 166 writeError(w, http.StatusBadRequest, "invalid request body")
ba75f34… noreply 167 return
ba75f34… noreply 168 }
ba75f34… noreply 169 p := s.policies.Get()
ba75f34… noreply 170 if p.OnJoinMessages == nil {
ba75f34… noreply 171 p.OnJoinMessages = make(map[string]string)
ba75f34… noreply 172 }
ba75f34… noreply 173 p.OnJoinMessages[channel] = req.Instructions
ba75f34… noreply 174 if err := s.policies.Set(p); err != nil {
ba75f34… noreply 175 writeError(w, http.StatusInternalServerError, "save failed")
ba75f34… noreply 176 return
ba75f34… noreply 177 }
ba75f34… noreply 178 w.WriteHeader(http.StatusNoContent)
ba75f34… noreply 179 }
ba75f34… noreply 180
ba75f34… noreply 181 // handleDeleteInstructions handles DELETE /v1/channels/{channel}/instructions.
ba75f34… noreply 182 func (s *Server) handleDeleteInstructions(w http.ResponseWriter, r *http.Request) {
ba75f34… noreply 183 channel := "#" + r.PathValue("channel")
ba75f34… noreply 184 if s.policies == nil {
ba75f34… noreply 185 writeError(w, http.StatusServiceUnavailable, "policies not configured")
ba75f34… noreply 186 return
ba75f34… noreply 187 }
ba75f34… noreply 188 p := s.policies.Get()
ba75f34… noreply 189 delete(p.OnJoinMessages, channel)
ba75f34… noreply 190 if err := s.policies.Set(p); err != nil {
ba75f34… noreply 191 writeError(w, http.StatusInternalServerError, "save failed")
ba75f34… noreply 192 return
ba75f34… noreply 193 }
f0853f5… lmata 194 w.WriteHeader(http.StatusNoContent)
dd3a887… lmata 195 }
dd3a887… lmata 196
dd3a887… lmata 197 // handleGetTopology handles GET /v1/topology.
dd3a887… lmata 198 // Returns the channel type rules and static channel names declared in config.
dd3a887… lmata 199 func (s *Server) handleGetTopology(w http.ResponseWriter, r *http.Request) {
319a0fe… lmata 200 if s.topoMgr == nil {
319a0fe… lmata 201 writeJSON(w, http.StatusOK, topologyResponse{})
319a0fe… lmata 202 return
319a0fe… lmata 203 }
dd3a887… lmata 204 policy := s.topoMgr.Policy()
dd3a887… lmata 205 if policy == nil {
dd3a887… lmata 206 writeJSON(w, http.StatusOK, topologyResponse{})
dd3a887… lmata 207 return
dd3a887… lmata 208 }
dd3a887… lmata 209
dd3a887… lmata 210 statics := policy.StaticChannels()
dd3a887… lmata 211 staticNames := make([]string, len(statics))
dd3a887… lmata 212 for i, sc := range statics {
dd3a887… lmata 213 staticNames[i] = sc.Name
dd3a887… lmata 214 }
dd3a887… lmata 215
dd3a887… lmata 216 types := policy.Types()
dd3a887… lmata 217 typeInfos := make([]channelTypeInfo, len(types))
dd3a887… lmata 218 for i, t := range types {
dd3a887… lmata 219 typeInfos[i] = channelTypeInfo{
dd3a887… lmata 220 Name: t.Name,
dd3a887… lmata 221 Prefix: t.Prefix,
dd3a887… lmata 222 Autojoin: t.Autojoin,
dd3a887… lmata 223 Supervision: t.Supervision,
dd3a887… lmata 224 Ephemeral: t.Ephemeral,
dd3a887… lmata 225 TTLSeconds: int64(t.TTL.Seconds()),
dd3a887… lmata 226 }
dd3a887… lmata 227 }
dd3a887… lmata 228
dd3a887… lmata 229 writeJSON(w, http.StatusOK, topologyResponse{
dd3a887… lmata 230 StaticChannels: staticNames,
dd3a887… lmata 231 Types: typeInfos,
900677e… noreply 232 ActiveChannels: s.topoMgr.ListChannels(),
dd3a887… lmata 233 })
dd3a887… lmata 234 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button