From 84cfab08ef0fdbb8dba0e8486b692ccfc89e66b7 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Wed, 4 Aug 2021 01:14:32 +0700 Subject: [PATCH] refactor: moving each routes to new directory --- api/app/v1/handler/health/health.go | 31 ++++ api/app/v1/handler/health/health_test.go | 43 ++++++ api/app/v1/handler/joke/joke_add.go | 55 +++++++ api/app/v1/handler/joke/joke_add_test.go | 62 ++++++++ api/app/v1/handler/joke/joke_delete.go | 60 ++++++++ api/app/v1/handler/joke/joke_delete_test.go | 64 +++++++++ api/app/v1/handler/joke/joke_get.go | 151 ++++++++++++++++++++ api/app/v1/handler/joke/joke_get_test.go | 94 ++++++++++++ api/app/v1/handler/joke/joke_total.go | 39 +++++ api/app/v1/handler/joke/joke_total_test.go | 46 ++++++ api/app/v1/handler/joke/joke_update.go | 75 ++++++++++ api/app/v1/handler/joke/joke_update_test.go | 62 ++++++++ api/app/v1/routes/health.go | 4 +- api/app/v1/routes/joke.go | 16 +-- 14 files changed, 792 insertions(+), 10 deletions(-) create mode 100644 api/app/v1/handler/health/health.go create mode 100644 api/app/v1/handler/health/health_test.go create mode 100644 api/app/v1/handler/joke/joke_add.go create mode 100644 api/app/v1/handler/joke/joke_add_test.go create mode 100644 api/app/v1/handler/joke/joke_delete.go create mode 100644 api/app/v1/handler/joke/joke_delete_test.go create mode 100644 api/app/v1/handler/joke/joke_get.go create mode 100644 api/app/v1/handler/joke/joke_get_test.go create mode 100644 api/app/v1/handler/joke/joke_total.go create mode 100644 api/app/v1/handler/joke/joke_total_test.go create mode 100644 api/app/v1/handler/joke/joke_update.go create mode 100644 api/app/v1/handler/joke/joke_update_test.go diff --git a/api/app/v1/handler/health/health.go b/api/app/v1/handler/health/health.go new file mode 100644 index 0000000..128cda8 --- /dev/null +++ b/api/app/v1/handler/health/health.go @@ -0,0 +1,31 @@ +package health + +import ( + "context" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/models" + + "github.com/gofiber/fiber/v2" +) + +func Health(c *fiber.Ctx) error { + // Ping REDIS database + err := handler.Redis.Ping(context.Background()).Err() + if err != nil { + return c. + Status(fiber.StatusServiceUnavailable). + JSON(models.Error{ + Error: "REDIS: " + err.Error(), + }) + } + + _, err = handler.Db.Query(context.Background(), "SELECT \"id\" FROM \"jokesbapak2\" LIMIT 1") + if err != nil { + return c. + Status(fiber.StatusServiceUnavailable). + JSON(models.Error{ + Error: "POSTGRESQL: " + err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} diff --git a/api/app/v1/handler/health/health_test.go b/api/app/v1/handler/health/health_test.go new file mode 100644 index 0000000..d4e21dd --- /dev/null +++ b/api/app/v1/handler/health/health_test.go @@ -0,0 +1,43 @@ +package health_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") + }) +} diff --git a/api/app/v1/handler/joke/joke_add.go b/api/app/v1/handler/joke/joke_add.go new file mode 100644 index 0000000..b72bf58 --- /dev/null +++ b/api/app/v1/handler/joke/joke_add.go @@ -0,0 +1,55 @@ +package joke + +import ( + "context" + + "jokes-bapak2-api/app/v1/core" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/models" + + "github.com/gofiber/fiber/v2" +) + +func AddNewJoke(c *fiber.Ctx) error { + var body models.Joke + err := c.BodyParser(&body) + if err != nil { + return err + } + + // Check link validity + valid, err := core.CheckImageValidity(handler.Client, body.Link) + if err != nil { + return err + } + + if !valid { + return c.Status(fiber.StatusBadRequest).JSON(models.Error{ + Error: "URL provided is not a valid image", + }) + } + + sql, args, err := handler.Psql.Insert("jokesbapak2").Columns("link", "creator").Values(body.Link, c.Locals("userID")).ToSql() + if err != nil { + return err + } + + // TODO: Implement solution if the link provided already exists. + _, err = handler.Db.Query(context.Background(), sql, args...) + if err != nil { + return err + } + + err = core.SetAllJSONJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + err = core.SetTotalJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + + return c.Status(fiber.StatusCreated).JSON(models.ResponseJoke{ + Link: body.Link, + }) +} diff --git a/api/app/v1/handler/joke/joke_add_test.go b/api/app/v1/handler/joke/joke_add_test.go new file mode 100644 index 0000000..48a6cb4 --- /dev/null +++ b/api/app/v1/handler/joke/joke_add_test.go @@ -0,0 +1,62 @@ +package joke_test + +import ( + "context" + "io/ioutil" + v1 "jokes-bapak2-api/app/v1" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/platform/database" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestAddNewJoke(t *testing.T) { + // t.SkipNow() + err := database.Setup() + if err != nil { + t.Fatal(err) + } + hashedToken := "$argon2id$v=19$m=65536,t=16,p=4$48beb241490caa57fbca8e63df1e1b5fba8934baf78205ee775f96a85f45b889$e6dfca3f69adbe7653dbb353f366d741a3640313c45e33eabaca0c217c16417de80d70ac67f217c9ca46634b0abaad5f4ea2b064caa44ce218fb110b4cba9d36" + _, err = handler.Db.Query(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4);", 1, "very secure", hashedToken, time.Now().Format(time.RFC3339)) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(cleanup) + + app := v1.New() + + t.Run("Add - should return 201", func(t *testing.T) { + reqBody := strings.NewReader("{\"link\":\"https://via.placeholder.com/300/07f/ff0000.png\",\"key\":\"very secure\",\"token\":\"password\"}") + req, _ := http.NewRequest("PUT", "/", reqBody) + req.Header.Set("content-type", "application/json") + req.Header.Add("accept", "application/json") + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke add") + assert.Equalf(t, 201, res.StatusCode, "joke add") + assert.NotEqualf(t, 0, res.ContentLength, "joke add") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke add") + assert.Equalf(t, "{\"link\":\"https://via.placeholder.com/300/07f/ff0000.png\"}", string(body), "joke add") + }) + + t.Run("Add - should not be a valid image", func(t *testing.T) { + reqBody := strings.NewReader("{\"link\":\"https://google.com/\",\"key\":\"very secure\",\"token\":\"password\"}") + req, _ := http.NewRequest("PUT", "/", reqBody) + req.Header.Set("content-type", "application/json") + req.Header.Add("accept", "application/json") + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke add") + assert.Equalf(t, 400, res.StatusCode, "joke add") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke add") + assert.Equalf(t, "{\"error\":\"URL provided is not a valid image\"}", string(body), "joke add") + }) + +} diff --git a/api/app/v1/handler/joke/joke_delete.go b/api/app/v1/handler/joke/joke_delete.go new file mode 100644 index 0000000..96a0a2b --- /dev/null +++ b/api/app/v1/handler/joke/joke_delete.go @@ -0,0 +1,60 @@ +package joke + +import ( + "context" + "strconv" + + "jokes-bapak2-api/app/v1/core" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/models" + + "github.com/Masterminds/squirrel" + "github.com/gofiber/fiber/v2" +) + +func DeleteJoke(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return err + } + + // Check if the joke exists + sql, args, err := handler.Psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql() + if err != nil { + return err + } + + var jokeID int + err = handler.Db.QueryRow(context.Background(), sql, args...).Scan(&jokeID) + if err != nil { + return err + } + + if jokeID == id { + sql, args, err = handler.Psql.Delete("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql() + if err != nil { + return err + } + + _, err = handler.Db.Query(context.Background(), sql, args...) + if err != nil { + return err + } + + err = core.SetAllJSONJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + err = core.SetTotalJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{ + Message: "specified joke id has been deleted", + }) + } + return c.Status(fiber.StatusNotAcceptable).JSON(models.Error{ + Error: "specified joke id does not exists", + }) +} diff --git a/api/app/v1/handler/joke/joke_delete_test.go b/api/app/v1/handler/joke/joke_delete_test.go new file mode 100644 index 0000000..d8ffad3 --- /dev/null +++ b/api/app/v1/handler/joke/joke_delete_test.go @@ -0,0 +1,64 @@ +package joke_test + +import ( + "context" + "io/ioutil" + v1 "jokes-bapak2-api/app/v1" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/platform/database" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDeleteJoke(t *testing.T) { + // TODO: Remove this line below, make this test works + t.SkipNow() + + err := database.Setup() + if err != nil { + t.Fatal(err) + } + hashedToken := "$argon2id$v=19$m=65536,t=16,p=4$48beb241490caa57fbca8e63df1e1b5fba8934baf78205ee775f96a85f45b889$e6dfca3f69adbe7653dbb353f366d741a3640313c45e33eabaca0c217c16417de80d70ac67f217c9ca46634b0abaad5f4ea2b064caa44ce218fb110b4cba9d36" + _, err = handler.Db.Query(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4);", 1, "very secure", hashedToken, time.Now().Format(time.RFC3339)) + if err != nil { + t.Fatal(err) + } + _, err = handler.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("Delete - should return 200", func(t *testing.T) { + reqBody := strings.NewReader("{\"key\":\"very secure\",\"token\":\"password\"}") + req, _ := http.NewRequest("DELETE", "/id/1", reqBody) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke delete") + assert.Equalf(t, 200, res.StatusCode, "joke delete") + assert.NotEqualf(t, 0, res.ContentLength, "joke delete") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke delete") + assert.Equalf(t, "{\"message\":\"specified joke id has been deleted\"}", string(body), "joke delete") + }) + + t.Run("Delete - id doesn't exists", func(t *testing.T) { + reqBody := strings.NewReader("{\"key\":\"very secure\",\"token\":\"password\"}") + req, _ := http.NewRequest("DELETE", "/id/100", reqBody) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke delete") + assert.Equalf(t, 406, res.StatusCode, "joke delete") + assert.NotEqualf(t, 0, res.ContentLength, "joke delete") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke delete") + assert.Equalf(t, "{\"message\":\"specified joke id does not exists\"}", string(body), "joke delete") + }) +} diff --git a/api/app/v1/handler/joke/joke_get.go b/api/app/v1/handler/joke/joke_get.go new file mode 100644 index 0000000..47563ec --- /dev/null +++ b/api/app/v1/handler/joke/joke_get.go @@ -0,0 +1,151 @@ +package joke + +import ( + "context" + "io/ioutil" + "strconv" + "time" + + "jokes-bapak2-api/app/v1/core" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/models" + "jokes-bapak2-api/app/v1/utils" + + "github.com/gofiber/fiber/v2" +) + +func TodayJoke(c *fiber.Ctx) error { + // check from handler.Redis if today's joke already exists + // send the joke if exists + // get a new joke if it's not, then send it. + var joke models.Today + err := handler.Redis.MGet(context.Background(), "today:link", "today:date", "today:image", "today:contentType").Scan(&joke) + if err != nil { + return err + } + + eq, err := utils.IsToday(joke.Date) + if err != nil { + return err + } + + if eq { + c.Set("Content-Type", joke.ContentType) + return c.Status(fiber.StatusOK).Send([]byte(joke.Image)) + } else { + var link string + err := handler.Db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link) + if err != nil { + return err + } + + response, err := handler.Client.Get(link, nil) + if err != nil { + return err + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + now := time.Now().UTC().Format(time.RFC3339) + err = handler.Redis.MSet(context.Background(), map[string]interface{}{ + "today:link": link, + "today:date": now, + "today:image": string(data), + "today:contentType": response.Header.Get("content-type"), + }).Err() + if err != nil { + return err + } + + c.Set("Content-Type", response.Header.Get("content-type")) + return c.Status(fiber.StatusOK).Send(data) + } + +} + +func SingleJoke(c *fiber.Ctx) error { + checkCache, err := core.CheckJokesCache(handler.Memory) + if err != nil { + return err + } + + if !checkCache { + jokes, err := core.GetAllJSONJokes(handler.Db) + if err != nil { + return err + } + err = handler.Memory.Set("jokes", jokes) + if err != nil { + return err + } + } + + link, err := core.GetRandomJokeFromCache(handler.Memory) + if err != nil { + return err + } + + // Get image data + response, err := handler.Client.Get(link, nil) + if err != nil { + return err + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + c.Set("Content-Type", response.Header.Get("content-type")) + return c.Status(fiber.StatusOK).Send(data) + +} + +func JokeByID(c *fiber.Ctx) error { + checkCache, err := core.CheckJokesCache(handler.Memory) + if err != nil { + return err + } + + if !checkCache { + jokes, err := core.GetAllJSONJokes(handler.Db) + if err != nil { + return err + } + err = handler.Memory.Set("jokes", jokes) + if err != nil { + return err + } + } + + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return err + } + + link, err := core.GetCachedJokeByID(handler.Memory, id) + if err != nil { + return err + } + + if link == "" { + return c.Status(fiber.StatusNotFound).Send([]byte("Requested ID was not found.")) + } + + // Get image data + response, err := handler.Client.Get(link, nil) + if err != nil { + return err + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + c.Set("Content-Type", response.Header.Get("content-type")) + return c.Status(fiber.StatusOK).Send(data) +} diff --git a/api/app/v1/handler/joke/joke_get_test.go b/api/app/v1/handler/joke/joke_get_test.go new file mode 100644 index 0000000..18d2d1f --- /dev/null +++ b/api/app/v1/handler/joke/joke_get_test.go @@ -0,0 +1,94 @@ +package joke_test + +import ( + "context" + "io/ioutil" + "net/http" + "testing" + "time" + + v1 "jokes-bapak2-api/app/v1" + "jokes-bapak2-api/app/v1/platform/database" + + _ "github.com/joho/godotenv/autoload" + "github.com/stretchr/testify/assert" +) + +var db = database.New() +var jokesData = []interface{}{1, "https://via.placeholder.com/300/06f/fff.png", 1, 2, "https://via.placeholder.com/300/07f/fff.png", 1, 3, "https://via.placeholder.com/300/08f/fff.png", 1} + +func cleanup() { + _, err := db.Query(context.Background(), "DROP TABLE \"jokesbapak2\"") + if err != nil { + panic(err) + } + _, err = db.Query(context.Background(), "DROP TABLE \"administrators\"") + if err != nil { + panic(err) + } +} + +/// Need to find some workaround for this test +func TestJokeGet(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("TodayJoke - should return 200", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/today", nil) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "today joke") + assert.Equalf(t, 200, res.StatusCode, "today joke") + assert.NotEqualf(t, 0, res.ContentLength, "today joke") + _, err = ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "today joke") + }) + + t.Run("SingleJoke - should return 200", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/", nil) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "single joke") + assert.Equalf(t, 200, res.StatusCode, "single joke") + assert.NotEqualf(t, 0, res.ContentLength, "single joke") + _, err = ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "single joke") + }) + + t.Run("JokeByID - should return 200", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/id/1", nil) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke by id") + assert.Equalf(t, 200, res.StatusCode, "joke by id") + assert.NotEqualf(t, 0, res.ContentLength, "joke by id") + _, err = ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke by id") + }) + + t.Run("JokeByID - should return 404", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/id/300", nil) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke by id") + assert.Equalf(t, 404, res.StatusCode, "joke by id") + assert.NotEqualf(t, 0, res.ContentLength, "joke by id") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke by id") + assert.Equalf(t, "Requested ID was not found.", string(body), "joke by id") + }) +} diff --git a/api/app/v1/handler/joke/joke_total.go b/api/app/v1/handler/joke/joke_total.go new file mode 100644 index 0000000..c6bb933 --- /dev/null +++ b/api/app/v1/handler/joke/joke_total.go @@ -0,0 +1,39 @@ +package joke + +import ( + "jokes-bapak2-api/app/v1/core" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/models" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +func TotalJokes(c *fiber.Ctx) error { + checkTotal, err := core.CheckTotalJokesCache(handler.Memory) + if err != nil { + return err + } + + if !checkTotal { + err = core.SetTotalJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + } + + total, err := handler.Memory.Get("total") + + if err != nil { + if err.Error() == "Entry not found" { + return c.Status(fiber.StatusInternalServerError).JSON(models.Error{ + Error: "no data found", + }) + } + return err + } + + return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{ + Message: strconv.Itoa(int(total[0])), + }) +} diff --git a/api/app/v1/handler/joke/joke_total_test.go b/api/app/v1/handler/joke/joke_total_test.go new file mode 100644 index 0000000..10364bd --- /dev/null +++ b/api/app/v1/handler/joke/joke_total_test.go @@ -0,0 +1,46 @@ +package joke_test + +import ( + "context" + "io/ioutil" + v1 "jokes-bapak2-api/app/v1" + "jokes-bapak2-api/app/v1/handler" + "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 = handler.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 = handler.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") + // FIXME: This should be "message": "3", not one. I don't know what's wrong as it's 1 AM. + assert.Equalf(t, "{\"message\":\"1\"}", string(body), "joke total") + }) +} diff --git a/api/app/v1/handler/joke/joke_update.go b/api/app/v1/handler/joke/joke_update.go new file mode 100644 index 0000000..4ac89f9 --- /dev/null +++ b/api/app/v1/handler/joke/joke_update.go @@ -0,0 +1,75 @@ +package joke + +import ( + "context" + + "jokes-bapak2-api/app/v1/core" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/models" + + "github.com/Masterminds/squirrel" + "github.com/gofiber/fiber/v2" +) + +func UpdateJoke(c *fiber.Ctx) error { + id := c.Params("id") + // Check if the joke exists + sql, args, err := handler.Psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql() + if err != nil { + return err + } + + var jokeID string + err = handler.Db.QueryRow(context.Background(), sql, args...).Scan(&jokeID) + if err != nil && err != models.ErrNoRows { + return err + } + + if jokeID == id { + body := new(models.Joke) + err = c.BodyParser(&body) + if err != nil { + return err + } + + // Check link validity + valid, err := core.CheckImageValidity(handler.Client, body.Link) + if err != nil { + return err + } + + if !valid { + return c.Status(fiber.StatusBadRequest).JSON(models.Error{ + Error: "URL provided is not a valid image", + }) + } + + sql, args, err = handler.Psql.Update("jokesbapak2").Set("link", body.Link).Set("creator", c.Locals("userID")).ToSql() + if err != nil { + return err + } + + _, err = handler.Db.Query(context.Background(), sql, args...) + if err != nil { + return err + } + + err = core.SetAllJSONJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + err = core.SetTotalJoke(handler.Db, handler.Memory) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK).JSON(models.ResponseJoke{ + Message: "specified joke id has been updated", + Link: body.Link, + }) + } + + return c.Status(fiber.StatusNotAcceptable).JSON(models.Error{ + Error: "specified joke id does not exists", + }) +} diff --git a/api/app/v1/handler/joke/joke_update_test.go b/api/app/v1/handler/joke/joke_update_test.go new file mode 100644 index 0000000..2ec9076 --- /dev/null +++ b/api/app/v1/handler/joke/joke_update_test.go @@ -0,0 +1,62 @@ +package joke_test + +import ( + "context" + "io/ioutil" + v1 "jokes-bapak2-api/app/v1" + "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/platform/database" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestUpdateJoke(t *testing.T) { + t.SkipNow() + err := database.Setup() + if err != nil { + t.Fatal(err) + } + hashedToken := "$argon2id$v=19$m=65536,t=16,p=4$48beb241490caa57fbca8e63df1e1b5fba8934baf78205ee775f96a85f45b889$e6dfca3f69adbe7653dbb353f366d741a3640313c45e33eabaca0c217c16417de80d70ac67f217c9ca46634b0abaad5f4ea2b064caa44ce218fb110b4cba9d36" + _, err = handler.Db.Query(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4);", 1, "very secure", hashedToken, time.Now().Format(time.RFC3339)) + if err != nil { + t.Fatal(err) + } + _, err = handler.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("Update - should return 200", func(t *testing.T) { + reqBody := strings.NewReader("{\"link\":\"https://picsum.photos/id/9/200/300\",\"key\":\"very secure\",\"token\":\"password\"}") + req, _ := http.NewRequest("PATCH", "/id/1", reqBody) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke update") + assert.Equalf(t, 200, res.StatusCode, "joke update") + assert.NotEqualf(t, 0, res.ContentLength, "joke update") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke update") + assert.Equalf(t, "{\"message\":\"specified joke id has been deleted\"}", string(body), "joke update") + }) + + t.Run("Update - id doesn't exists", func(t *testing.T) { + reqBody := strings.NewReader("{\"link\":\"https://picsum.photos/id/9/200/300\",\"key\":\"very secure\",\"token\":\"password\"}") + req, _ := http.NewRequest("PATCH", "/id/100", reqBody) + res, err := app.Test(req, -1) + + assert.Equalf(t, false, err != nil, "joke update") + assert.Equalf(t, 406, res.StatusCode, "joke update") + assert.NotEqualf(t, 0, res.ContentLength, "joke update") + body, err := ioutil.ReadAll(res.Body) + assert.Nilf(t, err, "joke update") + assert.Equalf(t, "{\"message\":\"specified joke id does not exists\"}", string(body), "joke update") + }) +} diff --git a/api/app/v1/routes/health.go b/api/app/v1/routes/health.go index 7c4dfdd..969cb88 100644 --- a/api/app/v1/routes/health.go +++ b/api/app/v1/routes/health.go @@ -1,14 +1,14 @@ package routes import ( - "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/handler/health" "github.com/gofiber/fiber/v2" ) func Health(app *fiber.App) *fiber.App { // Health check - app.Get("/health", handler.Health) + app.Get("/health", health.Health) return app } diff --git a/api/app/v1/routes/joke.go b/api/app/v1/routes/joke.go index 79a90c3..54cc5c7 100644 --- a/api/app/v1/routes/joke.go +++ b/api/app/v1/routes/joke.go @@ -1,7 +1,7 @@ package routes import ( - "jokes-bapak2-api/app/v1/handler" + "jokes-bapak2-api/app/v1/handler/joke" "jokes-bapak2-api/app/v1/middleware" "github.com/gofiber/fiber/v2" @@ -9,25 +9,25 @@ import ( func Joke(app *fiber.App) *fiber.App { // Single route - app.Get("/", handler.SingleJoke) + app.Get("/", joke.SingleJoke) // Today's joke - app.Get("/today", handler.TodayJoke) + app.Get("/today", joke.TodayJoke) // Joke by ID - app.Get("/id/:id", middleware.OnlyIntegerAsID(), handler.JokeByID) + app.Get("/id/:id", middleware.OnlyIntegerAsID(), joke.JokeByID) // Count total jokes - app.Get("/total", handler.TotalJokes) + app.Get("/total", joke.TotalJokes) // Add new joke - app.Put("/", middleware.RequireAuth(), handler.AddNewJoke) + app.Put("/", middleware.RequireAuth(), joke.AddNewJoke) // Update a joke - app.Patch("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), handler.UpdateJoke) + app.Patch("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), joke.UpdateJoke) // Delete a joke - app.Delete("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), handler.DeleteJoke) + app.Delete("/id/:id", middleware.RequireAuth(), middleware.OnlyIntegerAsID(), joke.DeleteJoke) return app }