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