refactor: move everything to their own packages

This commit is contained in:
Reinaldy Rafli 2021-10-18 15:31:17 +07:00
parent 729e4c039d
commit c18a81a7aa
No known key found for this signature in database
GPG Key ID: CFDB9400255D8CB6
35 changed files with 821 additions and 685 deletions

View File

@ -2,7 +2,7 @@ package app
import (
"context"
"jokes-bapak2-api/app/core"
"jokes-bapak2-api/app/core/joke"
"jokes-bapak2-api/app/platform/database"
"jokes-bapak2-api/app/routes"
"log"
@ -23,8 +23,6 @@ import (
)
func New() *fiber.App {
// Setup Context
ctx, cancel := context.WithCancel(context.Background())
// Setup PostgreSQL
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
@ -36,7 +34,7 @@ func New() *fiber.App {
poolConfig.MaxConns = 15
poolConfig.MinConns = 4
db, err := pgxpool.ConnectConfig(ctx, poolConfig)
db, err := pgxpool.ConnectConfig(context.Background(), poolConfig)
if err != nil {
log.Panicln("Unable to create connection", err)
}
@ -68,17 +66,18 @@ func New() *fiber.App {
}
defer sentry.Flush(2 * time.Second)
err = database.Setup(db, &ctx)
// TODO: These sequence below might be better wrapped as a Populate() function.
err = database.Setup(db)
if err != nil {
sentry.CaptureException(err)
log.Panicln(err)
}
err = core.SetAllJSONJoke(db, memory, &ctx)
err = joke.SetAllJSONJoke(db, context.Background(), memory)
if err != nil {
log.Panicln(err)
}
err = core.SetTotalJoke(db, memory, &ctx)
err = joke.SetTotalJoke(db, context.Background(), memory)
if err != nil {
log.Panicln(err)
}
@ -103,14 +102,12 @@ func New() *fiber.App {
app.Use(etag.New())
route := routes.Dependencies{
DB: db,
Redis: rdb,
Memory: memory,
HTTP: httpclient.NewClient(httpclient.WithHTTPTimeout(10 * time.Second)),
Query: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar),
App: app,
Context: &ctx,
Cancel: &cancel,
DB: db,
Redis: rdb,
Memory: memory,
HTTP: httpclient.NewClient(httpclient.WithHTTPTimeout(10 * time.Second)),
Query: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar),
App: app,
}
route.Health()
route.Joke()

View File

@ -0,0 +1,56 @@
package administrator
import (
"context"
"time"
"github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v4/pgxpool"
)
func GetUserID(db *pgxpool.Pool, ctx context.Context, key string) (int, error) {
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
c1, err := db.Acquire(ctx)
if err != nil {
return 0, err
}
defer c1.Release()
sql, args, err := query.
Update("administrators").
Set("last_used", time.Now().UTC().Format(time.RFC3339)).
ToSql()
if err != nil {
return 0, err
}
r, err := c1.Query(context.Background(), sql, args...)
if err != nil {
return 0, err
}
defer r.Close()
c2, err := db.Acquire(ctx)
if err != nil {
return 0, err
}
defer c2.Release()
sql, args, err = query.
Select("id").
From("administrators").
Where(squirrel.Eq{"key": key}).
ToSql()
if err != nil {
return 0, err
}
var id int
err = c2.QueryRow(context.Background(), sql, args...).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}

View File

@ -0,0 +1,41 @@
package administrator
import (
"context"
"errors"
"github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
func CheckKeyExists(db *pgxpool.Pool, ctx context.Context, key string) (string, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return "", err
}
defer conn.Release()
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
// Check if key exists
sql, args, err := query.
Select("token").
From("administrators").
Where(squirrel.Eq{"key": key}).
ToSql()
if err != nil {
return "", err
}
var token string
err = conn.QueryRow(context.Background(), sql, args...).Scan(&token)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return "", nil
}
return "", err
}
return token, nil
}

View File

@ -1,27 +1,31 @@
package core
package joke
import (
"context"
"errors"
"jokes-bapak2-api/app/core/schema"
"math/rand"
"strconv"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/georgysavva/scany/pgxscan"
"github.com/jackc/pgx"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/pquerna/ffjson/ffjson"
)
// GetAllJSONJokes fetch the database for all the jokes then output it as a JSON []byte.
// Keep in mind, you will need to store it to memory yourself.
func GetAllJSONJokes(db *pgxpool.Pool, ctx *context.Context) ([]byte, error) {
conn, err := db.Acquire(*ctx)
func GetAllJSONJokes(db *pgxpool.Pool, ctx context.Context) ([]byte, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return nil, err
return []byte{}, err
}
defer conn.Release()
var jokes []Joke
results, err := conn.Query(*ctx, "SELECT \"id\",\"link\" FROM \"jokesbapak2\" ORDER BY \"id\"")
var jokes []schema.Joke
results, err := conn.Query(context.Background(), "SELECT \"id\",\"link\" FROM \"jokesbapak2\" ORDER BY \"id\"")
if err != nil {
return nil, err
}
@ -40,17 +44,33 @@ func GetAllJSONJokes(db *pgxpool.Pool, ctx *context.Context) ([]byte, error) {
return data, nil
}
// Only return a link
func GetRandomJokeFromDB(db *pgxpool.Pool, ctx context.Context) (string, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return "", err
}
var link string
err = conn.QueryRow(context.Background(), "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link)
if err != nil {
return "", err
}
return link, nil
}
// GetRandomJokeFromCache returns a link string of a random joke from cache.
func GetRandomJokeFromCache(memory *bigcache.BigCache) (string, error) {
jokes, err := memory.Get("jokes")
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return "", ErrNotFound
return "", schema.ErrNotFound
}
return "", err
}
var data []Joke
var data []schema.Joke
err = ffjson.Unmarshal(jokes, &data)
if err != nil {
return "", nil
@ -59,7 +79,7 @@ func GetRandomJokeFromCache(memory *bigcache.BigCache) (string, error) {
// Return an error if the database is empty
dataLength := len(data)
if dataLength == 0 {
return "", ErrEmpty
return "", schema.ErrEmpty
}
random := rand.Intn(dataLength)
@ -99,12 +119,12 @@ func GetCachedJokeByID(memory *bigcache.BigCache, id int) (string, error) {
jokes, err := memory.Get("jokes")
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return "", ErrNotFound
return "", schema.ErrNotFound
}
return "", err
}
var data []Joke
var data []schema.Joke
err = ffjson.Unmarshal(jokes, &data)
if err != nil {
return "", nil
@ -125,10 +145,37 @@ func GetCachedTotalJokes(memory *bigcache.BigCache) (int, error) {
total, err := memory.Get("total")
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return 0, ErrNotFound
return 0, schema.ErrNotFound
}
return 0, err
}
return int(total[0]), nil
}
func CheckJokeExists(db *pgxpool.Pool, ctx context.Context, id string) (bool, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return false, err
}
defer conn.Release()
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
sql, args, err := query.
Select("id").
From("jokesbapak2").
Where(squirrel.Eq{"id": id}).
ToSql()
if err != nil {
return false, err
}
var jokeID int
err = conn.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
if err != nil && err != pgx.ErrNoRows {
return false, err
}
return strconv.Itoa(jokeID) == id, nil
}

View File

@ -0,0 +1,73 @@
package joke_test
import (
"context"
"jokes-bapak2-api/app/core/joke"
"testing"
"github.com/jackc/pgx/v4"
)
func TestGetAllJSONJokes(t *testing.T) {
defer Teardown()
conn, err := db.Acquire(context.Background())
if err != nil {
t.Error("an error was thrown:", err)
}
err = conn.BeginFunc(context.Background(), func(t pgx.Tx) error {
_, err := t.Exec(context.Background(), "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4), ($5, $6, $7, $8);", administratorsData...)
if err != nil {
return err
}
_, err = t.Exec(context.Background(), "INSERT INTO \"jokesbapak2\" (id, link, creator) VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9);", jokesData...)
if err != nil {
return err
}
return nil
})
if err != nil {
t.Error("an error was thrown:", err)
}
j, err := joke.GetAllJSONJokes(db, context.Background())
if err != nil {
t.Error("an error was thrown:", err)
}
if string(j) == "" {
t.Error("j should not be empty")
}
}
func TestGetRandomJokeFromCache(t *testing.T) {
defer Teardown()
//
}
func TestCheckJokesCache(t *testing.T) {
defer Teardown()
//
}
func TestCheckTotalJokesCache(t *testing.T) {
defer Teardown()
//
}
func TestGetCachedJokeByID(t *testing.T) {
defer Teardown()
//
}
func TestGetCachedTotalJokes(t *testing.T) {
defer Teardown()
//
}
func TestCheckJokeExists(t *testing.T) {
defer Teardown()
//
}

View File

@ -0,0 +1,153 @@
package joke_test
import (
"context"
"os"
"testing"
"time"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
var db *pgxpool.Pool
var cache *redis.Client
var memory *bigcache.BigCache
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 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 administratorsData = []interface{}{
1, "very secure", "not the real one", time.Now().Format(time.RFC3339), 2, "test", "$argon2id$v=19$m=65536,t=16,p=4$3a08c79fbf2222467a623df9a9ebf75802c65a4f9be36eb1df2f5d2052d53cb7$ce434bd38f7ba1fc1f2eb773afb8a1f7f2dad49140803ac6cb9d7256ce9826fb3b4afa1e2488da511c852fc6c33a76d5657eba6298a8e49d617b9972645b7106", "",
}
func TestMain(m *testing.M) {
Setup()
defer Teardown()
os.Exit(m.Run())
}
func Setup() {
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
db, err = pgxpool.ConnectConfig(context.Background(), poolConfig)
if err != nil {
panic(err)
}
opt, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil {
panic(err)
}
cache = redis.NewClient(opt)
memory, err = bigcache.NewBigCache(bigcache.DefaultConfig(6 * time.Hour))
if err != nil {
panic(err)
}
conn, err := db.Acquire(context.Background())
if err != nil {
panic(err)
}
defer conn.Release()
err = conn.BeginFunc(context.Background(), func(tx pgx.Tx) error {
_, err := tx.Exec(
context.Background(),
`CREATE TABLE IF NOT EXISTS administrators (
id SERIAL PRIMARY KEY,
key VARCHAR(255) NOT NULL UNIQUE,
token TEXT,
last_used VARCHAR(255)
);`,
)
if err != nil {
return err
}
_, err = tx.Exec(
context.Background(),
`CREATE TABLE IF NOT EXISTS jokesbapak2 (
id SERIAL PRIMARY KEY,
link TEXT UNIQUE,
creator INT NOT NULL REFERENCES "administrators" ("id")
);`,
)
if err != nil {
return err
}
_, err = tx.Exec(
context.Background(),
`CREATE TABLE IF NOT EXISTS submission (
id SERIAL PRIMARY KEY,
link UNIQUE NOT NULL,
created_at VARCHAR(255),
author VARCHAR(255) NOT NULL,
status SMALLINT DEFAULT 0
);`,
)
return err
})
if err != nil {
panic(err)
}
}
func Teardown() (err error) {
db.Close()
err = cache.Close()
if err != nil {
return
}
err = memory.Close()
return
}
func TruncateTable(db *pgxpool.Pool, cache *redis.Client, memory *bigcache.BigCache) error {
conn, err := db.Acquire(context.Background())
if err != nil {
return err
}
defer conn.Release()
err = conn.BeginFunc(context.Background(), func(tx pgx.Tx) error {
_, err := tx.Exec(context.Background(), "TRUNCATE TABLE submission;")
if err != nil {
return err
}
_, err = tx.Exec(context.Background(), "TRUNCATE TABLE jokesbapak2;")
if err != nil {
return err
}
_, err = tx.Exec(context.Background(), "TRUNCATE TABLE administrators;")
return err
})
if err != nil {
return err
}
err = cache.FlushAll(context.Background()).Err()
if err != nil {
return err
}
err = memory.Reset()
if err != nil {
return err
}
return nil
}

133
api/app/core/joke/setter.go Normal file
View File

@ -0,0 +1,133 @@
package joke
import (
"context"
"jokes-bapak2-api/app/core/schema"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/pquerna/ffjson/ffjson"
)
// SetAllJSONJoke fetches jokes data from GetAllJSONJokes then set it to memory cache.
func SetAllJSONJoke(db *pgxpool.Pool, ctx context.Context, memory *bigcache.BigCache) error {
jokes, err := GetAllJSONJokes(db, ctx)
if err != nil {
return err
}
err = memory.Set("jokes", jokes)
if err != nil {
return err
}
return nil
}
func SetTotalJoke(db *pgxpool.Pool, ctx context.Context, memory *bigcache.BigCache) error {
check, err := CheckJokesCache(memory)
if err != nil {
return err
}
if !check {
err = SetAllJSONJoke(db, ctx, memory)
if err != nil {
return err
}
}
jokes, err := memory.Get("jokes")
if err != nil {
return err
}
var data []schema.Joke
err = ffjson.Unmarshal(jokes, &data)
if err != nil {
return err
}
var total = []byte{byte(len(data))}
err = memory.Set("total", total)
if err != nil {
return err
}
return nil
}
func InsertJokeIntoDB(db *pgxpool.Pool, ctx context.Context, joke schema.Joke) error {
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
sql, args, err := query.
Insert("jokesbapak2").
Columns("link", "creator").
Values(joke.Link, joke.Creator).
ToSql()
if err != nil {
return err
}
r, err := conn.Query(context.Background(), sql, args...)
if err != nil {
return err
}
defer r.Close()
return nil
}
func DeleteSingleJoke(db *pgxpool.Pool, ctx context.Context, id int) error {
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
sql, args, err := query.
Delete("jokesbapak2").
Where(squirrel.Eq{"id": id}).
ToSql()
if err != nil {
return err
}
r, err := conn.Query(context.Background(), sql, args...)
if err != nil {
return err
}
defer r.Close()
return nil
}
func UpdateJoke(db *pgxpool.Pool, ctx context.Context, link, creator string) error {
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
sql, args, err := query.
Update("jokesbapak2").
Set("link", link).
Set("creator", creator).
ToSql()
if err != nil {
return err
}
r, err := conn.Query(context.Background(), sql, args...)
if err != nil {
return err
}
defer r.Close()
return nil
}

View File

@ -1,55 +0,0 @@
package core
import (
"context"
"github.com/allegro/bigcache/v3"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/pquerna/ffjson/ffjson"
)
// SetAllJSONJoke fetches jokes data from GetAllJSONJokes then set it to memory cache.
func SetAllJSONJoke(db *pgxpool.Pool, memory *bigcache.BigCache, ctx *context.Context) error {
jokes, err := GetAllJSONJokes(db, ctx)
if err != nil {
return err
}
err = memory.Set("jokes", jokes)
if err != nil {
return err
}
return nil
}
func SetTotalJoke(db *pgxpool.Pool, memory *bigcache.BigCache, ctx *context.Context) error {
check, err := CheckJokesCache(memory)
if err != nil {
return err
}
if !check {
err = SetAllJSONJoke(db, memory, ctx)
if err != nil {
return err
}
}
jokes, err := memory.Get("jokes")
if err != nil {
return err
}
var data []Joke
err = ffjson.Unmarshal(jokes, &data)
if err != nil {
return err
}
var total = []byte{byte(len(data))}
err = memory.Set("total", total)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,6 @@
package schema
import "errors"
var ErrNotFound = errors.New("record not found")
var ErrEmpty = errors.New("record is empty")

View File

@ -1,12 +1,4 @@
package core
import "errors"
type Joke struct {
ID int `json:"id" form:"id" db:"id"`
Link string `json:"link" form:"link" db:"link"`
Creator int `json:"creator" form:"creator" db:"creator"`
}
package schema
type ImageAPI struct {
Data ImageAPIData `json:"data"`
@ -21,6 +13,3 @@ type ImageAPIData struct {
URL string `json:"url"`
DisplayURL string `json:"display_url"`
}
var ErrNotFound = errors.New("record not found")
var ErrEmpty = errors.New("record is empty")

View File

@ -0,0 +1,7 @@
package schema
type Joke struct {
ID int `json:"id" form:"id" db:"id"`
Link string `json:"link" form:"link" db:"link"`
Creator int `json:"creator" form:"creator" db:"creator"`
}

View File

@ -1,9 +1,10 @@
package core
package submit
import (
"bytes"
"io"
"io/ioutil"
"jokes-bapak2-api/app/core/schema"
"jokes-bapak2-api/app/utils"
"mime/multipart"
"net/http"
@ -70,7 +71,7 @@ func UploadImage(client *httpclient.Client, image io.Reader) (string, error) {
return "", err
}
var data ImageAPI
var data schema.ImageAPI
err = ffjson.Unmarshal(responseBody, &data)
if err != nil {
return "", err

View File

@ -1,4 +1,4 @@
package core
package validator
import (
"regexp"

View File

@ -1,4 +1,4 @@
package core
package validator
import (
"errors"

View File

@ -0,0 +1,68 @@
package validator
import (
"context"
"errors"
"github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
// Validate if link already exists
func LinkAlreadyExists(db *pgxpool.Pool, ctx context.Context, link string) (bool, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return false, err
}
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
defer conn.Release()
sql, args, err := query.
Select("link").
From("jokesbapak2").
Where(squirrel.Eq{"link": link}).
ToSql()
if err != nil {
return false, err
}
var validateLink string
err = conn.QueryRow(context.Background(), sql, args...).Scan(&validateLink)
if err != nil && err != pgx.ErrNoRows {
return false, err
}
return validateLink != "", nil
}
// Check if the joke exists
func IDAlreadyExists(db *pgxpool.Pool, ctx context.Context, id int) (bool, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return false, err
}
defer conn.Release()
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
sql, args, err := query.
Select("id").
From("jokesbapak2").
Where(squirrel.Eq{"id": id}).
ToSql()
if err != nil {
return false, err
}
var jokeID int
err = conn.QueryRow(context.Background(), sql, args...).Scan(&jokeID)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
return false, err
}
if errors.Is(err, pgx.ErrNoRows) {
return false, nil
}
return true, nil
}

View File

@ -1,28 +1,25 @@
package health
import (
"context"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v4/pgxpool"
)
type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client
Context *context.Context
DB *pgxpool.Pool
Redis *redis.Client
}
func (d *Dependencies) Health(c *fiber.Ctx) error {
conn, err := d.DB.Acquire(*d.Context)
conn, err := d.DB.Acquire(c.Context())
if err != nil {
return err
}
defer conn.Release()
// Ping REDIS database
err = d.Redis.Ping(*d.Context).Err()
err = d.Redis.Ping(c.Context()).Err()
if err != nil {
return c.
Status(fiber.StatusServiceUnavailable).
@ -31,7 +28,7 @@ func (d *Dependencies) Health(c *fiber.Ctx) error {
})
}
_, err = conn.Query(*d.Context, "SELECT \"id\" FROM \"jokesbapak2\" LIMIT 1")
_, err = conn.Query(c.Context(), "SELECT \"id\" FROM \"jokesbapak2\" LIMIT 1")
if err != nil {
return c.
Status(fiber.StatusServiceUnavailable).

View File

@ -1,8 +1,6 @@
package joke
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
@ -11,10 +9,9 @@ import (
)
type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
Context *context.Context
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
}

View File

@ -1,38 +1,25 @@
package joke
import (
"jokes-bapak2-api/app/core"
core "jokes-bapak2-api/app/core/joke"
"jokes-bapak2-api/app/core/schema"
"jokes-bapak2-api/app/core/validator"
"github.com/Masterminds/squirrel"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v4"
)
func (d *Dependencies) AddNewJoke(c *fiber.Ctx) error {
conn, err := d.DB.Acquire(*d.Context)
if err != nil {
return err
}
defer conn.Release()
tx, err := conn.Begin(*d.Context)
if err != nil {
return err
}
defer tx.Rollback(*d.Context)
var body core.Joke
err = c.BodyParser(&body)
var body schema.Joke
err := c.BodyParser(&body)
if err != nil {
return err
}
// Check link validity
valid, err := core.CheckImageValidity(d.HTTP, body.Link)
valid, err := validator.CheckImageValidity(d.HTTP, body.Link)
if err != nil {
return err
}
if !valid {
return c.
Status(fiber.StatusBadRequest).
@ -40,51 +27,36 @@ func (d *Dependencies) AddNewJoke(c *fiber.Ctx) error {
Error: "URL provided is not a valid image",
})
}
// Validate if link already exists
sql, args, err := d.Query.
Select("link").
From("jokesbapak2").
Where(squirrel.Eq{"link": body.Link}).
ToSql()
validateLink, err := validator.LinkAlreadyExists(d.DB, c.Context(), body.Link)
if err != nil {
return err
}
var validateLink string
err = conn.QueryRow(*d.Context, sql, args...).Scan(&validateLink)
if err != nil && err != pgx.ErrNoRows {
return err
}
if err == nil && validateLink != "" {
if !validateLink {
return c.Status(fiber.StatusConflict).JSON(Error{
Error: "Given link is already on the jokesbapak2 database",
})
}
sql, args, err = d.Query.
Insert("jokesbapak2").
Columns("link", "creator").
Values(body.Link, c.Locals("userID")).
ToSql()
err = core.InsertJokeIntoDB(
d.DB,
c.Context(),
schema.Joke{
Link: body.Link,
Creator: c.Locals("userID").(int),
},
)
if err != nil {
return err
}
_, err = tx.Exec(*d.Context, sql, args...)
err = core.SetAllJSONJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}
err = tx.Commit(*d.Context)
if err != nil {
return err
}
err = core.SetAllJSONJoke(d.DB, d.Memory, d.Context)
if err != nil {
return err
}
err = core.SetTotalJoke(d.DB, d.Memory, d.Context)
err = core.SetTotalJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}

View File

@ -1,75 +1,51 @@
package joke
import (
"jokes-bapak2-api/app/core"
core "jokes-bapak2-api/app/core/joke"
"jokes-bapak2-api/app/core/validator"
"strconv"
"github.com/Masterminds/squirrel"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) DeleteJoke(c *fiber.Ctx) error {
conn, err := d.DB.Acquire(*d.Context)
if err != nil {
return err
}
defer conn.Release()
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return err
}
// Check if the joke exists
sql, args, err := d.Query.
Select("id").
From("jokesbapak2").
Where(squirrel.Eq{"id": id}).
ToSql()
validate, err := validator.IDAlreadyExists(d.DB, c.Context(), id)
if err != nil {
return err
}
var jokeID int
err = conn.QueryRow(*d.Context, sql, args...).Scan(&jokeID)
if err != nil {
return err
}
if jokeID == id {
sql, args, err = d.Query.
Delete("jokesbapak2").
Where(squirrel.Eq{"id": id}).
ToSql()
if err != nil {
return err
}
r, err := conn.Query(*d.Context, sql, args...)
if err != nil {
return err
}
defer r.Close()
err = core.SetAllJSONJoke(d.DB, d.Memory, d.Context)
if err != nil {
return err
}
err = core.SetTotalJoke(d.DB, d.Memory, d.Context)
if err != nil {
return err
}
if validate {
return c.
Status(fiber.StatusOK).
JSON(ResponseJoke{
Message: "specified joke id has been deleted",
Status(fiber.StatusNotAcceptable).
JSON(Error{
Error: "specified joke id does not exists",
})
}
err = core.DeleteSingleJoke(d.DB, c.Context(), id)
if err != nil {
return err
}
err = core.SetAllJSONJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}
err = core.SetTotalJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}
return c.
Status(fiber.StatusNotAcceptable).
JSON(Error{
Error: "specified joke id does not exists",
Status(fiber.StatusOK).
JSON(ResponseJoke{
Message: "specified joke id has been deleted",
})
}

View File

@ -1,8 +1,10 @@
package joke
import (
"errors"
"io/ioutil"
"jokes-bapak2-api/app/core"
core "jokes-bapak2-api/app/core/joke"
"jokes-bapak2-api/app/core/schema"
"jokes-bapak2-api/app/utils"
"strconv"
"time"
@ -15,7 +17,7 @@ func (d *Dependencies) TodayJoke(c *fiber.Ctx) error {
// send the joke if exists
// get a new joke if it's not, then send it.
var joke Today
err := d.Redis.MGet(*d.Context, "today:link", "today:date", "today:image", "today:contentType").Scan(&joke)
err := d.Redis.MGet(c.Context(), "today:link", "today:date", "today:image", "today:contentType").Scan(&joke)
if err != nil {
return err
}
@ -30,13 +32,7 @@ func (d *Dependencies) TodayJoke(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).Send([]byte(joke.Image))
}
conn, err := d.DB.Acquire(*d.Context)
if err != nil {
return err
}
defer conn.Release()
var link string
err = conn.QueryRow(*d.Context, "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link)
link, err := core.GetRandomJokeFromDB(d.DB, c.Context())
if err != nil {
return err
}
@ -52,7 +48,7 @@ func (d *Dependencies) TodayJoke(c *fiber.Ctx) error {
}
now := time.Now().UTC().Format(time.RFC3339)
err = d.Redis.MSet(*d.Context, map[string]interface{}{
err = d.Redis.MSet(c.Context(), map[string]interface{}{
"today:link": link,
"today:date": now,
"today:image": string(data),
@ -73,10 +69,11 @@ func (d *Dependencies) SingleJoke(c *fiber.Ctx) error {
}
if !checkCache {
jokes, err := core.GetAllJSONJokes(d.DB, d.Context)
jokes, err := core.GetAllJSONJokes(d.DB, c.Context())
if err != nil {
return err
}
err = d.Memory.Set("jokes", jokes)
if err != nil {
return err
@ -84,7 +81,7 @@ func (d *Dependencies) SingleJoke(c *fiber.Ctx) error {
}
link, err := core.GetRandomJokeFromCache(d.Memory)
if err != nil {
if err != nil && !errors.Is(err, schema.ErrEmpty) {
return err
}
@ -111,10 +108,11 @@ func (d *Dependencies) JokeByID(c *fiber.Ctx) error {
}
if !checkCache {
jokes, err := core.GetAllJSONJokes(d.DB, d.Context)
jokes, err := core.GetAllJSONJokes(d.DB, c.Context())
if err != nil {
return err
}
err = d.Memory.Set("jokes", jokes)
if err != nil {
return err

View File

@ -2,7 +2,7 @@ package joke
import (
"errors"
"jokes-bapak2-api/app/core"
core "jokes-bapak2-api/app/core/joke"
"strconv"
"github.com/allegro/bigcache/v3"
@ -16,7 +16,7 @@ func (d *Dependencies) TotalJokes(c *fiber.Ctx) error {
}
if !checkTotal {
err = core.SetTotalJoke(d.DB, d.Memory, d.Context)
err = core.SetTotalJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}

View File

@ -1,94 +1,69 @@
package joke
import (
"jokes-bapak2-api/app/core"
"strconv"
core "jokes-bapak2-api/app/core/joke"
"jokes-bapak2-api/app/core/schema"
"jokes-bapak2-api/app/core/validator"
"github.com/Masterminds/squirrel"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v4"
)
func (d *Dependencies) UpdateJoke(c *fiber.Ctx) error {
conn, err := d.DB.Acquire(*d.Context)
if err != nil {
return err
}
defer conn.Release()
id := c.Params("id")
// Check if the joke exists
sql, args, err := d.Query.
Select("id").
From("jokesbapak2").
Where(squirrel.Eq{"id": id}).
ToSql()
jokeExists, err := core.CheckJokeExists(d.DB, c.Context(), id)
if err != nil {
return err
}
var jokeID int
err = conn.QueryRow(*d.Context, sql, args...).Scan(&jokeID)
if err != nil && err != pgx.ErrNoRows {
return err
}
if strconv.Itoa(jokeID) == id {
body := new(core.Joke)
err = c.BodyParser(&body)
if err != nil {
return err
}
// Check link validity
valid, err := core.CheckImageValidity(d.HTTP, body.Link)
if err != nil {
return err
}
if !valid {
return c.
Status(fiber.StatusBadRequest).
JSON(Error{
Error: "URL provided is not a valid image",
})
}
sql, args, err = d.Query.
Update("jokesbapak2").
Set("link", body.Link).
Set("creator", c.Locals("userID")).
ToSql()
if err != nil {
return err
}
r, err := conn.Query(*d.Context, sql, args...)
if err != nil {
return err
}
defer r.Close()
err = core.SetAllJSONJoke(d.DB, d.Memory, d.Context)
if err != nil {
return err
}
err = core.SetTotalJoke(d.DB, d.Memory, d.Context)
if err != nil {
return err
}
if !jokeExists {
return c.
Status(fiber.StatusOK).
JSON(ResponseJoke{
Message: "specified joke id has been updated",
Link: body.Link,
Status(fiber.StatusNotAcceptable).
JSON(Error{
Error: "specified joke id does not exists",
})
}
body := new(schema.Joke)
err = c.BodyParser(&body)
if err != nil {
return err
}
// Check link validity
valid, err := validator.CheckImageValidity(d.HTTP, body.Link)
if err != nil {
return err
}
if !valid {
return c.
Status(fiber.StatusBadRequest).
JSON(Error{
Error: "URL provided is not a valid image",
})
}
err = core.UpdateJoke(d.DB, c.Context(), body.Link, c.Locals("userID").(string))
if err != nil {
return err
}
err = core.SetAllJSONJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}
err = core.SetTotalJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}
return c.
Status(fiber.StatusNotAcceptable).
JSON(Error{
Error: "specified joke id does not exists",
Status(fiber.StatusOK).
JSON(ResponseJoke{
Message: "specified joke id has been updated",
Link: body.Link,
})
}

View File

@ -1,8 +1,6 @@
package submit
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
@ -11,10 +9,9 @@ import (
)
type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
Context *context.Context
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
}

View File

@ -2,7 +2,8 @@ package submit
import (
"context"
"jokes-bapak2-api/app/core"
core "jokes-bapak2-api/app/core/submit"
"jokes-bapak2-api/app/core/validator"
"net/url"
"strings"
"time"
@ -15,7 +16,7 @@ import (
)
func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
conn, err := d.DB.Acquire(*d.Context)
conn, err := d.DB.Acquire(c.Context())
if err != nil {
return err
}
@ -41,7 +42,7 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
})
} else {
// Validate format
valid := core.ValidateAuthor(body.Author)
valid := validator.ValidateAuthor(body.Author)
if !valid {
return c.Status(fiber.StatusBadRequest).JSON(Error{
Error: "Please stick to the format of \"yourname <youremail@mail>\" and within 200 characters",
@ -53,7 +54,7 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
// Check link validity if link was provided
if body.Link != "" {
valid, err := core.CheckImageValidity(d.HTTP, body.Link)
valid, err := validator.CheckImageValidity(d.HTTP, body.Link)
if err != nil {
return err
}
@ -77,7 +78,7 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
}
// Validate if link already exists
validateLink, err := validateIfLinkExists(conn, d.Context, d.Query, link)
validateLink, err := validateIfLinkExists(d.DB, c.Context(), d.Query, link)
if err != nil {
return err
}
@ -101,7 +102,7 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
}
var submission []Submission
result, err := conn.Query(*d.Context, sql, args...)
result, err := conn.Query(c.Context(), sql, args...)
if err != nil {
return err
}
@ -121,7 +122,13 @@ func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
})
}
func validateIfLinkExists(conn *pgxpool.Conn, ctx *context.Context, query squirrel.StatementBuilderType, link string) (bool, error) {
func validateIfLinkExists(db *pgxpool.Pool, ctx context.Context, query squirrel.StatementBuilderType, link string) (bool, error) {
conn, err := db.Acquire(ctx)
if err != nil {
return false, err
}
defer conn.Release()
sql, args, err := query.
Select("link").
From("submission").
@ -132,7 +139,7 @@ func validateIfLinkExists(conn *pgxpool.Conn, ctx *context.Context, query squirr
}
var validateLink string
err = conn.QueryRow(*ctx, sql, args...).Scan(&validateLink)
err = conn.QueryRow(context.Background(), sql, args...).Scan(&validateLink)
if err != nil && err != pgx.ErrNoRows {
return false, err
}

View File

@ -83,7 +83,7 @@ func (d *Dependencies) GetSubmission(c *fiber.Ctx) error {
sql = bob.ReplacePlaceholder(sqlQuery.String(), bob.Dollar)
var submissions []Submission
results, err := d.DB.Query(*d.Context, sql, args...)
results, err := d.DB.Query(c.Context(), sql, args...)
if err != nil {
return err
}

View File

@ -1,20 +1,14 @@
package middleware
import (
"context"
"errors"
"time"
"jokes-bapak2-api/app/core/administrator"
"github.com/Masterminds/squirrel"
phccrypto "github.com/aldy505/phc-crypto"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
var psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
func RequireAuth(db *pgxpool.Pool, ctx *context.Context) fiber.Handler {
func RequireAuth(db *pgxpool.Pool) fiber.Handler {
return func(c *fiber.Ctx) error {
var auth Auth
err := c.BodyParser(&auth)
@ -22,27 +16,17 @@ func RequireAuth(db *pgxpool.Pool, ctx *context.Context) fiber.Handler {
return err
}
// Check if key exists
sql, args, err := psql.
Select("token").
From("administrators").
Where(squirrel.Eq{"key": auth.Key}).
ToSql()
token, err := administrator.CheckKeyExists(db, c.Context(), auth.Key)
if err != nil {
return err
}
var token string
err = db.QueryRow(*ctx, sql, args...).Scan(&token)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return c.
Status(fiber.StatusForbidden).
JSON(Error{
Error: "Invalid key",
})
}
return err
if token == "" {
return c.
Status(fiber.StatusForbidden).
JSON(Error{
Error: "Invalid key",
})
}
crypto, err := phccrypto.Use(phccrypto.Argon2, phccrypto.Config{})
@ -56,33 +40,11 @@ func RequireAuth(db *pgxpool.Pool, ctx *context.Context) fiber.Handler {
}
if verify {
sql, args, err = psql.
Update("administrators").
Set("last_used", time.Now().UTC().Format(time.RFC3339)).
ToSql()
id, err := administrator.GetUserID(db, c.Context(), auth.Key)
if err != nil {
return err
}
_, err = db.Query(*ctx, sql, args...)
if err != nil {
return err
}
sql, args, err = psql.
Select("id").
From("administrators").
Where(squirrel.Eq{"key": auth.Key}).
ToSql()
if err != nil {
return err
}
var id int
err = db.QueryRow(*ctx, sql, args...).Scan(&id)
if err != nil {
return err
}
c.Locals("userID", id)
return c.Next()
}

View File

@ -2,45 +2,39 @@ package database
import (
"context"
"log"
"github.com/aldy505/bob"
"github.com/jackc/pgx/v4/pgxpool"
)
// Setup the table connection, create table if not exists
func Setup(db *pgxpool.Pool, ctx *context.Context) error {
conn, err := db.Acquire(*ctx)
if err != nil {
log.Fatalln("30 - err here")
return err
}
defer conn.Release()
err = setupAuthTable(conn, ctx)
func Setup(db *pgxpool.Pool) error {
conn, err := db.Acquire(context.Background())
if err != nil {
return err
}
conn2, err := db.Acquire(*ctx)
if err != nil {
log.Fatalln("32 - err here")
return err
}
defer conn2.Release()
err = setupJokesTable(conn2, ctx)
err = setupAuthTable(conn)
if err != nil {
return err
}
conn3, err := db.Acquire(*ctx)
conn, err = db.Acquire(context.Background())
if err != nil {
return err
}
defer conn3.Release()
err = setupSubmissionTable(conn3, ctx)
err = setupJokesTable(conn)
if err != nil {
return err
}
conn, err = db.Acquire(context.Background())
if err != nil {
return err
}
err = setupSubmissionTable(conn)
if err != nil {
return err
}
@ -48,10 +42,12 @@ func Setup(db *pgxpool.Pool, ctx *context.Context) error {
return nil
}
func setupAuthTable(conn *pgxpool.Conn, ctx *context.Context) error {
func setupAuthTable(conn *pgxpool.Conn) error {
defer conn.Release()
// Check if table exists
var tableAuthExists bool
err := conn.QueryRow(*ctx, `SELECT EXISTS (
err := conn.QueryRow(context.Background(), `SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'administrators'
@ -72,7 +68,7 @@ func setupAuthTable(conn *pgxpool.Conn, ctx *context.Context) error {
return err
}
q, err := conn.Query(*ctx, sql)
q, err := conn.Query(context.Background(), sql)
if err != nil {
return err
}
@ -81,10 +77,12 @@ func setupAuthTable(conn *pgxpool.Conn, ctx *context.Context) error {
return nil
}
func setupJokesTable(conn *pgxpool.Conn, ctx *context.Context) error {
func setupJokesTable(conn *pgxpool.Conn) error {
defer conn.Release()
// Check if table exists
var tableJokesExists bool
err := conn.QueryRow(*ctx, `SELECT EXISTS (
err := conn.QueryRow(context.Background(), `SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'jokesbapak2'
@ -104,7 +102,7 @@ func setupJokesTable(conn *pgxpool.Conn, ctx *context.Context) error {
return err
}
q, err := conn.Query(*ctx, sql)
q, err := conn.Query(context.Background(), sql)
if err != nil {
return err
}
@ -114,10 +112,12 @@ func setupJokesTable(conn *pgxpool.Conn, ctx *context.Context) error {
return nil
}
func setupSubmissionTable(conn *pgxpool.Conn, ctx *context.Context) error {
func setupSubmissionTable(conn *pgxpool.Conn) error {
defer conn.Release()
//Check if table exists
var tableSubmissionExists bool
err := conn.QueryRow(*ctx, `SELECT EXISTS (
err := conn.QueryRow(context.Background(), `SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'submission'
@ -139,11 +139,12 @@ func setupSubmissionTable(conn *pgxpool.Conn, ctx *context.Context) error {
return err
}
q, err := conn.Query(*ctx, sql)
q, err := conn.Query(context.Background(), sql)
if err != nil {
return err
}
defer q.Close()
}
return nil
}

View File

@ -3,6 +3,8 @@
-- key: test
-- token: password
INSERT INTO "administrators" ("id", "key", "token", "last_used") VALUES
(1, 'test', '$argon2id$v=19$m=65536,t=16,p=4$3a08c79fbf2222467a623df9a9ebf75802c65a4f9be36eb1df2f5d2052d53cb7$ce434bd38f7ba1fc1f2eb773afb8a1f7f2dad49140803ac6cb9d7256ce9826fb3b4afa1e2488da511c852fc6c33a76d5657eba6298a8e49d617b9972645b7106', '');

View File

@ -1,8 +1,6 @@
package routes
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
@ -12,12 +10,10 @@ import (
)
type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
App *fiber.App
Context *context.Context
Cancel *context.CancelFunc
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
App *fiber.App
}

View File

@ -10,9 +10,8 @@ import (
func (d *Dependencies) Health() {
// Health check
deps := health.Dependencies{
DB: d.DB,
Redis: d.Redis,
Context: d.Context,
DB: d.DB,
Redis: d.Redis,
}
d.App.Get("/health", cache.New(cache.Config{Expiration: 30 * time.Minute}), deps.Health)

View File

@ -10,12 +10,11 @@ import (
func (d *Dependencies) Joke() {
deps := joke.Dependencies{
DB: d.DB,
Redis: d.Redis,
Memory: d.Memory,
HTTP: d.HTTP,
Query: d.Query,
Context: d.Context,
DB: d.DB,
Redis: d.Redis,
Memory: d.Memory,
HTTP: d.HTTP,
Query: d.Query,
}
// Single route
d.App.Get("/", deps.SingleJoke)
@ -34,11 +33,11 @@ func (d *Dependencies) Joke() {
d.App.Get("/v1/total", cache.New(cache.Config{Expiration: 15 * time.Minute}), deps.TotalJokes)
// Add new joke
d.App.Put("/", middleware.RequireAuth(d.DB, d.Context), deps.AddNewJoke)
d.App.Put("/", middleware.RequireAuth(d.DB), deps.AddNewJoke)
// Update a joke
d.App.Patch("/id/:id", middleware.RequireAuth(d.DB, d.Context), middleware.OnlyIntegerAsID(), deps.UpdateJoke)
d.App.Patch("/id/:id", middleware.RequireAuth(d.DB), middleware.OnlyIntegerAsID(), deps.UpdateJoke)
// Delete a joke
d.App.Delete("/id/:id", middleware.RequireAuth(d.DB, d.Context), middleware.OnlyIntegerAsID(), deps.DeleteJoke)
d.App.Delete("/id/:id", middleware.RequireAuth(d.DB), middleware.OnlyIntegerAsID(), deps.DeleteJoke)
}

View File

@ -10,12 +10,11 @@ import (
func (d *Dependencies) Submit() {
deps := submit.Dependencies{
DB: d.DB,
Redis: d.Redis,
Memory: d.Memory,
HTTP: d.HTTP,
Query: d.Query,
Context: d.Context,
DB: d.DB,
Redis: d.Redis,
Memory: d.Memory,
HTTP: d.HTTP,
Query: d.Query,
}
// Get pending submitted joke

View File

@ -13,12 +13,13 @@ require (
github.com/go-redis/redis/v8 v8.11.0
github.com/gofiber/fiber/v2 v2.15.0
github.com/gojek/heimdall/v7 v7.0.2
github.com/jackc/pgx v3.6.2+incompatible
github.com/jackc/pgx/v4 v4.12.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/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.0 // indirect
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
@ -33,6 +34,7 @@ require (
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
github.com/jackc/pgconn v1.9.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect

View File

@ -215,6 +215,8 @@ github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=

View File

@ -1,261 +0,0 @@
package main_test
import (
"context"
"errors"
"flag"
"io/ioutil"
v1 "jokes-bapak2-api/app"
"jokes-bapak2-api/app/platform/database"
"log"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v4/pgxpool"
_ "github.com/joho/godotenv/autoload"
"github.com/stretchr/testify/assert"
)
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 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 administratorsData = []interface{}{1, "very secure", "not the real one", time.Now().Format(time.RFC3339), 2, "test", "$argon2id$v=19$m=65536,t=16,p=4$3a08c79fbf2222467a623df9a9ebf75802c65a4f9be36eb1df2f5d2052d53cb7$ce434bd38f7ba1fc1f2eb773afb8a1f7f2dad49140803ac6cb9d7256ce9826fb3b4afa1e2488da511c852fc6c33a76d5657eba6298a8e49d617b9972645b7106", ""}
var ctx context.Context = context.Background()
func TestMain(m *testing.M) {
flag.Parse()
log.Println("---- Preparing for integration test")
time.Sleep(time.Second * 5)
err := setup()
if err != nil {
log.Panicln(err)
}
time.Sleep(time.Second * 5)
log.Println("---- Preparation complete")
log.Print("\n")
os.Exit(m.Run())
}
func setup() error {
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
return errors.New("Unable to create pool config: " + err.Error())
}
db, err := pgxpool.ConnectConfig(ctx, poolConfig)
if err != nil {
return errors.New("Unable to create connection: " + err.Error())
}
dj, err := db.Query(ctx, "DROP TABLE IF EXISTS \"jokesbapak2\"")
if err != nil {
return err
}
dj.Close()
ds, err := db.Query(ctx, "DROP TABLE IF EXISTS \"submission\"")
if err != nil {
return err
}
ds.Close()
da, err := db.Query(ctx, "DROP TABLE IF EXISTS \"administrators\"")
if err != nil {
return err
}
da.Close()
err = database.Setup(db, &ctx)
if err != nil {
return err
}
ia, err := db.Query(ctx, "INSERT INTO \"administrators\" (id, key, token, last_used) VALUES ($1, $2, $3, $4), ($5, $6, $7, $8);", administratorsData...)
if err != nil {
return err
}
ia.Close()
ij, err := db.Query(ctx, "INSERT INTO \"jokesbapak2\" (id, link, creator) VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9);", jokesData...)
if err != nil {
return err
}
ij.Close()
is, err := db.Query(ctx, "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
}
is.Close()
db.Close()
return nil
}
var app *fiber.App = v1.New()
func TestHealth(t *testing.T) {
req, _ := http.NewRequest("GET", "/health", nil)
res, err := app.Test(req, int(time.Minute*2))
if err != nil {
t.Fatal(err)
}
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)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
assert.Nilf(t, err, "health")
}
func TestTodayJoke(t *testing.T) {
req, _ := http.NewRequest("GET", "/today", nil)
res, err := app.Test(req, int(time.Minute*2))
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)
defer res.Body.Close()
assert.Nilf(t, err, "today joke")
}
func TestSingleJoke(t *testing.T) {
req, _ := http.NewRequest("GET", "/", nil)
res, err := app.Test(req, int(time.Minute*2))
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)
defer res.Body.Close()
assert.Nilf(t, err, "single joke")
}
func TestJokeByID_200(t *testing.T) {
req, _ := http.NewRequest("GET", "/id/1", nil)
res, err := app.Test(req, int(time.Minute*2))
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)
defer res.Body.Close()
assert.Nilf(t, err, "joke by id")
}
func TestJokeByID_404(t *testing.T) {
req, _ := http.NewRequest("GET", "/id/300", nil)
res, err := app.Test(req, int(time.Minute*2))
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)
defer res.Body.Close()
assert.Nilf(t, err, "joke by id")
assert.Equalf(t, "Requested ID was not found.", string(body), "joke by id")
}
func TestTotalJokes(t *testing.T) {
req, _ := http.NewRequest("GET", "/total", nil)
res, err := app.Test(req, int(time.Minute*2))
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)
defer res.Body.Close()
assert.Nilf(t, err, "joke total")
assert.Equalf(t, "{\"message\":\"3\"}", string(body), "joke total")
}
func TestAddNewJoke_201(t *testing.T) {
// TODO: Remove this line below, make this test works
t.SkipNow()
reqBody := strings.NewReader("{\"link\":\"https://via.placeholder.com/300/04f/ff0000.png\",\"key\":\"test\",\"token\":\"password\"}")
req, _ := http.NewRequest("PUT", "/", reqBody)
req.Header.Set("content-type", "application/json")
req.Header.Add("accept", "application/json")
res, err := app.Test(req, int(time.Minute*2))
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)
defer res.Body.Close()
assert.Nilf(t, err, "joke add")
assert.Equalf(t, "{\"link\":\"https://via.placeholder.com/300/04f/ff0000.png\"}", string(body), "joke add")
}
func TestAddNewJoke_NotValidImage(t *testing.T) {
// TODO: Remove this line below, make this test works
t.SkipNow()
reqBody := strings.NewReader("{\"link\":\"https://google.com/\",\"key\":\"test\",\"token\":\"password\"}")
req, _ := http.NewRequest("PUT", "/", reqBody)
req.Header.Set("content-type", "application/json")
req.Header.Add("accept", "application/json")
res, err := app.Test(req, int(time.Minute*2))
assert.Equalf(t, false, err != nil, "joke add")
assert.Equalf(t, 400, res.StatusCode, "joke add")
body, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
assert.Nilf(t, err, "joke add")
assert.Equalf(t, "{\"error\":\"URL provided is not a valid image\"}", string(body), "joke add")
}
func TestGetSubmission_200(t *testing.T) {
req, _ := http.NewRequest("GET", "/submit", nil)
res, err := app.Test(req, int(time.Minute*2))
assert.Equalf(t, false, err != nil, "get submission")
assert.Equalf(t, 200, res.StatusCode, "get submission")
body, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
assert.Nilf(t, err, "get submission")
assert.Equalf(t, "{\"count\":2,\"jokes\":[{\"id\":1,\"link\":\"https://via.placeholder.com/300/01f/fff.png\",\"created_at\":\"2021-08-03T18:20:38Z\",\"author\":\"Test \\u003ctest@example.com\\u003e\",\"status\":0},{\"id\":2,\"link\":\"https://via.placeholder.com/300/02f/fff.png\",\"created_at\":\"2021-08-04T18:20:38Z\",\"author\":\"Test \\u003ctest@example.com\\u003e\",\"status\":1}]}", string(body), "get submission")
}
func TestGetSubmission_Params(t *testing.T) {
req, _ := http.NewRequest("GET", "/submit?page=1&limit=5&approved=true", nil)
res, err := app.Test(req, int(time.Minute*2))
assert.Equalf(t, false, err != nil, "get submission")
assert.Equalf(t, 200, res.StatusCode, "get submission")
body, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
assert.Nilf(t, err, "get submission")
assert.Equalf(t, "{\"count\":1,\"jokes\":[{\"id\":2,\"link\":\"https://via.placeholder.com/300/02f/fff.png\",\"created_at\":\"2021-08-04T18:20:38Z\",\"author\":\"Test \\u003ctest@example.com\\u003e\",\"status\":1}]}", string(body), "get submission")
}
func TestAddSubmission_200(t *testing.T) {
// TODO: Remove this line below, make this test works
t.Skip()
reqBody := strings.NewReader(`{"link":"https://via.placeholder.com/400/02f/fff.png","author":"Test <test@mail.com>"}`)
req, _ := http.NewRequest("POST", "/submit", reqBody)
req.Header.Set("content-type", "application/json")
req.Header.Add("accept", "application/json")
res, err := app.Test(req, int(time.Minute*2))
assert.Equalf(t, false, err != nil, "post submission")
assert.Equalf(t, 201, res.StatusCode, "post submission")
assert.NotEqualf(t, 0, res.ContentLength, "post submission")
_, err = ioutil.ReadAll(res.Body)
defer res.Body.Close()
assert.Nilf(t, err, "post submission")
}