refactor: swap package, clean up, added test

This commit is contained in:
Reinaldy Rafli 2021-07-18 12:28:24 +07:00
parent 80d9e047a7
commit 60431d3e38
20 changed files with 286 additions and 94 deletions

View File

@ -17,6 +17,8 @@ $ go run main.go
$ go build main.go $ go build main.go
``` ```
There is a placeholder data ready for you to query it manually in `/app/v1/platform/database/placeholder.sql`. Have a good time developing!
## Used packages ## Used packages
| Name | Version | Type | | Name | Version | Type |

View File

@ -6,9 +6,13 @@ import (
"jokes-bapak2-api/app/v1/platform/database" "jokes-bapak2-api/app/v1/platform/database"
"jokes-bapak2-api/app/v1/routes" "jokes-bapak2-api/app/v1/routes"
"log" "log"
"os"
"time"
"github.com/getsentry/sentry-go"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
gocache "github.com/patrickmn/go-cache" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/etag"
) )
var memory = cache.InMemory() var memory = cache.InMemory()
@ -18,23 +22,57 @@ func New() *fiber.App {
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
DisableKeepalive: true, DisableKeepalive: true,
CaseSensitive: true, CaseSensitive: true,
ErrorHandler: errorHandler,
}) })
checkCache := core.CheckJokesCache(memory) err := sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("SENTRY_DSN"),
Environment: os.Getenv("ENV"),
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
})
if err != nil {
log.Fatal(err)
}
defer sentry.Flush(2 * time.Second)
err = database.Setup()
if err != nil {
sentry.CaptureException(err)
log.Fatal(err)
}
checkCache, err := core.CheckJokesCache(memory)
if err != nil {
log.Fatalln(err)
}
if !checkCache { if !checkCache {
jokes, err := core.GetAllJSONJokes(db) jokes, err := core.GetAllJSONJokes(db)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
memory.Set("jokes", jokes, gocache.NoExpiration) err = memory.Set("jokes", jokes)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
} }
app.Use(cors.New())
app.Use(etag.New())
routes.Health(app) routes.Health(app)
routes.Joke(app) routes.Joke(app)
return app return app
} }
func errorHandler(c *fiber.Ctx, err error) error {
log.Println(err)
sentry.CaptureException(err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Something went wrong on our end",
})
}

View File

@ -2,13 +2,13 @@ package core
import ( import (
"context" "context"
"encoding/json"
"jokes-bapak2-api/app/v1/models" "jokes-bapak2-api/app/v1/models"
"math/rand" "math/rand"
"github.com/allegro/bigcache/v3"
"github.com/georgysavva/scany/pgxscan" "github.com/georgysavva/scany/pgxscan"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
"github.com/patrickmn/go-cache" "github.com/pquerna/ffjson/ffjson"
) )
// GetAllJSONJokes fetch the database for all the jokes then output it as a JSON []byte. // GetAllJSONJokes fetch the database for all the jokes then output it as a JSON []byte.
@ -25,7 +25,7 @@ func GetAllJSONJokes(db *pgxpool.Pool) ([]byte, error) {
return nil, err return nil, err
} }
data, err := json.Marshal(jokes) data, err := ffjson.Marshal(jokes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -34,18 +34,22 @@ func GetAllJSONJokes(db *pgxpool.Pool) ([]byte, error) {
} }
// GetRandomJokeFromCache returns a link string of a random joke from cache. // GetRandomJokeFromCache returns a link string of a random joke from cache.
func GetRandomJokeFromCache(memory *cache.Cache) (string, error) { func GetRandomJokeFromCache(memory *bigcache.BigCache) (string, error) {
jokes, found := memory.Get("jokes") jokes, err := memory.Get("jokes")
if !found { if err != nil {
return "", models.ErrNotFound if err.Error() == "Entry not found" {
return "", models.ErrNotFound
}
return "", err
} }
var data []models.Joke var data []models.Joke
err := json.Unmarshal(jokes.([]byte), &data) err = ffjson.Unmarshal(jokes, &data)
if err != nil { if err != nil {
return "", nil return "", nil
} }
// Return an error if the database is empty
dataLength := len(data) dataLength := len(data)
if dataLength == 0 { if dataLength == 0 {
return "", models.ErrEmpty return "", models.ErrEmpty
@ -58,20 +62,30 @@ func GetRandomJokeFromCache(memory *cache.Cache) (string, error) {
} }
// CheckJokesCache checks if there is some value inside jokes cache. // CheckJokesCache checks if there is some value inside jokes cache.
func CheckJokesCache(memory *cache.Cache) bool { func CheckJokesCache(memory *bigcache.BigCache) (bool, error) {
_, found := memory.Get("jokes") _, err := memory.Get("jokes")
return found if err != nil {
if err.Error() == "Entry not found" {
return false, nil
}
return false, err
}
return true, nil
} }
// GetCachedJokeByID returns a link string of a certain ID from cache. // GetCachedJokeByID returns a link string of a certain ID from cache.
func GetCachedJokeByID(memory *cache.Cache, id int) (string, error) { func GetCachedJokeByID(memory *bigcache.BigCache, id int) (string, error) {
jokes, found := memory.Get("jokes") jokes, err := memory.Get("jokes")
if !found { if err != nil {
return "", models.ErrNotFound if err.Error() == "Entry not found" {
return "", models.ErrNotFound
}
return "", err
} }
var data []models.Joke var data []models.Joke
err := json.Unmarshal(jokes.([]byte), &data) err = ffjson.Unmarshal(jokes, &data)
if err != nil { if err != nil {
return "", nil return "", nil
} }

View File

@ -0,0 +1,43 @@
package handler_test
import (
"context"
"io/ioutil"
v1 "jokes-bapak2-api/app/v1"
"jokes-bapak2-api/app/v1/platform/database"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestHealth(t *testing.T) {
err := database.Setup()
if err != nil {
t.Fatal(err)
}
_, err = db.Query(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4);", 1, "very secure", "not the real one", time.Now().Format(time.RFC3339))
if err != nil {
t.Fatal(err)
}
_, err = db.Query(context.Background(), "INSERT INTO \"jokesbapak2\" (id, link, creator) VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9);", jokesData...)
if err != nil {
t.Fatal(err)
}
t.Cleanup(cleanup)
app := v1.New()
t.Run("Health - should return 200", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/health", nil)
res, err := app.Test(req, -1)
assert.Equalf(t, false, err != nil, "health")
assert.Equalf(t, 200, res.StatusCode, "health")
assert.NotEqualf(t, 0, res.ContentLength, "health")
_, err = ioutil.ReadAll(res.Body)
assert.Nilf(t, err, "health")
})
}

View File

@ -7,7 +7,6 @@ import (
"jokes-bapak2-api/app/v1/models" "jokes-bapak2-api/app/v1/models"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/patrickmn/go-cache"
) )
func AddNewJoke(c *fiber.Ctx) error { func AddNewJoke(c *fiber.Ctx) error {
@ -32,7 +31,10 @@ func AddNewJoke(c *fiber.Ctx) error {
if err != nil { if err != nil {
return err return err
} }
memory.Set("jokes", jokes, cache.NoExpiration) err = memory.Set("jokes", jokes)
if err != nil {
return err
}
return c.Status(fiber.StatusCreated).JSON(models.ResponseJoke{ return c.Status(fiber.StatusCreated).JSON(models.ResponseJoke{
Link: body.Link, Link: body.Link,

View File

@ -2,17 +2,20 @@ package handler
import ( import (
"context" "context"
"strconv"
"jokes-bapak2-api/app/v1/core" "jokes-bapak2-api/app/v1/core"
"jokes-bapak2-api/app/v1/models" "jokes-bapak2-api/app/v1/models"
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/patrickmn/go-cache"
) )
func DeleteJoke(c *fiber.Ctx) error { func DeleteJoke(c *fiber.Ctx) error {
id := c.Params("id") id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return err
}
// Check if the joke exists // Check if the joke exists
sql, args, err := psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql() sql, args, err := psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
@ -20,7 +23,7 @@ func DeleteJoke(c *fiber.Ctx) error {
return err return err
} }
var jokeID string var jokeID int
err = db.QueryRow(context.Background(), sql, args...).Scan(&jokeID) err = db.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
if err != nil { if err != nil {
return err return err
@ -41,7 +44,10 @@ func DeleteJoke(c *fiber.Ctx) error {
if err != nil { if err != nil {
return err return err
} }
memory.Set("jokes", jokes, cache.NoExpiration) err = memory.Set("jokes", jokes)
if err != nil {
return err
}
return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{ return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{
Message: "specified joke id has been deleted", Message: "specified joke id has been deleted",

View File

@ -11,7 +11,6 @@ import (
"jokes-bapak2-api/app/v1/utils" "jokes-bapak2-api/app/v1/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/patrickmn/go-cache"
) )
func TodayJoke(c *fiber.Ctx) error { func TodayJoke(c *fiber.Ctx) error {
@ -67,14 +66,20 @@ func TodayJoke(c *fiber.Ctx) error {
} }
func SingleJoke(c *fiber.Ctx) error { func SingleJoke(c *fiber.Ctx) error {
checkCache := core.CheckJokesCache(memory) checkCache, err := core.CheckJokesCache(memory)
if err != nil {
return err
}
if !checkCache { if !checkCache {
jokes, err := core.GetAllJSONJokes(db) jokes, err := core.GetAllJSONJokes(db)
if err != nil { if err != nil {
return err return err
} }
memory.Set("jokes", jokes, cache.NoExpiration) err = memory.Set("jokes", jokes)
if err != nil {
return err
}
} }
link, err := core.GetRandomJokeFromCache(memory) link, err := core.GetRandomJokeFromCache(memory)
@ -99,14 +104,17 @@ func SingleJoke(c *fiber.Ctx) error {
} }
func JokeByID(c *fiber.Ctx) error { func JokeByID(c *fiber.Ctx) error {
checkCache := core.CheckJokesCache(memory) checkCache, err := core.CheckJokesCache(memory)
if err != nil {
return err
}
if !checkCache { if !checkCache {
jokes, err := core.GetAllJSONJokes(db) jokes, err := core.GetAllJSONJokes(db)
if err != nil { if err != nil {
return err return err
} }
memory.Set("jokes", jokes, cache.NoExpiration) err = memory.Set("jokes", jokes)
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,7 +15,7 @@ import (
) )
var db = database.New() var db = database.New()
var jokesData = []interface{}{1, "https://loremflickr.com/320/240", 1, 2, "https://loremflickr.com/320/240", 1, 3, "https://loremflickr.com/320/240", 1} var jokesData = []interface{}{1, "https://picsum.photos/id/1/200/300", 1, 2, "https://picsum.photos/id/2/200/300", 1, 3, "https://picsum.photos/id/3/200/300", 1}
func cleanup() { func cleanup() {
_, err := db.Query(context.Background(), "DROP TABLE \"jokesbapak2\"") _, err := db.Query(context.Background(), "DROP TABLE \"jokesbapak2\"")
@ -34,7 +34,7 @@ func TestJokeGet(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = db.Query(context.Background(), "INSERT INTO \"administrators\" (key, token, last_used) VALUES ($1, $2, $3);", "very secure", "not the real one", time.Now().Format(time.RFC3339)) _, err = db.Query(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4);", 1, "very secure", "not the real one", time.Now().Format(time.RFC3339))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -48,7 +48,6 @@ func TestJokeGet(t *testing.T) {
app := v1.New() app := v1.New()
t.Run("TodayJoke - should return 200", func(t *testing.T) { t.Run("TodayJoke - should return 200", func(t *testing.T) {
t.SkipNow()
req, _ := http.NewRequest("GET", "/today", nil) req, _ := http.NewRequest("GET", "/today", nil)
res, err := app.Test(req, -1) res, err := app.Test(req, -1)
@ -60,7 +59,6 @@ func TestJokeGet(t *testing.T) {
}) })
t.Run("SingleJoke - should return 200", func(t *testing.T) { t.Run("SingleJoke - should return 200", func(t *testing.T) {
t.SkipNow()
req, _ := http.NewRequest("GET", "/", nil) req, _ := http.NewRequest("GET", "/", nil)
res, err := app.Test(req, -1) res, err := app.Test(req, -1)
@ -72,8 +70,7 @@ func TestJokeGet(t *testing.T) {
}) })
t.Run("JokeByID - should return 200", func(t *testing.T) { t.Run("JokeByID - should return 200", func(t *testing.T) {
t.SkipNow() req, _ := http.NewRequest("GET", "/id/1", nil)
req, _ := http.NewRequest("GET", "/2", nil)
res, err := app.Test(req, -1) res, err := app.Test(req, -1)
assert.Equalf(t, false, err != nil, "joke by id") assert.Equalf(t, false, err != nil, "joke by id")
@ -84,8 +81,7 @@ func TestJokeGet(t *testing.T) {
}) })
t.Run("JokeByID - should return 404", func(t *testing.T) { t.Run("JokeByID - should return 404", func(t *testing.T) {
t.SkipNow() req, _ := http.NewRequest("GET", "/id/300", nil)
req, _ := http.NewRequest("GET", "/300", nil)
res, err := app.Test(req, -1) res, err := app.Test(req, -1)
assert.Equalf(t, false, err != nil, "joke by id") assert.Equalf(t, false, err != nil, "joke by id")

View File

@ -1,35 +1,43 @@
package handler package handler
import ( import (
"encoding/json"
"jokes-bapak2-api/app/v1/core" "jokes-bapak2-api/app/v1/core"
"jokes-bapak2-api/app/v1/models" "jokes-bapak2-api/app/v1/models"
"strconv" "strconv"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/patrickmn/go-cache" "github.com/pquerna/ffjson/ffjson"
) )
func TotalJokes(c *fiber.Ctx) error { func TotalJokes(c *fiber.Ctx) error {
checkCache := core.CheckJokesCache(memory) checkCache, err := core.CheckJokesCache(memory)
if err != nil {
return err
}
if !checkCache { if !checkCache {
jokes, err := core.GetAllJSONJokes(db) jokes, err := core.GetAllJSONJokes(db)
if err != nil { if err != nil {
return err return err
} }
memory.Set("jokes", jokes, cache.NoExpiration) err = memory.Set("jokes", jokes)
if err != nil {
return err
}
} }
jokes, found := memory.Get("jokes") jokes, err := memory.Get("jokes")
if !found { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(models.Error{ if err.Error() == "Entry not found" {
Error: "no data found", return c.Status(fiber.StatusInternalServerError).JSON(models.Error{
}) Error: "no data found",
})
}
return err
} }
var data []models.Joke var data []models.Joke
err := json.Unmarshal(jokes.([]byte), &data) err = ffjson.Unmarshal(jokes, &data)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,45 @@
package handler_test
import (
"context"
"io/ioutil"
v1 "jokes-bapak2-api/app/v1"
"jokes-bapak2-api/app/v1/platform/database"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTotalJokes(t *testing.T) {
err := database.Setup()
if err != nil {
t.Fatal(err)
}
_, err = db.Query(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4);", 1, "very secure", "not the real one", time.Now().Format(time.RFC3339))
if err != nil {
t.Fatal(err)
}
_, err = db.Query(context.Background(), "INSERT INTO \"jokesbapak2\" (id, link, creator) VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9);", jokesData...)
if err != nil {
t.Fatal(err)
}
t.Cleanup(cleanup)
app := v1.New()
t.Run("Total - should return 200", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/total", nil)
res, err := app.Test(req, -1)
assert.Equalf(t, false, err != nil, "joke total")
assert.Equalf(t, 200, res.StatusCode, "joke total")
assert.NotEqualf(t, 0, res.ContentLength, "joke total")
body, err := ioutil.ReadAll(res.Body)
assert.Nilf(t, err, "joke total")
assert.Equalf(t, "{\"message\":\"3\"}", string(body), "joke total")
})
}

View File

@ -8,7 +8,6 @@ import (
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/patrickmn/go-cache"
) )
func UpdateJoke(c *fiber.Ctx) error { func UpdateJoke(c *fiber.Ctx) error {
@ -46,7 +45,11 @@ func UpdateJoke(c *fiber.Ctx) error {
if err != nil { if err != nil {
return err return err
} }
memory.Set("jokes", jokes, cache.NoExpiration)
err = memory.Set("jokes", jokes)
if err != nil {
return err
}
return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{ return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{
Message: "specified joke id has been updated", Message: "specified joke id has been updated",

View File

@ -0,0 +1,26 @@
package middleware
import (
"jokes-bapak2-api/app/v1/models"
"regexp"
"github.com/gofiber/fiber/v2"
)
func OnlyIntegerAsID() fiber.Handler {
return func(c *fiber.Ctx) error {
regex, err := regexp.Compile(`([0-9]+)`)
if err != nil {
return err
}
loc := regex.FindStringIndex(c.Params("id"))
if loc[1] == len(c.Params("id")) {
return c.Next()
}
return c.Status(fiber.StatusBadRequest).JSON(models.Error{
Error: "only numbers are allowed as ID",
})
}
}

View File

@ -1,12 +1,16 @@
package cache package cache
import ( import (
"log"
"time" "time"
gocache "github.com/patrickmn/go-cache" "github.com/allegro/bigcache/v3"
) )
func InMemory() *gocache.Cache { func InMemory() *bigcache.BigCache {
cache := gocache.New(6*time.Hour, 6*time.Hour) cache, err := bigcache.NewBigCache(bigcache.DefaultConfig(6 * time.Hour))
if err != nil {
log.Fatalln(err)
}
return cache return cache
} }

View File

@ -0,0 +1,21 @@
-- Access the data from your HTTP Request software (Postman or Insomnia)
-- with this auth:
-- key: test
-- token: password
INSERT INTO "administrators" ("id", "key", "token", "last_used") VALUES
(1, 'test', '$argon2id$v=19$m=65536,t=16,p=4$3a08c79fbf2222467a623df9a9ebf75802c65a4f9be36eb1df2f5d2052d53cb7$ce434bd38f7ba1fc1f2eb773afb8a1f7f2dad49140803ac6cb9d7256ce9826fb3b4afa1e2488da511c852fc6c33a76d5657eba6298a8e49d617b9972645b7106', '');
-- 10 jokes is enough right?
INSERT INTO "jokesbapak2" ("id", "link", "creator") VALUES
(1, 'https://picsum.photos/id/1000/500/500', 1),
(2, 'https://picsum.photos/id/1001/500/500', 1),
(3, 'https://picsum.photos/id/1002/500/500', 1),
(4, 'https://picsum.photos/id/1003/500/500', 1),
(5, 'https://picsum.photos/id/1004/500/500', 1),
(6, 'https://picsum.photos/id/1005/500/500', 1),
(7, 'https://picsum.photos/id/1006/500/500', 1),
(8, 'https://picsum.photos/id/1010/500/500', 1),
(9, 'https://picsum.photos/id/1008/500/500', 1),
(10, 'https://picsum.photos/id/1009/500/500', 1);

View File

@ -15,7 +15,7 @@ func Joke(app *fiber.App) *fiber.App {
app.Get("/today", handler.TodayJoke) app.Get("/today", handler.TodayJoke)
// Joke by ID // Joke by ID
app.Get("/id/:id", handler.JokeByID) app.Get("/id/:id", middleware.OnlyIntegerAsID(), handler.JokeByID)
// Count total jokes // Count total jokes
app.Get("/total", handler.TotalJokes) app.Get("/total", handler.TotalJokes)
@ -24,10 +24,10 @@ func Joke(app *fiber.App) *fiber.App {
app.Put("/", middleware.RequireAuth(), handler.AddNewJoke) app.Put("/", middleware.RequireAuth(), handler.AddNewJoke)
// Update a joke // Update a joke
app.Patch("/:id", middleware.RequireAuth(), handler.UpdateJoke) app.Patch("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), handler.UpdateJoke)
// Delete a joke // Delete a joke
app.Delete("/:id", middleware.RequireAuth(), handler.DeleteJoke) app.Delete("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), handler.DeleteJoke)
return app return app
} }

View File

@ -1,8 +1,9 @@
package utils package utils
import ( import (
"encoding/json"
"strconv" "strconv"
"github.com/pquerna/ffjson/ffjson"
) )
// ParseToFormBody converts a body to form data type // ParseToFormBody converts a body to form data type
@ -25,7 +26,7 @@ func ParseToFormBody(body map[string]interface{}) ([]byte, error) {
// ParseToJSONBody converts a body to json data type // ParseToJSONBody converts a body to json data type
func ParseToJSONBody(body map[string]interface{}) ([]byte, error) { func ParseToJSONBody(body map[string]interface{}) ([]byte, error) {
b, err := json.Marshal(body) b, err := ffjson.Marshal(body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,7 @@
package utils_test package utils_test
import ( import (
"strings"
"testing" "testing"
"jokes-bapak2-api/app/v1/utils" "jokes-bapak2-api/app/v1/utils"
@ -27,16 +28,16 @@ func TestParseToJSONBody(t *testing.T) {
func TestParseToFormBody(t *testing.T) { func TestParseToFormBody(t *testing.T) {
t.Run("should be able to parse a form body", func(t *testing.T) { t.Run("should be able to parse a form body", func(t *testing.T) {
body := map[string]interface{}{ body := map[string]interface{}{
"name": "Scott",
"age": 32, "age": 32,
"fat": true, "fat": true,
"name": "Scott",
} }
parsed, err := utils.ParseToFormBody(body) parsed, err := utils.ParseToFormBody(body)
if err != nil { if err != nil {
t.Error(err.Error()) t.Error(err.Error())
} }
result := "name=Scott&age=32&fat=true&" result := [3]string{"age=32&", "fat=true&", "name=Scott&"}
if string(parsed) != result { if !strings.Contains(string(parsed), result[0]) && !strings.Contains(string(parsed), result[1]) && !strings.Contains(string(parsed), result[2]) {
t.Error("parsed string is not the same as result:", string(parsed)) t.Error("parsed string is not the same as result:", string(parsed))
} }
}) })

View File

@ -6,6 +6,7 @@ require (
github.com/Masterminds/squirrel v1.5.0 github.com/Masterminds/squirrel v1.5.0
github.com/aldy505/bob v0.0.1 github.com/aldy505/bob v0.0.1
github.com/aldy505/phc-crypto v1.1.0 github.com/aldy505/phc-crypto v1.1.0
github.com/allegro/bigcache/v3 v3.0.0
github.com/georgysavva/scany v0.2.9 github.com/georgysavva/scany v0.2.9
github.com/getsentry/sentry-go v0.11.0 github.com/getsentry/sentry-go v0.11.0
github.com/go-redis/redis/v8 v8.11.0 github.com/go-redis/redis/v8 v8.11.0
@ -14,5 +15,6 @@ require (
github.com/jackc/pgx/v4 v4.11.0 github.com/jackc/pgx/v4 v4.11.0
github.com/joho/godotenv v1.3.0 github.com/joho/godotenv v1.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7
github.com/stretchr/testify v1.5.1 github.com/stretchr/testify v1.5.1
) )

View File

@ -24,6 +24,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allegro/bigcache/v3 v3.0.0 h1:5Hxq+GTy8gHEeQccCZZDCfZRTydUfErdUf0iVDcMAFg=
github.com/allegro/bigcache/v3 v3.0.0/go.mod h1:t5TAJn1B9qvf/VlJrSM1r6NlFAYoFDubYUsCuIO9nUQ=
github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -404,6 +406,8 @@ github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6J
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=

View File

@ -7,12 +7,8 @@ import (
"time" "time"
v1 "jokes-bapak2-api/app/v1" v1 "jokes-bapak2-api/app/v1"
"jokes-bapak2-api/app/v1/platform/database"
"github.com/getsentry/sentry-go"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/etag"
"github.com/gofiber/fiber/v2/middleware/favicon" "github.com/gofiber/fiber/v2/middleware/favicon"
"github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/fiber/v2/middleware/limiter"
_ "github.com/joho/godotenv/autoload" _ "github.com/joho/godotenv/autoload"
@ -20,37 +16,17 @@ import (
func main() { func main() {
timeoutDefault, _ := time.ParseDuration("1m") timeoutDefault, _ := time.ParseDuration("1m")
err := sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("SENTRY_DSN"),
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
})
if err != nil {
log.Fatal(err)
}
defer sentry.Flush(2 * time.Second)
err = database.Setup()
if err != nil {
sentry.CaptureException(err)
log.Fatal(err)
}
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
ReadTimeout: timeoutDefault, ReadTimeout: timeoutDefault,
WriteTimeout: timeoutDefault, WriteTimeout: timeoutDefault,
ErrorHandler: errorHandler,
}) })
app.Use(cors.New())
app.Use(limiter.New(limiter.Config{ app.Use(limiter.New(limiter.Config{
Max: 15, Max: 15,
Expiration: 1 * time.Minute, Expiration: 1 * time.Minute,
LimitReached: limitHandler, LimitReached: limitHandler,
})) }))
app.Use(etag.New())
app.Use(favicon.New(favicon.Config{ app.Use(favicon.New(favicon.Config{
File: "./favicon.png", File: "./favicon.png",
})) }))
@ -65,14 +41,6 @@ func main() {
} }
} }
func errorHandler(c *fiber.Ctx, err error) error {
log.Println(err)
sentry.CaptureException(err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Something went wrong on our end",
})
}
func limitHandler(c *fiber.Ctx) error { func limitHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{ return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"message": "we only allow up to 15 request per minute", "message": "we only allow up to 15 request per minute",