Skip to content

Commit 40b9cd4

Browse files
authored
Merge pull request #3 from etilite/dev
Prepare v1.0.0
2 parents b3a1c7e + bc8ec51 commit 40b9cd4

File tree

14 files changed

+461
-129
lines changed

14 files changed

+461
-129
lines changed

.github/workflows/go.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: go build
22

33
on:
44
push:
5-
branches: [ "master" ]
5+
branches: [ "main" ]
66
pull_request:
7-
branches: [ "master" ]
7+
branches: [ "main" ]
88

99
jobs:
1010

@@ -32,7 +32,7 @@ jobs:
3232
run: go build -v ./...
3333

3434
- name: Lint
35-
uses: golangci/golangci-lint-action@v3.3.1
35+
uses: golangci/golangci-lint-action@v6
3636
with:
3737
version: latest
3838
args: --timeout 5m

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# qr-coder
2+
[![codecov](https://codecov.io/gh/etilite/qr-coder/graph/badge.svg?token=A70ZRV50JV)](https://codecov.io/gh/etilite/qr-coder)
3+
24
Microservice to generate QR-codes in png format.
3-
Text content is encoded in UTF-8.
5+
6+
By default, `qr-coder` adds to content `UTF-8 BOM` prefix for better unicode-compatibility with scanners.
47

58
## Usage
69
### Build project

cmd/qr-coder/main.go

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,25 @@
11
package main
22

33
import (
4+
"context"
45
"log/slog"
5-
"os"
66
"os/signal"
77
"syscall"
8-
"time"
98

109
"github.com/etilite/qr-coder/internal/app"
1110
httpserver "github.com/etilite/qr-coder/internal/delivery/http"
1211
)
1312

14-
const (
15-
shutdownTime time.Duration = 10 * time.Second
16-
)
17-
1813
func main() {
19-
cfg := app.NewConfigFromEnv()
14+
ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
15+
defer done()
2016

21-
a := app.New(cfg)
17+
cfg := app.NewConfigFromEnv()
2218

2319
server := httpserver.NewServer(cfg.HTTPAddr)
2420

25-
if err := a.Run(server); err != nil {
21+
if err := server.Run(ctx); err != nil {
2622
slog.Error("unable to start app", "error", err)
2723
panic(err)
2824
}
29-
30-
shutdown := make(chan bool)
31-
gracefulStop(shutdown)
32-
<-shutdown
33-
slog.Info("signal caught, shutting down")
34-
35-
go aggressiveStop()
36-
37-
if err := a.Stop(); err != nil {
38-
slog.Error("unable to stop app", "error", err)
39-
panic(err)
40-
}
41-
}
42-
43-
func gracefulStop(shutdown chan<- bool) {
44-
c := make(chan os.Signal)
45-
signal.Notify(c,
46-
syscall.SIGHUP,
47-
syscall.SIGINT,
48-
syscall.SIGTERM,
49-
syscall.SIGQUIT)
50-
51-
go func() {
52-
<-c
53-
shutdown <- true
54-
}()
55-
}
56-
57-
func aggressiveStop() {
58-
ticker := time.NewTicker(shutdownTime)
59-
<-ticker.C
60-
61-
slog.Warn("application is aggressive stopped")
62-
os.Exit(0)
6325
}

internal/app/app.go

Lines changed: 0 additions & 44 deletions
This file was deleted.

internal/coder/coder.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ package coder
33
import (
44
"fmt"
55

6+
"github.com/etilite/qr-coder/internal/model"
67
"github.com/skip2/go-qrcode"
78
)
89

9-
const utf8BOM = "\ufeff"
10-
11-
type QRCode struct {
12-
Content string `json:"content"`
13-
Size int `json:"size"`
14-
}
10+
const (
11+
utf8BOM = "\ufeff"
12+
)
1513

16-
func (code *QRCode) Generate() ([]byte, error) {
14+
func Encode(code model.QRCode) ([]byte, error) {
1715
qrCode, err := qrcode.Encode(utf8BOM+code.Content, qrcode.Medium, code.Size)
1816
if err != nil {
1917
return nil, fmt.Errorf("could not generate a QR code: %v", err)

internal/coder/coder_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package coder
2+
3+
import (
4+
"testing"
5+
6+
"github.com/etilite/qr-coder/internal/model"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestQRCode_Generate(t *testing.T) {
11+
t.Parallel()
12+
13+
t.Run("success", func(t *testing.T) {
14+
t.Parallel()
15+
16+
code := model.QRCode{
17+
Content: "test",
18+
Size: 32,
19+
}
20+
21+
got, err := Encode(code)
22+
23+
assert.NoError(t, err)
24+
assert.NotEmpty(t, got)
25+
})
26+
27+
t.Run("encode error", func(t *testing.T) {
28+
t.Parallel()
29+
30+
code := model.QRCode{
31+
Content: tooBigContent,
32+
Size: 32,
33+
}
34+
35+
got, err := Encode(code)
36+
37+
assert.Error(t, err)
38+
assert.Empty(t, got)
39+
})
40+
}
41+
42+
const tooBigContent = `this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
43+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
44+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
45+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
46+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
47+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
48+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
49+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
50+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
51+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
52+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
53+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
54+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
55+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
56+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
57+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
58+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
59+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
60+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
61+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
62+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
63+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
64+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
65+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
66+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
67+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
68+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
69+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
70+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
71+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
72+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
73+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
74+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
75+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
76+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_
77+
this_content_is_too_big_this_content_is_too_big_this_content_is_too_big_`

internal/delivery/http/handler.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,56 @@ package http
33
import (
44
"encoding/json"
55
"fmt"
6-
"log"
6+
"log/slog"
77
"net/http"
88

99
"github.com/etilite/qr-coder/internal/coder"
10+
"github.com/etilite/qr-coder/internal/model"
1011
)
1112

1213
type QRCodeHandler struct {
14+
encode func(code model.QRCode) ([]byte, error)
1315
}
1416

15-
func (h *QRCodeHandler) handle() http.HandlerFunc {
16-
return func(writer http.ResponseWriter, request *http.Request) {
17-
qrCode := coder.QRCode{}
18-
err := json.NewDecoder(request.Body).Decode(&qrCode)
17+
func NewQRCodeHandler() *QRCodeHandler {
18+
return &QRCodeHandler{encode: coder.Encode}
19+
}
1920

20-
//writer.Header().Set("Content-Type", "application/json")
21+
func (h *QRCodeHandler) handle() http.HandlerFunc {
22+
return func(w http.ResponseWriter, r *http.Request) {
23+
defer func() {
24+
if err := r.Body.Close(); err != nil {
25+
slog.Error("handler: failed to close request body", "error", err)
26+
}
27+
}()
28+
29+
qrCode := model.QRCode{}
30+
31+
if err := json.NewDecoder(r.Body).Decode(&qrCode); err != nil {
32+
err = fmt.Errorf("failed to decode JSON: %v", err)
33+
slog.Error("handler: bad request", "error", err)
34+
http.Error(w, err.Error(), http.StatusBadRequest)
35+
return
36+
}
2137

22-
if err != nil {
23-
err = fmt.Errorf("failed to decode JSON: %w", err)
24-
log.Print(err)
25-
http.Error(writer, err.Error(), http.StatusBadRequest)
38+
if err := qrCode.Validate(); err != nil {
39+
err = fmt.Errorf("failed to validate QR Code: %v", err)
40+
slog.Error("handler: bad request", "error", err)
41+
http.Error(w, err.Error(), http.StatusBadRequest)
2642
return
2743
}
2844

29-
//var image []byte
30-
image, err := qrCode.Generate()
45+
image, err := h.encode(qrCode)
3146
if err != nil {
32-
writer.WriteHeader(400)
33-
json.NewEncoder(writer).Encode(
34-
fmt.Sprintf("Could not generate QR code. %v", err),
35-
)
47+
err = fmt.Errorf("failed to generate QR-code: %v", err)
48+
slog.Error("handler: internal error", "error", err)
49+
http.Error(w, err.Error(), http.StatusInternalServerError)
3650
return
3751
}
3852

39-
writer.Header().Set("Content-Type", "image/png")
40-
writer.Write(image)
53+
w.Header().Set("Content-Type", "image/png")
54+
if _, err := w.Write(image); err != nil {
55+
slog.Error("handler: failed to write response", "error", err)
56+
}
4157
}
4258
}

0 commit comments

Comments
 (0)