package main import ( "net/http" "crypto/rand" "encoding/base64" "database/sql" "strings" "errors" "github.com/gin-gonic/gin" _ "github.com/mattn/go-sqlite3" "water/api/lib" ) func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } func generateToken() string { token := make([]byte, 32) rand.Read(token) return base64.StdEncoding.EncodeToString(token) } func establishDBConnection() *sql.DB { db, err := sql.Open("sqlite3", "../db/water.sqlite3") if err != nil { panic(err) } return db } func checkForTokenInContext(c *gin.Context) (string, error) { authorizationHeader := c.GetHeader("Authorization") if authorizationHeader == "" { return "", errors.New("Authorization header is missing") } parts := strings.Split(authorizationHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { return "", errors.New("Invalid Authorization header format") } return parts[1], nil } func TokenRequired() gin.HandlerFunc { return func(c *gin.Context) { _, err := checkForTokenInContext(c) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) c.Abort() return } c.Next() } } type User struct { Username string Password string } var users = map[string]User{ "user1": {"user1", "password1"}, } func setupRouter() *gin.Engine { // Disable Console Color // gin.DisableConsoleColor() r := gin.Default() r.Use(CORSMiddleware()) r.Use(gin.Logger()) r.Use(gin.Recovery()) api := r.Group("api/v1") api.POST("/auth", func(c *gin.Context) { username, password, ok := c.Request.BasicAuth() if !ok { c.Header("WWW-Authenticate", `Basic realm="Please enter your username and password."`) c.AbortWithStatus(http.StatusUnauthorized) return } user, exists := users[username] if !exists || user.Password != password { c.AbortWithStatus(http.StatusUnauthorized) return } // Generate a simple API token apiToken := generateToken() c.JSON(http.StatusOK, gin.H{"token": apiToken}) }) stats := api.Group("stats") stats.Use(TokenRequired()) { stats.GET("/", func(c *gin.Context) { db := establishDBConnection() defer db.Close() rows, err := db.Query("SELECT * FROM statistics"); if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } defer rows.Close() var data []models.Statistic for rows.Next() { var stat models.Statistic if err := rows.Scan(&stat.ID, &stat.Date, &stat.UserID, &stat.Quantity); err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } data = append(data, stat) } c.JSON(http.StatusOK, data) }) stats.POST("/", func(c *gin.Context) { var stat models.Statistic if err := c.BindJSON(&stat); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } db := establishDBConnection() defer db.Close() result, err := db.Exec("INSERT INTO statistics (date, user_id, quantity) values (?, ?, ?)", stat.Date, stat.UserID, stat.Quantity) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } id, err := result.LastInsertId() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) }) stats.GET("/:uuid", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) }) stats.PATCH("/:uuid", func(c *gin.Context) { c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) }) stats.DELETE("/:uuid", func(c *gin.Context) { c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) }) } return r } func main() { r := setupRouter() // Listen and Server in 0.0.0.0:8080 r.Run(":8080") }