Files
lambdaiot-core/internal/handler/handlers.go

217 lines
6.2 KiB
Go

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"})
}