Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/shortener/shortener.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ func main() {
var serverAddr string
var baseURL string
var dbFile string
var dbCredentials string

flag.StringVar(&serverAddr, "a", os.Getenv("SERVER_ADDRESS"), "server address")
flag.StringVar(&baseURL, "b", os.Getenv("BASE_URL"), "base URL")
flag.StringVar(&dbFile, "f", os.Getenv("FILE_STORAGE_PATH"), "file storage path")
flag.StringVar(&dbCredentials, "d", os.Getenv("DATABASE_DSN"), "database credentials")
flag.Parse()

if serverAddr == "" {
Expand All @@ -25,7 +27,7 @@ func main() {
baseURL = "http://" + serverAddr
}

err := app.Start(serverAddr, baseURL, dbFile)
err := app.Start(serverAddr, baseURL, dbFile, dbCredentials)
if err != nil {
panic(err)
}
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ require (
)

require (
github.com/google/uuid v1.3.0
github.com/jmoiron/sqlx v1.3.5
github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.3.1 // indirect
github.com/lib/pq v1.10.7
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
Expand All @@ -29,10 +34,14 @@ github.com/labstack/echo/v4 v4.8.0 h1:wdc6yKVaHxkNOEdz4cRZs1pQkwSXPiRjq69yWP4QQS
github.com/labstack/echo/v4 v4.8.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
Expand Down
7 changes: 5 additions & 2 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"github.com/labstack/echo/v4/middleware"
)

func Start(serverAddr, baseURL, dbFile string) error {
h, err := handler.New(serverAddr, baseURL, dbFile)
func Start(serverAddr, baseURL, dbFile, dbCredentials string) error {
h, err := handler.New(serverAddr, baseURL, dbFile, dbCredentials)
if err != nil {
return fmt.Errorf("handler: %v", err)
}
Expand All @@ -22,6 +22,9 @@ func Start(serverAddr, baseURL, dbFile string) error {
e.POST("/", h.CreateURL)
e.GET("/:id", h.RetrieveURL)
e.POST("/api/shorten", h.CreateURLInJSON)
e.POST("/api/shorten/batch", h.CreateBatchURL)
e.GET("/api/user/urls", h.ListURL)
e.GET("/ping", h.Ping)

e.Logger.Fatal(e.Start(serverAddr))

Expand Down
147 changes: 135 additions & 12 deletions internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package handler

import (
"crypto"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
Expand All @@ -12,24 +15,29 @@ import (
"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
}

Expand All @@ -38,14 +46,15 @@ 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 err != nil {
return fmt.Errorf("fetch id: %v", err)
}
Expand All @@ -54,6 +63,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
Expand All @@ -70,7 +80,7 @@ func (h *Handler) CreateURLInJSON(c echo.Context) error {
return errors.New("error reading json")
}

id, err := h.fetchID(c, link)
id, err := h.fetchID(c, user, link)
if err != nil {
return fmt.Errorf("fetchID: %v", err)
}
Expand All @@ -81,6 +91,47 @@ func (h *Handler) CreateURLInJSON(c echo.Context) error {
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
}

var l []LinkJSON
err = json.Unmarshal(body, &l)
if err != nil {
return err
}

var bl []BatchLink
for result := range l {
link := l[result]

id, err := h.fetchID(c, user, link.OriginalURL)
if err != nil {
return fmt.Errorf("fetchID: %v", err)
}

bl = append(bl, BatchLink{
CorrelationID: link.CorrelationID,
ShortURL: h.BaseURL + "/" + id,
})
}

return c.JSON(http.StatusCreated, bl)
}

func (h *Handler) RetrieveURL(c echo.Context) error {
id := c.Param("id")

Expand All @@ -92,7 +143,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")
}
Expand All @@ -110,7 +193,7 @@ 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 err != nil {
return "", c.String(http.StatusInternalServerError, "error, failed to store a shortened URL")
}
Expand All @@ -130,3 +213,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
}
2 changes: 1 addition & 1 deletion internal/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var (

func TestInit(t *testing.T) {
var err error
H, err = New("localhost:8080", "http://localhost:8080", "")
H, err = New("localhost:8080", "http://localhost:8080", "", "")
if err != nil {
t.Fatal("can't start test")
}
Expand Down
Loading