logger mv; get recipe http handler
This commit is contained in:
parent
a28beb0e9c
commit
55c7c2bff6
|
@ -5,6 +5,7 @@ import (
|
|||
"log/slog"
|
||||
"os"
|
||||
"recipes/internal/config"
|
||||
"recipes/internal/lib/logger/sl"
|
||||
"recipes/internal/media_storage/minio"
|
||||
"recipes/internal/parser"
|
||||
"recipes/internal/storage/postgresql"
|
||||
|
@ -32,7 +33,7 @@ func main() {
|
|||
cfg.Postgresql.DBName,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("failed to init storage", "err", err)
|
||||
log.Error("failed to init storage", sl.Err(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
// init media storage
|
||||
|
@ -43,13 +44,13 @@ func main() {
|
|||
cfg.Minio.Password,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("failed to init media storage", "err", err)
|
||||
log.Error("failed to init media storage", sl.Err(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
// run parser
|
||||
_, err = parser.SavePage(log, 1, mstorage, storage, storage)
|
||||
if err != nil {
|
||||
log.Error("Parse failed", "err", err)
|
||||
log.Error("Parse failed", sl.Err(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info("parsing was completed successfully")
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"recipes/internal/config"
|
||||
"syscall"
|
||||
|
||||
prettyLogger "github.com/charmbracelet/log"
|
||||
)
|
||||
|
@ -15,6 +17,13 @@ const (
|
|||
envProd = "prod"
|
||||
)
|
||||
|
||||
//TODO
|
||||
// cache
|
||||
// http server
|
||||
// app
|
||||
// graceful sd
|
||||
// tests
|
||||
|
||||
func main() {
|
||||
// load config
|
||||
cfg := config.MustLoad()
|
||||
|
@ -23,15 +32,20 @@ func main() {
|
|||
log.Info("starting application", slog.String("env", cfg.Env))
|
||||
log.Debug("debug messages are enabled")
|
||||
log.Debug("Application config", slog.Any("config", fmt.Sprintf("%+v", *cfg)))
|
||||
// init storage
|
||||
|
||||
// init cache
|
||||
|
||||
// init media storage
|
||||
|
||||
// init app
|
||||
// init app (storage, cache, media storage)
|
||||
|
||||
// graceful shutdown
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
<-stop
|
||||
|
||||
log.Info("stopping application...")
|
||||
|
||||
// application.GRPCSrv.Stop()
|
||||
|
||||
log.Info("application stopped")
|
||||
}
|
||||
|
||||
func setupLogger(env string) *slog.Logger {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -14,6 +14,7 @@ require (
|
|||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.8.1 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||
|
@ -21,8 +22,14 @@ require (
|
|||
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.11 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.17.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
|
@ -34,6 +41,7 @@ require (
|
|||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
|
|
21
go.sum
21
go.sum
|
@ -6,6 +6,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
|||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
@ -40,10 +42,22 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
|||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
|
||||
|
@ -80,6 +94,8 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
|
@ -135,8 +151,13 @@ github.com/s32x/httpclient v0.0.0-20220217184346-6df4d4d51c14/go.mod h1:FqlhGa3u
|
|||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package recipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"recipes/internal/domain/models"
|
||||
resp "recipes/internal/lib/api/response"
|
||||
"recipes/internal/lib/logger/sl"
|
||||
"recipes/internal/storage"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
RecipeId uint `json:"recipe_id" validate:"required"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
resp.Response
|
||||
Recipe models.Recipe `json:"recipe"`
|
||||
}
|
||||
|
||||
type RecipeProvider interface {
|
||||
GetRecipe(ctx context.Context, r_id uint) (models.Recipe, error)
|
||||
}
|
||||
|
||||
func New(log *slog.Logger, recipeProvider RecipeProvider) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
const op = "http-server.handlers.recipe.New"
|
||||
|
||||
log = log.With(
|
||||
slog.String("op", op),
|
||||
slog.String("request_id", middleware.GetReqID(r.Context())),
|
||||
)
|
||||
|
||||
var req Request
|
||||
// decode request
|
||||
err := render.DecodeJSON(r.Body, &req)
|
||||
if err != nil {
|
||||
log.Error("failed to decode request body", sl.Err(err))
|
||||
render.JSON(w, r, resp.Error("failed to decode request"))
|
||||
return
|
||||
}
|
||||
log.Debug("request body decoded", slog.Any("request", req))
|
||||
// validate request
|
||||
if err := validator.New().Struct(req); err != nil {
|
||||
validateErr := err.(validator.ValidationErrors)
|
||||
|
||||
log.Error("invalid request", sl.Err(err))
|
||||
render.JSON(w, r, resp.ValidationError(validateErr))
|
||||
return
|
||||
}
|
||||
// get from storage
|
||||
recipe, err := recipeProvider.GetRecipe(r.Context(), req.RecipeId)
|
||||
if err != nil {
|
||||
log.Error("failed to get recipe from storage", sl.Err(err))
|
||||
if errors.Is(err, storage.ErrRecipeNotFound) {
|
||||
render.JSON(w, r, resp.Error("recipe not found"))
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, resp.Error("failed to get recipe"))
|
||||
return
|
||||
}
|
||||
// render response
|
||||
var resp Response = Response{
|
||||
Response: resp.OK(),
|
||||
Recipe: recipe,
|
||||
}
|
||||
log.Debug("response", slog.Any("resp", resp))
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
func New(log *slog.Logger) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
log = log.With(
|
||||
slog.String("component", "midddleware/logger"),
|
||||
)
|
||||
|
||||
log.Info("logger middleware enabled")
|
||||
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
entry := log.With(
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("remote_addr", r.RemoteAddr),
|
||||
slog.String("user_agent", r.UserAgent()),
|
||||
slog.String("request_id", middleware.GetReqID(r.Context())),
|
||||
)
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
entry.Info("request completed",
|
||||
slog.Int("status", ww.Status()),
|
||||
slog.Int("bytes", ww.BytesWritten()),
|
||||
slog.String("duration", time.Since(t1).String()),
|
||||
)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(ww, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
StatusOK = "OK"
|
||||
StatusError = "Error"
|
||||
)
|
||||
|
||||
func OK() Response {
|
||||
return Response{
|
||||
Status: StatusOK,
|
||||
}
|
||||
}
|
||||
|
||||
func Error(msg string) Response {
|
||||
return Response{
|
||||
Status: StatusError,
|
||||
Error: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func ValidationError(errs validator.ValidationErrors) Response {
|
||||
var errMsgs []string
|
||||
|
||||
for _, err := range errs {
|
||||
switch err.ActualTag() {
|
||||
case "required":
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("field %s is a required field", err.Field()))
|
||||
default:
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("field %s is not valid", err.Field()))
|
||||
}
|
||||
}
|
||||
|
||||
return Response{
|
||||
Status: StatusError,
|
||||
Error: strings.Join(errMsgs, ", "),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package sl
|
||||
|
||||
import "log/slog"
|
||||
|
||||
func Err(err error) slog.Attr {
|
||||
return slog.Attr{
|
||||
Key: "error",
|
||||
Value: slog.StringValue(err.Error()),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue