diff --git a/README.md b/README.md index a6caff8..04783e9 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,10 @@ This is some kind of [icanhazdadjokes](https://icanhazdadjoke.com/) but it's Ind You can consume this API via a website (linked in the front facing web) with a few endpoints: - * `/v1/` - Random jokes bapak2 - * `/v1/id/{number}` - Jokes bapak2 based on ID - * `/v1/today` - Jokes bapak2 of the day - * `/v1/total` - Total available jokes bapak2 + * `/` - Random jokes bapak2 + * `/id/{number}` - Jokes bapak2 based on ID + * `/today` - Jokes bapak2 of the day + * `/total` - Total available jokes bapak2 Currently I'm (still) searching for an alternative for AWS S3 that I can use for free. diff --git a/api/app/handler/joke/dependencies_test.go b/api/app/handler/joke/dependencies_test.go deleted file mode 100644 index 2d98a9b..0000000 --- a/api/app/handler/joke/dependencies_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package joke_test - -import ( - v1 "jokes-bapak2-api/app" - - "github.com/gofiber/fiber/v2" -) - -var app *fiber.App = v1.New() diff --git a/api/app/handler/joke/joke_add_test.go b/api/app/handler/joke/joke_add_test.go deleted file mode 100644 index e4bd308..0000000 --- a/api/app/handler/joke/joke_add_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package joke_test - -import ( - "io/ioutil" - "net/http" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestAddNewJoke_201(t *testing.T) { - // TODO: Remove this line below, make this test works - t.SkipNow() - - reqBody := strings.NewReader("{\"link\":\"https://via.placeholder.com/300/07f/ff0000.png\",\"key\":\"test\",\"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, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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") -} - -func TestAddNewJoke_NotValidImage(t *testing.T) { - // TODO: Remove this line below, make this test works - t.SkipNow() - - reqBody := strings.NewReader("{\"link\":\"https://google.com/\",\"key\":\"test\",\"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, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - assert.Equalf(t, false, err != nil, "joke add") - assert.Equalf(t, 400, res.StatusCode, "joke add") - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - 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/handler/joke/joke_delete_test.go b/api/app/handler/joke/joke_delete_test.go deleted file mode 100644 index d2defe4..0000000 --- a/api/app/handler/joke/joke_delete_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package joke_test - -import ( - "io/ioutil" - "net/http" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestDeleteJoke_200(t *testing.T) { - // TODO: Remove this line below, make this test works - t.SkipNow() - - reqBody := strings.NewReader("{\"key\":\"very secure\",\"token\":\"password\"}") - req, _ := http.NewRequest("DELETE", "/id/100", reqBody) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "joke delete") - assert.Equalf(t, "{\"message\":\"specified joke id has been deleted\"}", string(body), "joke delete") -} -func TestDeleteJoke_NotExists(t *testing.T) { - // TODO: Remove this line below, make this test works - t.SkipNow() - - reqBody := strings.NewReader("{\"key\":\"very secure\",\"token\":\"password\"}") - req, _ := http.NewRequest("DELETE", "/id/100", reqBody) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - 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/handler/joke/joke_get.go b/api/app/handler/joke/joke_get.go index c9ee9cd..0e57e68 100644 --- a/api/app/handler/joke/joke_get.go +++ b/api/app/handler/joke/joke_get.go @@ -28,43 +28,42 @@ func (d *Dependencies) TodayJoke(c *fiber.Ctx) error { if eq { c.Set("Content-Type", joke.ContentType) return c.Status(fiber.StatusOK).Send([]byte(joke.Image)) - } else { - conn, err := d.DB.Acquire(*d.Context) - if err != nil { - return err - } - defer conn.Release() - var link string - err = conn.QueryRow(*d.Context, "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link) - if err != nil { - return err - } - - response, err := d.HTTP.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 = d.Redis.MSet(*d.Context, 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) } + conn, err := d.DB.Acquire(*d.Context) + if err != nil { + return err + } + defer conn.Release() + var link string + err = conn.QueryRow(*d.Context, "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link) + if err != nil { + return err + } + + response, err := d.HTTP.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 = d.Redis.MSet(*d.Context, 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 (d *Dependencies) SingleJoke(c *fiber.Ctx) error { diff --git a/api/app/handler/joke/joke_get_test.go b/api/app/handler/joke/joke_get_test.go deleted file mode 100644 index 6d374f3..0000000 --- a/api/app/handler/joke/joke_get_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package joke_test - -import ( - "io/ioutil" - "net/http" - "testing" - "time" - - _ "github.com/joho/godotenv/autoload" - "github.com/stretchr/testify/assert" -) - -/// Need to find some workaround for this test -func TestTodayJoke(t *testing.T) { - req, _ := http.NewRequest("GET", "/today", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "today joke") -} - -func TestSingleJoke(t *testing.T) { - req, _ := http.NewRequest("GET", "/", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "single joke") -} - -func TestJokeByID_200(t *testing.T) { - req, _ := http.NewRequest("GET", "/id/1", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "joke by id") -} - -func TestJokeByID_404(t *testing.T) { - req, _ := http.NewRequest("GET", "/id/300", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - 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/handler/joke/joke_total_test.go b/api/app/handler/joke/joke_total_test.go deleted file mode 100644 index e7cd8f9..0000000 --- a/api/app/handler/joke/joke_total_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package joke_test - -import ( - "io/ioutil" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestTotalJokes(t *testing.T) { - req, _ := http.NewRequest("GET", "/total", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - 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\":\"3\"}", string(body), "joke total") - -} diff --git a/api/app/handler/joke/joke_update_test.go b/api/app/handler/joke/joke_update_test.go deleted file mode 100644 index f1a8898..0000000 --- a/api/app/handler/joke/joke_update_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package joke_test - -import ( - "io/ioutil" - "net/http" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestUpdateJoke_200(t *testing.T) { - t.SkipNow() - - reqBody := strings.NewReader("{\"link\":\"https://picsum.photos/id/9/200/300\",\"key\":\"test\",\"token\":\"password\"}") - req, _ := http.NewRequest("PATCH", "/id/1", reqBody) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "joke update") - assert.Equalf(t, "{\"message\":\"specified joke id has been deleted\"}", string(body), "joke update") -} - -func TestUpdateJoke_NotExists(t *testing.T) { - // TODO: Remove this line below, make this test works - t.SkipNow() - - reqBody := strings.NewReader("{\"link\":\"https://picsum.photos/id/9/200/300\",\"key\":\"test\",\"token\":\"password\"}") - req, _ := http.NewRequest("PATCH", "/id/100", reqBody) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - 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) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - 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/handler/submit/dependencies_test.go b/api/app/handler/submit/dependencies_test.go deleted file mode 100644 index 1733fac..0000000 --- a/api/app/handler/submit/dependencies_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package submit_test - -import ( - v1 "jokes-bapak2-api/app" - - "github.com/gofiber/fiber/v2" -) - -var app *fiber.App = v1.New() diff --git a/api/app/handler/submit/submit_add.go b/api/app/handler/submit/submit_add.go index 6afd4e5..234a5cf 100644 --- a/api/app/handler/submit/submit_add.go +++ b/api/app/handler/submit/submit_add.go @@ -1,6 +1,7 @@ package submit import ( + "context" "jokes-bapak2-api/app/core" "net/url" "strings" @@ -10,6 +11,7 @@ import ( "github.com/georgysavva/scany/pgxscan" "github.com/gofiber/fiber/v2" "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" ) func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error { @@ -73,23 +75,14 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error { return err } } + // Validate if link already exists - sql, args, err := d.Query. - Select("link"). - From("submission"). - Where(squirrel.Eq{"link": link}). - ToSql() + validateLink, err := validateIfLinkExists(conn, d.Context, d.Query, link) if err != nil { return err } - var validateLink string - err = conn.QueryRow(*d.Context, sql, args...).Scan(&validateLink) - if err != nil && err != pgx.ErrNoRows { - return err - } - - if err == nil && validateLink != "" { + if validateLink { return c.Status(fiber.StatusConflict).JSON(Error{ Error: "Given link is already on the submission queue.", }) @@ -97,7 +90,7 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error { now := time.Now().UTC().Format(time.RFC3339) - sql, args, err = d.Query. + sql, args, err := d.Query. Insert("submission"). Columns("link", "created_at", "author"). Values(link, now, body.Author). @@ -127,3 +120,26 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error { AuthorPage: "/submit?author=" + url.QueryEscape(body.Author), }) } + +func validateIfLinkExists(conn *pgxpool.Conn, ctx *context.Context, query squirrel.StatementBuilderType, link string) (bool, error) { + sql, args, err := query. + Select("link"). + From("submission"). + Where(squirrel.Eq{"link": link}). + ToSql() + if err != nil { + return false, err + } + + var validateLink string + err = conn.QueryRow(*ctx, sql, args...).Scan(&validateLink) + if err != nil && err != pgx.ErrNoRows { + return false, err + } + + if err == nil && validateLink != "" { + return true, nil + } + + return false, nil +} diff --git a/api/app/handler/submit/submit_get_test.go b/api/app/handler/submit/submit_get_test.go deleted file mode 100644 index 864ba97..0000000 --- a/api/app/handler/submit/submit_get_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package submit_test - -import ( - "io/ioutil" - "net/http" - "testing" - "time" - - _ "github.com/joho/godotenv/autoload" - - "github.com/stretchr/testify/assert" -) - -func TestGetSubmission_200(t *testing.T) { - req, _ := http.NewRequest("GET", "/submit", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - assert.Equalf(t, false, err != nil, "get submission") - assert.Equalf(t, 200, res.StatusCode, "get submission") - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "get submission") - assert.Equalf(t, "{\"count\":2,\"jokes\":[{\"id\":1,\"link\":\"https://via.placeholder.com/300/01f/fff.png\",\"created_at\":\"2021-08-03T18:20:38Z\",\"author\":\"Test \\u003ctest@example.com\\u003e\",\"status\":0},{\"id\":2,\"link\":\"https://via.placeholder.com/300/02f/fff.png\",\"created_at\":\"2021-08-04T18:20:38Z\",\"author\":\"Test \\u003ctest@example.com\\u003e\",\"status\":1}]}", string(body), "get submission") -} - -func TestGetSubmission_Params(t *testing.T) { - req, _ := http.NewRequest("GET", "/submit?page=1&limit=5&approved=true", nil) - res, err := app.Test(req, int(time.Minute*2)) - if err != nil { - t.Fatal(err) - } - - assert.Equalf(t, false, err != nil, "get submission") - assert.Equalf(t, 200, res.StatusCode, "get submission") - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - assert.Nilf(t, err, "get submission") - assert.Equalf(t, "{\"count\":1,\"jokes\":[{\"id\":2,\"link\":\"https://via.placeholder.com/300/02f/fff.png\",\"created_at\":\"2021-08-04T18:20:38Z\",\"author\":\"Test \\u003ctest@example.com\\u003e\",\"status\":1}]}", string(body), "get submission") -} diff --git a/api/app/platform/database/create.go b/api/app/platform/database/create.go index 1b4ea70..c570f02 100644 --- a/api/app/platform/database/create.go +++ b/api/app/platform/database/create.go @@ -17,15 +17,46 @@ func Setup(db *pgxpool.Pool, ctx *context.Context) error { } defer conn.Release() - // administrators table + err = setupAuthTable(conn, ctx) + if err != nil { + return err + } + + conn2, err := db.Acquire(*ctx) + if err != nil { + log.Fatalln("32 - err here") + return err + } + defer conn2.Release() + + err = setupJokesTable(conn2, ctx) + if err != nil { + return err + } + + conn3, err := db.Acquire(*ctx) + if err != nil { + return err + } + defer conn3.Release() + + err = setupSubmissionTable(conn3, ctx) + if err != nil { + return err + } + + return nil +} + +func setupAuthTable(conn *pgxpool.Conn, ctx *context.Context) error { + // Check if table exists var tableAuthExists bool - err = conn.QueryRow(*ctx, `SELECT EXISTS ( + err := conn.QueryRow(*ctx, `SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'administrators' );`).Scan(&tableAuthExists) if err != nil { - log.Fatalln("16 - failed on checking table: ", err) return err } @@ -38,36 +69,27 @@ func Setup(db *pgxpool.Pool, ctx *context.Context) error { StringColumn("last_used"). ToSql() if err != nil { - log.Fatalln("17 - failed on table creation: ", err) return err } q, err := conn.Query(*ctx, sql) if err != nil { - log.Fatalln("18 - failed on table creation: ", err) return err } defer q.Close() } + return nil +} - conn2, err := db.Acquire(*ctx) - if err != nil { - log.Fatalln("32 - err here") - return err - } - defer conn2.Release() - - // Jokesbapak2 table - +func setupJokesTable(conn *pgxpool.Conn, ctx *context.Context) error { // Check if table exists var tableJokesExists bool - err = conn2.QueryRow(*ctx, `SELECT EXISTS ( + err := conn.QueryRow(*ctx, `SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'jokesbapak2' );`).Scan(&tableJokesExists) if err != nil { - log.Fatalln("10 - failed on checking table: ", err) return err } @@ -79,34 +101,28 @@ func Setup(db *pgxpool.Pool, ctx *context.Context) error { AddColumn(bob.ColumnDef{Name: "creator", Type: "INT", Extras: []string{"NOT NULL", "REFERENCES \"administrators\" (\"id\")"}}). ToSql() if err != nil { - log.Fatalln("11 - failed on table creation: ", err) return err } - q, err := conn2.Query(*ctx, sql) + q, err := conn.Query(*ctx, sql) if err != nil { - log.Fatalln("12 - failed on table creation: ", err) return err } defer q.Close() } - // Submission table - conn3, err := db.Acquire(*ctx) - if err != nil { - return err - } - defer conn3.Release() + return nil +} +func setupSubmissionTable(conn *pgxpool.Conn, ctx *context.Context) error { //Check if table exists var tableSubmissionExists bool - err = conn3.QueryRow(*ctx, `SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = 'submission' - );`).Scan(&tableSubmissionExists) + err := conn.QueryRow(*ctx, `SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = 'submission' + );`).Scan(&tableSubmissionExists) if err != nil { - log.Fatalln("13 - failed on checking table: ", err) return err } @@ -120,15 +136,14 @@ func Setup(db *pgxpool.Pool, ctx *context.Context) error { AddColumn(bob.ColumnDef{Name: "status", Type: "SMALLINT", Extras: []string{"DEFAULT 0"}}). ToSql() if err != nil { - log.Fatalln("14 - failed on table creation: ", err) + return err } - q, err := conn3.Query(*ctx, sql) + q, err := conn.Query(*ctx, sql) if err != nil { - log.Fatalln("15 - failed on table creation: ", err) + return err } defer q.Close() } - return nil }