Skip to content

Commit

Permalink
feat: closes #13 and implementspreview endpoint and logo via base64 i…
Browse files Browse the repository at this point in the history
…n json body arg 'logo'
  • Loading branch information
bolovsky committed Jun 5, 2023
1 parent f845171 commit 2c228f9
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 99 deletions.
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ EOF
# it will overlay the logo on qrcode center

curl -XPOST https://yourdomain.something/api/v1/short \
-H 'token: ThisIsA5uper$ecureAPIToken' \
-H 'token: Whatever_Token_you_put_in_your_env' \
-H 'Content-Type: application/json' \
-d $BODY
```
Expand All @@ -73,7 +73,6 @@ This would render an answer like:
"id":"4b677dfe-e17a-46e7-9cd2-25a45e8cb19c",
"link":"https://www.google.pt/",
"TTL":"2023-03-25T23:59:59Z",
"redirection_limit": 5,
"created_at":"2023-03-20T10:50:38.399449Z",
"deleted_at":null,
"accesses":null,
Expand All @@ -83,9 +82,32 @@ This would render an answer like:
}
```

If you want to preview the QR code only, you can use the preview endpoint with the same body as above
No TTL exists in that endpoint though (as its only preview mode), and the link is exactly the one you sent
```bash
read -r -d '' BODY <<EOF
{
"link": "https://www.google.pt/",
"qr_code": {
"create": true,
"width" : 50,
"height": 50,
"foreground_color": "#000000",
"background_color": "#ffffff",
"shape": "circle"
}
}
EOF

curl -XPOST https://yourdomain.something/api/v1/preview \
-H 'token: Whatever_Token_you_put_in_your_env' \
-H 'Content-Type: application/json' \
-d $BODY
```

### Get statistics from a visited link
```bash
curl -H 'token: ThisIsA5uper$ecureAPIToken' http://localhost:8080/api/v1/short/c62cbe57-7e45-4e87-a7c1-11cfb006870b
curl -H 'token: ThisIsA5uper$ecureAPIToken' https://yourdomain.something/api/v1/short/c62cbe57-7e45-4e87-a7c1-11cfb006870b
```

This would render an answer like ("visits" and "redirects" will only be equal to 1 if you access the link once):
Expand Down Expand Up @@ -128,7 +150,7 @@ This would render an answer like ("visits" and "redirects" will only be equal to

### Get a list of links
```bash
curl -H 'token: ThisIsA5uper$ecureAPIToken' http://localhost:8080/api/v1/short/?limit=20&offset=0
curl -H 'token: ThisIsA5uper$ecureAPIToken' https://yourdomain.something/api/v1/short/?limit=20&offset=0
```

Which will return a list of short links, using pagination.
Expand Down
26 changes: 17 additions & 9 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ import (

type API struct {
BaseGinServer
log *zap.Logger
config *Config
service *service.ShortyService
log *zap.Logger
config *Config
shortySvc *service.ShortyService
qrSvc *service.QRCodeService
}

func NewAPI(log *zap.Logger, config *Config, shortyService *service.ShortyService) *API {
func NewAPI(
log *zap.Logger,
config *Config,
shortyService *service.ShortyService,
qrSvc *service.QRCodeService,
) *API {
return &API{
log: log,
config: config,
service: shortyService,
log: log,
config: config,
shortySvc: shortyService,
qrSvc: qrSvc,
}
}

Expand All @@ -39,13 +46,14 @@ func (a *API) Run() {
gzip.Gzip(gzip.DefaultCompression),
)

a.PushHandlerWithGroup(NewURLHandler(a.config.UnknownPage, a.service), g.Group("/"))
a.PushHandlerWithGroup(NewURLHandler(a.config.UnknownPage, a.shortySvc), g.Group("/"))

authMiddleware := NewAuthenticationMiddleware(a.config.Token)

apiGroup := g.Group("/api/v1")
apiGroup.Use(authMiddleware.Authenticated)
a.PushHandlerWithGroup(NewShortenerHandler(a.service), apiGroup)
a.PushHandlerWithGroup(NewShortenerHandler(a.shortySvc), apiGroup)
a.PushHandlerWithGroup(NewPreviewHandler(a.qrSvc), apiGroup)

if err := g.Run(fmt.Sprintf(":%d", a.config.Port)); err != nil {
a.log.Fatal(err.Error())
Expand Down
3 changes: 2 additions & 1 deletion api/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"net/http"

"github.com/doutorfinancas/pun-sho/api/response"
"github.com/gin-gonic/gin"
)

Expand All @@ -20,7 +21,7 @@ func (a *AuthenticationMiddleware) Authenticated(c *gin.Context) {
token := c.GetHeader("token")

if token != a.token {
c.AbortWithStatusJSON(http.StatusUnauthorized, NewErrorResponse("unauthorized"))
c.AbortWithStatusJSON(http.StatusUnauthorized, response.NewErrorResponse("unauthorized"))
return
}

Expand Down
55 changes: 55 additions & 0 deletions api/preview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package api

import (
"net/http"

"github.com/doutorfinancas/pun-sho/api/request"
"github.com/doutorfinancas/pun-sho/api/response"
"github.com/doutorfinancas/pun-sho/service"
"github.com/doutorfinancas/pun-sho/str"
"github.com/gin-gonic/gin"
)

type previewHandler struct {
qrSvc *service.QRCodeService
}

func NewPreviewHandler(qrSvc *service.QRCodeService) HTTPHandler {
return &previewHandler{
qrSvc: qrSvc,
}
}

func (h *previewHandler) Routes(rg *gin.RouterGroup) {
rg.POST("", h.CreateLink)
rg.POST("/", h.CreateLink)
}

func (h *previewHandler) Group() *string {
return str.ToStringNil("preview")
}

func (h *previewHandler) CreateLink(c *gin.Context) {
m := &request.GeneratePreview{}
err := c.BindJSON(m)

if err != nil {
c.JSON(
http.StatusBadRequest,
response.NewErrorResponse("invalid payload"),
)
return
}
s, err := h.qrSvc.Generate(m.QRCode, m.Link)
if err != nil {
c.JSON(
http.StatusBadRequest,
response.NewErrorResponse("kaput, no save"),
)
return
}

a := response.NewGeneratePreviewResponse(s, nil)

c.JSON(http.StatusCreated, a)
}
9 changes: 0 additions & 9 deletions api/request/create_shorty.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,3 @@ type CreateShorty struct {
RedirectionLimit *int `json:"redirection_limit"`
QRCode *QRCode `json:"qr_code"`
}

type QRCode struct {
Create bool `json:"create"`
Width int `json:"width"`
BorderWidth int `json:"border_width"`
FgColor string `json:"foreground_color"`
BgColor string `json:"background_color"`
Shape string `json:"shape"`
}
6 changes: 6 additions & 0 deletions api/request/generate_preview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package request

type GeneratePreview struct {
Link string `json:"link"`
QRCode *QRCode `json:"qr_code"`
}
11 changes: 11 additions & 0 deletions api/request/qr_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package request

type QRCode struct {
Create bool `json:"create"`
Width int `json:"width"`
BorderWidth int `json:"border_width"`
FgColor string `json:"foreground_color"`
BgColor string `json:"background_color"`
Shape string `json:"shape"`
LogoImage string `json:"logo"`
}
16 changes: 16 additions & 0 deletions api/response/generate_preview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package response

type GeneratePreviewResponse struct {
BaseResponse
QrCode string `json:"qr_code"`
Message *[]string `json:"message,omitempty"`
}

// NewGeneratePreviewResponse creates a base preview response
func NewGeneratePreviewResponse(qrCode string, message *[]string) *GeneratePreviewResponse {
return &GeneratePreviewResponse{
BaseResponse{Status: Ok},
qrCode,
message,
}
}
10 changes: 5 additions & 5 deletions api/response.go → api/response/response.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package api
package response

const ResponseOk = "ok"
const ResponseError = "error"
const Ok = "ok"
const Error = "error"

type BaseResponse struct {
Status string `json:"status"`
Expand All @@ -13,9 +13,9 @@ type ErrorResponse struct {
}

func NewErrorResponse(message string) *ErrorResponse {
return &ErrorResponse{BaseResponse{Status: ResponseError}, []string{message}}
return &ErrorResponse{BaseResponse{Status: Error}, []string{message}}
}

func NewOkResponse() *BaseResponse {
return &BaseResponse{Status: ResponseOk}
return &BaseResponse{Status: Ok}
}
31 changes: 17 additions & 14 deletions api/shortener.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"net/http"

"github.com/doutorfinancas/pun-sho/api/response"
"github.com/gin-gonic/gin"
"github.com/google/uuid"

Expand All @@ -12,11 +13,13 @@ import (
)

type shortenerHandler struct {
service *service.ShortyService
shortySvc *service.ShortyService
}

func NewShortenerHandler(svc *service.ShortyService) HTTPHandler {
return &shortenerHandler{service: svc}
func NewShortenerHandler(shortySvc *service.ShortyService) HTTPHandler {
return &shortenerHandler{
shortySvc: shortySvc,
}
}

func (h *shortenerHandler) Routes(rg *gin.RouterGroup) {
Expand All @@ -35,11 +38,11 @@ func (h *shortenerHandler) GetLinkInformation(c *gin.Context) {
if id == "" {
c.JSON(
http.StatusBadRequest,
NewErrorResponse("no id provided"),
response.NewErrorResponse("no id provided"),
)
}
parsed := uuid.MustParse(id)
shorty, err := h.service.FindShortyByID(parsed)
shorty, err := h.shortySvc.FindShortyByID(parsed)
if err != nil {
c.JSON(http.StatusNotFound, "shorty not found")
return
Expand All @@ -56,16 +59,16 @@ func (h *shortenerHandler) ListLinks(c *gin.Context) {
if err != nil {
c.JSON(
http.StatusBadRequest,
NewErrorResponse(message),
response.NewErrorResponse(message),
)
return
}

links, err := h.service.List(limit, offset)
links, err := h.shortySvc.List(limit, offset)
if err != nil {
c.JSON(
http.StatusBadRequest,
NewErrorResponse("kaput, no links for you"),
response.NewErrorResponse("kaput, no links for you"),
)
return
}
Expand All @@ -80,15 +83,15 @@ func (h *shortenerHandler) CreateLink(c *gin.Context) {
if err != nil {
c.JSON(
http.StatusBadRequest,
NewErrorResponse("invalid payload"),
response.NewErrorResponse("invalid payload"),
)
return
}
s, err := h.service.Create(m)
s, err := h.shortySvc.Create(m)
if err != nil {
c.JSON(
http.StatusBadRequest,
NewErrorResponse("kaput, no save"),
response.NewErrorResponse("kaput, no save"),
)
return
}
Expand All @@ -101,15 +104,15 @@ func (h *shortenerHandler) RemoveLink(c *gin.Context) {
if id == "" {
c.JSON(
http.StatusBadRequest,
NewErrorResponse("no id provided"),
response.NewErrorResponse("no id provided"),
)
}
parsed := uuid.MustParse(id)
err := h.service.DeleteShortyByUUID(parsed)
err := h.shortySvc.DeleteShortyByUUID(parsed)
if err != nil {
c.JSON(
http.StatusBadRequest,
NewErrorResponse("kaput, no delete"),
response.NewErrorResponse("kaput, no delete"),
)
}
c.JSON(http.StatusOK, nil)
Expand Down
5 changes: 3 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ func main() {

shortyRepo := entity.NewShortyRepository(db, log)
shortyAccessRepo := entity.NewShortyAccessRepository(db, log)
shortySvc := service.NewShortyService(cfg.HostName, cfg.QRLogo, log, shortyRepo, shortyAccessRepo)
qrSvc := service.NewQRCodeService(cfg.QRLogo)
shortySvc := service.NewShortyService(cfg.HostName, cfg.QRLogo, log, shortyRepo, shortyAccessRepo, qrSvc)

a := api.NewAPI(log, cfg, shortySvc)
a := api.NewAPI(log, cfg, shortySvc, qrSvc)

a.Run()
}
Expand Down
Loading

0 comments on commit 2c228f9

Please sign in to comment.