feat: joke submission
This commit is contained in:
parent
9d9311e90c
commit
c265da52a1
|
@ -0,0 +1,81 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"jokes-bapak2-api/app/v1/models"
|
||||||
|
"jokes-bapak2-api/app/v1/utils"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gojek/heimdall/v7/httpclient"
|
||||||
|
"github.com/pquerna/ffjson/ffjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadImage process the image from the user to be uploaded to the cloud storage.
|
||||||
|
// Returns the image URL.
|
||||||
|
func UploadImage(client *httpclient.Client, image io.Reader) (string, error) {
|
||||||
|
hostURL := os.Getenv("IMAGE_API_URL")
|
||||||
|
fileName, err := utils.RandomString(10)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
fw, err := writer.CreateFormField("image")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(fw, image)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := http.Header{
|
||||||
|
"Content-Type": []string{writer.FormDataContentType()},
|
||||||
|
"User-Agent": []string{"JokesBapak2 API"},
|
||||||
|
"Accept": []string{"application/json"},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestURL, err := url.Parse(hostURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("key", os.Getenv("IMAGE_API_KEY"))
|
||||||
|
params.Add("name", fileName)
|
||||||
|
|
||||||
|
requestURL.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
res, err := client.Post(requestURL.String(), bytes.NewReader(body.Bytes()), headers)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data models.ImageAPI
|
||||||
|
err = ffjson.Unmarshal(responseBody, &data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.Data.URL, nil
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateAuthor(author string) bool {
|
||||||
|
if len(author) > 200 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(author, " ")
|
||||||
|
if strings.HasPrefix(split[0], "<") && strings.HasSuffix(split[0], ">") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(split[len(split)-1], "<") && !strings.HasSuffix(split[len(split)-1], ">") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
email := strings.Replace(split[len(split)-1], "<", "", 1)
|
||||||
|
email = strings.Replace(email, ">", "", 1)
|
||||||
|
pattern := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
return pattern.MatchString(email)
|
||||||
|
}
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"github.com/gojek/heimdall/v7/httpclient"
|
"github.com/gojek/heimdall/v7/httpclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
var psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
var Psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||||
var db = database.New()
|
var Db = database.New()
|
||||||
var redis = cache.New()
|
var Redis = cache.New()
|
||||||
var memory = cache.InMemory()
|
var Memory = cache.InMemory()
|
||||||
var client = httpclient.NewClient(httpclient.WithHTTPTimeout(10 * time.Second))
|
var Client = httpclient.NewClient(httpclient.WithHTTPTimeout(10 * time.Second))
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"jokes-bapak2-api/app/v1/models"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Health(c *fiber.Ctx) error {
|
|
||||||
// Ping REDIS database
|
|
||||||
err := redis.Ping(context.Background()).Err()
|
|
||||||
if err != nil {
|
|
||||||
return c.
|
|
||||||
Status(fiber.StatusServiceUnavailable).
|
|
||||||
JSON(models.Error{
|
|
||||||
Error: "REDIS: " + err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = 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)
|
|
||||||
}
|
|
|
@ -12,6 +12,20 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHealth(t *testing.T) {
|
func TestHealth(t *testing.T) {
|
||||||
err := database.Setup()
|
err := database.Setup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
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")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"jokes-bapak2-api/app/v1/core"
|
|
||||||
"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(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 := 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 = db.Query(context.Background(), sql, args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = core.SetAllJSONJoke(db, memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = core.SetTotalJoke(db, memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusCreated).JSON(models.ResponseJoke{
|
|
||||||
Link: body.Link,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package handler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io/ioutil"
|
|
||||||
v1 "jokes-bapak2-api/app/v1"
|
|
||||||
"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 = 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")
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 := psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var jokeID int
|
|
||||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if jokeID == id {
|
|
||||||
sql, args, err = psql.Delete("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Query(context.Background(), sql, args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = core.SetAllJSONJoke(db, memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = core.SetTotalJoke(db, 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",
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package handler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io/ioutil"
|
|
||||||
v1 "jokes-bapak2-api/app/v1"
|
|
||||||
"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 = 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 = 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")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"jokes-bapak2-api/app/v1/core"
|
|
||||||
"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 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 := 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 := db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := 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 = 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(memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkCache {
|
|
||||||
jokes, err := core.GetAllJSONJokes(db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = memory.Set("jokes", jokes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
link, err := core.GetRandomJokeFromCache(memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get image data
|
|
||||||
response, err := 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(memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkCache {
|
|
||||||
jokes, err := core.GetAllJSONJokes(db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = 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(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 := 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)
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package handler_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")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"jokes-bapak2-api/app/v1/core"
|
|
||||||
"jokes-bapak2-api/app/v1/models"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TotalJokes(c *fiber.Ctx) error {
|
|
||||||
checkTotal, err := core.CheckTotalJokesCache(memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkTotal {
|
|
||||||
err = core.SetTotalJoke(db, memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
total, err := 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])),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
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")
|
|
||||||
// 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")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"jokes-bapak2-api/app/v1/core"
|
|
||||||
"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 := psql.Select("id").From("jokesbapak2").Where(squirrel.Eq{"id": id}).ToSql()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var jokeID string
|
|
||||||
err = 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(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 = psql.Update("jokesbapak2").Set("link", body.Link).Set("creator", c.Locals("userID")).ToSql()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Query(context.Background(), sql, args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = core.SetAllJSONJoke(db, memory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = core.SetTotalJoke(db, 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",
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package handler_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io/ioutil"
|
|
||||||
v1 "jokes-bapak2-api/app/v1"
|
|
||||||
"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 = 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 = 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")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package submit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"jokes-bapak2-api/app/v1/core"
|
||||||
|
"jokes-bapak2-api/app/v1/handler"
|
||||||
|
"jokes-bapak2-api/app/v1/models"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/georgysavva/scany/pgxscan"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SubmitJoke(c *fiber.Ctx) error {
|
||||||
|
var body models.Submission
|
||||||
|
err := c.BodyParser(&body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image and/or Link should not be empty
|
||||||
|
if body.Image == "" && body.Link == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(models.Error{
|
||||||
|
Error: "a link or an image should be supplied in a form of multipart/form-data",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author should be supplied
|
||||||
|
if body.Author == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(models.Error{
|
||||||
|
Error: "an author key consisting on the format \"yourname <youremail@mail>\" must be supplied",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Validate format
|
||||||
|
valid := core.ValidateAuthor(body.Author)
|
||||||
|
if !valid {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(models.Error{
|
||||||
|
Error: "please stick to the format of \"yourname <youremail@mail>\" and within 200 characters",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var url string
|
||||||
|
|
||||||
|
// Check link validity if link was provided
|
||||||
|
if body.Link != "" {
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
url = body.Link
|
||||||
|
}
|
||||||
|
|
||||||
|
// If image was provided
|
||||||
|
if body.Image != "" {
|
||||||
|
image := strings.NewReader(body.Image)
|
||||||
|
|
||||||
|
url, err = core.UploadImage(handler.Client, image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
|
||||||
|
sql, args, err := handler.Psql.
|
||||||
|
Insert("submission").
|
||||||
|
Columns("link", "created_at", "author").
|
||||||
|
Values(url, now, body.Author).
|
||||||
|
Suffix("RETURNING id,created_at,link,author,status").
|
||||||
|
ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var submission []models.Submission
|
||||||
|
result, err := handler.Db.Query(context.Background(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer result.Close()
|
||||||
|
|
||||||
|
err = pgxscan.ScanAll(&submission, result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.
|
||||||
|
Status(fiber.StatusOK).
|
||||||
|
JSON(models.ResponseSubmission{
|
||||||
|
Message: "Joke submitted. Please wait for a few days for admin to approve your submission.",
|
||||||
|
Data: submission[0],
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package submit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"jokes-bapak2-api/app/v1/handler"
|
||||||
|
"jokes-bapak2-api/app/v1/models"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/aldy505/bob"
|
||||||
|
"github.com/georgysavva/scany/pgxscan"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSubmission(c *fiber.Ctx) error {
|
||||||
|
query := new(models.SubmissionQuery)
|
||||||
|
err := c.QueryParser(query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var limit int
|
||||||
|
var offset int
|
||||||
|
var approved bool
|
||||||
|
|
||||||
|
if query.Limit != "" {
|
||||||
|
limit, err = strconv.Atoi(query.Limit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if query.Page != "" {
|
||||||
|
page, err := strconv.Atoi(query.Page)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
offset = (page - 1) * 20
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Approved != "" {
|
||||||
|
approved, err = strconv.ParseBool(query.Approved)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var status int
|
||||||
|
|
||||||
|
if approved {
|
||||||
|
status = 1
|
||||||
|
} else {
|
||||||
|
status = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var sql string
|
||||||
|
var args []interface{}
|
||||||
|
|
||||||
|
var sqlQuery *bytes.Buffer = &bytes.Buffer{}
|
||||||
|
sqlQuery.WriteString("SELECT * FROM submission WHERE TRUE")
|
||||||
|
|
||||||
|
if query.Author != "" {
|
||||||
|
sqlQuery.WriteString(" AND author = ?")
|
||||||
|
args = append(args, query.Author)
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Approved != "" {
|
||||||
|
sqlQuery.WriteString(" AND status = ?")
|
||||||
|
args = append(args, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 0 {
|
||||||
|
sqlQuery.WriteString(" LIMIT " + strconv.Itoa(limit))
|
||||||
|
} else {
|
||||||
|
sqlQuery.WriteString(" LIMIT 20")
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Page != "" {
|
||||||
|
sqlQuery.WriteString(" OFFSET " + strconv.Itoa(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = bob.ReplacePlaceholder(sqlQuery.String(), bob.Dollar)
|
||||||
|
|
||||||
|
var submissions []models.Submission
|
||||||
|
results, err := handler.Db.Query(context.Background(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer results.Close()
|
||||||
|
|
||||||
|
err = pgxscan.ScanAll(&submissions, results)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.
|
||||||
|
Status(fiber.StatusOK).
|
||||||
|
JSON(fiber.Map{
|
||||||
|
"count": len(submissions),
|
||||||
|
"jokes": submissions,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrNoRows = errors.New("no rows in result set")
|
||||||
|
var ErrConnDone = errors.New("connection is already closed")
|
||||||
|
var ErrTxDone = errors.New("transaction has already been committed or rolled back")
|
||||||
|
|
||||||
|
var ErrNotFound = errors.New("record not found")
|
||||||
|
var ErrEmpty = errors.New("record is empty")
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
ID int `json:"id" form:"id" db:"id"`
|
||||||
|
Key string `json:"key" form:"key" db:"key"`
|
||||||
|
Token string `json:"token" form:"token" db:"token"`
|
||||||
|
LastUsed string `json:"last_used" form:"last_used" db:"last_used"`
|
||||||
|
}
|
|
@ -6,15 +6,13 @@ type Joke struct {
|
||||||
Creator int `json:"creator" form:"creator" db:"creator"`
|
Creator int `json:"creator" form:"creator" db:"creator"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct {
|
|
||||||
ID int `json:"id" form:"id" db:"id"`
|
|
||||||
Key string `json:"key" form:"key" db:"key"`
|
|
||||||
Token string `json:"token" form:"token" db:"token"`
|
|
||||||
LastUsed string `json:"last_used" form:"last_used" db:"last_used"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Today struct {
|
type Today struct {
|
||||||
Date string `redis:"today:date"`
|
Date string `redis:"today:date"`
|
||||||
Image string `redis:"today:image"`
|
Image string `redis:"today:image"`
|
||||||
ContentType string `redis:"today:contentType"`
|
ContentType string `redis:"today:contentType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResponseJoke struct {
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseJoke struct {
|
|
||||||
Link string `json:"link,omitempty"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Submission struct {
|
||||||
|
ID int `json:"id,omitempty" db:"id"`
|
||||||
|
Link string `json:"link" form:"link" db:"link"`
|
||||||
|
Image string `json:"image,omitempty" form:"image"`
|
||||||
|
CreatedAt string `json:"created_at" db:"created_at"`
|
||||||
|
Author string `json:"author" form:"author" db:"author"`
|
||||||
|
Status int `json:"status" db:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmissionQuery struct {
|
||||||
|
Author string `query:"author"`
|
||||||
|
Limit string `query:"limit"`
|
||||||
|
Page string `query:"page"`
|
||||||
|
Approved string `query:"approved"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseSubmission struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Data Submission `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageAPI struct {
|
||||||
|
Data ImageAPIData `json:"data"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageAPIData struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
URLViewer string `json:"url_viewer"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
DisplayURL string `json:"display_url"`
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"jokes-bapak2-api/app/v1/handler/submit"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Submit(app *fiber.App) *fiber.App {
|
||||||
|
// Get pending submitted joke
|
||||||
|
app.Get("/submit", submit.GetSubmission)
|
||||||
|
|
||||||
|
// Add a joke
|
||||||
|
app.Post("/submit", submit.SubmitJoke)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
Loading…
Reference in New Issue