Загрузка данных


package main

import (
    "database/sql"
    "encoding/json"
    "errors"
    "fmt"
    "log"
    "net/http"
    "os"

    _ "github.com/jackc/pgx/v5/stdlib"
    "github.com/joho/godotenv"
)

var (
    errIncorrectMethod error = errors.New("Method not allowed - errIncorrectMethod")
    errInternalServer  error = errors.New("Internal server error - errInternalServer")
)

type Handler struct {
    db *sql.DB
}

type HandlerResponse struct {
    Page  string `json:"page"`
    Count int64  `json:"count"`
}

func (h *Handler) methodGet(page string) (int64, error) {
    query := "SELECT visits FROM counter_table WHERE page = $1"

    var stats int64
    err := h.db.QueryRow(query, page).Scan(&stats)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return 0, nil
        }

        return 0, err
    }

    return stats, nil
}

func (h *Handler) methodPost(page string) error {
    query := `
            INSERT INTO counter_table (page, visits)
            VALUES ($1, 1)
            ON CONFLICT (page)
            DO UPDATE SET visits = counter_table.visits + 1
    `

    if _, err := h.db.Exec(query, page); err != nil {
        return err
    }

    return nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    path := r.URL.Path
    defer r.Body.Close()

    switch r.Method {
    case http.MethodGet:
        if path != "/stats" {
            http.Error(w, errIncorrectMethod.Error(), http.StatusMethodNotAllowed)
            return
        }

        page := r.URL.Query().Get("page")
        stats, err := h.methodGet(page)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        w.Header().Set("Content-Type", "application/json")

        //creating JSON response
        hresp := HandlerResponse{
            Page:  page,
            Count: stats,
        }

        if err := json.NewEncoder(w).Encode(hresp); err != nil {
            http.Error(w, errInternalServer.Error(), http.StatusInternalServerError)
        }

    case http.MethodPost:
        if path != "/hit" {
            http.Error(w, errIncorrectMethod.Error(), http.StatusMethodNotAllowed)
            return
        }

        page := r.URL.Query().Get("page")

        if err := h.methodPost(page); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        w.WriteHeader(http.StatusNoContent)
    }
}

func main() {
    if err := godotenv.Load(); err != nil {
        log.Fatal(err)
    }

    dsn := fmt.Sprintf("host=localhost port=5432 user=counter_user password=%s dbname=counter_db sslmode=disable", os.Getenv("USER_PASS"))
    db, err := sql.Open("pgx", dsn)
    if err != nil {
        log.Fatal(err)
    }

    db.SetMaxIdleConns(25)
    db.SetMaxOpenConns(25)

    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }

    handler := &Handler{db}

    mux := http.NewServeMux()
    mux.Handle("/hit", handler)
    mux.Handle("/stats", handler)

    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatal(err)
    }
}