refactor: swap package, clean up, added test
This commit is contained in:
parent
80d9e047a7
commit
60431d3e38
|
@ -17,6 +17,8 @@ $ go run 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
|
||||
|
||||
| Name | Version | Type |
|
||||
|
|
|
@ -6,9 +6,13 @@ import (
|
|||
"jokes-bapak2-api/app/v1/platform/database"
|
||||
"jokes-bapak2-api/app/v1/routes"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"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()
|
||||
|
@ -18,23 +22,57 @@ func New() *fiber.App {
|
|||
app := fiber.New(fiber.Config{
|
||||
DisableKeepalive: 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 {
|
||||
jokes, err := core.GetAllJSONJokes(db)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
memory.Set("jokes", jokes, gocache.NoExpiration)
|
||||
err = memory.Set("jokes", jokes)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
app.Use(cors.New())
|
||||
app.Use(etag.New())
|
||||
|
||||
routes.Health(app)
|
||||
routes.Joke(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",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ package core
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"jokes-bapak2-api/app/v1/models"
|
||||
"math/rand"
|
||||
|
||||
"github.com/allegro/bigcache/v3"
|
||||
"github.com/georgysavva/scany/pgxscan"
|
||||
"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.
|
||||
|
@ -25,7 +25,7 @@ func GetAllJSONJokes(db *pgxpool.Pool) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(jokes)
|
||||
data, err := ffjson.Marshal(jokes)
|
||||
if err != nil {
|
||||
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.
|
||||
func GetRandomJokeFromCache(memory *cache.Cache) (string, error) {
|
||||
jokes, found := memory.Get("jokes")
|
||||
if !found {
|
||||
return "", models.ErrNotFound
|
||||
func GetRandomJokeFromCache(memory *bigcache.BigCache) (string, error) {
|
||||
jokes, err := memory.Get("jokes")
|
||||
if err != nil {
|
||||
if err.Error() == "Entry not found" {
|
||||
return "", models.ErrNotFound
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data []models.Joke
|
||||
err := json.Unmarshal(jokes.([]byte), &data)
|
||||
err = ffjson.Unmarshal(jokes, &data)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Return an error if the database is empty
|
||||
dataLength := len(data)
|
||||
if dataLength == 0 {
|
||||
return "", models.ErrEmpty
|
||||
|
@ -58,20 +62,30 @@ func GetRandomJokeFromCache(memory *cache.Cache) (string, error) {
|
|||
}
|
||||
|
||||
// CheckJokesCache checks if there is some value inside jokes cache.
|
||||
func CheckJokesCache(memory *cache.Cache) bool {
|
||||
_, found := memory.Get("jokes")
|
||||
return found
|
||||
func CheckJokesCache(memory *bigcache.BigCache) (bool, error) {
|
||||
_, err := memory.Get("jokes")
|
||||
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.
|
||||
func GetCachedJokeByID(memory *cache.Cache, id int) (string, error) {
|
||||
jokes, found := memory.Get("jokes")
|
||||
if !found {
|
||||
return "", models.ErrNotFound
|
||||
func GetCachedJokeByID(memory *bigcache.BigCache, id int) (string, error) {
|
||||
jokes, err := memory.Get("jokes")
|
||||
if err != nil {
|
||||
if err.Error() == "Entry not found" {
|
||||
return "", models.ErrNotFound
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data []models.Joke
|
||||
err := json.Unmarshal(jokes.([]byte), &data)
|
||||
err = ffjson.Unmarshal(jokes, &data)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||
"jokes-bapak2-api/app/v1/models"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
func AddNewJoke(c *fiber.Ctx) error {
|
||||
|
@ -32,7 +31,10 @@ func AddNewJoke(c *fiber.Ctx) error {
|
|||
if err != nil {
|
||||
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{
|
||||
Link: body.Link,
|
||||
|
|
|
@ -2,17 +2,20 @@ package handler
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"jokes-bapak2-api/app/v1/core"
|
||||
"jokes-bapak2-api/app/v1/models"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
var jokeID string
|
||||
var jokeID int
|
||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -41,7 +44,10 @@ func DeleteJoke(c *fiber.Ctx) error {
|
|||
if err != nil {
|
||||
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{
|
||||
Message: "specified joke id has been deleted",
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"jokes-bapak2-api/app/v1/utils"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
func TodayJoke(c *fiber.Ctx) error {
|
||||
|
@ -67,14 +66,20 @@ func TodayJoke(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 {
|
||||
jokes, err := core.GetAllJSONJokes(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memory.Set("jokes", jokes, cache.NoExpiration)
|
||||
err = memory.Set("jokes", jokes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
link, err := core.GetRandomJokeFromCache(memory)
|
||||
|
@ -99,14 +104,17 @@ func SingleJoke(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 {
|
||||
jokes, err := core.GetAllJSONJokes(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memory.Set("jokes", jokes, cache.NoExpiration)
|
||||
err = memory.Set("jokes", jokes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
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() {
|
||||
_, err := db.Query(context.Background(), "DROP TABLE \"jokesbapak2\"")
|
||||
|
@ -34,7 +34,7 @@ func TestJokeGet(t *testing.T) {
|
|||
if err != nil {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ func TestJokeGet(t *testing.T) {
|
|||
app := v1.New()
|
||||
|
||||
t.Run("TodayJoke - should return 200", func(t *testing.T) {
|
||||
t.SkipNow()
|
||||
req, _ := http.NewRequest("GET", "/today", nil)
|
||||
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.SkipNow()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
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.SkipNow()
|
||||
req, _ := http.NewRequest("GET", "/2", nil)
|
||||
req, _ := http.NewRequest("GET", "/id/1", nil)
|
||||
res, err := app.Test(req, -1)
|
||||
|
||||
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.SkipNow()
|
||||
req, _ := http.NewRequest("GET", "/300", nil)
|
||||
req, _ := http.NewRequest("GET", "/id/300", nil)
|
||||
res, err := app.Test(req, -1)
|
||||
|
||||
assert.Equalf(t, false, err != nil, "joke by id")
|
||||
|
|
|
@ -1,35 +1,43 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"jokes-bapak2-api/app/v1/core"
|
||||
"jokes-bapak2-api/app/v1/models"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/pquerna/ffjson/ffjson"
|
||||
)
|
||||
|
||||
func TotalJokes(c *fiber.Ctx) error {
|
||||
checkCache := core.CheckJokesCache(memory)
|
||||
checkCache, err := core.CheckJokesCache(memory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !checkCache {
|
||||
jokes, err := core.GetAllJSONJokes(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memory.Set("jokes", jokes, cache.NoExpiration)
|
||||
err = memory.Set("jokes", jokes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
jokes, found := memory.Get("jokes")
|
||||
if !found {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(models.Error{
|
||||
Error: "no data found",
|
||||
})
|
||||
jokes, err := memory.Get("jokes")
|
||||
if err != nil {
|
||||
if err.Error() == "Entry not found" {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(models.Error{
|
||||
Error: "no data found",
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var data []models.Joke
|
||||
err := json.Unmarshal(jokes.([]byte), &data)
|
||||
err = ffjson.Unmarshal(jokes, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
})
|
||||
}
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
func UpdateJoke(c *fiber.Ctx) error {
|
||||
|
@ -46,7 +45,11 @@ func UpdateJoke(c *fiber.Ctx) error {
|
|||
if err != nil {
|
||||
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{
|
||||
Message: "specified joke id has been updated",
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
gocache "github.com/patrickmn/go-cache"
|
||||
"github.com/allegro/bigcache/v3"
|
||||
)
|
||||
|
||||
func InMemory() *gocache.Cache {
|
||||
cache := gocache.New(6*time.Hour, 6*time.Hour)
|
||||
func InMemory() *bigcache.BigCache {
|
||||
cache, err := bigcache.NewBigCache(bigcache.DefaultConfig(6 * time.Hour))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -15,7 +15,7 @@ func Joke(app *fiber.App) *fiber.App {
|
|||
app.Get("/today", handler.TodayJoke)
|
||||
|
||||
// Joke by ID
|
||||
app.Get("/id/:id", handler.JokeByID)
|
||||
app.Get("/id/:id", middleware.OnlyIntegerAsID(), handler.JokeByID)
|
||||
|
||||
// Count total jokes
|
||||
app.Get("/total", handler.TotalJokes)
|
||||
|
@ -24,10 +24,10 @@ func Joke(app *fiber.App) *fiber.App {
|
|||
app.Put("/", middleware.RequireAuth(), handler.AddNewJoke)
|
||||
|
||||
// Update a joke
|
||||
app.Patch("/:id", middleware.RequireAuth(), handler.UpdateJoke)
|
||||
app.Patch("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), handler.UpdateJoke)
|
||||
|
||||
// Delete a joke
|
||||
app.Delete("/:id", middleware.RequireAuth(), handler.DeleteJoke)
|
||||
app.Delete("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), handler.DeleteJoke)
|
||||
|
||||
return app
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/pquerna/ffjson/ffjson"
|
||||
)
|
||||
|
||||
// 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
|
||||
func ParseToJSONBody(body map[string]interface{}) ([]byte, error) {
|
||||
b, err := json.Marshal(body)
|
||||
b, err := ffjson.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package utils_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"jokes-bapak2-api/app/v1/utils"
|
||||
|
@ -27,16 +28,16 @@ func TestParseToJSONBody(t *testing.T) {
|
|||
func TestParseToFormBody(t *testing.T) {
|
||||
t.Run("should be able to parse a form body", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "Scott",
|
||||
"age": 32,
|
||||
"fat": true,
|
||||
"name": "Scott",
|
||||
}
|
||||
parsed, err := utils.ParseToFormBody(body)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
result := "name=Scott&age=32&fat=true&"
|
||||
if string(parsed) != result {
|
||||
result := [3]string{"age=32&", "fat=true&", "name=Scott&"}
|
||||
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))
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/Masterminds/squirrel v1.5.0
|
||||
github.com/aldy505/bob v0.0.1
|
||||
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/getsentry/sentry-go v0.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/joho/godotenv v1.3.0
|
||||
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
|
||||
)
|
||||
|
|
|
@ -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/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/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/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
|
|
34
api/main.go
34
api/main.go
|
@ -7,12 +7,8 @@ import (
|
|||
"time"
|
||||
|
||||
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/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/etag"
|
||||
"github.com/gofiber/fiber/v2/middleware/favicon"
|
||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
|
@ -20,37 +16,17 @@ import (
|
|||
|
||||
func main() {
|
||||
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{
|
||||
ReadTimeout: timeoutDefault,
|
||||
WriteTimeout: timeoutDefault,
|
||||
ErrorHandler: errorHandler,
|
||||
})
|
||||
app.Use(cors.New())
|
||||
|
||||
app.Use(limiter.New(limiter.Config{
|
||||
Max: 15,
|
||||
Expiration: 1 * time.Minute,
|
||||
LimitReached: limitHandler,
|
||||
}))
|
||||
app.Use(etag.New())
|
||||
app.Use(favicon.New(favicon.Config{
|
||||
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 {
|
||||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
|
||||
"message": "we only allow up to 15 request per minute",
|
||||
|
|
Loading…
Reference in New Issue