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