92 lines
2.1 KiB
Go
92 lines
2.1 KiB
Go
|
package save
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"log/slog"
|
||
|
"net/http"
|
||
|
resp "url-shortener/internal/lib/api/response"
|
||
|
"url-shortener/internal/lib/logger/sl"
|
||
|
"url-shortener/internal/lib/random"
|
||
|
"url-shortener/internal/storage"
|
||
|
|
||
|
"github.com/go-chi/chi/v5/middleware"
|
||
|
"github.com/go-chi/render"
|
||
|
"github.com/go-playground/validator/v10"
|
||
|
)
|
||
|
|
||
|
type Request struct {
|
||
|
URL string `json:"url" validate:"required,url"`
|
||
|
Alias string `json:"alias,omitempty"`
|
||
|
}
|
||
|
|
||
|
type Response struct {
|
||
|
resp.Response
|
||
|
Alias string `json:"alias,omitempty"`
|
||
|
}
|
||
|
|
||
|
// TODO: move to config
|
||
|
const aliasLength = 6
|
||
|
|
||
|
//go:generate go run github.com/vektra/mockery/v2@v2.38.0 --name=URLSaver
|
||
|
type URLSaver interface {
|
||
|
SaveURL(urlToSave string, alias string) error
|
||
|
}
|
||
|
|
||
|
func New(log *slog.Logger, urlSaver URLSaver) http.HandlerFunc {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
const op = "handlers.url.save.New"
|
||
|
|
||
|
log = log.With(
|
||
|
slog.String("op", op),
|
||
|
slog.String("request_id", middleware.GetReqID(r.Context())),
|
||
|
)
|
||
|
|
||
|
var req Request
|
||
|
// decode
|
||
|
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 fields
|
||
|
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 or generate alias
|
||
|
// TODO:
|
||
|
alias := req.Alias
|
||
|
if alias == "" {
|
||
|
alias = random.NewRandomString(aliasLength)
|
||
|
}
|
||
|
// save to storage
|
||
|
err = urlSaver.SaveURL(req.URL, alias)
|
||
|
if errors.Is(err, storage.ErrURLExists) {
|
||
|
log.Info("alias already exists", slog.String("url", req.URL))
|
||
|
|
||
|
render.JSON(w, r, resp.Error("alias already exists"))
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
log.Error("failed to add url", sl.Err(err))
|
||
|
render.JSON(w, r, resp.Error("failed to add url"))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
log.Info("url added", slog.String("alias", alias))
|
||
|
|
||
|
render.JSON(w, r, Response{
|
||
|
Response: resp.OK(),
|
||
|
Alias: alias,
|
||
|
})
|
||
|
}
|
||
|
}
|