From 06db96dd7ce32ccccdbcadee8aa31a89a21b14f2 Mon Sep 17 00:00:00 2001 From: Nishant Bansal <103022832+NishantBansal2003@users.noreply.github.com> Date: Sun, 26 Jan 2025 22:42:54 +0530 Subject: [PATCH] fixes samples-go linter errors (#128) * fixes samples-go linter errors Signed-off-by: Nishant Bansal * fixes error handling Signed-off-by: Nishant Bansal * fixes log handling Signed-off-by: Nishant Bansal --------- Signed-off-by: Nishant Bansal --- .github/workflows/golangci-lint.yml | 7 +- S3-Keploy/bucket/bucket.go | 6 +- echo-mysql/main.go | 18 +- echo-mysql/uss/short.go | 2 + echo-mysql/uss/store.go | 26 +- fasthttp-postgres/internal/app/app.go | 26 +- fasthttp-postgres/internal/entity/model.go | 1 + .../internal/handlers/handler.go | 21 +- .../internal/repository/model.go | 1 + .../internal/repository/repository.go | 8 +- .../internal/repository/statements.go | 6 +- fasthttp-postgres/main.go | 11 +- gin-redis/helpers/token/token.go | 2 +- gin-redis/server/server.go | 11 +- go-grpc/client/client.go | 5 +- go-grpc/server.go | 15 +- go-jwt/main.go | 14 +- go-twilio/main.go | 8 +- graphql-sql/main.go | 4 +- .../internal/models/location_models.go | 130 +++-- .../internal/models/pokemon_models.go | 546 +++++++++--------- http-pokeapi/internal/pokeapi/poke_req.go | 260 +++++---- http-pokeapi/location.go | 55 +- http-pokeapi/main.go | 86 +-- http-pokeapi/middleware.go | 32 +- http-pokeapi/pokemon.go | 150 ++--- http-pokeapi/response.go | 67 ++- mux-elasticsearch/app.go | 50 +- mux-elasticsearch/main.go | 1 + mux-mysql/controller/controller.go | 2 +- mux-mysql/helpers/response.go | 4 +- mux-mysql/main.go | 7 +- mux-sql/app.go | 401 ++++++------- mux-sql/app_test.go | 38 +- sse-svelte/main.go | 11 +- users-profile/main.go | 4 +- 36 files changed, 1081 insertions(+), 955 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 08fa2d79..647ce959 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.21' + go-version: '1.23.4' cache: false - name: golangci-lint uses: golangci/golangci-lint-action@v3 @@ -54,13 +54,10 @@ jobs: # Require: The version of golangci-lint to use. # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. - version: v1.54 + version: v1.63.4 # Optional: working directory, useful for monorepos working-directory: ${{matrix.working-directory}} - # Optional: show only new issues if it's a pull request. The default value is `false`. - only-new-issues: true - # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. install-mode: "goinstall" \ No newline at end of file diff --git a/S3-Keploy/bucket/bucket.go b/S3-Keploy/bucket/bucket.go index 176228ce..36032174 100644 --- a/S3-Keploy/bucket/bucket.go +++ b/S3-Keploy/bucket/bucket.go @@ -100,13 +100,13 @@ func (basics Basics) DeleteAllObjects(bucketName string) (message string) { for _, key := range result.Contents { objectKeys = append(objectKeys, *key.Key) } - var objectIds []types.ObjectIdentifier + var objectIDs []types.ObjectIdentifier for _, key := range objectKeys { - objectIds = append(objectIds, types.ObjectIdentifier{Key: aws.String(key)}) + objectIDs = append(objectIDs, types.ObjectIdentifier{Key: aws.String(key)}) } _, err = basics.S3Client.DeleteObjects(context.TODO(), &s3.DeleteObjectsInput{ Bucket: aws.String(bucketName), - Delete: &types.Delete{Objects: objectIds}, + Delete: &types.Delete{Objects: objectIDs}, }) if err != nil { return "Couldn't delete objects from bucket " + bucketName + " . Here's why: " + err.Error() + "\n" diff --git a/echo-mysql/main.go b/echo-mysql/main.go index e7c7770c..f126b2b0 100644 --- a/echo-mysql/main.go +++ b/echo-mysql/main.go @@ -1,3 +1,4 @@ +// Package main starts the application package main import ( @@ -19,7 +20,7 @@ func main() { log.Fatalf("Error reading .env file %s", err.Error()) } - uss.MetaStore = &uss.USSStore{} + uss.MetaStore = &uss.Store{} err = uss.MetaStore.Connect(appConfig) if err != nil { log.Fatalf("Failed to connect to db %s", err.Error()) @@ -33,10 +34,7 @@ func StartHTTPServer() { e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: `${remote_ip} [${time_rfc3339}] "${method} ${uri} HTTP/1.0" ${status} ${latency_human} ${bytes_out} ${error} "${user_agent}"` + "\n", Skipper: func(c echo.Context) bool { - if c.Request().RequestURI == "/healthcheck" { - return true - } - return false + return c.Request().RequestURI == "/healthcheck" }, })) e.Use(middleware.Recover()) @@ -52,9 +50,9 @@ func StartHTTPServer() { info := uss.MetaStore.FindByShortCode(code) if info != nil { return c.JSON(http.StatusOK, info) - } else { - return c.String(http.StatusNotFound, "Not Found.") } + + return c.String(http.StatusNotFound, "Not Found.") }) e.POST("/shorten", func(c echo.Context) error { @@ -67,10 +65,10 @@ func StartHTTPServer() { err := uss.MetaStore.Persist(req) if err != nil { return c.String(http.StatusInternalServerError, fmt.Sprintf("Failed Persisiting Entity with Error %s", err.Error())) - } else { - req.UpdatedAt = req.UpdatedAt.Truncate(time.Second) - return c.JSON(http.StatusOK, req) } + + req.UpdatedAt = req.UpdatedAt.Truncate(time.Second) + return c.JSON(http.StatusOK, req) }) // automatically add routers for net/http/pprof e.g. /debug/pprof, /debug/pprof/heap, etc. diff --git a/echo-mysql/uss/short.go b/echo-mysql/uss/short.go index b4ca0497..26ad463c 100644 --- a/echo-mysql/uss/short.go +++ b/echo-mysql/uss/short.go @@ -1,3 +1,5 @@ +// Package uss generates short links using SHA-256 and Base58 encoding. Provides methods to connect to and interact with a MySQL database +// for storing and retrieving short URLs. package uss import ( diff --git a/echo-mysql/uss/store.go b/echo-mysql/uss/store.go index c54df75a..1a157ebe 100644 --- a/echo-mysql/uss/store.go +++ b/echo-mysql/uss/store.go @@ -3,6 +3,7 @@ package uss import ( "fmt" "log" + "os" "time" "gorm.io/driver/mysql" @@ -16,11 +17,11 @@ type ShortCodeInfo struct { UpdatedAt time.Time `json:"updated_at" gorm:"datetime(0);autoUpdateTime"` } -type USSStore struct { +type Store struct { db *gorm.DB } -func (s *USSStore) Connect(config map[string]string) error { +func (s *Store) Connect(config map[string]string) error { // Open up our database connection. var err error mysqlDSN := fmt.Sprintf( @@ -49,31 +50,34 @@ func (s *USSStore) Connect(config map[string]string) error { sqlDB.SetMaxOpenConns(512) if err = s.db.AutoMigrate(&ShortCodeInfo{}); err != nil { - log.Fatal(fmt.Sprintf("Failed to create/update db tables with error %s", err.Error())) + log.Printf("Failed to create/update db tables with error %s", err.Error()) + return err } return nil } -func (s *USSStore) Close() { +func (s *Store) Close() { db, _ := s.db.DB() - db.Close() + if err := db.Close(); err != nil { + fmt.Fprintf(os.Stderr, "Could not close database connection: %v\n", err) + } } -func (s *USSStore) Persist(info *ShortCodeInfo) error { +func (s *Store) Persist(info *ShortCodeInfo) error { s.db.Save(info) return nil } -func (s *USSStore) FindByShortCode(shortCode string) *ShortCodeInfo { +func (s *Store) FindByShortCode(shortCode string) *ShortCodeInfo { var infos []ShortCodeInfo s.db.Order("updated_at desc").Find(&infos, "short_code = ?", shortCode) if len(infos) == 0 { return nil - } else { - urlInfo := infos[0] - return &urlInfo } + + urlInfo := infos[0] + return &urlInfo } -var MetaStore *USSStore +var MetaStore *Store diff --git a/fasthttp-postgres/internal/app/app.go b/fasthttp-postgres/internal/app/app.go index b440cb1d..42446c15 100644 --- a/fasthttp-postgres/internal/app/app.go +++ b/fasthttp-postgres/internal/app/app.go @@ -1,3 +1,4 @@ +// Package app initializes the application, sets up database connections, routes, and handles server startup and graceful shutdown. package app import ( @@ -5,25 +6,32 @@ import ( "fasthttp-postgres/internal/handlers" "fasthttp-postgres/internal/repository" "log" + "net/http" "os" "os/signal" "syscall" "time" "github.com/fasthttp/router" - _ "github.com/lib/pq" "github.com/valyala/fasthttp" ) -func InitApp() { +func InitApp() error { time.Sleep(2 * time.Second) // Database connection initialization uri := "postgresql://postgres:password@localhost:5432/db?sslmode=disable" db, err := sql.Open("postgres", uri) if err != nil { - log.Fatal("Error connecting to database:", err) + log.Print("Error connecting to database:", err) + return err } - defer db.Close() // Close the database connection when the application exits + + defer func() { + // Close the database connection when the application exits + if closeErr := db.Close(); closeErr != nil { + log.Println("Error closing database connection:", closeErr) + } + }() repo := repository.NewRepository(db) ctrl := handlers.NewHandler(repo) @@ -32,8 +40,8 @@ func InitApp() { router := router.New() router.GET("/authors", ctrl.GetAllAuthors) router.GET("/books", ctrl.GetAllBooks) - router.GET("/books/{id}", ctrl.GetBookById) - router.GET("/authors/{id}", ctrl.GetBooksByAuthorId) + router.GET("/books/{id}", ctrl.GetBookByID) + router.GET("/authors/{id}", ctrl.GetBooksByAuthorID) router.POST("/books", ctrl.CreateBook) router.POST("/authors", ctrl.CreateAuthor) @@ -46,7 +54,7 @@ func InitApp() { // Start server in a goroutine go func() { log.Println("Starting server: http://localhost:8080") - if err := server.ListenAndServe(":8080"); err != nil { + if err := server.ListenAndServe(":8080"); err != nil && err != http.ErrServerClosed { log.Fatalf("Error starting server: %s\n", err) } }() @@ -61,8 +69,10 @@ func InitApp() { // Attempt to gracefully shut down the server if err := server.Shutdown(); err != nil { - log.Fatalf("Error shutting down server: %s\n", err) + log.Printf("Error shutting down server: %s\n", err) + return err } log.Println("Server gracefully stopped") + return nil } diff --git a/fasthttp-postgres/internal/entity/model.go b/fasthttp-postgres/internal/entity/model.go index 5dd565e7..3b04466c 100644 --- a/fasthttp-postgres/internal/entity/model.go +++ b/fasthttp-postgres/internal/entity/model.go @@ -1,3 +1,4 @@ +// Package entity defines the data models for the application, including authors and books. package entity type Author struct { diff --git a/fasthttp-postgres/internal/handlers/handler.go b/fasthttp-postgres/internal/handlers/handler.go index b82388e8..ad7dda6b 100644 --- a/fasthttp-postgres/internal/handlers/handler.go +++ b/fasthttp-postgres/internal/handlers/handler.go @@ -1,3 +1,4 @@ +// Package handlers provides HTTP request handlers for managing authors and books. package handlers import ( @@ -17,8 +18,8 @@ type Handler struct { type Repository interface { GetAllAuthors(context.Context) ([]entity.Author, error) GetAllBooks(context.Context) ([]entity.Book, error) - GetBookById(context.Context, int) ([]entity.Book, error) - GetBooksByAuthorId(context.Context, int) ([]entity.Book, error) + GetBookByID(context.Context, int) ([]entity.Book, error) + GetBooksByAuthorID(context.Context, int) ([]entity.Book, error) CreateBook(context.Context, entity.Book) error CreateAuthor(context.Context, entity.Author) error } @@ -47,14 +48,14 @@ func (h *Handler) GetAllBooks(ctx *fasthttp.RequestCtx) { sendData(ctx, books) } -func (h *Handler) GetBookById(ctx *fasthttp.RequestCtx) { - bookId := ctx.UserValue("id").(string) - id, err := strconv.Atoi(bookId) +func (h *Handler) GetBookByID(ctx *fasthttp.RequestCtx) { + bookID := ctx.UserValue("id").(string) + id, err := strconv.Atoi(bookID) if err != nil { sendError(ctx, nil, http.StatusNotFound) return } - books, err := h.repository.GetBookById(ctx, id) + books, err := h.repository.GetBookByID(ctx, id) if err != nil { sendError(ctx, nil, http.StatusNotFound) return @@ -62,14 +63,14 @@ func (h *Handler) GetBookById(ctx *fasthttp.RequestCtx) { sendData(ctx, books[0]) } -func (h *Handler) GetBooksByAuthorId(ctx *fasthttp.RequestCtx) { - authorId := ctx.UserValue("id").(string) - id, err := strconv.Atoi(authorId) +func (h *Handler) GetBooksByAuthorID(ctx *fasthttp.RequestCtx) { + authorID := ctx.UserValue("id").(string) + id, err := strconv.Atoi(authorID) if err != nil { sendError(ctx, nil, http.StatusNotFound) return } - books, err := h.repository.GetBooksByAuthorId(ctx, id) + books, err := h.repository.GetBooksByAuthorID(ctx, id) if err != nil { sendError(ctx, nil, http.StatusNotFound) return diff --git a/fasthttp-postgres/internal/repository/model.go b/fasthttp-postgres/internal/repository/model.go index 628a18b9..f5952407 100644 --- a/fasthttp-postgres/internal/repository/model.go +++ b/fasthttp-postgres/internal/repository/model.go @@ -1,3 +1,4 @@ +// Package repository provides functions for converting between entity models and database models. package repository import "fasthttp-postgres/internal/entity" diff --git a/fasthttp-postgres/internal/repository/repository.go b/fasthttp-postgres/internal/repository/repository.go index e119925b..4edf5e6c 100644 --- a/fasthttp-postgres/internal/repository/repository.go +++ b/fasthttp-postgres/internal/repository/repository.go @@ -49,9 +49,9 @@ func (r *Repository) GetAllBooks(ctx context.Context) ([]entity.Book, error) { return mm.convert(), nil } -func (r *Repository) GetBookById(ctx context.Context, id int) ([]entity.Book, error) { +func (r *Repository) GetBookByID(ctx context.Context, id int) ([]entity.Book, error) { var mm models - rows, err := r.db.QueryContext(ctx, getBookById, id) + rows, err := r.db.QueryContext(ctx, getBookByID, id) if err != nil { fmt.Println("1") return nil, err @@ -67,9 +67,9 @@ func (r *Repository) GetBookById(ctx context.Context, id int) ([]entity.Book, er return mm.convert(), nil } -func (r *Repository) GetBooksByAuthorId(ctx context.Context, id int) ([]entity.Book, error) { +func (r *Repository) GetBooksByAuthorID(ctx context.Context, id int) ([]entity.Book, error) { var mm models - rows, err := r.db.QueryContext(ctx, getBooksByAuthorId, id) + rows, err := r.db.QueryContext(ctx, getBooksByAuthorID, id) if err != nil { return nil, err } diff --git a/fasthttp-postgres/internal/repository/statements.go b/fasthttp-postgres/internal/repository/statements.go index 29e56418..db51a965 100644 --- a/fasthttp-postgres/internal/repository/statements.go +++ b/fasthttp-postgres/internal/repository/statements.go @@ -2,12 +2,12 @@ package repository const ( getAllAuthors = `SELECT * FROM authors` - // getAuthorById = `SELECT * FROM authors WHERE id = $1` - getBookById = `SELECT b.id, b.title, b.year, b.author_id, a.first_name, a.last_name + // getAuthorByID = `SELECT * FROM authors WHERE id = $1` + getBookByID = `SELECT b.id, b.title, b.year, b.author_id, a.first_name, a.last_name FROM books b LEFT JOIN authors a on b.author_id=a.id WHERE b.id = $1;` - getBooksByAuthorId = `SELECT b.id, b.title, b.year, b.author_id, a.first_name, a.last_name + getBooksByAuthorID = `SELECT b.id, b.title, b.year, b.author_id, a.first_name, a.last_name FROM authors a LEFT JOIN books b on a.id=b.author_id WHERE a.id = $1;` diff --git a/fasthttp-postgres/main.go b/fasthttp-postgres/main.go index 7b36b953..7c20d440 100644 --- a/fasthttp-postgres/main.go +++ b/fasthttp-postgres/main.go @@ -1,7 +1,14 @@ +// Package main is the entry point of the application. package main -import "fasthttp-postgres/internal/app" +import ( + "fasthttp-postgres/internal/app" + "log" +) func main() { - app.InitApp() + err := app.InitApp() + if err != nil { + log.Fatalf("Error occured: %s\n", err) + } } diff --git a/gin-redis/helpers/token/token.go b/gin-redis/helpers/token/token.go index f0277482..1b9f5ee1 100644 --- a/gin-redis/helpers/token/token.go +++ b/gin-redis/helpers/token/token.go @@ -34,7 +34,7 @@ func GenerateToken(value string, secretKey string) (string, error) { } func VerifyToken(tokenString string, secretKey string) (*CustomClaims, error) { - token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(_ *jwt.Token) (interface{}, error) { return []byte(secretKey), nil }) diff --git a/gin-redis/server/server.go b/gin-redis/server/server.go index c71588a7..98bf118b 100644 --- a/gin-redis/server/server.go +++ b/gin-redis/server/server.go @@ -1,14 +1,15 @@ +// Package server sets up and manages the HTTP server with routes and graceful shutdown. package server import ( - "time" - "github.com/keploy/gin-redis/routes" - "os" "context" "fmt" + "github.com/keploy/gin-redis/routes" "net/http" + "os" "os/signal" "syscall" + "time" ) func Init() { @@ -16,7 +17,7 @@ func Init() { r := routes.NewRouter() port := "3001" srv := &http.Server{ - Addr: ":" + port, + Addr: ":" + port, Handler: r, } go func() { @@ -42,4 +43,4 @@ func GracefulShutdown(srv *http.Server) { } fmt.Println("Server exiting") -} \ No newline at end of file +} diff --git a/go-grpc/client/client.go b/go-grpc/client/client.go index 7bc42793..c2c68734 100644 --- a/go-grpc/client/client.go +++ b/go-grpc/client/client.go @@ -327,5 +327,8 @@ func main() { r.DELETE("/users/stream", deleteUsersStream) // Start Gin server - r.Run(":8080") + err := r.Run(":8080") + if err != nil && err != http.ErrServerClosed { + log.Fatalf("Failed to start server: %v", err) + } } diff --git a/go-grpc/server.go b/go-grpc/server.go index 0eff5cad..28008e89 100644 --- a/go-grpc/server.go +++ b/go-grpc/server.go @@ -1,3 +1,4 @@ +// Package main starts the application. package main import ( @@ -35,7 +36,7 @@ func incrementID() { } // CreateUser RPC -func (s *server) CreateUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) { +func (s *server) CreateUser(_ context.Context, req *pb.UserRequest) (*pb.UserResponse, error) { mu.Lock() defer mu.Unlock() @@ -61,7 +62,7 @@ func (s *server) CreateUser(ctx context.Context, req *pb.UserRequest) (*pb.UserR } // GetUsers RPC -func (s *server) GetUsers(ctx context.Context, req *pb.Empty) (*pb.UsersResponse, error) { +func (s *server) GetUsers(_ context.Context, _ *pb.Empty) (*pb.UsersResponse, error) { mu.Lock() defer mu.Unlock() @@ -81,7 +82,7 @@ func (s *server) GetUsers(ctx context.Context, req *pb.Empty) (*pb.UsersResponse } // UpdateUser RPC -func (s *server) UpdateUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) { +func (s *server) UpdateUser(_ context.Context, req *pb.UserRequest) (*pb.UserResponse, error) { mu.Lock() defer mu.Unlock() @@ -103,7 +104,7 @@ func (s *server) UpdateUser(ctx context.Context, req *pb.UserRequest) (*pb.UserR } // DeleteUser RPC -func (s *server) DeleteUser(ctx context.Context, req *pb.UserID) (*pb.Empty, error) { +func (s *server) DeleteUser(_ context.Context, req *pb.UserID) (*pb.Empty, error) { mu.Lock() defer mu.Unlock() @@ -169,14 +170,12 @@ func (s *server) DeleteUsersStream(stream pb.UserService_DeleteUsersStreamServer return err } - if _, exists := userStore[int(req.GetId())]; exists { - delete(userStore, int(req.GetId())) - } + delete(userStore, int(req.GetId())) } } // GetUsersStream RPC (Server Streaming) -func (s *server) GetUsersStream(req *pb.Empty, stream pb.UserService_GetUsersStreamServer) error { +func (s *server) GetUsersStream(_ *pb.Empty, stream pb.UserService_GetUsersStreamServer) error { mu.Lock() defer mu.Unlock() diff --git a/go-jwt/main.go b/go-jwt/main.go index f2dd4530..5060467e 100644 --- a/go-jwt/main.go +++ b/go-jwt/main.go @@ -1,3 +1,4 @@ +// Package main starts the application package main import ( @@ -96,7 +97,7 @@ func CheckTokenHandler(c *gin.Context) { claims := &Claims{} // Parse the JWT string and store the result in `claims` - sentTokenObj, err := jwt.ParseWithClaims(sentToken, claims, func(token *jwt.Token) (interface{}, error) { + sentTokenObj, err := jwt.ParseWithClaims(sentToken, claims, func(_ *jwt.Token) (interface{}, error) { return jwtKey, nil }) @@ -133,7 +134,11 @@ func CheckTokenHandler(c *gin.Context) { func main() { time.Sleep(2 * time.Second) initDB() - defer db.Close() + defer func() { + if err := db.Close(); err != nil { + log.Println("Error closing database connection:", err) + } + }() router := gin.Default() @@ -141,5 +146,8 @@ func main() { router.GET("/generate-token", GenerateTokenHandler) router.GET("/check-token", CheckTokenHandler) - router.Run(":8000") + err = router.Run(":8000") + if err != nil && err != http.ErrServerClosed { + log.Fatalf("Failed to start server: %v", err) + } } diff --git a/go-twilio/main.go b/go-twilio/main.go index ef1d7219..99801876 100644 --- a/go-twilio/main.go +++ b/go-twilio/main.go @@ -1,6 +1,8 @@ +// Package main starts the application package main import ( + "context" "fmt" "log" "net/http" @@ -67,8 +69,8 @@ func gracefulShutdown(router *gin.Engine) { fmt.Println("Shutting down server...") // The context is used to inform the server it has 5 seconds to complete the ongoing requests - if err := srv.Shutdown(nil); err != nil { - log.Fatal("Server forced to shutdown:", err) + if err := srv.Shutdown(context.TODO()); err != nil { + fmt.Fprintf(os.Stderr, "Server forced to shutdown: %v\n", err) } fmt.Println("Server exiting") @@ -87,7 +89,7 @@ func main() { // Run the server and listen for graceful shutdown go func() { - if err := router.Run(":8080"); err != nil { + if err := router.Run(":8080"); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed to start: %v", err) } }() diff --git a/graphql-sql/main.go b/graphql-sql/main.go index 0802319d..d3a3df44 100644 --- a/graphql-sql/main.go +++ b/graphql-sql/main.go @@ -1,9 +1,11 @@ +// Package main starts the application package main import ( "context" "database/sql" "fmt" + "log" "net/http" "os" "os/signal" @@ -60,7 +62,7 @@ func main() { defer func() { err = db.Close() if err != nil { - panic(err) + log.Println(err) } }() diff --git a/http-pokeapi/internal/models/location_models.go b/http-pokeapi/internal/models/location_models.go index 386d8dde..24ce64dc 100644 --- a/http-pokeapi/internal/models/location_models.go +++ b/http-pokeapi/internal/models/location_models.go @@ -1,64 +1,66 @@ -package models - -type Location struct { - Count int `json:"count"` - Next *string `json:"next"` - Previous *string `json:"previous"` - Results []struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"results"` -} - -type Pokelocation struct { - EncounterMethodRates []struct { - EncounterMethod struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"encounter_method"` - VersionDetails []struct { - Rate int `json:"rate"` - Version struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"version"` - } `json:"version_details"` - } `json:"encounter_method_rates"` - GameIndex int `json:"game_index"` - ID int `json:"id"` - Location struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"location"` - Name string `json:"name"` - Names []struct { - Language struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"language"` - Name string `json:"name"` - } `json:"names"` - PokemonEncounters []struct { - Pokemon struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"pokemon"` - VersionDetails []struct { - EncounterDetails []struct { - Chance int `json:"chance"` - ConditionValues []interface{} `json:"condition_values"` - MaxLevel int `json:"max_level"` - Method struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"method"` - MinLevel int `json:"min_level"` - } `json:"encounter_details"` - MaxChance int `json:"max_chance"` - Version struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"version"` - } `json:"version_details"` - } `json:"pokemon_encounters"` -} +// Package models defines the data structures used to represent various +// objects in the PokeAPI, including Location, Pokelocation, and related fields. +package models + +type Location struct { + Count int `json:"count"` + Next *string `json:"next"` + Previous *string `json:"previous"` + Results []struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"results"` +} + +type Pokelocation struct { + EncounterMethodRates []struct { + EncounterMethod struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"encounter_method"` + VersionDetails []struct { + Rate int `json:"rate"` + Version struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"version"` + } `json:"version_details"` + } `json:"encounter_method_rates"` + GameIndex int `json:"game_index"` + ID int `json:"id"` + Location struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"location"` + Name string `json:"name"` + Names []struct { + Language struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"language"` + Name string `json:"name"` + } `json:"names"` + PokemonEncounters []struct { + Pokemon struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"pokemon"` + VersionDetails []struct { + EncounterDetails []struct { + Chance int `json:"chance"` + ConditionValues []interface{} `json:"condition_values"` + MaxLevel int `json:"max_level"` + Method struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"method"` + MinLevel int `json:"min_level"` + } `json:"encounter_details"` + MaxChance int `json:"max_chance"` + Version struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"version"` + } `json:"version_details"` + } `json:"pokemon_encounters"` +} diff --git a/http-pokeapi/internal/models/pokemon_models.go b/http-pokeapi/internal/models/pokemon_models.go index f38e4859..e1e384cb 100644 --- a/http-pokeapi/internal/models/pokemon_models.go +++ b/http-pokeapi/internal/models/pokemon_models.go @@ -1,273 +1,273 @@ -package models - -type Pokemon struct { - ID int `json:"id"` - Name string `json:"name"` - BaseExperience int `json:"base_experience"` - Height int `json:"height"` - IsDefault bool `json:"is_default"` - Order int `json:"order"` - Weight int `json:"weight"` - Abilities []struct { - IsHidden bool `json:"is_hidden"` - Slot int `json:"slot"` - Ability struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"ability"` - } `json:"abilities"` - Forms []struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"forms"` - GameIndices []struct { - GameIndex int `json:"game_index"` - Version struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"version"` - } `json:"game_indices"` - HeldItems []struct { - Item struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"item"` - VersionDetails []struct { - Rarity int `json:"rarity"` - Version struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"version"` - } `json:"version_details"` - } `json:"held_items"` - LocationAreaEncounters string `json:"location_area_encounters"` - Moves []struct { - Move struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"move"` - VersionGroupDetails []struct { - LevelLearnedAt int `json:"level_learned_at"` - VersionGroup struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"version_group"` - MoveLearnMethod struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"move_learn_method"` - } `json:"version_group_details"` - } `json:"moves"` - Species struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"species"` - Sprites struct { - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - Other struct { - DreamWorld struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - } `json:"dream_world"` - Home struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"home"` - OfficialArtwork struct { - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"official-artwork"` - Showdown struct { - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"showdown"` - } `json:"other"` - Versions struct { - GenerationI struct { - RedBlue struct { - BackDefault string `json:"back_default"` - BackGray string `json:"back_gray"` - FrontDefault string `json:"front_default"` - FrontGray string `json:"front_gray"` - } `json:"red-blue"` - Yellow struct { - BackDefault string `json:"back_default"` - BackGray string `json:"back_gray"` - FrontDefault string `json:"front_default"` - FrontGray string `json:"front_gray"` - } `json:"yellow"` - } `json:"generation-i"` - GenerationIi struct { - Crystal struct { - BackDefault string `json:"back_default"` - BackShiny string `json:"back_shiny"` - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"crystal"` - Gold struct { - BackDefault string `json:"back_default"` - BackShiny string `json:"back_shiny"` - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"gold"` - Silver struct { - BackDefault string `json:"back_default"` - BackShiny string `json:"back_shiny"` - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"silver"` - } `json:"generation-ii"` - GenerationIii struct { - Emerald struct { - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"emerald"` - FireredLeafgreen struct { - BackDefault string `json:"back_default"` - BackShiny string `json:"back_shiny"` - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"firered-leafgreen"` - RubySapphire struct { - BackDefault string `json:"back_default"` - BackShiny string `json:"back_shiny"` - FrontDefault string `json:"front_default"` - FrontShiny string `json:"front_shiny"` - } `json:"ruby-sapphire"` - } `json:"generation-iii"` - GenerationIv struct { - DiamondPearl struct { - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"diamond-pearl"` - HeartgoldSoulsilver struct { - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"heartgold-soulsilver"` - Platinum struct { - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"platinum"` - } `json:"generation-iv"` - GenerationV struct { - BlackWhite struct { - Animated struct { - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"animated"` - BackDefault string `json:"back_default"` - BackFemale interface{} `json:"back_female"` - BackShiny string `json:"back_shiny"` - BackShinyFemale interface{} `json:"back_shiny_female"` - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"black-white"` - } `json:"generation-v"` - GenerationVi struct { - OmegarubyAlphasapphire struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"omegaruby-alphasapphire"` - XY struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"x-y"` - } `json:"generation-vi"` - GenerationVii struct { - Icons struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - } `json:"icons"` - UltraSunUltraMoon struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - FrontShiny string `json:"front_shiny"` - FrontShinyFemale interface{} `json:"front_shiny_female"` - } `json:"ultra-sun-ultra-moon"` - } `json:"generation-vii"` - GenerationViii struct { - Icons struct { - FrontDefault string `json:"front_default"` - FrontFemale interface{} `json:"front_female"` - } `json:"icons"` - } `json:"generation-viii"` - } `json:"versions"` - } `json:"sprites"` - Cries struct { - Latest string `json:"latest"` - Legacy string `json:"legacy"` - } `json:"cries"` - Stats []struct { - BaseStat int `json:"base_stat"` - Effort int `json:"effort"` - Stat struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"stat"` - } `json:"stats"` - Types []struct { - Slot int `json:"slot"` - Type struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"type"` - } `json:"types"` - PastTypes []struct { - Generation struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"generation"` - Types []struct { - Slot int `json:"slot"` - Type struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"type"` - } `json:"types"` - } `json:"past_types"` -} +package models + +type Pokemon struct { + ID int `json:"id"` + Name string `json:"name"` + BaseExperience int `json:"base_experience"` + Height int `json:"height"` + IsDefault bool `json:"is_default"` + Order int `json:"order"` + Weight int `json:"weight"` + Abilities []struct { + IsHidden bool `json:"is_hidden"` + Slot int `json:"slot"` + Ability struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"ability"` + } `json:"abilities"` + Forms []struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"forms"` + GameIndices []struct { + GameIndex int `json:"game_index"` + Version struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"version"` + } `json:"game_indices"` + HeldItems []struct { + Item struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"item"` + VersionDetails []struct { + Rarity int `json:"rarity"` + Version struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"version"` + } `json:"version_details"` + } `json:"held_items"` + LocationAreaEncounters string `json:"location_area_encounters"` + Moves []struct { + Move struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"move"` + VersionGroupDetails []struct { + LevelLearnedAt int `json:"level_learned_at"` + VersionGroup struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"version_group"` + MoveLearnMethod struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"move_learn_method"` + } `json:"version_group_details"` + } `json:"moves"` + Species struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"species"` + Sprites struct { + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + Other struct { + DreamWorld struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + } `json:"dream_world"` + Home struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"home"` + OfficialArtwork struct { + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"official-artwork"` + Showdown struct { + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"showdown"` + } `json:"other"` + Versions struct { + GenerationI struct { + RedBlue struct { + BackDefault string `json:"back_default"` + BackGray string `json:"back_gray"` + FrontDefault string `json:"front_default"` + FrontGray string `json:"front_gray"` + } `json:"red-blue"` + Yellow struct { + BackDefault string `json:"back_default"` + BackGray string `json:"back_gray"` + FrontDefault string `json:"front_default"` + FrontGray string `json:"front_gray"` + } `json:"yellow"` + } `json:"generation-i"` + GenerationIi struct { + Crystal struct { + BackDefault string `json:"back_default"` + BackShiny string `json:"back_shiny"` + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"crystal"` + Gold struct { + BackDefault string `json:"back_default"` + BackShiny string `json:"back_shiny"` + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"gold"` + Silver struct { + BackDefault string `json:"back_default"` + BackShiny string `json:"back_shiny"` + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"silver"` + } `json:"generation-ii"` + GenerationIii struct { + Emerald struct { + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"emerald"` + FireredLeafgreen struct { + BackDefault string `json:"back_default"` + BackShiny string `json:"back_shiny"` + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"firered-leafgreen"` + RubySapphire struct { + BackDefault string `json:"back_default"` + BackShiny string `json:"back_shiny"` + FrontDefault string `json:"front_default"` + FrontShiny string `json:"front_shiny"` + } `json:"ruby-sapphire"` + } `json:"generation-iii"` + GenerationIv struct { + DiamondPearl struct { + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"diamond-pearl"` + HeartgoldSoulsilver struct { + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"heartgold-soulsilver"` + Platinum struct { + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"platinum"` + } `json:"generation-iv"` + GenerationV struct { + BlackWhite struct { + Animated struct { + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"animated"` + BackDefault string `json:"back_default"` + BackFemale interface{} `json:"back_female"` + BackShiny string `json:"back_shiny"` + BackShinyFemale interface{} `json:"back_shiny_female"` + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"black-white"` + } `json:"generation-v"` + GenerationVi struct { + OmegarubyAlphasapphire struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"omegaruby-alphasapphire"` + XY struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"x-y"` + } `json:"generation-vi"` + GenerationVii struct { + Icons struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + } `json:"icons"` + UltraSunUltraMoon struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + FrontShiny string `json:"front_shiny"` + FrontShinyFemale interface{} `json:"front_shiny_female"` + } `json:"ultra-sun-ultra-moon"` + } `json:"generation-vii"` + GenerationViii struct { + Icons struct { + FrontDefault string `json:"front_default"` + FrontFemale interface{} `json:"front_female"` + } `json:"icons"` + } `json:"generation-viii"` + } `json:"versions"` + } `json:"sprites"` + Cries struct { + Latest string `json:"latest"` + Legacy string `json:"legacy"` + } `json:"cries"` + Stats []struct { + BaseStat int `json:"base_stat"` + Effort int `json:"effort"` + Stat struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"stat"` + } `json:"stats"` + Types []struct { + Slot int `json:"slot"` + Type struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"type"` + } `json:"types"` + PastTypes []struct { + Generation struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"generation"` + Types []struct { + Slot int `json:"slot"` + Type struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"type"` + } `json:"types"` + } `json:"past_types"` +} diff --git a/http-pokeapi/internal/pokeapi/poke_req.go b/http-pokeapi/internal/pokeapi/poke_req.go index 8fff2341..b95390d6 100644 --- a/http-pokeapi/internal/pokeapi/poke_req.go +++ b/http-pokeapi/internal/pokeapi/poke_req.go @@ -1,123 +1,137 @@ -package pokeapi - -import ( - "encoding/json" - "fmt" - models "http-pokeapi/internal/models" - "io" - "net/http" -) - -const base_url = "https://pokeapi.co/api/v2" - -type Client struct { - http.Client -} - -func (client *Client) Pokemon(name string) (models.Pokemon, error) { - end_url := "/pokemon/" - full_url := base_url + end_url + name - - req, err := http.NewRequest("GET", full_url, nil) - - if err != nil { - return models.Pokemon{}, err - } - - res, err := client.Do(req) - - if err != nil { - return models.Pokemon{}, err - } - - if res.StatusCode > 399 { - return models.Pokemon{}, fmt.Errorf("bad status code : %v", res.StatusCode) - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return models.Pokemon{}, err - } - - defer res.Body.Close() - - pokeinfo := models.Pokemon{} - err = json.Unmarshal(data, &pokeinfo) - if err != nil { - return models.Pokemon{}, err - } - return pokeinfo, nil - -} - -func (client *Client) LocationArearesponse() (models.Location, error) { - end_url := "/location-area" - full_url := base_url + end_url - - req, err := http.NewRequest("GET", full_url, nil) - - if err != nil { - return models.Location{}, err - } - - res, err := client.Do(req) - - if err != nil { - return models.Location{}, err - } - - if res.StatusCode > 399 { - return models.Location{}, fmt.Errorf("bad status code : %v", res.StatusCode) - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return models.Location{}, err - } - - defer res.Body.Close() - - LocationAreaValues := models.Location{} - err = json.Unmarshal(data, &LocationAreaValues) - if err != nil { - return models.Location{}, err - } - return LocationAreaValues, nil - -} - -func (c *Client) Pokelocationres(arg string) (models.Pokelocation, error) { - end_url := "/location-area/" - full_url := base_url + end_url + arg - - req, err := http.NewRequest("GET", full_url, nil) - - if err != nil { - return models.Pokelocation{}, err - } - - res, err := c.Do(req) - - if err != nil { - return models.Pokelocation{}, err - } - - if res.StatusCode > 399 { - return models.Pokelocation{}, fmt.Errorf("bad status code : %v", res.StatusCode) - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return models.Pokelocation{}, err - } - - defer res.Body.Close() - - LocationAreaValues := models.Pokelocation{} - err = json.Unmarshal(data, &LocationAreaValues) - if err != nil { - return models.Pokelocation{}, err - } - return LocationAreaValues, nil - -} +// Package pokeapi provides a client to interact with the PokeAPI to fetch +// Pokémon, location area, and Pokémon location data. +package pokeapi + +import ( + "encoding/json" + "fmt" + models "http-pokeapi/internal/models" + "io" + "net/http" +) + +const baseURL = "https://pokeapi.co/api/v2" + +type Client struct { + http.Client +} + +func (client *Client) Pokemon(name string) (models.Pokemon, error) { + endURL := "/pokemon/" + fullURL := baseURL + endURL + name + + req, err := http.NewRequest("GET", fullURL, nil) + + if err != nil { + return models.Pokemon{}, err + } + + res, err := client.Do(req) + + if err != nil { + return models.Pokemon{}, err + } + + if res.StatusCode > 399 { + return models.Pokemon{}, fmt.Errorf("bad status code : %v", res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return models.Pokemon{}, err + } + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() + + pokeinfo := models.Pokemon{} + err = json.Unmarshal(data, &pokeinfo) + if err != nil { + return models.Pokemon{}, err + } + return pokeinfo, nil + +} + +func (client *Client) LocationArearesponse() (models.Location, error) { + endURL := "/location-area" + fullURL := baseURL + endURL + + req, err := http.NewRequest("GET", fullURL, nil) + + if err != nil { + return models.Location{}, err + } + + res, err := client.Do(req) + + if err != nil { + return models.Location{}, err + } + + if res.StatusCode > 399 { + return models.Location{}, fmt.Errorf("bad status code : %v", res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return models.Location{}, err + } + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() + + LocationAreaValues := models.Location{} + err = json.Unmarshal(data, &LocationAreaValues) + if err != nil { + return models.Location{}, err + } + return LocationAreaValues, nil + +} + +func (client *Client) Pokelocationres(arg string) (models.Pokelocation, error) { + endURL := "/location-area/" + fullURL := baseURL + endURL + arg + + req, err := http.NewRequest("GET", fullURL, nil) + + if err != nil { + return models.Pokelocation{}, err + } + + res, err := client.Do(req) + + if err != nil { + return models.Pokelocation{}, err + } + + if res.StatusCode > 399 { + return models.Pokelocation{}, fmt.Errorf("bad status code : %v", res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return models.Pokelocation{}, err + } + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() + + LocationAreaValues := models.Pokelocation{} + err = json.Unmarshal(data, &LocationAreaValues) + if err != nil { + return models.Pokelocation{}, err + } + return LocationAreaValues, nil + +} diff --git a/http-pokeapi/location.go b/http-pokeapi/location.go index 7c367527..ef4b43b5 100644 --- a/http-pokeapi/location.go +++ b/http-pokeapi/location.go @@ -1,27 +1,28 @@ -package main - -import "net/http" - -type locationres struct { - Location []string `json:"location"` -} - -func (cfg *apiconfig) FetchLocations(w http.ResponseWriter, r *http.Request) { - res, err := cfg.client.LocationArearesponse() - - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - var loc []string - - for _, location := range res.Results { - loc = append(loc, location.Name) - } - - respondWithJson(w, http.StatusOK, locationres{ - Location: loc, - }) - -} +// Package main provides handlers for location-related API endpoints. +package main + +import "net/http" + +type locationres struct { + Location []string `json:"location"` +} + +func (cfg *apiconfig) FetchLocations(w http.ResponseWriter, _ *http.Request) { + res, err := cfg.client.LocationArearesponse() + + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + var loc []string + + for _, location := range res.Results { + loc = append(loc, location.Name) + } + + respondWithJSON(w, http.StatusOK, locationres{ + Location: loc, + }) + +} diff --git a/http-pokeapi/main.go b/http-pokeapi/main.go index 3e3af927..ead58fe4 100644 --- a/http-pokeapi/main.go +++ b/http-pokeapi/main.go @@ -1,41 +1,45 @@ -package main - -import ( - "http-pokeapi/internal/pokeapi" - "log" - "net/http" - "time" - - "github.com/go-chi/chi" -) - -type apiconfig struct { - client pokeapi.Client -} - -func main() { - time.Sleep(2 * time.Second) - cfg := &apiconfig{ - client: pokeapi.Client{}, - } - port := "8080" - defer cfg.client.CloseIdleConnections() - - r := chi.NewRouter() - s := chi.NewRouter() - - r.Mount("/api", s) - - s.Get("/locations", cfg.FetchLocations) - s.Get("/locations/{location}", cfg.FetchPokemons) - s.Get("/pokemon/{name}", cfg.AboutPokemon) - - servmux := corsmiddleware(r) - - srv := &http.Server{ - Addr: ":" + port, - Handler: servmux, - } - log.Printf("The server is live on port %s\n", port) - log.Fatal(srv.ListenAndServe()) -} +package main + +import ( + "http-pokeapi/internal/pokeapi" + "log" + "net/http" + "time" + + "github.com/go-chi/chi" +) + +type apiconfig struct { + client pokeapi.Client +} + +func main() { + time.Sleep(2 * time.Second) + cfg := &apiconfig{ + client: pokeapi.Client{}, + } + port := "8080" + defer cfg.client.CloseIdleConnections() + + r := chi.NewRouter() + s := chi.NewRouter() + + r.Mount("/api", s) + + s.Get("/locations", cfg.FetchLocations) + s.Get("/locations/{location}", cfg.FetchPokemons) + s.Get("/pokemon/{name}", cfg.AboutPokemon) + + servmux := corsmiddleware(r) + + srv := &http.Server{ + Addr: ":" + port, + Handler: servmux, + } + log.Printf("The server is live on port %s\n", port) + + err := srv.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatalf("Error starting server: %v", err) + } +} diff --git a/http-pokeapi/middleware.go b/http-pokeapi/middleware.go index 95daf13e..fb236cbf 100644 --- a/http-pokeapi/middleware.go +++ b/http-pokeapi/middleware.go @@ -1,16 +1,16 @@ -package main - -import "net/http" - -func corsmiddleware(app http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE") - w.Header().Set("Access-Control-Allow-Headers", "*") - if r.Method == "OPTIONS" { - w.WriteHeader(http.StatusOK) - return - } - app.ServeHTTP(w, r) - }) -} \ No newline at end of file +package main + +import "net/http" + +func corsmiddleware(app http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE") + w.Header().Set("Access-Control-Allow-Headers", "*") + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + app.ServeHTTP(w, r) + }) +} diff --git a/http-pokeapi/pokemon.go b/http-pokeapi/pokemon.go index ff838e3c..04d2bf69 100644 --- a/http-pokeapi/pokemon.go +++ b/http-pokeapi/pokemon.go @@ -1,75 +1,75 @@ -package main - -import ( - "net/http" - - "github.com/go-chi/chi" -) - -type PokemonNamesres struct { - Names []string `json:"names"` -} -type pokemonstats struct { - Name string `json:"name"` - BaseStat int `json:"basestat"` -} - -type Pokemonres struct { - Name string `json:"name"` - Height int `json:"height"` - Weight int `json:"weight"` - Stats []pokemonstats `json:"stats"` - Types []string `json:"types"` -} - -func (cfg *apiconfig) FetchPokemons(w http.ResponseWriter, r *http.Request) { - location := chi.URLParam(r, "location") - - res, err := cfg.client.Pokelocationres(location) - - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - } - - var pokemon []string - - for _, poke := range res.PokemonEncounters { - pokemon = append(pokemon, poke.Pokemon.Name) - } - - respondWithJson(w, http.StatusOK, pokemon) - -} - -func (cfg *apiconfig) AboutPokemon(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "name") - - res, err := cfg.client.Pokemon(name) - - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - } - - var statsresponse []pokemonstats - - for _, stats := range res.Stats { - statsresponse = append(statsresponse, pokemonstats{ - Name: stats.Stat.Name, - BaseStat: stats.BaseStat, - }) - } - - var restype []string - - for _, t := range res.Types { - restype = append(restype, t.Type.Name) - } - - respondWithJson(w, http.StatusOK, Pokemonres{ - Name: res.Name, - Height: res.Height, - Weight: res.Weight, - Stats: statsresponse, - Types: restype, - }) -} +package main + +import ( + "net/http" + + "github.com/go-chi/chi" +) + +type PokemonNamesres struct { + Names []string `json:"names"` +} +type pokemonstats struct { + Name string `json:"name"` + BaseStat int `json:"basestat"` +} + +type Pokemonres struct { + Name string `json:"name"` + Height int `json:"height"` + Weight int `json:"weight"` + Stats []pokemonstats `json:"stats"` + Types []string `json:"types"` +} + +func (cfg *apiconfig) FetchPokemons(w http.ResponseWriter, r *http.Request) { + location := chi.URLParam(r, "location") + + res, err := cfg.client.Pokelocationres(location) + + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + } + + var pokemon []string + + for _, poke := range res.PokemonEncounters { + pokemon = append(pokemon, poke.Pokemon.Name) + } + + respondWithJSON(w, http.StatusOK, pokemon) + +} + +func (cfg *apiconfig) AboutPokemon(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + + res, err := cfg.client.Pokemon(name) + + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + } + + var statsresponse []pokemonstats + + for _, stats := range res.Stats { + statsresponse = append(statsresponse, pokemonstats{ + Name: stats.Stat.Name, + BaseStat: stats.BaseStat, + }) + } + + var restype []string + + for _, t := range res.Types { + restype = append(restype, t.Type.Name) + } + + respondWithJSON(w, http.StatusOK, Pokemonres{ + Name: res.Name, + Height: res.Height, + Weight: res.Weight, + Stats: statsresponse, + Types: restype, + }) +} diff --git a/http-pokeapi/response.go b/http-pokeapi/response.go index d4ba64ee..43546e74 100644 --- a/http-pokeapi/response.go +++ b/http-pokeapi/response.go @@ -1,32 +1,35 @@ -package main - -import ( - "encoding/json" - "log" - "net/http" -) - -func respondWithError(w http.ResponseWriter, code int, res string) { - if code > 499 { - log.Printf("Responding with 5XX error: %s", res) - } - type errresponse struct { - Error string `json:"error"` - } - respondWithJson(w, code, errresponse{ - Error: res, - }) -} - -func respondWithJson(w http.ResponseWriter, code int, res interface{}) { - w.Header().Set("content-type", "application/json") - data, err := json.Marshal(res) - if err != nil { - log.Printf("Error marshalling JSON: %s", err) - w.WriteHeader(500) - return - } - - w.WriteHeader(code) - w.Write(data) -} +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" +) + +func respondWithError(w http.ResponseWriter, code int, res string) { + if code > 499 { + log.Printf("Responding with 5XX error: %s", res) + } + type errresponse struct { + Error string `json:"error"` + } + respondWithJSON(w, code, errresponse{ + Error: res, + }) +} + +func respondWithJSON(w http.ResponseWriter, code int, res interface{}) { + w.Header().Set("content-type", "application/json") + data, err := json.Marshal(res) + if err != nil { + log.Printf("Error marshalling JSON: %s", err) + w.WriteHeader(500) + return + } + + w.WriteHeader(code) + if _, err := w.Write(data); err != nil { + fmt.Println("Error writing response:", err) + } +} diff --git a/mux-elasticsearch/app.go b/mux-elasticsearch/app.go index d3e7853a..76d15e7f 100644 --- a/mux-elasticsearch/app.go +++ b/mux-elasticsearch/app.go @@ -95,7 +95,12 @@ func (a *App) createDocument(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer res.Body.Close() + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() if res.IsError() { http.Error(w, res.String(), http.StatusInternalServerError) @@ -110,7 +115,11 @@ func (a *App) createDocument(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(map[string]string{"id": createResponse.ID}) + err = json.NewEncoder(w).Encode(map[string]string{"id": createResponse.ID}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } func (a *App) getDocument(w http.ResponseWriter, r *http.Request) { @@ -126,7 +135,12 @@ func (a *App) getDocument(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer res.Body.Close() + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() if res.IsError() { http.Error(w, res.String(), http.StatusNotFound) @@ -142,7 +156,11 @@ func (a *App) getDocument(w http.ResponseWriter, r *http.Request) { source := doc["_source"].(map[string]interface{}) w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(source) + err = json.NewEncoder(w).Encode(source) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } func (a *App) updateDocument(w http.ResponseWriter, r *http.Request) { @@ -172,7 +190,12 @@ func (a *App) updateDocument(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer res.Body.Close() + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() if res.IsError() { http.Error(w, res.String(), http.StatusInternalServerError) @@ -195,7 +218,12 @@ func (a *App) deleteDocument(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer res.Body.Close() + + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Printf("Error closing response body: %v\n", err) + } + }() if res.IsError() { http.Error(w, res.String(), http.StatusInternalServerError) @@ -222,15 +250,19 @@ func (a *App) Run(port string) { defer cancel() if err := a.Server.Shutdown(ctx); err != nil { - log.Fatalf("Server forced to shutdown: %v", err) + fmt.Fprintf(os.Stderr, "Server forced to shutdown: %v\n", err) } log.Println("Server exiting") } -func (a *App) Hello(res http.ResponseWriter, req *http.Request) { +func (a *App) Hello(res http.ResponseWriter, _ *http.Request) { var result = "Hello" - res.Write([]byte(result)) + _, err := res.Write([]byte(result)) + if err != nil { + http.Error(res, fmt.Sprintf("Failed to write response: %v", err), http.StatusInternalServerError) + return + } } func (a *App) initializeRoutes() { diff --git a/mux-elasticsearch/main.go b/mux-elasticsearch/main.go index 060613c6..06159eb8 100644 --- a/mux-elasticsearch/main.go +++ b/mux-elasticsearch/main.go @@ -1,3 +1,4 @@ +// Package main starts the application. package main import ( diff --git a/mux-mysql/controller/controller.go b/mux-mysql/controller/controller.go index a9a4f434..54039cb6 100644 --- a/mux-mysql/controller/controller.go +++ b/mux-mysql/controller/controller.go @@ -53,7 +53,7 @@ func RedirectUser(store *sql.DB) http.HandlerFunc { } func GetAllLinksFromWebsite(store *sql.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, _ *http.Request) { array, err := db.GetAllLinks(store) if err != nil { log.Print("Error ", err) diff --git a/mux-mysql/helpers/response.go b/mux-mysql/helpers/response.go index 5445c6a8..b1a0dc24 100644 --- a/mux-mysql/helpers/response.go +++ b/mux-mysql/helpers/response.go @@ -11,13 +11,13 @@ import ( func SendResponse(w http.ResponseWriter, code int, message string, link string, status bool) { w.WriteHeader(code) if err := json.NewEncoder(w).Encode(&models.Response{Message: message, Link: link, Status: status}); err != nil { - log.Fatalf("Error writing JSON: %v", err) + log.Printf("Error writing JSON: %v", err) } } func SendGetResponse(w http.ResponseWriter, data interface{}, status int, success bool) { w.WriteHeader(status) if err := json.NewEncoder(w).Encode(&models.GETResponse{Message: data, Status: success}); err != nil { - log.Fatalf("Error writing JSON: %v", err) + log.Printf("Error writing JSON: %v", err) } } diff --git a/mux-mysql/main.go b/mux-mysql/main.go index 9ff21383..f0c6200b 100644 --- a/mux-mysql/main.go +++ b/mux-mysql/main.go @@ -4,6 +4,7 @@ package main import ( "context" "database/sql" + "fmt" "log" "net/http" "os" @@ -29,7 +30,7 @@ func main() { defer func() { err = store.Close() if err != nil { - log.Fatal(err) + fmt.Fprintf(os.Stderr, "Could not close database connection: %v\n", err) } }() @@ -62,11 +63,11 @@ func main() { defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Fatalf("Could not gracefully shutdown the server: %v\n", err) + fmt.Fprintf(os.Stderr, "Could not gracefully shutdown the server: %v\n", err) } if err := store.Close(); err != nil { - log.Fatalf("Could not close database connection: %v\n", err) + fmt.Fprintf(os.Stderr, "Could not close database connection: %v\n", err) } log.Println("Server stopped") diff --git a/mux-sql/app.go b/mux-sql/app.go index d66615c3..19d612f2 100644 --- a/mux-sql/app.go +++ b/mux-sql/app.go @@ -1,198 +1,203 @@ -// app.go -package main - -import ( - "context" - "database/sql" - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "strconv" - "time" - - "github.com/gorilla/mux" - _ "github.com/lib/pq" -) - -type App struct { - Router *mux.Router - DB *sql.DB - Server *http.Server -} - -// tom: initial function is empty, it's filled afterwards -// func (a *App) Initialize(user, password, dbname string) { } - -// tom: added "sslmode=disable" to connection string -func (a *App) Initialize(host, user, password, dbname string) error { - time.Sleep(2 * time.Second) - connectionString := fmt.Sprintf("host=%s port=%s user=%s "+ - "password=%s dbname=%s sslmode=disable", - host, "5432", user, password, dbname) - // connectionString := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", user, password, dbname) - var err error - a.DB, err = sql.Open("postgres", connectionString) - if err != nil { - log.Fatal(err) - } - - a.Router = mux.NewRouter() - a.Server = &http.Server{ - Addr: ":8010", - Handler: a.Router, - } - - // tom: this line is added after initializeRoutes is created later on - a.initializeRoutes() - return err -} - -// tom: initial version -// func (a *App) Run(addr string) { } -// improved version -func (a *App) Run(addr string) { - go func() { - if err := a.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("Could not listen on %s: %v\n", addr, err) - } - }() - - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) - <-quit - - log.Println("Server is shutting down...") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := a.Server.Shutdown(ctx); err != nil { - log.Fatalf("Server forced to shutdown: %v", err) - } - - log.Println("Server exiting") -} - -// tom: these are added later -func (a *App) getProduct(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - id, err := strconv.Atoi(vars["id"]) - if err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid product ID") - return - } - p := product{ID: id} - if err := p.getProduct(r.Context(), a.DB); err != nil { - switch err { - case sql.ErrNoRows: - respondWithError(w, http.StatusNotFound, "Product not found") - default: - respondWithError(w, http.StatusInternalServerError, err.Error()) - } - return - } - - respondWithJSON(w, http.StatusOK, p) -} - -func respondWithError(w http.ResponseWriter, code int, message string) { - respondWithJSON(w, code, map[string]string{"error": message}) -} - -func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { - response, _ := json.Marshal(payload) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - w.Write(response) -} - -func (a *App) getProducts(w http.ResponseWriter, r *http.Request) { - count, _ := strconv.Atoi(r.FormValue("count")) - start, _ := strconv.Atoi(r.FormValue("start")) - - if count > 10 || count < 1 { - count = 10 - } - if start < 0 { - start = 0 - } - - products, err := getProducts(r.Context(), a.DB, start, count) - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusOK, products) -} - -func (a *App) createProduct(w http.ResponseWriter, r *http.Request) { - var p product - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(&p); err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid request payload") - return - } - defer r.Body.Close() - - if err := p.createProduct(r.Context(), a.DB); err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusCreated, p) -} - -func (a *App) updateProduct(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - id, err := strconv.Atoi(vars["id"]) - if err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid product ID") - return - } - - var p product - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(&p); err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid resquest payload") - return - } - defer r.Body.Close() - p.ID = id - - if err := p.updateProduct(r.Context(), a.DB); err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusOK, p) -} - -func (a *App) deleteProduct(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - id, err := strconv.Atoi(vars["id"]) - if err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid Product ID") - return - } - - p := product{ID: id} - if err := p.deleteProduct(r.Context(), a.DB); err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"}) -} - -func (a *App) initializeRoutes() { - a.Router.HandleFunc("/products", a.getProducts).Methods("GET") - a.Router.HandleFunc("/product", a.createProduct).Methods("POST") - a.Router.HandleFunc("/product/{id:[0-9]+}", a.getProduct).Methods("GET") - a.Router.HandleFunc("/product/{id:[0-9]+}", a.updateProduct).Methods("PUT") - a.Router.HandleFunc("/product/{id:[0-9]+}", a.deleteProduct).Methods("DELETE") -} +// app.go +package main + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strconv" + "time" + + "github.com/gorilla/mux" + _ "github.com/lib/pq" +) + +type App struct { + Router *mux.Router + DB *sql.DB + Server *http.Server +} + +func (a *App) Initialize(host, user, password, dbname string) error { + time.Sleep(2 * time.Second) + connectionString := fmt.Sprintf("host=%s port=%s user=%s "+ + "password=%s dbname=%s sslmode=disable", + host, "5432", user, password, dbname) + + var err error + a.DB, err = sql.Open("postgres", connectionString) + if err != nil { + log.Print(err) + return err + } + + a.Router = mux.NewRouter() + a.Server = &http.Server{ + Addr: ":8010", + Handler: a.Router, + } + + // tom: this line is added after initializeRoutes is created later on + a.initializeRoutes() + return err +} + +func (a *App) Run(addr string) { + go func() { + if err := a.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("Could not listen on %s: %v\n", addr, err) + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + log.Println("Server is shutting down...") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := a.Server.Shutdown(ctx); err != nil { + fmt.Fprintf(os.Stderr, "Server forced to shutdown: %v\n", err) + } + + log.Println("Server exiting") +} + +// tom: these are added later +func (a *App) getProduct(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid product ID") + return + } + p := product{ID: id} + if err := p.getProduct(r.Context(), a.DB); err != nil { + switch err { + case sql.ErrNoRows: + respondWithError(w, http.StatusNotFound, "Product not found") + default: + respondWithError(w, http.StatusInternalServerError, err.Error()) + } + return + } + + respondWithJSON(w, http.StatusOK, p) +} + +func respondWithError(w http.ResponseWriter, code int, message string) { + respondWithJSON(w, code, map[string]string{"error": message}) +} + +func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { + response, _ := json.Marshal(payload) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + if _, err := w.Write(response); err != nil { + fmt.Println("Error writing response:", err) + } +} + +func (a *App) getProducts(w http.ResponseWriter, r *http.Request) { + count, _ := strconv.Atoi(r.FormValue("count")) + start, _ := strconv.Atoi(r.FormValue("start")) + + if count > 10 || count < 1 { + count = 10 + } + if start < 0 { + start = 0 + } + + products, err := getProducts(r.Context(), a.DB, start, count) + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, products) +} + +func (a *App) createProduct(w http.ResponseWriter, r *http.Request) { + var p product + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&p); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + defer func() { + if err := r.Body.Close(); err != nil { + fmt.Printf("Error closing request body: %v\n", err) + } + }() + + if err := p.createProduct(r.Context(), a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusCreated, p) +} + +func (a *App) updateProduct(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid product ID") + return + } + + var p product + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&p); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid resquest payload") + return + } + + defer func() { + if err := r.Body.Close(); err != nil { + fmt.Printf("Error closing request body: %v\n", err) + } + }() + p.ID = id + + if err := p.updateProduct(r.Context(), a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, p) +} + +func (a *App) deleteProduct(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid Product ID") + return + } + + p := product{ID: id} + if err := p.deleteProduct(r.Context(), a.DB); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"}) +} + +func (a *App) initializeRoutes() { + a.Router.HandleFunc("/products", a.getProducts).Methods("GET") + a.Router.HandleFunc("/product", a.createProduct).Methods("POST") + a.Router.HandleFunc("/product/{id:[0-9]+}", a.getProduct).Methods("GET") + a.Router.HandleFunc("/product/{id:[0-9]+}", a.updateProduct).Methods("PUT") + a.Router.HandleFunc("/product/{id:[0-9]+}", a.deleteProduct).Methods("DELETE") +} diff --git a/mux-sql/app_test.go b/mux-sql/app_test.go index 988c2aee..8aa36a4b 100644 --- a/mux-sql/app_test.go +++ b/mux-sql/app_test.go @@ -1,22 +1,26 @@ -package main_test +package main import ( "bytes" "encoding/json" + "fmt" "log" "net/http" "net/http/httptest" "os" "testing" - "test-app-product-catelog" ) -var a main.App +var a App func TestMain(m *testing.M) { - a.Initialize( + err := a.Initialize( "localhost", "postgres", "password", "postgres") + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize application: %v\n", err) + os.Exit(1) + } ensureTableExists() code := m.Run() clearTable() @@ -30,8 +34,17 @@ func ensureTableExists() { } func clearTable() { - a.DB.Exec("DELETE FROM products") - a.DB.Exec("ALTER SEQUENCE products_id_seq RESTART WITH 1") + _, err := a.DB.Exec("DELETE FROM products") + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to execute query: %v\n", err) + os.Exit(1) + } + + _, err = a.DB.Exec("ALTER SEQUENCE products_id_seq RESTART WITH 1") + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to execute query: %v\n", err) + os.Exit(1) + } } const tableCreationQuery = `CREATE TABLE IF NOT EXISTS products @@ -77,7 +90,11 @@ func TestGetNonExistentProduct(t *testing.T) { checkResponseCode(t, http.StatusNotFound, response.Code) var m map[string]string - json.Unmarshal(response.Body.Bytes(), &m) + err := json.Unmarshal(response.Body.Bytes(), &m) + if err != nil { + t.Errorf("Failed to unmarshal JSON data: %v", err) + } + if m["error"] != "Product not found" { t.Errorf("Expected the 'error' key of the response to be set to 'Product not found'. Got '%s'", m["error"]) } @@ -95,7 +112,10 @@ func TestCreateProduct(t *testing.T) { checkResponseCode(t, http.StatusCreated, response.Code) var m map[string]interface{} - json.Unmarshal(response.Body.Bytes(), &m) + err := json.Unmarshal(response.Body.Bytes(), &m) + if err != nil { + t.Errorf("Failed to unmarshal JSON data: %v", err) + } if m["name"] != "test product" { t.Errorf("Expected product name to be 'test product'. Got '%v'", m["name"]) @@ -110,4 +130,4 @@ func TestCreateProduct(t *testing.T) { if m["id"] != 1.0 { t.Errorf("Expected product ID to be '1'. Got '%v'", m["id"]) } -} \ No newline at end of file +} diff --git a/sse-svelte/main.go b/sse-svelte/main.go index 23820a42..690a5c43 100644 --- a/sse-svelte/main.go +++ b/sse-svelte/main.go @@ -1,3 +1,4 @@ +// Package main starts the application. package main import ( @@ -60,7 +61,11 @@ func sseHandler(w http.ResponseWriter, r *http.Request) { case message := <-msgChan: fmt.Println("case message... sending message") fmt.Println(message) - fmt.Fprintf(w, "data: %s\n\n", message) + _, err := fmt.Fprintf(w, "data: %s\n\n", message) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to write response: %v", err), http.StatusInternalServerError) + return + } flusher.Flush() case <-r.Context().Done(): fmt.Println("Client closed connection") @@ -95,7 +100,7 @@ func main() { defer func() { err = client.Disconnect(context.Background()) if err != nil { - log.Fatal(err) + log.Println(err) } }() @@ -132,7 +137,7 @@ func main() { defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Fatalf("Could not gracefully shutdown the server: %v\n", err) + fmt.Fprintf(os.Stderr, "Could not gracefully shutdown the server: %v\n", err) } fmt.Println("Server stopped") diff --git a/users-profile/main.go b/users-profile/main.go index f08bdbbd..afffbcd9 100644 --- a/users-profile/main.go +++ b/users-profile/main.go @@ -1,7 +1,9 @@ +// Package main starts the application. package main import ( "context" + "fmt" "log" "net/http" "os" @@ -51,7 +53,7 @@ func main() { // Attempt graceful shutdown by shutting down the server if err := server.Shutdown(ctx); err != nil { - log.Fatalf("Server shutdown failed: %v", err) + fmt.Fprintf(os.Stderr, "Server shutdown failed: %v\n", err) } log.Println("Server gracefully stopped")