package main import ( "context" "embed" "errors" "github.com/gin-gonic/gin" "log" "net/http" "os" "strconv" ) //go:embed migrations/*.sql var migrations embed.FS var db *Db // ServerConfig holds configuration for the server. type ServerConfig struct { // The datasource to the SQLite database. // Use ":memory:" for an in-memory database. Datasource string // The port to listen on. // Use an empty string to listen on a random port. Port string // The channel that gets signaled when the server has started. Started chan bool } func getUsers(c *gin.Context) { users, err := db.GetUsers() if err != nil { log.Printf("Error getting users: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) return } c.IndentedJSON(http.StatusOK, users) } func getUser(c *gin.Context) { userIdStr := c.Param("userId") userId, err := strconv.Atoi(userIdStr) if err != nil { log.Printf("Invalid user ID: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) return } user, err := db.GetUser(userId) if err != nil { log.Printf("Error getting user: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) return } if user == nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } c.IndentedJSON(http.StatusOK, user) } func getUserGoals(c *gin.Context) { userIdStr := c.Param("userId") userId, err := strconv.Atoi(userIdStr) if err != nil { log.Printf("Invalid user ID: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) return } exists, err := db.UserExists(userId) if err != nil { log.Printf("Error checking user existence: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) return } if !exists { log.Printf("Error checking user existence: %v", err) c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } goals, err := db.GetUserGoals(userId) if err != nil { log.Printf("Error getting user goals: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) return } c.IndentedJSON(http.StatusOK, goals) } /* * Initialises the database, and then starts the server. If the context gets cancelled, the server is shutdown and the database is closed. */ func start(ctx context.Context, config *ServerConfig) { db = NewDb(config.Datasource) defer db.db.MustClose() router := gin.Default() router.GET("/api/users", getUsers) router.GET("/api/user/:userId", getUser) router.GET("/api/user/:userId/goals", getUserGoals) srv := &http.Server{ Addr: ":" + config.Port, Handler: router.Handler(), } go func() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("listen: %s\n", err) } }() log.Printf("Running server on port %s\n", config.Port) if config.Started != nil { config.Started <- true } <-ctx.Done() log.Println("Shutting down") if err := srv.Shutdown(context.Background()); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } } func main() { config := ServerConfig{ Datasource: os.Getenv("DB_PATH"), } start(context.Background(), &config) }