github.com/simpleiot/simpleiot@v0.18.3/api/nodes.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "io" 6 "log" 7 "net/http" 8 9 "github.com/google/uuid" 10 "github.com/nats-io/nats.go" 11 "github.com/simpleiot/simpleiot/client" 12 "github.com/simpleiot/simpleiot/data" 13 ) 14 15 // NodeMove is a data structure used in the /node/:id/parents api call 16 type NodeMove struct { 17 ID string 18 OldParent string 19 NewParent string 20 } 21 22 // NodeCopy is a data structured used in the /node/:id/parents api call 23 type NodeCopy struct { 24 ID string 25 NewParent string 26 Duplicate bool 27 } 28 29 // NodeDelete is a data structure used with /node/:id DELETE call 30 type NodeDelete struct { 31 Parent string 32 } 33 34 // Nodes handles node requests 35 type Nodes struct { 36 check RequestValidator 37 nc *nats.Conn 38 authToken string 39 } 40 41 // NewNodesHandler returns a new node handler 42 func NewNodesHandler(v RequestValidator, authToken string, 43 nc *nats.Conn) http.Handler { 44 return &Nodes{v, nc, authToken} 45 } 46 47 // Top level handler for http requests in the coap-server process 48 // TODO need to add node auth 49 func (h *Nodes) ServeHTTP(res http.ResponseWriter, req *http.Request) { 50 51 var id string 52 id, req.URL.Path = ShiftPath(req.URL.Path) 53 54 var head string 55 head, req.URL.Path = ShiftPath(req.URL.Path) 56 57 var validUser bool 58 var userID string 59 60 if req.Header.Get("Authorization") != h.authToken { 61 // all requests require valid JWT or authToken validation 62 validUser, userID = h.check.Valid(req) 63 64 if !validUser { 65 http.Error(res, "Unauthorized", http.StatusUnauthorized) 66 return 67 } 68 } 69 70 if id == "" { 71 switch req.Method { 72 case http.MethodGet: 73 if !validUser { 74 http.Error(res, "invalid user", http.StatusMethodNotAllowed) 75 return 76 } 77 78 nodes, err := client.GetNodesForUser(h.nc, userID) 79 if err != nil { 80 log.Println("Error getting nodes for user:", err) 81 } 82 83 if err != nil { 84 http.Error(res, err.Error(), http.StatusNotFound) 85 return 86 } 87 if len(nodes) > 0 { 88 en := json.NewEncoder(res) 89 err := en.Encode(nodes) 90 if err != nil { 91 http.Error(res, "encoding error", http.StatusMethodNotAllowed) 92 } 93 return 94 } 95 _, _ = res.Write([]byte("[]")) 96 case http.MethodPost: 97 // create node 98 h.insertNode(res, req, userID) 99 default: 100 http.Error(res, "invalid method", http.StatusMethodNotAllowed) 101 return 102 } 103 return 104 } 105 106 // process requests with an ID. 107 switch head { 108 case "": 109 switch req.Method { 110 case http.MethodGet: 111 body, err := io.ReadAll(req.Body) 112 if err != nil { 113 http.Error(res, err.Error(), http.StatusNotFound) 114 return 115 } 116 117 parent := string(body) 118 119 node, err := client.GetNodes(h.nc, parent, id, "", false) 120 if err != nil { 121 http.Error(res, err.Error(), http.StatusNotFound) 122 } else { 123 en := json.NewEncoder(res) 124 err := en.Encode(node) 125 if err != nil { 126 http.Error(res, "encoding error", http.StatusMethodNotAllowed) 127 return 128 } 129 } 130 case http.MethodDelete: 131 var nodeDelete NodeDelete 132 if err := decode(req.Body, &nodeDelete); err != nil { 133 http.Error(res, err.Error(), http.StatusBadRequest) 134 return 135 } 136 137 err := client.DeleteNode(h.nc, id, nodeDelete.Parent, userID) 138 139 if err != nil { 140 http.Error(res, err.Error(), http.StatusNotFound) 141 return 142 } 143 144 en := json.NewEncoder(res) 145 err = en.Encode(data.StandardResponse{Success: true, ID: id}) 146 if err != nil { 147 http.Error(res, "encoding error", http.StatusMethodNotAllowed) 148 149 } 150 default: 151 http.Error(res, "invalid method", http.StatusMethodNotAllowed) 152 return 153 } 154 155 case "samples", "points": 156 if req.Method == http.MethodPost { 157 h.processPoints(res, req, id, userID) 158 return 159 } 160 161 http.Error(res, "only POST allowed", http.StatusMethodNotAllowed) 162 return 163 164 case "parents": 165 switch req.Method { 166 case http.MethodPost: 167 var nodeMove NodeMove 168 if err := decode(req.Body, &nodeMove); err != nil { 169 http.Error(res, err.Error(), http.StatusBadRequest) 170 return 171 } 172 173 err := client.MoveNode(h.nc, id, nodeMove.OldParent, 174 nodeMove.NewParent, userID) 175 176 if err != nil { 177 log.Println("Error moving node:", err) 178 http.Error(res, err.Error(), http.StatusNotFound) 179 return 180 } 181 182 en := json.NewEncoder(res) 183 err = en.Encode(data.StandardResponse{Success: true, ID: id}) 184 if err != nil { 185 http.Error(res, "encoding error", http.StatusMethodNotAllowed) 186 } 187 188 case http.MethodPut: 189 var nodeCopy NodeCopy 190 if err := decode(req.Body, &nodeCopy); err != nil { 191 http.Error(res, err.Error(), http.StatusBadRequest) 192 return 193 } 194 195 if !nodeCopy.Duplicate { 196 err := client.MirrorNode(h.nc, id, nodeCopy.NewParent, userID) 197 198 if err != nil { 199 log.Println("Error mirroring node:", err) 200 http.Error(res, err.Error(), http.StatusNotFound) 201 return 202 } 203 } else { 204 err := client.DuplicateNode(h.nc, id, nodeCopy.NewParent, userID) 205 206 if err != nil { 207 log.Println("Error duplicating node:", err) 208 http.Error(res, err.Error(), http.StatusNotFound) 209 return 210 } 211 } 212 213 en := json.NewEncoder(res) 214 err := en.Encode(data.StandardResponse{Success: true, ID: id}) 215 if err != nil { 216 http.Error(res, "encoding error", http.StatusMethodNotAllowed) 217 } 218 219 return 220 221 default: 222 http.Error(res, "invalid method", http.StatusMethodNotAllowed) 223 } 224 225 case "not": 226 switch req.Method { 227 case http.MethodPost: 228 var not data.Notification 229 if err := decode(req.Body, ¬); err != nil { 230 http.Error(res, err.Error(), http.StatusBadRequest) 231 return 232 } 233 234 not.ID = uuid.New().String() 235 236 d, err := not.ToPb() 237 238 if err != nil { 239 http.Error(res, err.Error(), http.StatusBadRequest) 240 return 241 } 242 243 err = h.nc.Publish("node."+id+".not", d) 244 245 if err != nil { 246 http.Error(res, err.Error(), http.StatusBadRequest) 247 return 248 } 249 250 en := json.NewEncoder(res) 251 err = en.Encode(data.StandardResponse{Success: true, ID: id}) 252 if err != nil { 253 http.Error(res, "encoding error", http.StatusMethodNotAllowed) 254 } 255 default: 256 http.Error(res, "invalid method", http.StatusMethodNotAllowed) 257 } 258 } 259 } 260 261 // RequestValidator validates an HTTP request. 262 type RequestValidator interface { 263 Valid(req *http.Request) (bool, string) 264 } 265 266 func (h *Nodes) insertNode(res http.ResponseWriter, req *http.Request, userID string) { 267 var node data.NodeEdge 268 if err := decode(req.Body, &node); err != nil { 269 http.Error(res, err.Error(), http.StatusBadRequest) 270 return 271 } 272 273 if node.ID == "" { 274 node.ID = uuid.New().String() 275 } 276 277 // populate origin for all points 278 for i := range node.Points { 279 node.Points[i].Origin = userID 280 } 281 282 err := client.SendNode(h.nc, node, userID) 283 284 if err != nil { 285 http.Error(res, err.Error(), http.StatusNotFound) 286 return 287 } 288 289 err = encode(res, data.StandardResponse{Success: true, ID: node.ID}) 290 if err != nil { 291 http.Error(res, err.Error(), http.StatusNotFound) 292 return 293 } 294 } 295 296 func (h *Nodes) processPoints(res http.ResponseWriter, req *http.Request, id, userID string) { 297 decoder := json.NewDecoder(req.Body) 298 var points data.Points 299 err := decoder.Decode(&points) 300 if err != nil { 301 http.Error(res, err.Error(), http.StatusBadRequest) 302 return 303 } 304 305 // populate origin for all points 306 for i := range points { 307 points[i].Origin = userID 308 //points[i].Time = time.Now() 309 } 310 311 err = client.SendNodePoints(h.nc, id, points, true) 312 313 if err != nil { 314 http.Error(res, err.Error(), http.StatusBadRequest) 315 return 316 } 317 318 en := json.NewEncoder(res) 319 err = en.Encode(data.StandardResponse{Success: true, ID: id}) 320 if err != nil { 321 http.Error(res, err.Error(), http.StatusBadRequest) 322 return 323 } 324 }