refactor: move everything to their own packages
This commit is contained in:
parent
729e4c039d
commit
c18a81a7aa
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
//
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package schema
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrNotFound = errors.New("record not found")
|
||||
var ErrEmpty = errors.New("record is empty")
|
|
@ -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")
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
package core
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
|
@ -1,4 +1,4 @@
|
|||
package core
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
|
@ -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
|
||||
}
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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', '');
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
261
api/main_test.go
261
api/main_test.go
|
@ -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")
|
||||
}
|
Loading…
Reference in New Issue