package handler import ( "database/sql" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) type Handler struct { DB *sql.DB JWTSecret string } type Device struct { ID uuid.UUID `json:"id" db:"id"` Name string `json:"name" db:"name"` Description string `json:"description" db:"description"` Location string `json:"location" db:"location"` StatusID int `json:"status_id" db:"status_id"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } // Health returns basic service health func (h *Handler) Health(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) } // Hello returns a simple greeting func (h *Handler) Hello(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hello from lambda-iot core"}) } // Login issues a JWT token for demo purposes func (h *Handler) Login(c *gin.Context) { var req struct { Username string `json:"username"` Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid json"}) return } // demo credentials if req.Username != "admin" || req.Password != "password" { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) return } // create token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": req.Username, "exp": time.Now().Add(time.Hour).Unix(), }) signed, err := token.SignedString([]byte(h.JWTSecret)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "could not sign token"}) return } c.JSON(http.StatusOK, gin.H{"token": signed}) } // CreateDevice creates a new device func (h *Handler) CreateDevice(c *gin.Context) { var req struct { Name string `json:"name" binding:"required"` Description string `json:"description" binding:"required"` Location string `json:"location" binding:"required"` StatusID int `json:"status_id" binding:"required,min=1,max=4"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } id := uuid.New() _, err := h.DB.Exec("INSERT INTO devices (id, name, description, location, status_id) VALUES (UUID_TO_BIN(?), ?, ?, ?, ?)", id.String(), req.Name, req.Description, req.Location, req.StatusID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create device"}) return } c.JSON(http.StatusCreated, gin.H{"id": id.String()}) } // GetDevices retrieves all devices func (h *Handler) GetDevices(c *gin.Context) { rows, err := h.DB.Query("SELECT BIN_TO_UUID(id), name, description, location, status_id, created_at, updated_at FROM devices") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch devices"}) return } defer rows.Close() var devices []Device for rows.Next() { var d Device err := rows.Scan(&d.ID, &d.Name, &d.Description, &d.Location, &d.StatusID, &d.CreatedAt, &d.UpdatedAt) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan device"}) return } devices = append(devices, d) } c.JSON(http.StatusOK, devices) } // GetDevice retrieves a single device by ID func (h *Handler) GetDevice(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"}) return } var d Device err = h.DB.QueryRow("SELECT BIN_TO_UUID(id), name, description, location, status_id, created_at, updated_at FROM devices WHERE id = UUID_TO_BIN(?)", id.String()).Scan(&d.ID, &d.Name, &d.Description, &d.Location, &d.StatusID, &d.CreatedAt, &d.UpdatedAt) if err == sql.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{"error": "device not found"}) return } else if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch device"}) return } c.JSON(http.StatusOK, d) } // UpdateDevice updates a device func (h *Handler) UpdateDevice(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"}) return } var req struct { Name *string `json:"name"` Description *string `json:"description"` Location *string `json:"location"` StatusID *int `json:"status_id"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Build update query dynamically setParts := []string{} args := []interface{}{} if req.Name != nil { setParts = append(setParts, "name = ?") args = append(args, *req.Name) } if req.Description != nil { setParts = append(setParts, "description = ?") args = append(args, *req.Description) } if req.Location != nil { setParts = append(setParts, "location = ?") args = append(args, *req.Location) } if req.StatusID != nil { setParts = append(setParts, "status_id = ?") args = append(args, *req.StatusID) } if len(setParts) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"}) return } query := "UPDATE devices SET " + strings.Join(setParts, ", ") + " WHERE id = UUID_TO_BIN(?)" args = append(args, id.String()) _, err = h.DB.Exec(query, args...) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update device"}) return } c.JSON(http.StatusOK, gin.H{"message": "device updated"}) } // DeleteDevice deletes a device func (h *Handler) DeleteDevice(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid device id"}) return } _, err = h.DB.Exec("DELETE FROM devices WHERE id = UUID_TO_BIN(?)", id.String()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete device"}) return } c.JSON(http.StatusOK, gin.H{"message": "device deleted"}) } // Protected requires a valid JWT and returns the token claims func (h *Handler) Protected(c *gin.Context) { if v, ok := c.Get("claims"); ok { c.JSON(http.StatusOK, gin.H{"claims": v}) return } c.JSON(http.StatusUnauthorized, gin.H{"error": "no claims"}) }