From 10805ef2d5b9b924fe096db00eba4d46f601bd7c Mon Sep 17 00:00:00 2001 From: Sergey Chubaryan Date: Mon, 5 Aug 2024 09:58:45 +0300 Subject: [PATCH] add shortlink generator and handlers --- main.go | 9 +++++ src/handlers/shortlink_handlers.go | 61 ++++++++++++++++++++++++++++++ src/services/shortlink_service.go | 60 +++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/handlers/shortlink_handlers.go create mode 100644 src/services/shortlink_service.go diff --git a/main.go b/main.go index 1ed5c95..1c7d398 100644 --- a/main.go +++ b/main.go @@ -90,6 +90,11 @@ func main() { ActionTokenRepo: actionTokenRepo, }, ) + linkService := services.NewShortlinkSevice( + services.NewShortlinkServiceParams{ + Cache: repo.NewCacheInmem[string, string](7 * 24 * 60 * 60), + }, + ) if !debugMode { gin.SetMode(gin.ReleaseMode) @@ -99,6 +104,10 @@ func main() { r.Use(middleware.NewRequestLogMiddleware(logger)) r.Use(gin.Recovery()) + linkGroup := r.Group("/s") + linkGroup.POST("/new", handlers.NewShortlinkCreateHandler(linkService)) + linkGroup.GET("/:linkId", handlers.NewShortlinkResolveHandler(linkService)) + userGroup := r.Group("/user") userGroup.POST("/create", handlers.NewUserCreateHandler(userService)) userGroup.POST("/login", handlers.NewUserLoginHandler(userService)) diff --git a/src/handlers/shortlink_handlers.go b/src/handlers/shortlink_handlers.go new file mode 100644 index 0000000..f228e8e --- /dev/null +++ b/src/handlers/shortlink_handlers.go @@ -0,0 +1,61 @@ +package handlers + +import ( + "backend/src/services" + "encoding/json" + "fmt" + "net/url" + + "github.com/gin-gonic/gin" +) + +type shortlinkCreateOutput struct { + Link string `json:"link"` +} + +func NewShortlinkCreateHandler(shortlinkService services.ShortlinkService) gin.HandlerFunc { + return func(ctx *gin.Context) { + rawUrl := ctx.Query("url") + if rawUrl == "" { + ctx.AbortWithError(400, fmt.Errorf("no url param")) + return + } + + u, err := url.Parse(rawUrl) + if err != nil { + ctx.Data(500, "plain/text", []byte(err.Error())) + return + } + u.Scheme = "https" + + linkId, err := shortlinkService.CreateLink(u.String()) + if err != nil { + ctx.Data(500, "plain/text", []byte(err.Error())) + return + } + + resultBody, err := json.Marshal(shortlinkCreateOutput{ + Link: "https:/nucrea.ru/s/" + linkId, + }) + if err != nil { + ctx.AbortWithError(500, err) + return + } + + ctx.Data(200, "application/json", resultBody) + } +} + +func NewShortlinkResolveHandler(shortlinkService services.ShortlinkService) gin.HandlerFunc { + return func(ctx *gin.Context) { + linkId := ctx.Param("linkId") + + linkUrl, err := shortlinkService.GetLink(linkId) + if err != nil { + ctx.AbortWithError(500, err) + return + } + + ctx.Redirect(301, linkUrl) + } +} diff --git a/src/services/shortlink_service.go b/src/services/shortlink_service.go new file mode 100644 index 0000000..d3f2469 --- /dev/null +++ b/src/services/shortlink_service.go @@ -0,0 +1,60 @@ +package services + +import ( + "backend/src/repo" + "fmt" + "math/rand" + "strings" + "time" +) + +type ShortlinkService interface { + CreateLink(in string) (string, error) + GetLink(id string) (string, error) +} + +type NewShortlinkServiceParams struct { + Endpoint string + Cache repo.Cache[string, string] +} + +func NewShortlinkSevice(params NewShortlinkServiceParams) ShortlinkService { + return &shortlinkService{ + cache: params.Cache, + } +} + +type shortlinkService struct { + cache repo.Cache[string, string] +} + +func (s *shortlinkService) randomStr() string { + src := rand.NewSource(time.Now().UnixMicro()) + randGen := rand.New(src) + + builder := strings.Builder{} + for i := 0; i < 9; i++ { + offset := 0x41 + if randGen.Int()%2 == 1 { + offset = 0x61 + } + + byte := offset + (randGen.Int() % 26) + builder.WriteRune(rune(byte)) + } + return builder.String() +} + +func (s *shortlinkService) CreateLink(in string) (string, error) { + str := s.randomStr() + s.cache.Set(str, in, 7*24*60*60) + return str, nil +} + +func (s *shortlinkService) GetLink(id string) (string, error) { + val, ok := s.cache.Get(id) + if !ok { + return "", fmt.Errorf("link does not exist or expired") + } + return val, nil +}