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, &not); 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  }