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' (#15)
  • Loading branch information
bolovsky authored Jun 19, 2023
1 parent f845171 commit d59cf54
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 121 deletions.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ EOF
# by setting env property QR_PNG_LOGO to a png filepath,
# it will overlay the logo on qrcode center

curl -XPOST https://yourdomain.something/api/v1/short \
curl -XPOST http://localhost:8080/api/v1/short \
-H 'token: ThisIsA5uper$ecureAPIToken' \
-H 'Content-Type: application/json' \
-d $BODY
Expand All @@ -77,12 +77,36 @@ This would render an answer like:
"created_at":"2023-03-20T10:50:38.399449Z",
"deleted_at":null,
"accesses":null,
"qr_code": "data:image/png;base64,ASfojih134kjhas9f8798134lk2fasf...",
"short_link":"https://env.configured.domain/s/SEdeyZByeP",
"visits":0,
"redirects":0
}
```

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 http://localhost:8080/api/v1/preview \
-H 'token: ThisIsA5uper$ecureAPIToken' \
-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
Expand Down Expand Up @@ -177,6 +201,10 @@ make migration/clean
## Releases
We are currently working actively in the project and as such there still isn't a closed API.

However, we are already using it in production, in our own projects.

We consider this to be an MVP and as such use it at your own risk.

we will be releasing 0.X until we bind a contract to the API

## Next Steps
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.NewFailure("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.NewFailure("invalid payload"),
)
return
}
s, err := h.qrSvc.Generate(m.QRCode, m.Link)
if err != nil {
c.JSON(
http.StatusBadRequest,
response.NewFailure("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"`
}
21 changes: 0 additions & 21 deletions api/response.go

This file was deleted.

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,
}
}
21 changes: 21 additions & 0 deletions api/response/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package response

const Ok = "ok"
const Error = "error"

type BaseResponse struct {
Status string `json:"status"`
}

type FailureResponse struct {
BaseResponse
Message []string `json:"message,omitempty"`
}

func NewFailure(message string) *FailureResponse {
return &FailureResponse{BaseResponse{Status: Error}, []string{message}}
}

func NewOk() *BaseResponse {
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.NewFailure("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.NewFailure(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.NewFailure("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.NewFailure("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.NewFailure("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.NewFailure("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.NewFailure("kaput, no delete"),
)
}
c.JSON(http.StatusOK, nil)
Expand Down
Loading

0 comments on commit d59cf54

Please sign in to comment.