Skip to content

Commit

Permalink
Merge pull request #9 from doutorfinancas/rlopes/adds_qr_codes
Browse files Browse the repository at this point in the history
feat: resolve #6 by implementing qr code generation
  • Loading branch information
bolovsky authored Apr 14, 2023
2 parents 4813789 + f44633e commit b1db786
Show file tree
Hide file tree
Showing 19 changed files with 243 additions and 25 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ DB_NAME=pun_sho
DB_URL=something-somewhere-over-the-rainbow
DB_PORT=1337
DB_ADAPTOR=cockroach
QR_PNG_LOGO=./img/logo_df.png
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,29 @@ make migration/clean

### Create a short link
```bash
curl -H 'token: Whatever_Token_you_put_in_your_env' -XPOST -d '{"link": "https://www.google.pt/", "TTL": "2023-03-25T23:59:59Z"}' https://yourdomain.something/api/v1/short
read -r -d '' BODY <<EOF
{
"link": "https://www.google.pt/",
"TTL": "2023-03-25T23:59:59Z",
"qr_code": {
"create": true,
"width" : 50,
"height": 50,
"foreground_color": "#000000",
"background_color": "#ffffff",
"shape": "circle"
}
}
EOF

# you could use "background_color": "transparent" to request a png without background
# 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 \
-H 'token: Whatever_Token_you_put_in_your_env' \
-H 'Content-Type: application/json' \
-d $BODY
```

this would render an answer like:
Expand Down
1 change: 1 addition & 0 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Config struct {
Token string `env:"AUTH_TOKEN"`
HostName string `env:"HOST_NAME"`
UnknownPage string `env:"UNKNOWN_PAGE"`
QRLogo string `env:"QR_PNG_LOGO"`
DBUsername string `env:"DB_USERNAME"`
DBPassword string `env:"DB_PASSWORD"`
DBName string `env:"DB_NAME"`
Expand Down
14 changes: 12 additions & 2 deletions api/request/create_shorty.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import (
)

type CreateShorty struct {
Link string `json:"link"`
TTL *time.Time `json:"TTL"`
Link string `json:"link"`
TTL *time.Time `json:"TTL"`
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"`
}
20 changes: 20 additions & 0 deletions buf/write_closer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package buf

import (
"bufio"
"io"
)

type WriteCloser struct {
*bufio.Writer
}

func NewWriteCloser(w io.Writer) WriteCloser {
return WriteCloser{
bufio.NewWriter(w),
}
}

func (wc WriteCloser) Close() error {
return wc.Flush()
}
18 changes: 9 additions & 9 deletions db_migrations/000001_init.up.sql
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
CREATE TABLE IF NOT EXISTS pun_sho.shorties (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
public_id STRING,
link STRING,
public_id TEXT,
link TEXT,
ttl TIMESTAMP DEFAULT NULL,
created_at TIMESTAMP DEFAULT now(),
deleted_at TIMESTAMP DEFAULT NULL,
INDEX (ttl),
INDEX (deleted_at),
INDEX (created_at),
UNIQUE INDEX (public_id)
UNIQUE (public_id)
);

CREATE TABLE IF NOT EXISTS pun_sho.shorty_accesses (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
shorty_id UUID,
meta jsonb,
user_agent string,
ip_address string,
extra string,
operating_system STRING,
browser STRING,
meta JSONB,
user_agent TEXT,
ip_address TEXT,
extra TEXT,
operating_system TEXT,
browser TEXT,
created_at TIMESTAMP DEFAULT now(),
INDEX (shorty_id),
INDEX (ip_address),
Expand Down
2 changes: 1 addition & 1 deletion db_migrations/000002_add_visit_status.down.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ALTER TABLE pun_sho.shorty_accesses
DROP COLUMN status STRING;
DROP COLUMN status;
2 changes: 1 addition & 1 deletion db_migrations/000002_add_visit_status.up.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ALTER TABLE pun_sho.shorty_accesses
ADD COLUMN status STRING;
ADD COLUMN status TEXT;
2 changes: 2 additions & 0 deletions db_migrations/000003_add_qr_code_to_shorty.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE pun_sho.shorties
DROP COLUMN qr_code;
2 changes: 2 additions & 0 deletions db_migrations/000003_add_qr_code_to_shorty.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE pun_sho.shorties
ADD COLUMN qr_code TEXT;
1 change: 1 addition & 0 deletions entity/shorty_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Shorty struct {
ShortLink string `json:"short_link" gorm:"-"`
Visits int `json:"visits" gorm:"-"`
RedirectCount int `json:"redirects" gorm:"-"`
QRCode string `json:"qr_code,omitempty" gorm:"column:qr_code"`
}

func (*Shorty) TableName() string {
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ require (
github.com/mileusna/useragent v1.2.1
github.com/stretchr/testify v1.8.2
github.com/subosito/gotenv v1.4.2
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
go.uber.org/zap v1.24.0
golang.org/x/net v0.7.0
golang.org/x/text v0.7.0
Expand All @@ -24,12 +26,14 @@ require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
Expand All @@ -47,13 +51,15 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
go.opentelemetry.io/otel v1.10.0 // indirect
go.opentelemetry.io/otel/trace v1.10.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/goleak v1.1.12 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/sys v0.5.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
Expand Down Expand Up @@ -42,6 +44,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down Expand Up @@ -117,6 +121,12 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yeqown/go-qrcode/v2 v2.2.1 h1:Jc1Q916fwC05R8C7mpWDbrT9tyLPaLLKDABoC5XBCe8=
github.com/yeqown/go-qrcode/v2 v2.2.1/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24=
github.com/yeqown/go-qrcode/writer/standard v1.2.1 h1:FMRZiur5yApUIe4fqtqmcdl/XQTZAZWt2DhkPx4VIW0=
github.com/yeqown/go-qrcode/writer/standard v1.2.1/go.mod h1:ZelyDFiVymrauRjUn454iF7bjsabmB1vixkDA5kq2bw=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4=
Expand All @@ -143,6 +153,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand Down
Binary file added img/logo_df.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func main() {

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

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

Expand Down
83 changes: 74 additions & 9 deletions service/shorty.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package service

import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"strings"
"time"

"github.com/doutorfinancas/pun-sho/buf"
"github.com/google/uuid"
"github.com/mileusna/useragent"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
"go.uber.org/zap"

"github.com/doutorfinancas/pun-sho/api/request"
Expand All @@ -16,29 +21,33 @@ import (
)

const (
PublicIDSize = 10
StatusRedirected = "redirected"
StatusBlocked = "blocked"
StatusExpired = "expired"
StatusDeleted = "deleted"
VersionStringify = "%s %s"
PublicIDSize = 10
StatusRedirected = "redirected"
StatusBlocked = "blocked"
StatusExpired = "expired"
StatusDeleted = "deleted"
VersionStringify = "%s %s"
TransparentBackground = "transparent"
)

type ShortyService struct {
hostName string
logo string
log *zap.Logger
shortyRepository *entity.ShortyRepository
shortyAccessRepository *entity.ShortyAccessRepository
}

func NewShortyService(
hostName string,
hostName,
logo string,
log *zap.Logger,
shortyRepository *entity.ShortyRepository,
shortyAccessRepository *entity.ShortyAccessRepository,
) *ShortyService {
return &ShortyService{
hostName: strings.TrimSuffix(hostName, "/"),
logo: logo,
log: log,
shortyRepository: shortyRepository,
shortyAccessRepository: shortyAccessRepository,
Expand All @@ -52,12 +61,68 @@ func (s *ShortyService) Create(req *request.CreateShorty) (*entity.Shorty, error
TTL: req.TTL,
}

m.ShortLink = fmt.Sprintf("%s/s/%s", s.hostName, m.PublicID)
if req.QRCode != nil && req.QRCode.Create {
qrc, err := qrcode.New(m.ShortLink)
if err != nil {
return nil, err
}

bgColor := standard.WithBgColorRGBHex("#ffffff")
fgColor := standard.WithFgColorRGBHex("#000000")

if str.SubString(req.QRCode.BgColor, 0, 1) == "#" {
bgColor = standard.WithBgColorRGBHex(req.QRCode.BgColor)
}

if req.QRCode.BgColor == TransparentBackground {
bgColor = standard.WithBgTransparent()
}

if str.SubString(req.QRCode.FgColor, 0, 1) == "#" {
fgColor = standard.WithFgColorRGBHex(req.QRCode.FgColor)
}

options := []standard.ImageOption{
bgColor,
fgColor,
standard.WithBuiltinImageEncoder(standard.PNG_FORMAT),
}

if s.logo != "" {
fmt.Println(s.logo)
options = append(options, standard.WithLogoImageFilePNG(s.logo))
}

if req.QRCode.Width > 0 {
options = append(options, standard.WithQRWidth(uint8(req.QRCode.Width)))
}

if req.QRCode.BorderWidth > 0 {
options = append(options, standard.WithBorderWidth(req.QRCode.BorderWidth))
}

if req.QRCode.Shape == "circle" {
options = append(options, standard.WithCircleShape())
}

var b []byte
x := bytes.NewBuffer(b)
w := buf.NewWriteCloser(x)
wr := standard.NewWithWriter(w, options...)

err = qrc.Save(wr)
if err != nil {
return nil, err
}

m.QRCode = "data:image/png;base64," + base64.StdEncoding.EncodeToString(x.Bytes())
}

if err := s.shortyRepository.Create(m); err != nil {
return nil, err
}

m.ShortLink = fmt.Sprintf("%s/s/%s", s.hostName, m.PublicID)

return m, nil
}

Expand Down
2 changes: 1 addition & 1 deletion service/shorty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestShortyService_Create(t *testing.T) {
},
},
)
req := `INSERT INTO "shorties" ("public_id","link","ttl","created_at","deleted_at") VALUES ($1,$2,$3,$4,$5)`
req := `INSERT INTO "shorties" ("public_id","link","ttl","created_at","deleted_at","qr_code") VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id"`
m.ExpectBegin()
m.ExpectQuery(regexp.QuoteMeta(req)).WithArgs().WillReturnRows(rows)
m.ExpectCommit()
Expand Down
21 changes: 21 additions & 0 deletions str/sub_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package str

import "strings"

func SubString(str string, start int, end int) string {
wb := strings.Split(str, "")

if start < 0 || end < 0 {
return ""
}

if len(wb) < start {
return ""
}

if len(wb) < end {
return strings.Join(wb[start:], "")
}

return strings.Join(wb[start:end], "")
}
Loading

0 comments on commit b1db786

Please sign in to comment.