Merge pull request #8 from aldy505/api/submit
Let people submit their jokes to the API
This commit is contained in:
commit
27c0c82b40
|
@ -26,8 +26,9 @@ func New() *fiber.App {
|
||||||
})
|
})
|
||||||
|
|
||||||
err := sentry.Init(sentry.ClientOptions{
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: os.Getenv("SENTRY_DSN"),
|
Dsn: os.Getenv("SENTRY_DSN"),
|
||||||
Environment: os.Getenv("ENV"),
|
Environment: os.Getenv("ENV"),
|
||||||
|
AttachStacktrace: true,
|
||||||
// Enable printing of SDK debug messages.
|
// Enable printing of SDK debug messages.
|
||||||
// Useful when getting started or trying to figure something out.
|
// Useful when getting started or trying to figure something out.
|
||||||
Debug: true,
|
Debug: true,
|
||||||
|
@ -58,6 +59,7 @@ func New() *fiber.App {
|
||||||
|
|
||||||
routes.Health(app)
|
routes.Health(app)
|
||||||
routes.Joke(app)
|
routes.Joke(app)
|
||||||
|
routes.Submit(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ func GetAllJSONJokes(db *pgxpool.Pool) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer results.Close()
|
||||||
|
|
||||||
err = pgxscan.ScanAll(&jokes, results)
|
err = pgxscan.ScanAll(&jokes, results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -23,8 +23,8 @@ func CheckImageValidity(client *httpclient.Client, link string) (bool, error) {
|
||||||
if res.StatusCode == 200 && utils.IsIn(ValidContentType, res.Header.Get("content-type")) {
|
if res.StatusCode == 200 && utils.IsIn(ValidContentType, res.Header.Get("content-type")) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, errors.New("URL must use HTTPS protocol")
|
return false, errors.New("URL must use HTTPS protocol")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,507 @@
|
||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Jokesbapak2 Image API",
|
||||||
|
"description": "Jokes Bapak2 is an image API that you can use for free! I've been seeing lots and lots of Indonesian dad jokes on Twitter, Facebook and Instagram on early 2020. In a month, I made a Discord bot that provides the jokes. But I thought, why not make it as an API?\n",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"contact": {
|
||||||
|
"name": "Reinaldy Rafli",
|
||||||
|
"url": "https://github.com/aldy505",
|
||||||
|
"email": "aldy505@tutanota.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "GNU General Public License v3.0",
|
||||||
|
"url": "https://github.com/aldy505/jokes-bapak2/blob/master/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://jokesbapak2.herokuapp.com/v1",
|
||||||
|
"description": "Production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:5000",
|
||||||
|
"description": "Development"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"summary": "Get random Jokes Bapak2 image",
|
||||||
|
"description": "Returns a different image (PNG, JPG, or GIF) for every call.",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Image data",
|
||||||
|
"content": {
|
||||||
|
"image/gif": {},
|
||||||
|
"image/png": {},
|
||||||
|
"image/jpeg": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"summary": "Add a new joke into database",
|
||||||
|
"description": "asd",
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "asds",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/request.auth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/request.joke"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Image has been added",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/request.joke"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"link": "https://link.to/image.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"error": "URL provided is not a valid image"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Must be authenticated to submit a joke",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/id/{id}": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"description": "A number that represents image's ID"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"get": {
|
||||||
|
"summary": "Get random Jokes Bapak2 image by ID",
|
||||||
|
"description": "Returns consistent image for every call.",
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Image data",
|
||||||
|
"content": {
|
||||||
|
"image/jpeg": {},
|
||||||
|
"image/png": {},
|
||||||
|
"image/gif": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Provided image ID was not found",
|
||||||
|
"content": {
|
||||||
|
"text/plain": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "Requested ID was not found."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"summary": "Update a Joke with certain image ID",
|
||||||
|
"description": "Returns consistent image for every call.",
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Sucessfully updated an image item",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/response.confirmation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/request.joke"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Link provided is not a valid image",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Must be authenticated to submit a joke",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "If the Joke ID does not exists",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"summary": "Delete a Joke with certain image ID",
|
||||||
|
"description": "hi",
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Sucessfully deleted an image item",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.confirmation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Must be authenticated to submit a joke",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "If the Joke ID does not exists",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/today": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get the joke of the day",
|
||||||
|
"description": "A joke a day makes more of a dad out of you.",
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Image data",
|
||||||
|
"content": {
|
||||||
|
"image/jpeg": {},
|
||||||
|
"image/png": {},
|
||||||
|
"image/gif": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/total": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get total amount of jokes in database",
|
||||||
|
"tags": [
|
||||||
|
"Jokes"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Total jokes",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.confirmation"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"message": "154"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/submit": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get submitted Jokes",
|
||||||
|
"tags": [
|
||||||
|
"Submission"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "author",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"description": "Author to be queried",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approved",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"description": "Whether query just approved jokes or not",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "asd",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"jokes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/response.submission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"summary": "Submit a joke",
|
||||||
|
"description": "Must be in multipart/form-data format. Author must be in the format of \"Name <email>\".\n",
|
||||||
|
"tags": [
|
||||||
|
"Submission"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"link": {
|
||||||
|
"description": "Image link",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"description": "Image data",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"description": "Person who submitted this",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"author",
|
||||||
|
"image",
|
||||||
|
"link"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Joke successfully submitted",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/response.confirmation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/response.submission"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid data was sent",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/health": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Health check",
|
||||||
|
"description": "Ping the databases to make sure everything's alright",
|
||||||
|
"tags": [
|
||||||
|
"Health"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Everything is okay"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Something is not okay",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/response.error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"request.auth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request.joke": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"link": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.confirmation": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.error": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response.submission": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: Jokesbapak2 Image API
|
||||||
|
description: >
|
||||||
|
Jokes Bapak2 is an image API that you can use for free! I've been seeing lots and lots of Indonesian dad jokes on Twitter,
|
||||||
|
Facebook and Instagram on early 2020. In a month, I made a Discord bot that provides the jokes.
|
||||||
|
But I thought, why not make it as an API?
|
||||||
|
version: 0.0.1
|
||||||
|
contact:
|
||||||
|
name: Reinaldy Rafli
|
||||||
|
url: https://github.com/aldy505
|
||||||
|
email: aldy505@tutanota.com
|
||||||
|
license:
|
||||||
|
name: GNU General Public License v3.0
|
||||||
|
url: https://github.com/aldy505/jokes-bapak2/blob/master/LICENSE
|
||||||
|
servers:
|
||||||
|
- url: "https://jokesbapak2.herokuapp.com/v1"
|
||||||
|
description: Production
|
||||||
|
- url: "http://localhost:5000"
|
||||||
|
description: Development
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
summary: Get random Jokes Bapak2 image
|
||||||
|
description: Returns a different image (PNG, JPG, or GIF) for every call.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Image data
|
||||||
|
content:
|
||||||
|
'image/gif': {}
|
||||||
|
'image/png': {}
|
||||||
|
'image/jpeg': {}
|
||||||
|
put:
|
||||||
|
summary: Add a new joke into database
|
||||||
|
description: asd
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
requestBody:
|
||||||
|
description: asds
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/request.auth'
|
||||||
|
- $ref: '#/components/schemas/request.joke'
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: Image has been added
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/request.joke'
|
||||||
|
example:
|
||||||
|
link: https://link.to/image.jpg
|
||||||
|
400:
|
||||||
|
description: Bad request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
example:
|
||||||
|
error: URL provided is not a valid image
|
||||||
|
403:
|
||||||
|
description: Must be authenticated to submit a joke
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
/id/{id}:
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
description: A number that represents image's ID
|
||||||
|
get:
|
||||||
|
summary: Get random Jokes Bapak2 image by ID
|
||||||
|
description: Returns consistent image for every call.
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Image data
|
||||||
|
content:
|
||||||
|
'image/jpeg': {}
|
||||||
|
'image/png': {}
|
||||||
|
'image/gif': {}
|
||||||
|
404:
|
||||||
|
description: Provided image ID was not found
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
Requested ID was not found.
|
||||||
|
patch:
|
||||||
|
summary: Update a Joke with certain image ID
|
||||||
|
description: Returns consistent image for every call.
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Sucessfully updated an image item
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/response.confirmation'
|
||||||
|
- $ref: '#/components/schemas/request.joke'
|
||||||
|
400:
|
||||||
|
description: Link provided is not a valid image
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
403:
|
||||||
|
description: Must be authenticated to submit a joke
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
406:
|
||||||
|
description: If the Joke ID does not exists
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
delete:
|
||||||
|
summary: Delete a Joke with certain image ID
|
||||||
|
description: hi
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Sucessfully deleted an image item
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.confirmation'
|
||||||
|
403:
|
||||||
|
description: Must be authenticated to submit a joke
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
406:
|
||||||
|
description: If the Joke ID does not exists
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
/today:
|
||||||
|
get:
|
||||||
|
summary: Get the joke of the day
|
||||||
|
description: A joke a day makes more of a dad out of you.
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Image data
|
||||||
|
content:
|
||||||
|
'image/jpeg': {}
|
||||||
|
'image/png': {}
|
||||||
|
'image/gif': {}
|
||||||
|
/total:
|
||||||
|
get:
|
||||||
|
summary: Get total amount of jokes in database
|
||||||
|
tags:
|
||||||
|
- Jokes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Total jokes
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.confirmation'
|
||||||
|
example:
|
||||||
|
message: "154"
|
||||||
|
/submit:
|
||||||
|
get:
|
||||||
|
summary: Get submitted Jokes
|
||||||
|
tags:
|
||||||
|
- Submission
|
||||||
|
parameters:
|
||||||
|
- name: author
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Author to be queried
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: approved
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Whether query just approved jokes or not
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: asd
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: number
|
||||||
|
jokes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/response.submission'
|
||||||
|
post:
|
||||||
|
summary: Submit a joke
|
||||||
|
description: >
|
||||||
|
Must be in multipart/form-data format.
|
||||||
|
Author must be in the format of "Name <email>".
|
||||||
|
tags:
|
||||||
|
- Submission
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
link:
|
||||||
|
description: Image link
|
||||||
|
type: string
|
||||||
|
image:
|
||||||
|
description: Image data
|
||||||
|
type: string
|
||||||
|
author:
|
||||||
|
description: Person who submitted this
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- author
|
||||||
|
- image
|
||||||
|
- link
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: Joke successfully submitted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/response.confirmation'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/response.submission'
|
||||||
|
400:
|
||||||
|
description: Invalid data was sent
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
/health:
|
||||||
|
get:
|
||||||
|
summary: Health check
|
||||||
|
description: Ping the databases to make sure everything's alright
|
||||||
|
tags:
|
||||||
|
- Health
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Everything is okay
|
||||||
|
403:
|
||||||
|
description: Something is not okay
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/response.error'
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
request.auth:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
request.joke:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
link:
|
||||||
|
type: string
|
||||||
|
response.confirmation:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
response.error:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
response.submission:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
link:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
author:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: number
|
|
@ -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,7 +1,8 @@
|
||||||
package handler
|
package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"jokes-bapak2-api/app/v1/handler"
|
||||||
"jokes-bapak2-api/app/v1/models"
|
"jokes-bapak2-api/app/v1/models"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
@ -9,7 +10,7 @@ import (
|
||||||
|
|
||||||
func Health(c *fiber.Ctx) error {
|
func Health(c *fiber.Ctx) error {
|
||||||
// Ping REDIS database
|
// Ping REDIS database
|
||||||
err := redis.Ping(context.Background()).Err()
|
err := handler.Redis.Ping(context.Background()).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.
|
return c.
|
||||||
Status(fiber.StatusServiceUnavailable).
|
Status(fiber.StatusServiceUnavailable).
|
||||||
|
@ -18,7 +19,7 @@ func Health(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Query(context.Background(), "SELECT \"id\" FROM \"jokesbapak2\" LIMIT 1")
|
_, err = handler.Db.Query(context.Background(), "SELECT \"id\" FROM \"jokesbapak2\" LIMIT 1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.
|
return c.
|
||||||
Status(fiber.StatusServiceUnavailable).
|
Status(fiber.StatusServiceUnavailable).
|
|
@ -0,0 +1,72 @@
|
||||||
|
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/gofiber/fiber/v2"
|
||||||
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *pgxpool.Pool = 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}
|
||||||
|
var app *fiber.App = v1.New()
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
j, err := db.Query(context.Background(), "DROP TABLE \"jokesbapak2\"")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
a, err := db.Query(context.Background(), "DROP TABLE \"administrators\"")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer j.Close()
|
||||||
|
defer a.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() error {
|
||||||
|
err := database.Setup()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a, 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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
j, 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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer a.Close()
|
||||||
|
defer j.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealth(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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,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")
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
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.
|
||||||
|
r, err := handler.Db.Query(context.Background(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package joke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddNewJoke_201(t *testing.T) {
|
||||||
|
// TODO: Remove this line below, make this test works
|
||||||
|
t.SkipNow()
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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, -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddNewJoke_NotValidImage(t *testing.T) {
|
||||||
|
// TODO: Remove this line below, make this test works
|
||||||
|
t.SkipNow()
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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, -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")
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := handler.Db.Query(context.Background(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package joke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeleteJoke_200(t *testing.T) {
|
||||||
|
// TODO: Remove this line below, make this test works
|
||||||
|
t.SkipNow()
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := db.Query(context.Background(), "INSERT INTO \"jokesbapak2\" (id, link, creator) VALUES ($1, $2, $3);", 100, "https://via.placeholder.com/300/01f/fff.png", 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer j.Close()
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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, -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")
|
||||||
|
}
|
||||||
|
func TestDeleteJoke_NotExists(t *testing.T) {
|
||||||
|
// TODO: Remove this line below, make this test works
|
||||||
|
t.SkipNow()
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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, -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,4 +1,4 @@
|
||||||
package handler
|
package joke
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"jokes-bapak2-api/app/v1/core"
|
"jokes-bapak2-api/app/v1/core"
|
||||||
|
"jokes-bapak2-api/app/v1/handler"
|
||||||
"jokes-bapak2-api/app/v1/models"
|
"jokes-bapak2-api/app/v1/models"
|
||||||
"jokes-bapak2-api/app/v1/utils"
|
"jokes-bapak2-api/app/v1/utils"
|
||||||
|
|
||||||
|
@ -14,11 +15,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TodayJoke(c *fiber.Ctx) error {
|
func TodayJoke(c *fiber.Ctx) error {
|
||||||
// check from redis if today's joke already exists
|
// check from handler.Redis if today's joke already exists
|
||||||
// send the joke if exists
|
// send the joke if exists
|
||||||
// get a new joke if it's not, then send it.
|
// get a new joke if it's not, then send it.
|
||||||
var joke models.Today
|
var joke models.Today
|
||||||
err := redis.MGet(context.Background(), "today:link", "today:date", "today:image", "today:contentType").Scan(&joke)
|
err := handler.Redis.MGet(context.Background(), "today:link", "today:date", "today:image", "today:contentType").Scan(&joke)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -33,12 +34,12 @@ func TodayJoke(c *fiber.Ctx) error {
|
||||||
return c.Status(fiber.StatusOK).Send([]byte(joke.Image))
|
return c.Status(fiber.StatusOK).Send([]byte(joke.Image))
|
||||||
} else {
|
} else {
|
||||||
var link string
|
var link string
|
||||||
err := db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link)
|
err := handler.Db.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.Get(link, nil)
|
response, err := handler.Client.Get(link, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -49,7 +50,7 @@ func TodayJoke(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC().Format(time.RFC3339)
|
now := time.Now().UTC().Format(time.RFC3339)
|
||||||
err = redis.MSet(context.Background(), map[string]interface{}{
|
err = handler.Redis.MSet(context.Background(), map[string]interface{}{
|
||||||
"today:link": link,
|
"today:link": link,
|
||||||
"today:date": now,
|
"today:date": now,
|
||||||
"today:image": string(data),
|
"today:image": string(data),
|
||||||
|
@ -66,29 +67,29 @@ func TodayJoke(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SingleJoke(c *fiber.Ctx) error {
|
func SingleJoke(c *fiber.Ctx) error {
|
||||||
checkCache, err := core.CheckJokesCache(memory)
|
checkCache, err := core.CheckJokesCache(handler.Memory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkCache {
|
if !checkCache {
|
||||||
jokes, err := core.GetAllJSONJokes(db)
|
jokes, err := core.GetAllJSONJokes(handler.Db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = memory.Set("jokes", jokes)
|
err = handler.Memory.Set("jokes", jokes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
link, err := core.GetRandomJokeFromCache(memory)
|
link, err := core.GetRandomJokeFromCache(handler.Memory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get image data
|
// Get image data
|
||||||
response, err := client.Get(link, nil)
|
response, err := handler.Client.Get(link, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -104,17 +105,17 @@ func SingleJoke(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func JokeByID(c *fiber.Ctx) error {
|
func JokeByID(c *fiber.Ctx) error {
|
||||||
checkCache, err := core.CheckJokesCache(memory)
|
checkCache, err := core.CheckJokesCache(handler.Memory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkCache {
|
if !checkCache {
|
||||||
jokes, err := core.GetAllJSONJokes(db)
|
jokes, err := core.GetAllJSONJokes(handler.Db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = memory.Set("jokes", jokes)
|
err = handler.Memory.Set("jokes", jokes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -125,17 +126,19 @@ func JokeByID(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
link, err := core.GetCachedJokeByID(memory, id)
|
link, err := core.GetCachedJokeByID(handler.Memory, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if link == "" {
|
if link == "" {
|
||||||
return c.Status(fiber.StatusNotFound).Send([]byte("Requested ID was not found."))
|
return c.
|
||||||
|
Status(fiber.StatusNotFound).
|
||||||
|
Send([]byte("Requested ID was not found."))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get image data
|
// Get image data
|
||||||
response, err := client.Get(link, nil)
|
response, err := handler.Client.Get(link, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package joke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v1 "jokes-bapak2-api/app/v1"
|
||||||
|
"jokes-bapak2-api/app/v1/platform/database"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *pgxpool.Pool = 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}
|
||||||
|
var app *fiber.App = v1.New()
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
j, err := db.Query(context.Background(), "DROP TABLE \"jokesbapak2\"")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
a, err := db.Query(context.Background(), "DROP TABLE \"administrators\"")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer j.Close()
|
||||||
|
defer a.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() error {
|
||||||
|
err := database.Setup()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := db.Query(context.Background(), "INSERT INTO \"administrators\" (\"id\", \"key\", \"token\", \"last_used\") VALUES (1, 'test', '$argon2id$v=19$m=65536,t=16,p=4$3a08c79fbf2222467a623df9a9ebf75802c65a4f9be36eb1df2f5d2052d53cb7$ce434bd38f7ba1fc1f2eb773afb8a1f7f2dad49140803ac6cb9d7256ce9826fb3b4afa1e2488da511c852fc6c33a76d5657eba6298a8e49d617b9972645b7106', '');")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer a.Close()
|
||||||
|
|
||||||
|
j, 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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer j.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Need to find some workaround for this test
|
||||||
|
func TestTodayJoke(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSingleJoke(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJokeByID_200(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJokeByID_404(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
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])),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package joke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTotalJokes(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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\":\"3\"}", string(body), "joke total")
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := handler.Db.Query(context.Background(), sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package joke_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateJoke_200(t *testing.T) {
|
||||||
|
t.SkipNow()
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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, -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateJoke_NotExists(t *testing.T) {
|
||||||
|
// TODO: Remove this line below, make this test works
|
||||||
|
t.SkipNow()
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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, -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")
|
||||||
|
}
|
|
@ -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,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.StatusCreated).
|
||||||
|
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,79 @@
|
||||||
|
package submit_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
v1 "jokes-bapak2-api/app/v1"
|
||||||
|
"jokes-bapak2-api/app/v1/platform/database"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *pgxpool.Pool = database.New()
|
||||||
|
var submissionData = []interface{}{1, "https://via.placeholder.com/300/01f/fff.png", "2021-08-03T18:20:38Z", "Test <test@example.com>", 0, 2, "https://via.placeholder.com/300/02f/fff.png", "2021-08-04T18:20:38Z", "Test <test@example.com>", 1}
|
||||||
|
var app *fiber.App = v1.New()
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
s, err := db.Query(context.Background(), "DROP TABLE \"submission\"")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() error {
|
||||||
|
err := database.Setup()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := db.Query(context.Background(), "INSERT INTO \"submission\" (id, link, created_at, author, status) VALUES ($1, $2, $3, $4, $5), ($6, $7, $8, $9, $10);", submissionData...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func TestGetSubmission_200(t *testing.T) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "/submit", nil)
|
||||||
|
res, err := app.Test(req, -1)
|
||||||
|
|
||||||
|
assert.Equalf(t, false, err != nil, "get submission")
|
||||||
|
assert.Equalf(t, 200, res.StatusCode, "get submission")
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
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) {
|
||||||
|
err := setup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "/submit?page=1&limit=5&approved=true", nil)
|
||||||
|
res, err := app.Test(req, -1)
|
||||||
|
|
||||||
|
assert.Equalf(t, false, err != nil, "get submission")
|
||||||
|
assert.Equalf(t, 200, res.StatusCode, "get submission")
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
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")
|
||||||
|
}
|
|
@ -24,7 +24,11 @@ func RequireAuth() fiber.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if key exists
|
// Check if key exists
|
||||||
sql, args, err := psql.Select("token").From("administrators").Where(squirrel.Eq{"key": auth.Key}).ToSql()
|
sql, args, err := psql.
|
||||||
|
Select("token").
|
||||||
|
From("administrators").
|
||||||
|
Where(squirrel.Eq{"key": auth.Key}).
|
||||||
|
ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -33,9 +37,11 @@ func RequireAuth() fiber.Handler {
|
||||||
err = db.QueryRow(context.Background(), sql, args...).Scan(&token)
|
err = db.QueryRow(context.Background(), sql, args...).Scan(&token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "no rows in result set" {
|
if err.Error() == "no rows in result set" {
|
||||||
return c.Status(fiber.StatusForbidden).JSON(models.Error{
|
return c.
|
||||||
Error: "Invalid key",
|
Status(fiber.StatusForbidden).
|
||||||
})
|
JSON(models.Error{
|
||||||
|
Error: "Invalid key",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -51,7 +57,10 @@ func RequireAuth() fiber.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if verify {
|
if verify {
|
||||||
sql, args, err = psql.Update("administrators").Set("last_used", time.Now().UTC().Format(time.RFC3339)).ToSql()
|
sql, args, err = psql.
|
||||||
|
Update("administrators").
|
||||||
|
Set("last_used", time.Now().UTC().Format(time.RFC3339)).
|
||||||
|
ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +70,11 @@ func RequireAuth() fiber.Handler {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sql, args, err = psql.Select("id").From("administrators").Where(squirrel.Eq{"key": auth.Key}).ToSql()
|
sql, args, err = psql.
|
||||||
|
Select("id").
|
||||||
|
From("administrators").
|
||||||
|
Where(squirrel.Eq{"key": auth.Key}).
|
||||||
|
ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -75,8 +88,10 @@ func RequireAuth() fiber.Handler {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusForbidden).JSON(models.Error{
|
return c.
|
||||||
Error: "Invalid key",
|
Status(fiber.StatusForbidden).
|
||||||
})
|
JSON(models.Error{
|
||||||
|
Error: "Invalid key",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,10 @@ func OnlyIntegerAsID() fiber.Handler {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(models.Error{
|
return c.
|
||||||
Error: "only numbers are allowed as ID",
|
Status(fiber.StatusBadRequest).
|
||||||
})
|
JSON(models.Error{
|
||||||
|
Error: "only numbers are allowed as ID",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,3 +8,7 @@ var ErrTxDone = errors.New("transaction has already been committed or rolled bac
|
||||||
|
|
||||||
var ErrNotFound = errors.New("record not found")
|
var ErrNotFound = errors.New("record not found")
|
||||||
var ErrEmpty = errors.New("record is empty")
|
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"`
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func Setup() error {
|
||||||
log.Fatalln("17 - failed on table creation: ", err)
|
log.Fatalln("17 - failed on table creation: ", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Query(context.Background(), sql)
|
_, err = db.Query(context.Background(), sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("18 - failed on table creation: ", err)
|
log.Fatalln("18 - failed on table creation: ", err)
|
||||||
|
@ -76,5 +76,38 @@ func Setup() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Submission table
|
||||||
|
|
||||||
|
//Check if table exists
|
||||||
|
var tableSubmissionExists bool
|
||||||
|
err = db.QueryRow(context.Background(), `SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'submission'
|
||||||
|
);`).Scan(&tableJokesExists)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("13 - failed on checking table: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tableSubmissionExists {
|
||||||
|
sql, _, err := bob.
|
||||||
|
CreateTable("submission").
|
||||||
|
AddColumn(bob.ColumnDef{Name: "id", Type: "SERIAL", Extras: []string{"PRIMARY KEY"}}).
|
||||||
|
TextColumn("link", "UNIQUE", "NOT NULL").
|
||||||
|
StringColumn("created_at").
|
||||||
|
StringColumn("author", "NOT NULL").
|
||||||
|
AddColumn(bob.ColumnDef{Name: "status", Type: "SMALLINT", Extras: []string{"DEFAULT 0"}}).
|
||||||
|
ToSql()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("14 - failed on table creation: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Query(context.Background(), sql)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("15 - failed on table creation: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"jokes-bapak2-api/app/v1/handler"
|
"jokes-bapak2-api/app/v1/handler/health"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Health(app *fiber.App) *fiber.App {
|
func Health(app *fiber.App) *fiber.App {
|
||||||
// Health check
|
// Health check
|
||||||
app.Get("/health", handler.Health)
|
app.Get("/health", cache.New(cache.Config{Expiration: 30 * time.Minute}), health.Health)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,35 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"jokes-bapak2-api/app/v1/handler"
|
"jokes-bapak2-api/app/v1/handler/joke"
|
||||||
"jokes-bapak2-api/app/v1/middleware"
|
"jokes-bapak2-api/app/v1/middleware"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Joke(app *fiber.App) *fiber.App {
|
func Joke(app *fiber.App) *fiber.App {
|
||||||
// Single route
|
// Single route
|
||||||
app.Get("/", handler.SingleJoke)
|
app.Get("/", joke.SingleJoke)
|
||||||
|
|
||||||
// Today's joke
|
// Today's joke
|
||||||
app.Get("/today", handler.TodayJoke)
|
app.Get("/today", cache.New(cache.Config{Expiration: 6 * time.Hour}), joke.TodayJoke)
|
||||||
|
|
||||||
// Joke by ID
|
// Joke by ID
|
||||||
app.Get("/id/:id", middleware.OnlyIntegerAsID(), handler.JokeByID)
|
app.Get("/id/:id", middleware.OnlyIntegerAsID(), joke.JokeByID)
|
||||||
|
|
||||||
// Count total jokes
|
// Count total jokes
|
||||||
app.Get("/total", handler.TotalJokes)
|
app.Get("/total", cache.New(cache.Config{Expiration: 15 * time.Minute}), joke.TotalJokes)
|
||||||
|
|
||||||
// Add new joke
|
// Add new joke
|
||||||
app.Put("/", middleware.RequireAuth(), handler.AddNewJoke)
|
app.Put("/", middleware.RequireAuth(), joke.AddNewJoke)
|
||||||
|
|
||||||
// Update a joke
|
// 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
|
// 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
|
return app
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"jokes-bapak2-api/app/v1/handler/submit"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Submit(app *fiber.App) *fiber.App {
|
||||||
|
// Get pending submitted joke
|
||||||
|
app.Get(
|
||||||
|
"/submit",
|
||||||
|
cache.New(cache.Config{
|
||||||
|
Expiration: 5 * time.Minute,
|
||||||
|
KeyGenerator: func(c *fiber.Ctx) string {
|
||||||
|
return c.OriginalURL()
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
submit.GetSubmission)
|
||||||
|
|
||||||
|
// Add a joke
|
||||||
|
app.Post("/submit", submit.SubmitJoke)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
|
@ -8,4 +8,4 @@ func IsIn(arr []string, value string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,18 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsIn(t *testing.T) {
|
func TestIsIn_True(t *testing.T) {
|
||||||
arr := []string{"John", "Matthew", "Thomas", "Adam"}
|
arr := []string{"John", "Matthew", "Thomas", "Adam"}
|
||||||
t.Run("should return true", func(t *testing.T) {
|
check := utils.IsIn(arr, "Thomas")
|
||||||
check := utils.IsIn(arr, "Thomas")
|
if !check {
|
||||||
if !check {
|
t.Error("check should be true: ", check)
|
||||||
t.Error("check should be true: ", check)
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should return false", func(t *testing.T) {
|
func TestIsIn_False(t *testing.T) {
|
||||||
check := utils.IsIn(arr, "James")
|
arr := []string{"John", "Matthew", "Thomas", "Adam"}
|
||||||
if check {
|
check := utils.IsIn(arr, "James")
|
||||||
t.Error("check should be false: ", check)
|
if check {
|
||||||
}
|
t.Error("check should be false: ", check)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,34 +7,42 @@ import (
|
||||||
"jokes-bapak2-api/app/v1/utils"
|
"jokes-bapak2-api/app/v1/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsToday(t *testing.T) {
|
func TestIsToday_Today(t *testing.T) {
|
||||||
t.Run("should be able to tell if it's today", func(t *testing.T) {
|
today, err := utils.IsToday(time.Now().Format(time.RFC3339))
|
||||||
today, err := utils.IsToday(time.Now().Format(time.RFC3339))
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err.Error())
|
||||||
t.Error(err.Error())
|
}
|
||||||
}
|
if today == false {
|
||||||
if today == false {
|
t.Error("today should be true:", today)
|
||||||
t.Error("today should be true:", today)
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
func TestIsToday_NotToday(t *testing.T) {
|
||||||
t.Run("should be able to tell if it's not today", func(t *testing.T) {
|
today, err := utils.IsToday("2021-01-01T11:48:24Z")
|
||||||
today, err := utils.IsToday("2021-01-01T11:48:24Z")
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err.Error())
|
||||||
t.Error(err.Error())
|
}
|
||||||
}
|
if today == true {
|
||||||
if today == true {
|
t.Error("today should be false:", today)
|
||||||
t.Error("today should be false:", today)
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
func TestIsToday_ErrorIfEmpty(t *testing.T) {
|
||||||
t.Run("should return false with no error if no date is supplied", func(t *testing.T) {
|
today, err := utils.IsToday("")
|
||||||
today, err := utils.IsToday("")
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err.Error())
|
||||||
t.Error(err.Error())
|
}
|
||||||
}
|
if today != false {
|
||||||
if today != false {
|
t.Error("it should be false:", today)
|
||||||
t.Error("it should be false:", today)
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
func TestIsToday_ErrorIfInvalid(t *testing.T) {
|
||||||
|
today, err := utils.IsToday("asdfghjkl")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("it should be error:", today, err)
|
||||||
|
}
|
||||||
|
if today != false {
|
||||||
|
t.Error("it should be false:", today)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,37 +8,33 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseToJSONBody(t *testing.T) {
|
func TestParseToJSONBody(t *testing.T) {
|
||||||
t.Run("should be able to parse a json string", func(t *testing.T) {
|
body := map[string]interface{}{
|
||||||
body := map[string]interface{}{
|
"name": "Scott",
|
||||||
"name": "Scott",
|
"age": 32,
|
||||||
"age": 32,
|
"fat": true,
|
||||||
"fat": true,
|
}
|
||||||
}
|
parsed, err := utils.ParseToJSONBody(body)
|
||||||
parsed, err := utils.ParseToJSONBody(body)
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err.Error())
|
||||||
t.Error(err.Error())
|
}
|
||||||
}
|
result := "{\"age\":32,\"fat\":true,\"name\":\"Scott\"}"
|
||||||
result := "{\"age\":32,\"fat\":true,\"name\":\"Scott\"}"
|
if string(parsed) != result {
|
||||||
if string(parsed) != result {
|
t.Error("parsed string is not the same as result:", string(parsed))
|
||||||
t.Error("parsed string is not the same as result:", string(parsed))
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseToFormBody(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{}{
|
||||||
body := map[string]interface{}{
|
"age": 32,
|
||||||
"age": 32,
|
"fat": true,
|
||||||
"fat": true,
|
"name": "Scott",
|
||||||
"name": "Scott",
|
}
|
||||||
}
|
parsed, err := utils.ParseToFormBody(body)
|
||||||
parsed, err := utils.ParseToFormBody(body)
|
if err != nil {
|
||||||
if err != nil {
|
t.Error(err.Error())
|
||||||
t.Error(err.Error())
|
}
|
||||||
}
|
result := [3]string{"age=32&", "fat=true&", "name=Scott&"}
|
||||||
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]) {
|
||||||
if !strings.Contains(string(parsed), result[0]) && !strings.Contains(string(parsed), result[1]) && !strings.Contains(string(parsed), result[2]) {
|
t.Error("parsed string is not the same as result:", string(parsed))
|
||||||
t.Error("parsed string is not the same as result:", string(parsed))
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomString generates a random string with p bytes of length.
|
||||||
|
// Specifying 10 in the p parameter will result in the length of 20.
|
||||||
|
func RandomString(p int) (string, error) {
|
||||||
|
if p <= 0 {
|
||||||
|
p = 10
|
||||||
|
}
|
||||||
|
arr := make([]byte, p)
|
||||||
|
_, err := rand.Read(arr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(arr), nil
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package utils_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"jokes-bapak2-api/app/v1/utils"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRandomString_Valid(t *testing.T) {
|
||||||
|
random, err := utils.RandomString(10)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(random) != 20 {
|
||||||
|
t.Error("result is not within the length of 10")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRandomString_Invalid(t *testing.T) {
|
||||||
|
random, err := utils.RandomString(-10)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(random) != 20 {
|
||||||
|
t.Error("result is not within the length of 10")
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,21 +7,19 @@ import (
|
||||||
"jokes-bapak2-api/app/v1/utils"
|
"jokes-bapak2-api/app/v1/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequest(t *testing.T) {
|
func TestRequest_Get(t *testing.T) {
|
||||||
t.Run("should be able to do a get request", func(t *testing.T) {
|
res, err := utils.Request(utils.RequestConfig{
|
||||||
res, err := utils.Request(utils.RequestConfig{
|
URL: "https://jsonplaceholder.typicode.com/todos/1",
|
||||||
URL: "https://jsonplaceholder.typicode.com/todos/1",
|
Method: http.MethodGet,
|
||||||
Method: http.MethodGet,
|
Headers: map[string]interface{}{
|
||||||
Headers: map[string]interface{}{
|
"User-Agent": "Jokesbapak2 Test API",
|
||||||
"User-Agent": "Jokesbapak2 Test API",
|
"Accept": "application/json",
|
||||||
"Accept": "application/json",
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err.Error())
|
|
||||||
}
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
t.Error("response does not have 200 status", res.Status)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
t.Error("response does not have 200 status", res.Status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,12 @@ require (
|
||||||
github.com/gojek/heimdall/v7 v7.0.2
|
github.com/gojek/heimdall/v7 v7.0.2
|
||||||
github.com/jackc/pgx/v4 v4.12.0
|
github.com/jackc/pgx/v4 v4.12.0
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7
|
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
)
|
)
|
||||||
|
|
19
api/go.sum
19
api/go.sum
|
@ -70,6 +70,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -308,12 +309,12 @@ github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||||
|
@ -372,6 +373,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
|
@ -623,8 +626,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -688,8 +692,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
@ -708,11 +713,13 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -67,7 +67,7 @@ func StartServerWithGracefulShutdown(a *fiber.App) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Run server.
|
// Run server.
|
||||||
if err := a.Listen(":" + os.Getenv("PORT")); err != nil {
|
if err := a.Listen(os.Getenv("HOST") + ":" + os.Getenv("PORT")); err != nil {
|
||||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ func StartServerWithGracefulShutdown(a *fiber.App) {
|
||||||
// StartServer func for starting a simple server.
|
// StartServer func for starting a simple server.
|
||||||
func StartServer(a *fiber.App) {
|
func StartServer(a *fiber.App) {
|
||||||
// Run server.
|
// Run server.
|
||||||
if err := a.Listen(":" + os.Getenv("PORT")); err != nil {
|
if err := a.Listen(os.Getenv("HOST") + ":" + os.Getenv("PORT")); err != nil {
|
||||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
log.Printf("Oops... Server is not running! Reason: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue