-
Notifications
You must be signed in to change notification settings - Fork 0
Increment13 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Increment13 #29
Changes from all commits
86ba166
146ab5f
4e7b244
932b262
9979fd4
8789ef3
aba8d7b
d1e4ead
286531f
9f6a70b
446ad7c
3810250
2129571
a77e461
a43930d
0495319
2eb016a
6e3dd23
1f74211
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,34 +2,43 @@ package handler | |
|
||
import ( | ||
"crypto" | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/combodga/Project/internal/storage" | ||
|
||
"github.com/btcsuite/btcutil/base58" | ||
"github.com/google/uuid" | ||
"github.com/labstack/echo/v4" | ||
) | ||
|
||
type Handler struct { | ||
ServerAddr string | ||
BaseURL string | ||
Storage *storage.Storage | ||
ServerAddr string | ||
BaseURL string | ||
Storage *storage.Storage | ||
DBCredentials string | ||
Key string | ||
} | ||
|
||
func New(serverAddr, baseURL, dbFile string) (*Handler, error) { | ||
s, err := storage.New(dbFile) | ||
func New(serverAddr, baseURL, dbFile, dbCredentials string) (*Handler, error) { | ||
s, err := storage.New(dbFile, dbCredentials) | ||
if err != nil { | ||
err = fmt.Errorf("storage: %v", err) | ||
} | ||
return &Handler{ | ||
ServerAddr: serverAddr, | ||
BaseURL: baseURL, | ||
Storage: s, | ||
ServerAddr: serverAddr, | ||
BaseURL: baseURL, | ||
Storage: s, | ||
DBCredentials: dbCredentials, | ||
Key: "b8ffa0f4-3f11-44b1-b0bf-9109f47e468b", | ||
}, err | ||
} | ||
|
||
|
@@ -38,14 +47,18 @@ type Link struct { | |
} | ||
|
||
func (h *Handler) CreateURL(c echo.Context) error { | ||
user := getUser(c, h.Key) | ||
body, err := io.ReadAll(c.Request().Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
link := string(body) | ||
|
||
id, err := h.fetchID(c, link) | ||
id, err := h.fetchID(c, user, link) | ||
if strings.Contains(fmt.Sprintf("%v", err), "shortener_short_key") { | ||
return c.String(http.StatusConflict, h.BaseURL+"/"+id) | ||
} | ||
if err != nil { | ||
return fmt.Errorf("fetch id: %v", err) | ||
} | ||
|
@@ -54,6 +67,7 @@ func (h *Handler) CreateURL(c echo.Context) error { | |
} | ||
|
||
func (h *Handler) CreateURLInJSON(c echo.Context) error { | ||
user := getUser(c, h.Key) | ||
body, err := io.ReadAll(c.Request().Body) | ||
if err != nil { | ||
return err | ||
|
@@ -70,17 +84,78 @@ func (h *Handler) CreateURLInJSON(c echo.Context) error { | |
return errors.New("error reading json") | ||
} | ||
|
||
id, err := h.fetchID(c, link) | ||
uniqueErr := false | ||
id, err := h.fetchID(c, user, link) | ||
if strings.Contains(fmt.Sprintf("%v", err), "shortener_short_key") { | ||
err = nil | ||
uniqueErr = true | ||
} | ||
if err != nil { | ||
return fmt.Errorf("fetchID: %v", err) | ||
return fmt.Errorf("fetch id: %v", err) | ||
} | ||
|
||
l := &Link{ | ||
Result: h.BaseURL + "/" + id, | ||
} | ||
|
||
if uniqueErr { | ||
return c.JSON(http.StatusConflict, l) | ||
} | ||
|
||
return c.JSON(http.StatusCreated, l) | ||
} | ||
|
||
type LinkJSON struct { | ||
CorrelationID string `json:"correlation_id"` | ||
OriginalURL string `json:"original_url"` | ||
} | ||
|
||
type BatchLink struct { | ||
CorrelationID string `json:"correlation_id"` | ||
ShortURL string `json:"short_url"` | ||
} | ||
|
||
func (h *Handler) CreateBatchURL(c echo.Context) error { | ||
user := getUser(c, h.Key) | ||
body, err := io.ReadAll(c.Request().Body) | ||
if err != nil { | ||
return err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ни в коем случае не стоит возвращать ошибки без дополнительной информации о том, где произошла ошибка. Иначе по ошибке нельзя будет локализовать место, в котором она произошла, либо на локализацию потребуются значительные усилия. Этот момент хорошо описан в учебнике в разделе "Интроспекция и логирование ошибок". Можно воспользоваться стандартным средством |
||
} | ||
|
||
var l []LinkJSON | ||
err = json.Unmarshal(body, &l) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var bl []BatchLink | ||
var uniqueErr bool | ||
for result := range l { | ||
link := l[result] | ||
|
||
var id string | ||
id, err = h.fetchID(c, user, link.OriginalURL) | ||
if strings.Contains(fmt.Sprintf("%v", err), "shortener_short_key") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Заменить на кастомную ошибку |
||
err = nil | ||
uniqueErr = true | ||
} | ||
if err != nil { | ||
return fmt.Errorf("fetch id: %v", err) | ||
} | ||
|
||
bl = append(bl, BatchLink{ | ||
CorrelationID: link.CorrelationID, | ||
ShortURL: h.BaseURL + "/" + id, | ||
}) | ||
} | ||
|
||
if uniqueErr { | ||
return c.JSON(http.StatusConflict, bl) | ||
} | ||
|
||
return c.JSON(http.StatusCreated, bl) | ||
} | ||
|
||
func (h *Handler) RetrieveURL(c echo.Context) error { | ||
id := c.Param("id") | ||
|
||
|
@@ -92,7 +167,39 @@ func (h *Handler) RetrieveURL(c echo.Context) error { | |
return c.Redirect(http.StatusTemporaryRedirect, url) | ||
} | ||
|
||
func (h *Handler) fetchID(c echo.Context, link string) (string, error) { | ||
type Element struct { | ||
ShortURL string `json:"short_url"` | ||
OriginalURL string `json:"original_url"` | ||
} | ||
|
||
func (h *Handler) ListURL(c echo.Context) error { | ||
user := getUser(c, h.Key) | ||
list, ok := h.Storage.ListURL(user) | ||
if !ok { | ||
return c.String(http.StatusNoContent, "error, you haven't any saved links") | ||
} | ||
|
||
var arr []*Element | ||
for shortURL, originalURL := range list { | ||
arr = append(arr, &Element{ | ||
ShortURL: h.BaseURL + "/" + shortURL, | ||
OriginalURL: originalURL, | ||
}) | ||
} | ||
|
||
return c.JSON(http.StatusOK, arr) | ||
} | ||
|
||
func (h *Handler) Ping(c echo.Context) error { | ||
ok := h.Storage.Ping() | ||
if !ok { | ||
return c.String(http.StatusInternalServerError, "error, no connection to db") | ||
} | ||
|
||
return c.String(http.StatusOK, "db connected") | ||
} | ||
|
||
func (h *Handler) fetchID(c echo.Context, user, link string) (string, error) { | ||
if len(link) > 2048 { | ||
return "", c.String(http.StatusBadRequest, "error, the link cannot be longer than 2048 characters") | ||
} | ||
|
@@ -110,12 +217,15 @@ func (h *Handler) fetchID(c echo.Context, link string) (string, error) { | |
} | ||
} | ||
|
||
err = h.Storage.SetURL(id, link) | ||
err = h.Storage.SetURL(user, id, link) | ||
if strings.Contains(fmt.Sprintf("%v", err), "shortener_short_key") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Заменить на кастомную ошибку |
||
return id, err | ||
} | ||
if err != nil { | ||
return "", c.String(http.StatusInternalServerError, "error, failed to store a shortened URL") | ||
} | ||
|
||
return id, nil | ||
return id, err | ||
} | ||
|
||
func shortener(s string) (string, error) { | ||
|
@@ -130,3 +240,43 @@ func shortener(s string) (string, error) { | |
|
||
return id, nil | ||
} | ||
|
||
func getUser(c echo.Context, key string) string { | ||
user, err1 := readCookie(c, "user") | ||
sign, err2 := readCookie(c, "sign") | ||
if err1 == nil && err2 == nil && sign == getSign(user, key) { | ||
return user | ||
} | ||
|
||
user = randUser() | ||
writeCookie(c, "user", user) | ||
writeCookie(c, "sign", getSign(user, key)) | ||
return user | ||
} | ||
|
||
func randUser() string { | ||
uuidWithHyphen := uuid.New() | ||
return uuidWithHyphen.String() | ||
} | ||
|
||
func getSign(user, key string) string { | ||
h := hmac.New(sha256.New, []byte(key)) | ||
h.Write([]byte(user)) | ||
dst := h.Sum(nil) | ||
return hex.EncodeToString(dst)[:32] | ||
} | ||
|
||
func writeCookie(c echo.Context, name, value string) { | ||
cookie := new(http.Cookie) | ||
cookie.Name = name | ||
cookie.Value = value | ||
c.SetCookie(cookie) | ||
} | ||
|
||
func readCookie(c echo.Context, name string) (string, error) { | ||
cookie, err := c.Cookie(name) | ||
if err != nil { | ||
return "", err | ||
} | ||
return cookie.Value, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сравнение ошибки со строкой лишает нас преимущества проверки типов и чревато ошибками. Правильный путь проверки ошибки описан в учебнике в разделе "Интроспекция и логирование ошибок".
Тут самым простым вариантом будет создание своего типа или своей ошибки в пакете storage, например,
var ErrDupKey = fmt.Errorf("duplicate key")
и проверка на ошибку на уровне хэндлера черезerrors.Is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кроме того вижу, что код дублируется в функции fetchID