Skip to content

Commit

Permalink
Merge pull request #10 from codeinuit/feat/add-stat-route
Browse files Browse the repository at this point in the history
Feat/add stat route
  • Loading branch information
codeinuit committed Aug 10, 2023
2 parents ab8801f + 115e637 commit 9b54cbd
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 24 deletions.
29 changes: 29 additions & 0 deletions cmd/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ package main
import (
"net/http"

"github.com/codeinuit/fizzbuzz-api/pkg/database"
"github.com/codeinuit/fizzbuzz-api/pkg/fizzbuzz"
logger "github.com/codeinuit/fizzbuzz-api/pkg/log"
"github.com/codeinuit/fizzbuzz-api/pkg/models"

"github.com/gin-gonic/gin"
)

// handlers is the main struct for handling endpoints
type handlers struct {
log logger.Logger
db database.Database
}

// getFizzBuzzBody represent the input structure for
Expand Down Expand Up @@ -45,5 +49,30 @@ func (h handlers) fizzbuzz(c *gin.Context) {
return
}

err = h.db.UsageUpdate(models.Stats{
Int1: v.Int1,
Int2: v.Int2,
Int3: v.Int3,
String1: v.String1,
String2: v.String2,
})
if err != nil {
h.log.Warn("could not count usage for route /fizzbuzz: ", err.Error())
}

c.String(http.StatusOK, fb)
}

// stats return the fizzbuzz stats
// GET /stats
func (h handlers) stats(c *gin.Context) {
res, err := h.db.CountUsage()

if err != nil {
h.log.Error(err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"message": "internal server error"})
return
}

c.JSON(http.StatusOK, gin.H{"int1": res.Int1, "int2": res.Int2, "int3": res.Int3, "string1": res.String1, "string2": res.String2, "used": res.Use})
}
25 changes: 17 additions & 8 deletions cmd/api/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import (
"net/http/httptest"
"testing"

"github.com/codeinuit/fizzbuzz-api/pkg/database/mock"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)

func TestGetHealth(t *testing.T) {
fb := setupRouter()
fb, err := setupRouter()
assert.Nil(t, err)
initRoutes(fb, mock.NewDatabaseMock())

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/health", nil)
fb.engine.ServeHTTP(w, req)

var want gin.H = gin.H{"message": "OK"}
var got gin.H
err := json.Unmarshal(w.Body.Bytes(), &got)
err = json.Unmarshal(w.Body.Bytes(), &got)
if err != nil {
t.Fatal(err)
}
Expand All @@ -31,7 +34,9 @@ func TestGetHealth(t *testing.T) {
}

func TestGetFizzBuzzOK(t *testing.T) {
fb := setupRouter()
fb, err := setupRouter()
assert.Nil(t, err)
initRoutes(fb, mock.NewDatabaseMock())

data := getFizzBuzzBody{
Int1: 3,
Expand All @@ -42,7 +47,7 @@ func TestGetFizzBuzzOK(t *testing.T) {
}
var b bytes.Buffer

err := json.NewEncoder(&b).Encode(data)
err = json.NewEncoder(&b).Encode(data)
if err != nil {
log.Fatal(err)
}
Expand All @@ -58,7 +63,9 @@ func TestGetFizzBuzzOK(t *testing.T) {
}

func TestGetFizzBuzzMissingParameter(t *testing.T) {
fb := setupRouter()
fb, err := setupRouter()
assert.Nil(t, err)
initRoutes(fb, mock.NewDatabaseMock())

data := getFizzBuzzBody{
Int1: 3,
Expand All @@ -68,7 +75,7 @@ func TestGetFizzBuzzMissingParameter(t *testing.T) {
}
var b bytes.Buffer

err := json.NewEncoder(&b).Encode(data)
err = json.NewEncoder(&b).Encode(data)
if err != nil {
log.Fatal(err)
}
Expand All @@ -81,7 +88,9 @@ func TestGetFizzBuzzMissingParameter(t *testing.T) {
}

func TestGetFizzBuzzWrongParameter(t *testing.T) {
fb := setupRouter()
fb, err := setupRouter()
assert.Nil(t, err)
initRoutes(fb, mock.NewDatabaseMock())

data := getFizzBuzzBody{
Int1: 3,
Expand All @@ -92,7 +101,7 @@ func TestGetFizzBuzzWrongParameter(t *testing.T) {
}
var b bytes.Buffer

err := json.NewEncoder(&b).Encode(data)
err = json.NewEncoder(&b).Encode(data)
if err != nil {
log.Fatal(err)
}
Expand Down
57 changes: 43 additions & 14 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"

"github.com/codeinuit/fizzbuzz-api/pkg/database"
"github.com/codeinuit/fizzbuzz-api/pkg/database/mysql"
logger "github.com/codeinuit/fizzbuzz-api/pkg/log"
"github.com/codeinuit/fizzbuzz-api/pkg/log/logrus"

"github.com/gin-gonic/gin"
)

// FizzBuzz represents the main structure
type FizzBuzz struct {
engine *gin.Engine
srv *http.Server
Expand All @@ -25,13 +28,13 @@ type FizzBuzz struct {
quit chan os.Signal
}

func setupRouter() (fb *FizzBuzz) {
// setupRouter init the main structure and the http router as well
func setupRouter() (fb *FizzBuzz, err error) {
l := logrus.NewLogrusLogger()
_, isDebug := os.LookupEnv("DEBUG")
if !isDebug {
gin.SetMode(gin.ReleaseMode)
}

r := gin.New()

fb = &FizzBuzz{
Expand All @@ -40,16 +43,11 @@ func setupRouter() (fb *FizzBuzz) {
log: l,
}

h := handlers{
log: l,
}

r.GET("/health", h.healthcheck)
r.GET("/fizzbuzz", h.fizzbuzz)

return fb
return fb, nil
}

// Run will run main program along the http server.
// It should be run as goroutine and stopped using Stop function
func (fb *FizzBuzz) Run(port string) {
fb.srv = &http.Server{
Addr: fmt.Sprintf(":%s", port),
Expand All @@ -59,10 +57,11 @@ func (fb *FizzBuzz) Run(port string) {
fb.log.Info("running server on port ", port)
err := fb.srv.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
fb.log.Error(err.Error())
}
}

// Stop is used to stop the main program and its http server properly
func (fb FizzBuzz) Stop() {
fb.log.Warn("stop signal catched, closing server")

Expand All @@ -77,11 +76,41 @@ func (fb FizzBuzz) Stop() {
fb.log.Info("server closed, exiting")
}

// initRoutes is used to init the base routes and attach them
// to the router
func initRoutes(fb *FizzBuzz, db database.Database) {
h := handlers{
log: fb.log,
db: db,
}

fb.engine.GET("/health", h.healthcheck)
fb.engine.GET("/fizzbuzz", h.fizzbuzz)
fb.engine.GET("/stats", h.stats)
}

func main() {
fb := setupRouter()
fb, err := setupRouter()
if err != nil {
os.Exit(1)
}

port := os.Getenv("PORT")
if _, err := strconv.Atoi(port); err != nil {
fb.log.Error(err.Error())
os.Exit(1)
}

db, err := mysql.InitDatabase()
if err != nil {
fb.log.Error(err.Error())
os.Exit(1)
}

initRoutes(fb, db)

signal.Notify(fb.quit, syscall.SIGINT, syscall.SIGTERM)
go fb.Run(os.Getenv("PORT"))
go fb.Run(port)
<-fb.quit

fb.Stop()
Expand Down
29 changes: 29 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: '3.8'

services:
api:
depends_on:
- db
build: ./
environment:
- PORT=8080
- MYSQL_HOST=database
- MYSQL_PORT=3306
- MYSQL_USER=user
- MYSQL_PASS=password
- MYSQL_DB=main
ports:
- '8080:8080'
links:
- "db:database"
restart: always
db:
image: mysql:8.0
restart: always
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=main
- MYSQL_USER=user
- MYSQL_PASSWORD=password
ports:
- "3306:3306"
64 changes: 63 additions & 1 deletion fizzbuzz_api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ paths:
get:
summary: Generate a FizzBuzz response
description: Generate a custom FizzBuzz response depending of the input
consumes:
- application/json
parameters:
- in: body
schema:
type: object
properties:
int1:
type: integer
exemple: 3
int2:
type: integer
exemple: 5
int3:
type: integer
exemple: 16
string1:
type: string
exemple: fizz
string2:
type: string
exemple: buzz
responses:
'200':
description: OK
Expand All @@ -43,4 +65,44 @@ paths:
properties:
error:
type: string
example: "limit must be a positive integer at least supperior or equal to 1"
example: "limit must be a positive integer at least supperior or equal to 1"
/stats:
get:
summary: Returns the most used FizzBuzz request
description: Returns the most used FizzBuzz request with request and usage details
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
int1:
type: integer
exemple: 3
int2:
type: integer
exemple: 5
int3:
type: integer
exemple: 16
string1:
type: string
exemple: fizz
string2:
type: string
exemple: buzz
use:
type: integer
exemple: 3
'500':
description: Internal server error
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "internal server error"
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ require (
golang.org/x/sys v0.8.0 // indirect
)

require github.com/gin-gonic/gin v1.9.1
require (
github.com/gin-gonic/gin v1.9.1
gorm.io/driver/mysql v1.5.1
gorm.io/gorm v1.25.2
)

require (
github.com/bytedance/sonic v1.9.1 // indirect
Expand All @@ -19,7 +23,10 @@ require (
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.14.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
Expand Down
Loading

0 comments on commit 9b54cbd

Please sign in to comment.